diff --git a/scripts/seed-demo.ts b/scripts/seed-demo.ts index 57abb04a7..aad6f50f2 100644 --- a/scripts/seed-demo.ts +++ b/scripts/seed-demo.ts @@ -90,6 +90,7 @@ import { import { ENVIRONMENT_LOCATION_AAD } from "../src/lib/crypto/encrypted-columns"; import { sealInsightsCachedText } from "../src/lib/insights/cached-text-cipher"; import { + DEFAULT_CHART_OVERLAY_PREFS, DEFAULT_DASHBOARD_LAYOUT, serializeDashboardLayout, } from "../src/lib/dashboard-layout"; @@ -497,16 +498,40 @@ async function seed() { // One row of six tiles (weight, the two blood-pressure values, resting // heart rate, HRV and mood) and the weight chart right below it, so the // first screen of the public demo shows the summary, the tiles and the - // start of a chart together. Every other tile stays one switch away in - // the layout settings; its data is seeded either way. Writing the layout - // verbatim keeps the demo's home page stable across default tweaks. + // start of a chart together. Below it only the measurement charts: the + // mood and medication cards, achievements, recent workouts and check-ups + // stay one switch away in the layout settings, their data seeded either + // way. Weight, BMI and blood pressure open on 90 days, resting heart rate + // on 30 with its 7-day trend and trend line, so each chart shows a shape + // rather than a flat week. Writing the layout verbatim keeps the demo's + // home page stable across default tweaks. const SHOWCASE_TILE_IDS = new Set(["weight", "bp", "pulse", "hrv", "mood"]); + const HIDDEN_CARD_IDS = new Set([ + "mood", + "medications", + "achievements", + "recentWorkouts", + "vorsorge", + ]); const demoDashboardLayout = serializeDashboardLayout({ ...DEFAULT_DASHBOARD_LAYOUT, widgets: DEFAULT_DASHBOARD_LAYOUT.widgets.map((w) => ({ ...w, + visible: w.visible && !HIDDEN_CARD_IDS.has(w.id), tileVisible: SHOWCASE_TILE_IDS.has(w.id), })), + chartOverlayPrefs: { + weight: { ...DEFAULT_CHART_OVERLAY_PREFS, rangePoints: 90 }, + bmi: { ...DEFAULT_CHART_OVERLAY_PREFS, rangePoints: 90 }, + bp: { ...DEFAULT_CHART_OVERLAY_PREFS, rangePoints: 90 }, + // The resting heart rate chart is the dashboard's `pulse` chart. + pulse: { + ...DEFAULT_CHART_OVERLAY_PREFS, + rangePoints: 30, + showTrendIndicator: true, + showTrendArrow: true, + }, + }, }); await client.query( `UPDATE users SET dashboard_widgets_json = $1 WHERE id = $2`, diff --git a/src/app/__tests__/dashboard-demo-checklist.test.ts b/src/app/__tests__/dashboard-demo-checklist.test.ts new file mode 100644 index 000000000..0e7f0b50f --- /dev/null +++ b/src/app/__tests__/dashboard-demo-checklist.test.ts @@ -0,0 +1,35 @@ +import { describe, it, expect } from "vitest"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +/** + * The setup checklist on a demo instance. + * + * The demo is one shared, read-only account carrying onboarding answers, so + * `isStillInSetup` keeps the checklist on screen for every visitor although + * none of its rows can be done there. The shell already leaves out the tour + * under `DEMO_MODE`; the dashboard leaves out the checklist the same way. + * Pinned here: every render path of the page threads the flag, and the + * client mounts the checklist only without it. + */ +const PAGE_PATH = join(process.cwd(), "src/app/page.tsx"); +const CLIENT_PATH = join(process.cwd(), "src/app/page-client.tsx"); + +describe("dashboard setup checklist on a demo instance", () => { + it("threads DEMO_MODE into every render of the dashboard client", () => { + const src = readFileSync(PAGE_PATH, "utf8"); + expect(src).toContain('process.env.DEMO_MODE === "true"'); + const renders = src.match(/]*>/g) ?? []; + expect(renders.length).toBeGreaterThan(0); + for (const render of renders) { + expect(render).toContain("demoMode={demoMode}"); + } + }); + + it("mounts the checklist only outside demo mode", () => { + const src = readFileSync(CLIENT_PATH, "utf8"); + const mounts = src.match(/]*\/>/g) ?? []; + expect(mounts).toHaveLength(1); + expect(src).toContain("{demoMode ? null : }"); + }); +}); diff --git a/src/app/page-client.tsx b/src/app/page-client.tsx index cb3aab93e..b72534282 100644 --- a/src/app/page-client.tsx +++ b/src/app/page-client.tsx @@ -193,6 +193,7 @@ import { export default function DashboardPageClient({ batchWindow: batchWindowProp, + demoMode = false, }: { /** * v1.30.9 — the batched-series ISO window computed server-side from the @@ -204,6 +205,12 @@ export default function DashboardPageClient({ * under, so the prefetched slice lands instead of triggering a refetch. */ batchWindow?: BatchWindow; + /** + * True on a `DEMO_MODE` instance. The demo is one shared, read-only + * account a visitor cannot set up, so the setup checklist has nothing to + * offer there, as the shell already leaves out the tour. + */ + demoMode?: boolean; } = {}) { const { isAuthenticated } = useAuth(); const mounted = useMounted(); @@ -2312,7 +2319,7 @@ export default function DashboardPageClient({ * content (tiles, charts) keeps a stable position, and the * near-empty dashboards of the new accounts the card targets keep * it visible without scrolling anyway. */} - + {demoMode ? null : } ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 493c3f27e..48af2ec52 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -76,12 +76,15 @@ const DIGEST_SOFT_BUDGET_MS = 400; * others. */ export default async function DashboardPage() { + // `DEMO_MODE` is a server-only env var; resolved here and threaded in, as + // the layout does for the shell. + const demoMode = process.env.DEMO_MODE === "true"; // Operator/test escape hatch: `DASHBOARD_SSR_PREFETCH=false` renders the // pure client-fetch dashboard (pre-prefetch behaviour). The e2e server // sets it so Playwright route mocks — which only see CLIENT fetches — // keep governing what the dashboard paints. It gates EVERY prefetch below. if (process.env.DASHBOARD_SSR_PREFETCH === "false") { - return ; + return ; } let dehydratedState = null; let batchWindow: BatchWindow | undefined; @@ -171,11 +174,13 @@ export default async function DashboardPage() { } if (dehydratedState === null) { - return ; + return ( + + ); } return ( - + ); }