diff --git a/src/components/insights/hero-strip.tsx b/src/components/insights/hero-strip.tsx index b540a68f5..aebd44c72 100644 --- a/src/components/insights/hero-strip.tsx +++ b/src/components/insights/hero-strip.tsx @@ -383,6 +383,7 @@ export function HeroStrip({ report={healthScore} tension={tensionProp} returnToBand={returnToBandProp} + className="w-full md:shrink-0 md:grow-0 md:basis-[22rem] xl:basis-[26rem]" /> )} diff --git a/src/components/settings/__tests__/dashboard-layout-section.test.tsx b/src/components/settings/__tests__/dashboard-layout-section.test.tsx index 65a90d3d1..909026b67 100644 --- a/src/components/settings/__tests__/dashboard-layout-section.test.tsx +++ b/src/components/settings/__tests__/dashboard-layout-section.test.tsx @@ -69,6 +69,16 @@ vi.mock("@/hooks/use-auth", () => ({ useAuth: () => ({ user: { modules: authState.modules } }), })); +// The hero fieldset gates its presence via `useMounted()` (v1.34, hydration +// fix). Under SSR (`renderToStaticMarkup`) `useMounted()` returns `false`, +// which would hide the fieldset from every test below regardless of +// `layout`. Force it to the hydrated (`true`) snapshot so these tests +// exercise the real post-mount content — same convention as +// layout-section.test.tsx for the identical `LayoutModuleGate` pattern. +vi.mock("@/hooks/use-mounted", () => ({ + useMounted: () => true, +})); + import { I18nProvider } from "@/lib/i18n/context"; import { DashboardLayoutSection, diff --git a/src/components/settings/dashboard-layout-section.tsx b/src/components/settings/dashboard-layout-section.tsx index e1b2c4ad5..445b1954f 100644 --- a/src/components/settings/dashboard-layout-section.tsx +++ b/src/components/settings/dashboard-layout-section.tsx @@ -49,6 +49,7 @@ import { type PriorityItemKind, } from "@/lib/daily/priority-item"; import { apiDelete, apiGet, apiPut, ApiError } from "@/lib/api/api-fetch"; +import { useMounted } from "@/hooks/use-mounted"; import { useAuth } from "@/hooks/use-auth"; import { NATIVE_ONLY_WIDGET_LABEL_KEYS, @@ -204,6 +205,17 @@ export function DashboardLayoutSection({ id }: { id: string }) { const dragHintId = useId(); const heroDescriptionId = useId(); const heroNotificationNoteId = useId(); + // v1.34 — the "Today highlights" fieldset below is gated on `layout`, + // which can be truthy on the very first client render whenever a + // returning visitor's browser already has a warm, offline-persisted + // `dashboardWidgets` cache (queryKeys.dashboardWidgets() sits in + // PERSIST_ALLOWLIST_HEADS). The static build has no session, so + // `layout` is always null in the server HTML — a warm cache then + // diverges the very first client paint from that HTML (React #418). + // `useMounted()` forces the fieldset's presence check to agree with + // the server on the hydrating render (both false), the same pattern + // `LayoutModuleGate` already uses for the same class of mismatch. + const hydrated = useMounted(); // v1.4.47 W4 — sensors: pointer for mouse/touch, keyboard for Tab + Space // + arrow-key reordering. The KeyboardSensor still works for users who @@ -452,7 +464,7 @@ export function DashboardLayoutSection({ id }: { id: string }) { /> } /> - {layout && ( + {hydrated && layout && (