diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx
index df65d17cd..5e13eae83 100644
--- a/src/app/insights/page.tsx
+++ b/src/app/insights/page.tsx
@@ -100,9 +100,10 @@ const RhythmEventsCard = dynamic(
// v1.10.3 — "Today's signal" headline card. Promotes COINCIDENT_DEVIATION from
// a buried below-the-fold tile to the top-of-overview daily read (the
// always-present Apple/WHOOP/Oura pattern). Deferred behind `next/dynamic`; it
-// owns its own derived-metric query and renders four calm states. A
-// fixed-height skeleton matches the resolved card so the top of the page does
-// not shift while the chunk + data resolve.
+// owns its own derived-metric query and renders four calm states. The chunk
+// loader's skeleton shares the card's `min-h-48` footprint (and the in-card
+// `CardSkeleton` matches it too) so the top of the page does not shift across
+// loading → any resolved state. Decorative → `aria-hidden`.
const CoincidentDeviationCard = dynamic(
() =>
import("@/components/insights/coincident-deviation-card").then((mod) => ({
@@ -111,7 +112,10 @@ const CoincidentDeviationCard = dynamic(
{
ssr: false,
loading: () => (
-
+
),
},
);
diff --git a/src/components/insights/__tests__/coincident-deviation-card.test.tsx b/src/components/insights/__tests__/coincident-deviation-card.test.tsx
index ee444c618..d4fb07e8e 100644
--- a/src/components/insights/__tests__/coincident-deviation-card.test.tsx
+++ b/src/components/insights/__tests__/coincident-deviation-card.test.tsx
@@ -89,6 +89,30 @@ describe("", () => {
const html = render();
expect(html).toContain('data-slot="coincident-deviation-card-skeleton"');
expect(html).not.toContain('data-slot="coincident-deviation-card"');
+ // The skeleton reserves the resolved card's footprint (no downward shift).
+ expect(html).toContain("min-h-48");
+ });
+
+ it("the skeleton and a resolved card share the same min-height footprint", () => {
+ mock(undefined);
+ const skeleton = render();
+ mock(
+ ok({
+ fired: true,
+ day: "2026-06-03",
+ vitals: [
+ deviation("RESTING_HEART_RATE", true),
+ deviation("RESPIRATORY_RATE", true),
+ ],
+ contributing: [
+ deviation("RESTING_HEART_RATE", true),
+ deviation("RESPIRATORY_RATE", true),
+ ],
+ }),
+ );
+ const fired = render();
+ expect(skeleton).toContain("min-h-48");
+ expect(fired).toContain("min-h-48");
});
it("renders the insufficient (building baselines) state, never an alarm", () => {
@@ -163,6 +187,9 @@ describe("", () => {
expect(html).not.toContain("text-destructive");
// The provenance affordance reaches the card.
expect(html).toContain('data-slot="provenance-explainer-trigger"');
+ // The state change is announced politely to SR users (not assertively).
+ expect(html).toContain('role="status"');
+ expect(html).not.toContain('role="alert"');
});
it("softens a fired flag to the watch tone when history is thin", () => {
diff --git a/src/components/insights/coincident-deviation-card.tsx b/src/components/insights/coincident-deviation-card.tsx
index b7a5619a1..68eb1d2f7 100644
--- a/src/components/insights/coincident-deviation-card.tsx
+++ b/src/components/insights/coincident-deviation-card.tsx
@@ -99,7 +99,13 @@ function CardShell({
data-slot="coincident-deviation-card"
data-state={state}
className={cn(
- "bg-card flex w-full min-w-0 flex-col gap-2 rounded-xl border p-4 md:p-6",
+ // `min-h` sized to the tallest realistic state (fired: 44px provenance
+ // header + the named-vitals body + the mandatory factors line). Every
+ // calmer state shares this footprint so the card — and everything below
+ // it on the overview — never shifts as the state resolves (the CLS the
+ // design pass closes). `min-h` not fixed `h`: a rare taller body still
+ // grows, it just never shrinks below the reserved height.
+ "bg-card flex min-h-48 w-full min-w-0 flex-col gap-2 rounded-xl border p-4 md:p-6",
// At most amber — never destructive/red. The fired state borders
// warning; every calmer state keeps the neutral card border.
state === "fired" ? "border-warning/40" : "border-border",
@@ -116,16 +122,24 @@ function CardShell({
);
}
-/** A fixed-footprint skeleton matching the resolved card (CLS-safe). */
+/**
+ * A fixed-footprint skeleton matching the resolved card geometry exactly
+ * (CLS-safe): the same `min-h-48` shell, the same gap, and a 44px header row
+ * mirroring the provenance trigger the resolved header carries — so the card
+ * does not shift when the real content drops in.
+ */
function CardSkeleton() {
return (
);
@@ -247,10 +261,13 @@ export function CoincidentDeviationCard({
}
// fired — amber awareness, named vitals, possible-factors line (mandatory).
+ // `role="status"` (a polite live region, not the assertive `role="alert"`)
+ // so a screen-reader user gets the day-to-day state change in the calm tone
+ // the card keeps visually — the one state whose meaning shifts vs all-clear.
return (