diff --git a/src/components/insights/__tests__/insight-status-card.test.tsx b/src/components/insights/__tests__/insight-status-card.test.tsx index 519434238..9d74cc659 100644 --- a/src/components/insights/__tests__/insight-status-card.test.tsx +++ b/src/components/insights/__tests__/insight-status-card.test.tsx @@ -81,7 +81,7 @@ describe("", () => { 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"); }); diff --git a/src/components/insights/insights-tab-strip.tsx b/src/components/insights/insights-tab-strip.tsx index 6c62c5302..7b69eb7cf 100644 --- a/src/components/insights/insights-tab-strip.tsx +++ b/src/components/insights/insights-tab-strip.tsx @@ -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[] { diff --git a/src/lib/insights/sub-page-metric.ts b/src/lib/insights/sub-page-metric.ts index 5084aa2a5..becab143a 100644 --- a/src/lib/insights/sub-page-metric.ts +++ b/src/lib/insights/sub-page-metric.ts @@ -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; export type SubPageSlug = keyof typeof SUB_PAGE_METRIC;