diff --git a/src/components/admin/__tests__/recovery-readiness-card.test.tsx b/src/components/admin/__tests__/recovery-readiness-card.test.tsx
index 3d130ed3b..ac09f58fa 100644
--- a/src/components/admin/__tests__/recovery-readiness-card.test.tsx
+++ b/src/components/admin/__tests__/recovery-readiness-card.test.tsx
@@ -24,7 +24,10 @@ import type {
ReadinessSignal,
RecoveryReadiness,
} from "@/components/admin/use-recovery-readiness";
-import { READINESS_FIX_PATHS } from "@/lib/recovery/readiness-model";
+import {
+ READINESS_FIX_PATHS,
+ READINESS_SIGNALS,
+} from "@/lib/recovery/readiness-model";
import { primeLocales } from "@/__tests__/helpers/prime-locales";
primeLocales("de");
@@ -97,6 +100,23 @@ function row(html: string, id: string): string {
}
describe("RecoveryReadinessCard", () => {
+ it("holds one row per signal while it loads, so the cards below stay put", () => {
+ // A read that has not answered: no data, no error.
+ const client = new QueryClient();
+ const html = renderToStaticMarkup(
+
+
+
+
+ ,
+ );
+ const start = html.indexOf('data-testid="recovery-readiness-loading"');
+ expect(start).toBeGreaterThan(-1);
+ expect(html.slice(start).match(/
{
pathname.current = "/admin";
const html = render(READINESS);
diff --git a/src/components/admin/recovery-readiness-card.tsx b/src/components/admin/recovery-readiness-card.tsx
index d77ae64d1..34189912b 100644
--- a/src/components/admin/recovery-readiness-card.tsx
+++ b/src/components/admin/recovery-readiness-card.tsx
@@ -24,7 +24,6 @@ import {
CheckCircle2,
Clock,
LifeBuoy,
- Loader2,
MinusCircle,
XCircle,
} from "lucide-react";
@@ -34,6 +33,7 @@ import { SettingsCard } from "@/components/settings/settings-card";
import { Badge } from "@/components/ui/badge";
import { InlineAction } from "@/components/ui/inline-action";
import { QueryErrorRow } from "@/components/ui/query-error-row";
+import { Skeleton } from "@/components/ui/skeleton";
import { useFormatters, useTranslations } from "@/lib/i18n/context";
import { cn } from "@/lib/utils";
@@ -44,6 +44,17 @@ import {
type RecoveryReadiness,
} from "./use-recovery-readiness";
+/** One placeholder row per readiness signal the card always lists. */
+const READINESS_SIGNAL_SLOTS = [
+ "database_dump",
+ "weekly_copy",
+ "offhost_copies",
+ "restore_drill",
+ "offhost_setup",
+ "dump_retention",
+ "recovery_kit",
+] as const satisfies readonly ReadinessSignal["id"][];
+
const STATE_ICON: Record<
ReadinessState,
React.ComponentType<{ className?: string; "aria-hidden"?: boolean }>
@@ -194,14 +205,27 @@ export function RecoveryReadinessCard() {
onRetry={() => void refetch()}
/>
) : (
-
-
-
+ // Holds the list's height while it loads. The card opens the backups
+ // page, and a one-line spinner here pushed every card below it down
+ // by the whole list once the signals landed.
+
+
{t("admin.recoveryReadiness.loading")}
+
+ {READINESS_SIGNAL_SLOTS.map((slot) => (
+ -
+
+
+
+
+
+
+ ))}
+
)}