fix(hrv): keep RMSSD on the display surfaces for an account with no SDNN

Fitbit and Google Health HRV is filed as RMSSD since migration 0420, so
an account whose only HRV comes from them lost it wherever a display
surface read SDNN alone. The dashboard HRV tile keeps its RMSSD fallback
with Recovery off while the account has no SDNN, and its toggle stays
offered for either estimator. A briefing HRV signal counts as measured
today when either series has a reading. The doctor report already files
RMSSD as its own ungated leaf; a guard now holds that. Readiness, HRV
balance and stress stay on SDNN; a test pins that an RMSSD-only account
renormalises over the other components.
This commit is contained in:
Marc-André Bombeck committed 2026-10-21 14:32:56 +02:00
1 parent 8d16799479
commit bf3830203f
18 files changed
+232 -66

No files matched your search

+3 -3
View File
@@ -164,9 +164,9 @@ export const HRV_LABEL_KEY: Record<HrvSummaryType, string> = {
* Which HRV series backs the dashboard tile.
*
* HRV is stored under two types and they are not the same measure: Apple
* Health / Fitbit / Google Health write SDNN as `HEART_RATE_VARIABILITY`,
* while Oura / Polar / WHOOP — and a Garmin-style feed pushed in through a
* bridge — write nightly RMSSD as `HRV_RMSSD`. Every other HRV surface
* Health and Health Connect write SDNN as `HEART_RATE_VARIABILITY`, while
* Oura / Polar / WHOOP / Fitbit / Google Health — and a Garmin-style feed
* pushed in through a bridge — write nightly RMSSD as `HRV_RMSSD`. Every other HRV surface
* already unions the two: `/insights/hrv` passes `fallbackMeasurementType`
* (`app/insights/hrv/page.tsx`), the Coach resolves both
* (`COACH_SOURCE_MEASUREMENT_TYPES.hrv`), and the MCP reads swap through
@@ -59,9 +59,9 @@ export interface HealthKitMetricPageProps {
/**
* v1.17.0 — optional secondary MeasurementType the page falls back to
* when the primary type has no rows but the fallback does. Used by HRV:
* the primary `HEART_RATE_VARIABILITY` (SDNN, Apple / Fitbit) is empty
* for a ring / strap user whose nightly HRV is stored as `HRV_RMSSD`
* (Oura / Polar / WHOOP). When the swap is active the chart, stat strip,
* the primary `HEART_RATE_VARIABILITY` (SDNN, Apple Health / Health
* Connect) is empty for a ring / strap user whose nightly HRV is stored as
* `HRV_RMSSD` (Oura / Polar / WHOOP / Fitbit / Google Health). When the swap is active the chart, stat strip,
* "all values" list, and diversity nudge key off the fallback type and
* `fallbackMeasureLabel` names the measure so the two are never silently
* merged. Omit it and every other page renders byte-identically.