diff --git a/messages/en.json b/messages/en.json
index 5534da01c..19ce48706 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -4803,7 +4803,11 @@
"notificationsCta": "Open notifications",
"insightsTitle": "Turn on insights (optional)",
"insightsDescription": "Add a local model or your own AI key to unlock the daily briefing and Coach.",
- "insightsCta": "Turn on insights"
+ "insightsCta": "Turn on insights",
+ "visitTitle": "Prepare for the visit",
+ "visitDescription": "Put the appointment on the calendar; the doctor report bundles what to bring.",
+ "visitCta": "Open visits",
+ "dataSourceNamedTitle": "Connect {source}"
}
},
"trendHints": {
@@ -6615,7 +6619,15 @@
},
"dangerZoneDetail": "Your account and the credentials you sign in with are kept.",
"deleteAccountCardDetail": "This cannot be undone.",
- "passkeyStepUpRequired": "For your security, sign in again before removing a passkey."
+ "passkeyStepUpRequired": "For your security, sign in again before removing a passkey.",
+ "setup": {
+ "title": "Setup",
+ "description": "The questions that decide what HealthLog shows first, and the getting-started checklist on the dashboard.",
+ "setUpAgain": "Set up again",
+ "showChecklist": "Show the checklist again",
+ "checklistShown": "The checklist is back on the dashboard.",
+ "checklistNothingHidden": "The checklist was not hidden; it opens on the dashboard."
+ }
},
"admin": {
"title": "Administration",
@@ -8887,7 +8899,8 @@
"unavailable": {
"description": "The operator switched {module} off for every account on this server. No setting of your own can bring it back.",
"title": "Not available on this server"
- }
+ },
+ "openModules": "Open Settings, Modules"
},
"vaccinations": {
"title": "Vaccinations",
diff --git a/src/components/layout/auth-shell.tsx b/src/components/layout/auth-shell.tsx
index 1e48d0367..093044697 100644
--- a/src/components/layout/auth-shell.tsx
+++ b/src/components/layout/auth-shell.tsx
@@ -27,6 +27,7 @@ import { DemoBanner } from "./demo-banner";
import { OfflineBanner } from "./offline-banner";
import { SharedRecordBanner } from "./shared-record-banner";
import { SharedRecordUnavailable } from "./shared-record-unavailable";
+import { ModulePageGate } from "@/components/layout/module-page-gate";
import { RecordScopeHydrationGate } from "./record-scope-hydration-gate";
import { SidebarNav } from "./sidebar-nav";
import { TopBar } from "./top-bar";
@@ -466,7 +467,11 @@ export function AuthShell({
data-slot="main-content-wrapper"
className="mx-auto max-w-screen-xl px-4 pt-6 pb-20 md:px-6"
>
- {outsideSharedRecord ? : children}
+ {outsideSharedRecord ? (
+
+ ) : (
+ {children}
+ )}
diff --git a/src/components/layout/module-page-gate.tsx b/src/components/layout/module-page-gate.tsx
new file mode 100644
index 000000000..9046f72e9
--- /dev/null
+++ b/src/components/layout/module-page-gate.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import type { ReactNode } from "react";
+
+import { ModuleDisabledNotice } from "@/components/layout/module-disabled-notice";
+import {
+ isNavDestinationActive,
+ NAV_DESTINATIONS,
+} from "@/components/layout/nav-model";
+import { Button } from "@/components/ui/button";
+import { useAuth } from "@/hooks/use-auth";
+import { useTranslations } from "@/lib/i18n/context";
+
+/**
+ * v1.39 (C2) — the page of a module that is switched off answers with an
+ * empty state that names the switch (design spec §After the flow), in one
+ * place for every module page.
+ *
+ * The setup flow switches modules OFF for a new record — an explicit `false`
+ * for every module the answers did not name — and the navigation drops the
+ * entry. A person who reaches the page anyway (a bookmark, a link in a
+ * report, the tour) used to get the page's own empty state, which said
+ * "nothing here yet" about a module that is not there at all. This maps the
+ * route to the module through the navigation model's own gate, so a module
+ * page and its nav entry can never disagree about which switch they follow,
+ * and renders the shared notice with the one action that can help: the
+ * Modules settings, offered only when the record's own switch is what is off.
+ *
+ * Paint, not enforcement: every route behind a module refuses on its own
+ * through the server gate. Mounted inside the shell, which renders children
+ * only after the account payload resolved, so the map is never read on a
+ * hydration render.
+ */
+export function ModulePageGate({ children }: { children: ReactNode }) {
+ const pathname = usePathname();
+ const { user } = useAuth();
+ const { t } = useTranslations();
+
+ const destination = NAV_DESTINATIONS.find(
+ (d) => d.requiresModule && isNavDestinationActive(d.href, pathname),
+ );
+ const moduleKey = destination?.requiresModule;
+ const off = moduleKey !== undefined && user?.modules?.[moduleKey] === false;
+
+ if (!off || !destination || !moduleKey) return <>{children}>;
+
+ const Icon = destination.icon;
+ return (
+ }
+ action={
+
+ }
+ />
+ );
+}
diff --git a/src/components/onboarding/choice-chip.tsx b/src/components/onboarding/choice-chip.tsx
index c2e67c6b7..f2062ca48 100644
--- a/src/components/onboarding/choice-chip.tsx
+++ b/src/components/onboarding/choice-chip.tsx
@@ -37,7 +37,7 @@ export function ChoiceChip({
return (
{t(labels.description)}
diff --git a/src/components/settings/account-section/index.tsx b/src/components/settings/account-section/index.tsx
index 21335c2b7..e07b9c7fa 100644
--- a/src/components/settings/account-section/index.tsx
+++ b/src/components/settings/account-section/index.tsx
@@ -48,6 +48,7 @@ import {
type StatusMessage,
} from "./account-section-utils";
import { AvatarSection } from "./avatar-section";
+import { SetupCard } from "./setup-card";
export { resolveInitialTimezone } from "./account-section-utils";
@@ -330,6 +331,10 @@ export function AccountSection() {
{/* Profile photo card */}
+ {/* v1.39 (C2) — the way back into the first-run experience: the setup
+ questions again, and the dashboard checklist this browser hid. */}
+
+