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) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-10 10:19:46 +02:00
1 parent 06f18dfbdb
commit b388fc0355
3 files changed
+130 -33

No files matched your search

@@ -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
* `<ReferenceLine label="Your normal">`. 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 `<ReferenceLine>` 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) => { ... <ReferenceLine ... y={baseline}`
// appearing without a `showTrend` / `showBaseline` guard above it.
const moduleUrl = new URL("../health-chart.tsx", import.meta.url);
const src = readFileSync(fileURLToPath(moduleUrl), "utf8");
// Personal-baseline rendering must be guarded — the JSX block that
// emits the dashed baseline must be wrapped in a conditional
// expression starting with a state flag.
const baselineSection = src.match(
/\{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+<ReferenceLine\n/,
);
});
});
+35 -32
View File
@@ -1077,38 +1077,41 @@ export function HealthChart({
}
/>
))}
{/* 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 (
<ReferenceLine
key={`baseline-${type}`}
y={baseline}
stroke={colors[i % colors.length]}
strokeDasharray="2 4"
strokeOpacity={0.4}
strokeWidth={1}
ifOverflow="discard"
label={
i === 0
? {
value: t("charts.personalBaseline"),
position: "insideTopLeft",
fill: "var(--muted-foreground)",
fontSize: 10,
opacity: 0.7,
}
: undefined
}
/>
);
})}
{/* 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 (
<ReferenceLine
key={`baseline-${type}`}
y={baseline}
stroke={colors[i % colors.length]}
strokeDasharray="2 4"
strokeOpacity={0.4}
strokeWidth={1}
ifOverflow="discard"
label={
i === 0
? {
value: t("charts.personalBaseline"),
position: "insideTopLeft",
fill: "var(--muted-foreground)",
fontSize: 10,
opacity: 0.7,
}
: undefined
}
/>
);
})}
<Tooltip
filterNull={false}
cursor={{
+4 -1
View File
@@ -636,7 +636,10 @@ export function MoodChart({
ifOverflow="discard"
/>
))}
{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 && (
<ReferenceLine
y={personalBaseline}
stroke={COLOR_MAIN}