diff --git a/src/components/settings/dashboard-layout-section.tsx b/src/components/settings/dashboard-layout-section.tsx index 5785fd739..60cde6dcd 100644 --- a/src/components/settings/dashboard-layout-section.tsx +++ b/src/components/settings/dashboard-layout-section.tsx @@ -132,6 +132,16 @@ const WIDGET_LABEL_KEYS: Record = { vo2Max: "dashboard.vo2Max", // v1.4.32 — Recent workouts tile (default-on). recentWorkouts: "dashboard.recentWorkouts.title", + // v1.11.2 B5 — v1.10 additive HealthKit signals, now pinnable. Each + // reuses the existing measurement-type label key. + cardioRecovery: "measurements.typeCardioRecovery", + sixMinuteWalk: "measurements.typeSixMinuteWalkDistance", + stairAscentSpeed: "measurements.typeStairAscentSpeed", + stairDescentSpeed: "measurements.typeStairDescentSpeed", + breathingDisturbances: "measurements.typeBreathingDisturbances", + wristTemperature: "measurements.typeWristTemperature", + falls: "measurements.typeFallCount", + walkingSteadiness: "measurements.typeWalkingSteadiness", }; export function DashboardLayoutSection({ id }: { id: string }) { diff --git a/src/lib/__tests__/dashboard-layout.test.ts b/src/lib/__tests__/dashboard-layout.test.ts index c972eb2e0..5ac048573 100644 --- a/src/lib/__tests__/dashboard-layout.test.ts +++ b/src/lib/__tests__/dashboard-layout.test.ts @@ -382,11 +382,13 @@ describe("resolveDashboardLayout() — chartOverlayPrefs (v1.4.18)", () => { * iOS-only ids extend it without touching the writable PUT enum. */ describe("DASHBOARD_WIDGET_CATALOGUE_IDS — 27-id catalogue", () => { - it("carries exactly 27 distinct ids (16 server-known + 11 iOS-only)", () => { - expect(DASHBOARD_WIDGET_IDS).toHaveLength(16); + it("carries exactly 35 distinct ids (24 server-known + 11 iOS-only)", () => { + // v1.11.2 B5 — the 8 v1.10 additive metrics became web-writable, so + // the server-known set grew 16 → 24 and the catalogue 27 → 35. + expect(DASHBOARD_WIDGET_IDS).toHaveLength(24); expect(DASHBOARD_IOS_ONLY_WIDGET_IDS).toHaveLength(11); - expect(DASHBOARD_WIDGET_CATALOGUE_IDS).toHaveLength(27); - expect(new Set(DASHBOARD_WIDGET_CATALOGUE_IDS).size).toBe(27); + expect(DASHBOARD_WIDGET_CATALOGUE_IDS).toHaveLength(35); + expect(new Set(DASHBOARD_WIDGET_CATALOGUE_IDS).size).toBe(35); }); it("is a superset of the server-known ids in declaration order", () => { @@ -483,17 +485,17 @@ describe("resolveDashboardLayout() — iOS-only id retention (v1.7.0)", () => { expect(ids).not.toContain("totally-made-up"); }); - it("keeps the default layout at the 16 web tiles (no iOS-only seeded)", () => { + it("keeps the default layout at the 24 web tiles (no iOS-only seeded)", () => { const ids = DEFAULT_DASHBOARD_LAYOUT.widgets.map((w) => w.id); - expect(ids).toHaveLength(16); + expect(ids).toHaveLength(24); for (const iosId of DASHBOARD_IOS_ONLY_WIDGET_IDS) { expect(ids).not.toContain(iosId); } }); it("does NOT auto-append iOS-only ids when a web-only layout is read", () => { - // A web account that saved only `weight` must auto-upgrade to the 16 - // web defaults — never to the 27 catalogue. iOS-only ids appear only + // A web account that saved only `weight` must auto-upgrade to the 24 + // web defaults — never to the 35 catalogue. iOS-only ids appear only // once a native client has explicitly sent them. const partial = { version: 1, @@ -501,7 +503,7 @@ describe("resolveDashboardLayout() — iOS-only id retention (v1.7.0)", () => { }; const resolved = resolveDashboardLayout(partial); const ids = resolved.widgets.map((w) => w.id); - expect(ids).toHaveLength(16); + expect(ids).toHaveLength(24); for (const iosId of DASHBOARD_IOS_ONLY_WIDGET_IDS) { expect(ids).not.toContain(iosId); } diff --git a/src/lib/dashboard-layout.ts b/src/lib/dashboard-layout.ts index 139814d2f..6e3b5e8ec 100644 --- a/src/lib/dashboard-layout.ts +++ b/src/lib/dashboard-layout.ts @@ -42,6 +42,18 @@ export const DASHBOARD_WIDGET_IDS = [ // cue is discoverable from the dashboard rather than hidden // behind Settings. "recentWorkouts", + // v1.11.2 B5 — the v1.10 additive HealthKit signals become pinnable. + // Each maps 1:1 to a MeasurementType the server already stores; + // default-invisible so existing dashboards stay unchanged until the + // user opts in via Settings → Dashboard. + "cardioRecovery", + "sixMinuteWalk", + "stairAscentSpeed", + "stairDescentSpeed", + "breathingDisturbances", + "wristTemperature", + "falls", + "walkingSteadiness", ] as const; export type DashboardWidgetId = (typeof DASHBOARD_WIDGET_IDS)[number]; @@ -323,6 +335,22 @@ export const DEFAULT_DASHBOARD_LAYOUT: DashboardLayout = { // workouts list and renders an Apple-Health onboarding hint // otherwise. { id: "recentWorkouts", visible: true, tileVisible: true, order: 15 }, + // v1.11.2 B5 — v1.10 additive HealthKit signals, pinnable but + // default-invisible on both surfaces. The user opts in via + // Settings → Dashboard; each tile self-gates on having any sample. + { id: "cardioRecovery", visible: false, tileVisible: false, order: 16 }, + { id: "sixMinuteWalk", visible: false, tileVisible: false, order: 17 }, + { id: "stairAscentSpeed", visible: false, tileVisible: false, order: 18 }, + { id: "stairDescentSpeed", visible: false, tileVisible: false, order: 19 }, + { + id: "breathingDisturbances", + visible: false, + tileVisible: false, + order: 20, + }, + { id: "wristTemperature", visible: false, tileVisible: false, order: 21 }, + { id: "falls", visible: false, tileVisible: false, order: 22 }, + { id: "walkingSteadiness", visible: false, tileVisible: false, order: 23 }, ], };