feat(insights): give every category page a metric explainer + normalise keys to english

Two parts to the explainer surface, landed together because they share
the same i18n block and page props.

Coverage: every routed insights category page now passes an
`explainerMetric` prop, so the heading-side `?` glyph is present on all
37 pages instead of 13. The 24 newly covered metrics — body
composition (body water, bone/fat/muscle/lean mass, fat-free mass,
visceral fat), mobility (flights, walking distance/speed/steadiness,
asymmetry, double-support, step length, walking heart rate),
cardiovascular (pulse-wave velocity, vascular age), hearing
(environmental + headphone levels, loud-sound events), respiratory
rate, daylight, blood glucose, and skin temperature — each gain a one
to two sentence layperson definition grounded in standard clinical
references, shipped in all six locales.

Keys: the explainer key segments were a German/English mix
(`blutdruck`, `puls`, `gewicht`, `schlaf`, `stimmung`, `medikamente`
alongside English ones). Rename them to English to match the
naming-convention ADR — `bloodPressure`, `pulse`, `weight`, `sleep`,
`mood`, `medications` — across every locale, and update the
`explainerMetric` prop values that reference them. These are i18n key
segments, not route slugs, so the slug contract is untouched.
This commit is contained in:
Marc-André Bombeck committed 2026-05-31 22:35:02 +02:00
1 parent 8f120cfdef
commit 05097df1ee
37 files changed
+440 -101

No files matched your search

@@ -41,25 +41,52 @@ function KeyProbe({ metric }: { metric: string }) {
);
}
// v1.8.0 — English key segments (ADR-0001). Every routed insights
// category page now passes one of these; the explainer copy ships in
// all six locales.
const METRICS = [
"blutdruck",
"puls",
"gewicht",
"bloodPressure",
"pulse",
"weight",
"bmi",
"schlaf",
"stimmung",
"medikamente",
"sleep",
"mood",
"medications",
"restingHr",
"hrv",
"oxygenSaturation",
"bodyTemperature",
"activeEnergy",
"workouts",
"respiratoryRate",
"bodyWater",
"boneMass",
"fatFreeMass",
"fatMass",
"muscleMass",
"visceralFat",
"leanBodyMass",
"flightsClimbed",
"walkingDistance",
"walkingSteadiness",
"walkingHeartRate",
"walkingAsymmetry",
"doubleSupportTime",
"stepLength",
"walkingSpeed",
"pulseWaveVelocity",
"vascularAge",
"environmentalAudio",
"headphoneAudio",
"audioEvents",
"daylight",
"bloodGlucose",
"skinTemperature",
] as const;
describe("<MetricExplainer>", () => {
it("renders an icon-only trigger button with a metric-specific accessible label (EN)", () => {
const html = ssr(<MetricExplainer metric="blutdruck" />);
const html = ssr(<MetricExplainer metric="bloodPressure" />);
const trigger = html.match(
/<button[^>]*data-slot="metric-explainer-trigger"[^>]*>/,
);
@@ -72,7 +99,7 @@ describe("<MetricExplainer>", () => {
// `min-h-11 min-w-11`; the surrounding heading row keeps its stride
// because `-my-3 -mx-2` swallows the extra reach. Pinned so a future
// refactor can't collapse the hit surface back to the visual chip.
const html = ssr(<MetricExplainer metric="puls" />);
const html = ssr(<MetricExplainer metric="pulse" />);
const trigger = html.match(
/<button[^>]*data-slot="metric-explainer-trigger"[^>]*>/,
);
@@ -94,13 +121,13 @@ describe("<MetricExplainer>", () => {
// the user activates the trigger. Pinning the negative keeps a
// future "open on render" refactor from leaking copy into the
// static markup.
const html = ssr(<MetricExplainer metric="gewicht" />);
const html = ssr(<MetricExplainer metric="weight" />);
expect(html).not.toContain('data-slot="metric-explainer-body"');
expect(html).not.toContain('data-slot="metric-explainer-title"');
});
it("localises the trigger label in German", () => {
const html = ssr(<MetricExplainer metric="schlaf" />, "de");
const html = ssr(<MetricExplainer metric="sleep" />, "de");
const trigger = html.match(
/<button[^>]*data-slot="metric-explainer-trigger"[^>]*>/,
);