From b388fc03559ff5749c01d6cc6a194bd5e26a09d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 10 May 2026 10:19:36 +0200 Subject: [PATCH] revert(charts): remove auto-overlay personal baseline (now opt-in via Trend toggle) The 90-day-rolling-median baseline used to render unconditionally on every health-chart and the mood-chart as a faint dashed reference line labelled "Your normal" / "Dein Mittel". Marc's rule is that the mean should only appear when a trend is actively being displayed, not as a default chrome. Gate the baseline ReferenceLine behind the Trend toggle on both HealthChart and MoodChart. Default state stays OFF, so a fresh chart loads as a clean line without the dashed mean. Toggling Trend on brings back the baseline alongside the regression line. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../health-chart-overlay-defaults.test.tsx | 91 +++++++++++++++++++ src/components/charts/health-chart.tsx | 67 +++++++------- src/components/charts/mood-chart.tsx | 5 +- 3 files changed, 130 insertions(+), 33 deletions(-) create mode 100644 src/components/charts/__tests__/health-chart-overlay-defaults.test.tsx diff --git a/src/components/charts/__tests__/health-chart-overlay-defaults.test.tsx b/src/components/charts/__tests__/health-chart-overlay-defaults.test.tsx new file mode 100644 index 000000000..623d4bbfe --- /dev/null +++ b/src/components/charts/__tests__/health-chart-overlay-defaults.test.tsx @@ -0,0 +1,91 @@ +import { describe, it, expect, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +/** + * v1.4.18 — Default overlay state on chart wrappers. + * + * Marc rejected the always-on personal-baseline reference line that B1a + * painted on every chart. The default surface for a new chart should be + * a clean line + axes + tooltip — no baseline, no target-zone shading, + * no trend line. Overlays are user-opt-in via the per-chart settings + * popover (commit "feat(charts): per-chart overlay-controls component") + * and persisted (commit "feat(charts): persist per-chart overlay + * prefs"). The default state of every toggle is OFF. + * + * The personal baseline used to render unconditionally as a + * ``. We assert the SSR markup ships + * no `personalBaseline` label and no in-target reference area when the + * chart starts up. + */ + +const sampleSeries = vi.hoisted(() => { + const out: Array<{ measuredAt: string; value: number }> = []; + for (let i = 0; i < 30; i++) { + const dt = new Date(Date.UTC(2026, 4, 1 + i, 12, 0, 0)); + out.push({ + measuredAt: dt.toISOString(), + value: 118 + (i % 5) - 2 + i * 0.05, + }); + } + return out; +}); + +vi.mock("@tanstack/react-query", () => ({ + useQuery: () => ({ + data: sampleSeries.map((row) => { + const ts = Date.parse(row.measuredAt); + return { + date: new Date(ts).toDateString(), + timestamp: ts, + BLOOD_PRESSURE_SYS: row.value, + }; + }), + isLoading: false, + }), +})); + +vi.mock("@/hooks/use-auth", () => ({ + useAuth: () => ({ + isAuthenticated: true, + user: null, + isLoading: false, + }), +})); + +describe("HealthChart default overlay state", () => { + it("source: personal baseline is gated behind a toggle (no unconditional ReferenceLine)", () => { + // The B1a wrapper rendered the baseline `` for every + // type unconditionally. Make sure the source no longer paints it + // outside an opt-in branch — we look for the tell-tale pattern of + // `types.map((type, i) => { ... \{[\s\S]*?const baseline = personalBaselines\.get\(type\);[\s\S]*?\}\)\}/, + ); + if (baselineSection) { + // If the section still exists, it's not gated. Allow a guard + // wrapper test instead — match `{showSomething &&`. + throw new Error( + "Personal baseline ReferenceLine is rendered unconditionally — must be gated behind an opt-in toggle.", + ); + } + }); + + it("does NOT paint the personal-baseline label by default in MoodChart source", () => { + const moduleUrl = new URL("../mood-chart.tsx", import.meta.url); + const src = readFileSync(fileURLToPath(moduleUrl), "utf8"); + // Same guard — the baseline ReferenceLine must be wrapped in a + // condition (showTrend / similar), not always painted. + expect(src).not.toMatch( + /\{personalBaseline != null && \(\n\s+ ))} - {/* v1.4.16 B1a — personal-baseline reference line. - 90-day rolling median per type, painted as a faint - dashed line labelled "Your normal" / "Dein Mittel". - Only the *first* baseline gets an inline label so - multi-type charts (e.g. systolic + diastolic) don't - paint two overlapping labels. */} - {types.map((type, i) => { - const baseline = personalBaselines.get(type); - if (baseline == null) return null; - return ( - - ); - })} + {/* v1.4.18 — personal-baseline reference line is now + opt-in via the Trend toggle. Marc rejected the + always-on dashed mean line; it now only paints when + the user actively shows the trend overlay (matching + his rule: "only when a trend is being displayed"). + 90-day rolling median per type, faint dashed line, + only the first type gets the inline label so + multi-type charts don't paint duplicate labels. */} + {showTrend && + types.map((type, i) => { + const baseline = personalBaselines.get(type); + if (baseline == null) return null; + return ( + + ); + })} ))} - {personalBaseline != null && ( + {/* v1.4.18 — personal-baseline gated behind the + Trend toggle (Marc: "only when a trend is being + displayed"). Default OFF. */} + {showTrend && personalBaseline != null && (