mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
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:
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/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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={{
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in new issue
Block a user