From ec82ad3a93c68969ba9840d9da2e7ef121d354d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?=
Date: Thu, 10 Sep 2026 15:41:06 +0200
Subject: [PATCH] Carry the setup answers into the checklist, Settings and the
module pages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The getting-started checklist reads the answers: a visit named within a
month adds a "prepare the visit" row that is done once one is on the
calendar, and the data-source row names the wearable that was chosen.
Its per-browser state moves to one module so the done screen, the skip
and Settings write the same keys.
Settings → Account gains the two ways back in: "Set up again" through
the restart route, which resets the ledger, keeps the answers as
prefill and touches no module; and "Show the checklist again", which
forgets what this browser hid.
A page whose module is off now answers with the shared notice and the
one action that helps, in one place for every module page: the shell
maps the route to the module through the navigation model's own gate,
so a page and its nav entry cannot disagree about which switch they
follow. And the tour's two dead stops get their anchors on the
integrations and health-record group headings.
---
messages/en.json | 19 +++-
src/components/layout/auth-shell.tsx | 7 +-
src/components/layout/module-page-gate.tsx | 63 +++++++++++++
src/components/onboarding/choice-chip.tsx | 2 +-
.../onboarding/getting-started-checklist.tsx | 54 ++++++++++--
.../settings/account-section/index.tsx | 5 ++
.../settings/account-section/setup-card.tsx | 88 +++++++++++++++++++
.../settings/gesundheitsakte-section.tsx | 2 +
.../settings/integrations-section.tsx | 2 +
src/components/ui/section-heading.tsx | 8 ++
.../onboarding/__tests__/checklist.test.ts | 54 +++++++++++-
src/lib/onboarding/checklist.ts | 28 +++++-
src/lib/query-keys/onboarding.ts | 9 ++
13 files changed, 325 insertions(+), 16 deletions(-)
create mode 100644 src/components/layout/module-page-gate.tsx
create mode 100644 src/components/settings/account-section/setup-card.tsx
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. */}
+
+