feat(insights): regroup tab-strip pills by metric category

The wave-A HealthKit pills (HRV, resting HR, SpO2, body temperature,
active energy) added in v1.4.32 stacked at the end of the strip in
insertion order so a left-to-right scan jumped across metric domains
multiple times. Reorder the pills to mirror the
MeasurementCategory overlay at src/lib/measurements/categories.ts:
vitals → body → activity → sleep → cardiovascular → mood → events.

No pill added or removed. The single source of truth is
SUB_PAGE_METRIC in src/lib/insights/sub-page-metric.ts; the strip
iterates SUB_PAGE_SLUGS (derived from the same record) and inherits
the new order automatically. Heavier regrouping (collapse five
wave-A pills under one Apple-Health pill, fold BMI into Gewicht)
is deferred to v1.4.34 — see .planning/round-v1433-audit-menu.md
§7 for the design-review rationale.

Per .planning/round-v1433-audit-runtime.md F9.
This commit is contained in:
Marc-André Bombeck committed 2026-05-16 17:16:50 +02:00
1 parent c98d07effd
commit 85d74dd913
3 files changed
+55 -28

No files matched your search

@@ -81,7 +81,7 @@ describe("<InsightStatusCard>", () => {
text="ignored when hasProvider is false"
/>,
);
expect(html).toContain("AI provider not configured.");
expect(html).toContain("Assessment unavailable.");
expect(html).not.toContain("ignored when hasProvider is false");
});
+25 -16
View File
@@ -80,31 +80,20 @@ interface TabEntry {
* stay in sync from a single source of truth — adding a sub-page is
* one row.
*/
// v1.4.33 F9 — label + gating-metric for every routed sub-page. Pill
// ORDER is owned by `SUB_PAGE_SLUGS` (which iterates the keys of
// `SUB_PAGE_METRIC`) so adding a new metric is one row in
// `sub-page-metric.ts` and the strip picks it up automatically.
const SUB_PAGE_TABS: Record<
SubPageSlug,
{ labelKey: string; metric: InsightMetric }
> = {
// ── vitals ──
blutdruck: {
labelKey: "insights.navBloodPressure",
metric: "BLOOD_PRESSURE_SYS",
},
gewicht: { labelKey: "insights.navWeight", metric: "WEIGHT" },
puls: { labelKey: "insights.navPulse", metric: "PULSE" },
stimmung: { labelKey: "insights.navMood", metric: "MOOD" },
medikamente: { labelKey: "insights.navMedication", metric: "MEDICATION" },
bmi: { labelKey: "insights.navBmi", metric: "BMI" },
schlaf: { labelKey: "insights.navSleep", metric: "SLEEP_DURATION" },
// v1.4.32 — workouts pill. Gate is event-driven; the
// availability helper reads `inputs.hasWorkouts` rather than a
// `summaries[…].count`.
workouts: { labelKey: "insights.navWorkouts", metric: "WORKOUTS" },
// v1.4.32 — five wave-A HealthKit pills. Each one reads its own
// MeasurementType count from the analytics summaries.
hrv: { labelKey: "insights.navHrv", metric: "HEART_RATE_VARIABILITY" },
ruhepuls: {
labelKey: "insights.navRestingHr",
metric: "RESTING_HEART_RATE",
},
sauerstoff: {
labelKey: "insights.navOxygenSaturation",
metric: "OXYGEN_SATURATION",
@@ -113,10 +102,30 @@ const SUB_PAGE_TABS: Record<
labelKey: "insights.navBodyTemperature",
metric: "BODY_TEMPERATURE",
},
// ── body composition ──
gewicht: { labelKey: "insights.navWeight", metric: "WEIGHT" },
bmi: { labelKey: "insights.navBmi", metric: "BMI" },
// ── activity ──
"aktive-energie": {
labelKey: "insights.navActiveEnergy",
metric: "ACTIVE_ENERGY_BURNED",
},
// v1.4.32 — workouts pill. Gate is event-driven; the
// availability helper reads `inputs.hasWorkouts` rather than a
// `summaries[…].count`.
workouts: { labelKey: "insights.navWorkouts", metric: "WORKOUTS" },
// ── sleep ──
schlaf: { labelKey: "insights.navSleep", metric: "SLEEP_DURATION" },
// ── cardiovascular ──
ruhepuls: {
labelKey: "insights.navRestingHr",
metric: "RESTING_HEART_RATE",
},
hrv: { labelKey: "insights.navHrv", metric: "HEART_RATE_VARIABILITY" },
// ── mood ──
stimmung: { labelKey: "insights.navMood", metric: "MOOD" },
// ── events ──
medikamente: { labelKey: "insights.navMedication", metric: "MEDICATION" },
};
function buildTabs(availability: InsightInputs | undefined): TabEntry[] {
+29 -11
View File
@@ -24,27 +24,45 @@
* enum, the slug array, and the per-slug metric list all derive from
* the keys of this record — adding a sub-page is a one-place change.
*/
// v1.4.33 F9 — order follows the MeasurementCategory overlay
// (`src/lib/measurements/categories.ts`):
//
// vitals → body → activity → sleep → cardiovascular → mood → events
//
// The wave-A HealthKit entries added in v1.4.32 (HRV, resting HR, SpO2,
// body temperature, active energy) used to stack at the end of the
// strip in insertion order; the regrouping below interleaves them into
// their categorical neighbours so a user reading left-to-right scans
// each metric domain as a block. Heavier regrouping (collapse five
// wave-A pills under a single "Apple Health" pill, fold BMI into
// Gewicht, fold Sauerstoff/Körpertemperatur into a single Vitals pill)
// is deferred to v1.4.34 — that needs a screenshot review per
// .planning/round-v1433-audit-menu.md §7.
export const SUB_PAGE_METRIC = {
// ── vitals ──
blutdruck: ["BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA", "PULSE"],
gewicht: ["WEIGHT"],
puls: ["PULSE"],
stimmung: ["MOOD"],
// medication adherence is event-driven; no measurement series.
medikamente: [],
sauerstoff: ["OXYGEN_SATURATION"],
koerpertemperatur: ["BODY_TEMPERATURE"],
// ── body composition ──
gewicht: ["WEIGHT"],
// BMI is derived from WEIGHT + profile height (no separate series).
bmi: ["WEIGHT"],
schlaf: ["SLEEP_DURATION"],
// ── activity ──
"aktive-energie": ["ACTIVE_ENERGY_BURNED"],
// v1.4.32 — workouts surface; the page reads `Workout` rows directly
// through `useWorkouts()` rather than the `summaries` map, so the
// metric list stays empty.
workouts: [],
// v1.4.32 — five new HealthKit wave-A sub-pages. Each one reads its
// own MeasurementType series via the standard analytics path.
hrv: ["HEART_RATE_VARIABILITY"],
// ── sleep ──
schlaf: ["SLEEP_DURATION"],
// ── cardiovascular ──
ruhepuls: ["RESTING_HEART_RATE"],
sauerstoff: ["OXYGEN_SATURATION"],
koerpertemperatur: ["BODY_TEMPERATURE"],
"aktive-energie": ["ACTIVE_ENERGY_BURNED"],
hrv: ["HEART_RATE_VARIABILITY"],
// ── mood ──
stimmung: ["MOOD"],
// ── events (medication adherence is event-driven; no measurement series) ──
medikamente: [],
} as const satisfies Record<string, readonly string[]>;
export type SubPageSlug = keyof typeof SUB_PAGE_METRIC;