diff --git a/src/components/settings/sources-section.tsx b/src/components/settings/sources-section.tsx
index b4c07876e..fb0f48f19 100644
--- a/src/components/settings/sources-section.tsx
+++ b/src/components/settings/sources-section.tsx
@@ -40,6 +40,7 @@ import {
} from "lucide-react";
import { Button } from "@/components/ui/button";
+import { Skeleton } from "@/components/ui/skeleton";
import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
import {
@@ -314,10 +315,7 @@ export function SourcesSection() {
{isLoading || !priority ? (
-
-
- {t("common.loading")}
-
+
) : (
{SOURCE_PRIORITY_METRIC_KEYS.map((metric) => {
@@ -564,3 +562,33 @@ export function SourcesSection() {
);
}
+
+/**
+ * Skeleton placeholder rendered while `/api/auth/me/source-priority`
+ * is in flight. Reserves one row per `SOURCE_PRIORITY_METRIC_KEYS`
+ * entry at roughly the loaded height so the page does not jump when
+ * the fetched ladder list swaps in. The pulsing animation honours
+ * `prefers-reduced-motion` via Tailwind's `motion-reduce:animate-none`.
+ */
+function SourcesSkeletonList() {
+ return (
+
+ {SOURCE_PRIORITY_METRIC_KEYS.map((metric) => (
+
+ ))}
+
+ );
+}
diff --git a/src/components/settings/thresholds-editor-section.tsx b/src/components/settings/thresholds-editor-section.tsx
index 036142a11..2e337bf00 100644
--- a/src/components/settings/thresholds-editor-section.tsx
+++ b/src/components/settings/thresholds-editor-section.tsx
@@ -3,15 +3,11 @@
import { useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
-import {
- SlidersHorizontal,
- RotateCcw,
- Loader2,
- AlertTriangle,
-} from "lucide-react";
+import { SlidersHorizontal, RotateCcw, AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
+import { Skeleton } from "@/components/ui/skeleton";
import { Switch } from "@/components/ui/switch";
import { Badge } from "@/components/ui/badge";
import { useTranslations, useFormatters } from "@/lib/i18n/context";
@@ -147,10 +143,7 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
{isLoading || !data ? (
-
-
- {t("common.loading")}
-
+
) : (
{METRIC_ORDER.map((metric) => (
@@ -170,6 +163,41 @@ export function ThresholdsEditorSection({ id }: { id: string }) {
);
}
+/**
+ * Skeleton placeholder rendered while `/api/user/thresholds` is in
+ * flight. Reserves one row per `METRIC_ORDER` entry at roughly the
+ * loaded height so the page does not jump when the fetched list
+ * swaps in. The pulsing animation honours `prefers-reduced-motion`
+ * via Tailwind's `motion-reduce:animate-none`.
+ */
+function ThresholdsSkeletonList() {
+ return (
+
+ {METRIC_ORDER.map((metric) => (
+
+ ))}
+
+ );
+}
+
interface MetricRowProps {
metric: ThresholdMetric;
effective: EffectiveRange | undefined;