Merge branch 'fix/demo-dashboard' into release/v1.43.9

This commit is contained in:
Marc-André Bombeck committed 2026-10-20 22:43:38 +02:00
commit fed16a4c43
4 files changed
+79 -7

No files matched your search

+28 -3
View File
@@ -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`,
@@ -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(/<DashboardPageClient\b[^>]*>/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(/<GettingStartedChecklist\b[^>]*\/>/g) ?? [];
expect(mounts).toHaveLength(1);
expect(src).toContain("{demoMode ? null : <GettingStartedChecklist />}");
});
});
+8 -1
View File
@@ -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. */}
<GettingStartedChecklist />
{demoMode ? null : <GettingStartedChecklist />}
</div>
);
}
+8 -3
View File
@@ -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 <DashboardPageClient />;
return <DashboardPageClient demoMode={demoMode} />;
}
let dehydratedState = null;
let batchWindow: BatchWindow | undefined;
@@ -171,11 +174,13 @@ export default async function DashboardPage() {
}
if (dehydratedState === null) {
return <DashboardPageClient batchWindow={batchWindow} />;
return (
<DashboardPageClient batchWindow={batchWindow} demoMode={demoMode} />
);
}
return (
<HydrationBoundary state={dehydratedState}>
<DashboardPageClient batchWindow={batchWindow} />
<DashboardPageClient batchWindow={batchWindow} demoMode={demoMode} />
</HydrationBoundary>
);
}