diff --git a/messages/de.json b/messages/de.json index 8f654ac76..6e9a48271 100644 --- a/messages/de.json +++ b/messages/de.json @@ -3509,18 +3509,6 @@ "description": "Wie gut sich dein Körper erholt hat – die Erholungs-, Belastungs- und Load-Signale, die dein Wearable über Nacht aufzeichnet.", "emptyTitle": "Noch keine Erholungsdaten", "empty": "Noch keine Erholungsdaten. Verbinde ein WHOOP-, Polar- oder Oura-Konto, dann füllen sich diese Werte, sobald dein Gerät synchronisiert.", - "recharge": { - "title": "Recharge", - "subtitle": "Ladung des autonomen Nervensystems" - }, - "strain": { - "title": "Belastung & Load", - "subtitle": "Tägliche kardiovaskuläre Belastung" - }, - "cardio": { - "title": "Herz & Energie pro Tag", - "subtitle": "Herz- und Energiewerte des ganzen Tages" - }, "block": { "ansChargeExplainer": "Wie aufgeladen dein autonomes Nervensystem in den Tag startet – ein Recharge-Wert aus deinen nächtlichen Herz- und Atemsignalen.", "dayStrainExplainer": "Die kardiovaskuläre Tagesbelastung auf der Belastungsskala deines Geräts. Lies sie im Verhältnis zu deiner Erholung, nicht als Ziel für sich.", diff --git a/messages/en.json b/messages/en.json index accbf337f..c33810296 100644 --- a/messages/en.json +++ b/messages/en.json @@ -3509,18 +3509,6 @@ "description": "How well your body has bounced back — the recovery, strain and load signals your wearable records overnight.", "emptyTitle": "No recovery data yet", "empty": "No recovery data yet. Connect a WHOOP, Polar or Oura account and these scores fill in once your device syncs.", - "recharge": { - "title": "Recharge", - "subtitle": "Autonomic-nervous-system charge" - }, - "strain": { - "title": "Strain & load", - "subtitle": "Daily cardiovascular load" - }, - "cardio": { - "title": "Daily heart & energy", - "subtitle": "Whole-day cardiac and energy totals" - }, "block": { "ansChargeExplainer": "How charged your autonomic nervous system is for the day ahead — a recharge reading drawn from your overnight heart and breathing signals.", "dayStrainExplainer": "The day's whole-body cardiovascular load on your device's strain scale. Read it against your recovery, not as a goal on its own.", diff --git a/messages/es.json b/messages/es.json index 9fa3e2b1e..0a1b03839 100644 --- a/messages/es.json +++ b/messages/es.json @@ -3509,18 +3509,6 @@ "description": "Cómo se ha recuperado tu cuerpo: las señales de recuperación, esfuerzo y carga que tu wearable registra durante la noche.", "emptyTitle": "Aún no hay datos de recuperación", "empty": "Aún no hay datos de recuperación. Conecta una cuenta de WHOOP, Polar u Oura y estas puntuaciones se completarán cuando tu dispositivo sincronice.", - "recharge": { - "title": "Recarga", - "subtitle": "Carga del sistema nervioso autónomo" - }, - "strain": { - "title": "Esfuerzo y carga", - "subtitle": "Carga cardiovascular diaria" - }, - "cardio": { - "title": "Corazón y energía del día", - "subtitle": "Totales cardiacos y de energía de todo el día" - }, "block": { "ansChargeExplainer": "Cuán cargado está tu sistema nervioso autónomo para el día — una lectura de recarga obtenida de tus señales nocturnas de corazón y respiración.", "dayStrainExplainer": "La carga cardiovascular del día en la escala de esfuerzo de tu dispositivo. Léela en relación con tu recuperación, no como una meta en sí.", diff --git a/messages/fr.json b/messages/fr.json index 9c1a433c0..38a840ea8 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -3509,18 +3509,6 @@ "description": "À quel point votre corps a récupéré : les signaux de récupération, de charge et d'effort que votre wearable enregistre la nuit.", "emptyTitle": "Pas encore de données de récupération", "empty": "Pas encore de données de récupération. Connectez un compte WHOOP, Polar ou Oura et ces scores se rempliront dès que votre appareil se synchronise.", - "recharge": { - "title": "Recharge", - "subtitle": "Charge du système nerveux autonome" - }, - "strain": { - "title": "Effort et charge", - "subtitle": "Charge cardiovasculaire quotidienne" - }, - "cardio": { - "title": "Cœur et énergie du jour", - "subtitle": "Totaux cardiaques et énergétiques de la journée" - }, "block": { "ansChargeExplainer": "À quel point votre système nerveux autonome est rechargé pour la journée — une mesure de recharge issue de vos signaux nocturnes de cœur et de respiration.", "dayStrainExplainer": "La charge cardiovasculaire de la journée sur l'échelle d'effort de votre appareil. Lisez-la au regard de votre récupération, pas comme un objectif en soi.", diff --git a/messages/it.json b/messages/it.json index 77ba27740..489c26670 100644 --- a/messages/it.json +++ b/messages/it.json @@ -3509,18 +3509,6 @@ "description": "Quanto bene il tuo corpo si è ripreso: i segnali di recupero, sforzo e carico che il tuo wearable registra durante la notte.", "emptyTitle": "Ancora nessun dato di recupero", "empty": "Ancora nessun dato di recupero. Collega un account WHOOP, Polar o Oura e questi punteggi si popoleranno appena il dispositivo si sincronizza.", - "recharge": { - "title": "Ricarica", - "subtitle": "Carica del sistema nervoso autonomo" - }, - "strain": { - "title": "Sforzo e carico", - "subtitle": "Carico cardiovascolare giornaliero" - }, - "cardio": { - "title": "Cuore ed energia del giorno", - "subtitle": "Totali cardiaci ed energetici dell'intera giornata" - }, "block": { "ansChargeExplainer": "Quanto è carico il tuo sistema nervoso autonomo per la giornata — una lettura di ricarica ricavata dai segnali notturni di cuore e respiro.", "dayStrainExplainer": "Il carico cardiovascolare della giornata sulla scala di sforzo del tuo dispositivo. Leggilo in rapporto al tuo recupero, non come obiettivo a sé.", diff --git a/messages/pl.json b/messages/pl.json index 6bd2a8336..9b66dda9e 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -3509,18 +3509,6 @@ "description": "Jak dobrze Twój organizm się zregenerował – sygnały regeneracji, obciążenia i load, które Twoje urządzenie rejestruje w nocy.", "emptyTitle": "Brak danych o regeneracji", "empty": "Brak danych o regeneracji. Połącz konto WHOOP, Polar lub Oura, a te wyniki uzupełnią się po synchronizacji urządzenia.", - "recharge": { - "title": "Recharge", - "subtitle": "Ładowanie autonomicznego układu nerwowego" - }, - "strain": { - "title": "Obciążenie i load", - "subtitle": "Dzienne obciążenie sercowo-naczyniowe" - }, - "cardio": { - "title": "Serce i energia w ciągu dnia", - "subtitle": "Całodobowe sumy sercowe i energetyczne" - }, "block": { "ansChargeExplainer": "Jak naładowany jest Twój autonomiczny układ nerwowy na dany dzień — odczyt regeneracji oparty na nocnych sygnałach serca i oddechu.", "dayStrainExplainer": "Dzienne obciążenie sercowo-naczyniowe na skali wysiłku Twojego urządzenia. Odczytuj je w odniesieniu do regeneracji, nie jako cel sam w sobie.", diff --git a/src/components/insights/__tests__/device-score-surfaces.test.tsx b/src/components/insights/__tests__/device-score-surfaces.test.tsx index 73ae8e968..0018b2cab 100644 --- a/src/components/insights/__tests__/device-score-surfaces.test.tsx +++ b/src/components/insights/__tests__/device-score-surfaces.test.tsx @@ -206,7 +206,7 @@ describe(" data-gating", () => { // Routed through the shared (dashed bordered card), not the // old hand-rolled div — the empty title comes from the unified primitive. expect(html).toContain("border-dashed"); - expect(html).not.toContain('data-slot="recovery-group-strain"'); + expect(html).not.toContain('data-slot="recovery-block-DAY_STRAIN"'); }); it("renders a canonical chart block for the present signal", () => { @@ -218,13 +218,18 @@ describe(" data-gating", () => { }), ); const html = render(); - expect(html).toContain('data-slot="recovery-group-strain"'); + // v1.18.6 — the per-group sub-headings are gone; present blocks render as + // one flat stack. The data gate now reads at the block level. expect(html).toContain('data-slot="recovery-block-DAY_STRAIN"'); // B3 — the redundant "Recovery score" cross-link block is gone; this // surface is reached from the overview already. expect(html).not.toContain('data-slot="recovery-score-link"'); - // The recharge group (ANS charge only) has no data → hidden. - expect(html).not.toContain('data-slot="recovery-group-recharge"'); + // The recharge group's only signal (ANS charge) has no data → its block + // is dropped. + expect(html).not.toContain('data-slot="recovery-block-ANS_CHARGE"'); + // The removed sub-headings must not reappear. + expect(html).not.toContain('data-slot="recovery-group-strain"'); + expect(html).not.toContain("Strain & load"); // Empty note must not show when at least one signal is present. expect(html).not.toContain('data-slot="recovery-empty"'); }); diff --git a/src/components/insights/recovery/recovery-metric-block.tsx b/src/components/insights/recovery/recovery-metric-block.tsx index 09265d492..88527e76e 100644 --- a/src/components/insights/recovery/recovery-metric-block.tsx +++ b/src/components/insights/recovery/recovery-metric-block.tsx @@ -77,12 +77,12 @@ export function RecoveryMetricBlock({ className="text-muted-foreground size-4 shrink-0" aria-hidden="true" /> - {/* v1.18.1 — block title is an `

` (kept at `text-base`): it - nests under the group `SectionHeading` `

`, which itself - sits under the page `

` from `SubPageShell`. Demoting it - keeps the recovery page's heading outline strictly nested - (h1 → h2 → h3) instead of stacking two sibling `

`s. */} -

{title}

+ {/* v1.18.6 — block title is an `

` (at `text-base`): the per-group + sub-headings were removed, so each block now sits directly under + the page `

` from `SubPageShell`. Promoting it from `

` keeps + the recovery page's heading outline strictly nested (h1 → h2) + with no skipped level. */} +

{title}

{explainer} diff --git a/src/components/insights/recovery/recovery-section.tsx b/src/components/insights/recovery/recovery-section.tsx index 9fa4ba138..701442858 100644 --- a/src/components/insights/recovery/recovery-section.tsx +++ b/src/components/insights/recovery/recovery-section.tsx @@ -14,7 +14,6 @@ import { useInsightsAnalytics } from "@/hooks/use-insights-analytics"; import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query"; import { useTranslations } from "@/lib/i18n/context"; import { EmptyState } from "@/components/ui/empty-state"; -import { SectionHeading } from "@/components/insights/section-heading"; import { ChartSkeleton } from "@/components/charts/chart-skeleton"; import { RestModeBanner } from "@/components/insights/rest-mode-banner"; import type { RestModeAnnotation } from "@/lib/analytics/health-score"; @@ -36,9 +35,6 @@ interface BlockSpec { interface BlockGroup { key: string; - headingIcon: typeof Activity; - title: string; - subtitle: string; blocks: BlockSpec[]; } @@ -84,9 +80,6 @@ export function RecoverySection() { const groups: BlockGroup[] = [ { key: "recharge", - headingIcon: Battery, - title: t("insights.recovery.recharge.title"), - subtitle: t("insights.recovery.recharge.subtitle"), blocks: [ { type: "ANS_CHARGE", @@ -103,9 +96,6 @@ export function RecoverySection() { }, { key: "strain", - headingIcon: Gauge, - title: t("insights.recovery.strain.title"), - subtitle: t("insights.recovery.strain.subtitle"), blocks: [ { type: "DAY_STRAIN", @@ -143,9 +133,6 @@ export function RecoverySection() { }, { key: "cardio", - headingIcon: HeartPulse, - title: t("insights.recovery.cardio.title"), - subtitle: t("insights.recovery.cardio.subtitle"), blocks: [ { type: "AVERAGE_HEART_RATE", @@ -190,10 +177,6 @@ export function RecoverySection() { if (isLoading || summaries == null) { return (

-
); @@ -210,51 +193,36 @@ export function RecoverySection() { ); } + // v1.18.6 — the per-group sub-headings ("Recharge" / "Strain & load" / + // "Daily heart & energy") are gone: read next to the page's "Erholung" + // heading they were redundant chrome. The groups still drive ORDER + the + // data gate (a group with no present block contributes nothing); their + // present blocks now render as one flat, evenly-spaced stack, each block + // carrying its own title + explainer so nothing loses context. + const presentBlocks = groups.flatMap((group) => + group.blocks.filter((block) => (summaries[block.type]?.count ?? 0) > 0), + ); + return (
{/* v1.18.1 — calm Rest Mode cue at the head of the recovery surface. Self-gating: renders nothing unless an episode is active. */} - {groups.map((group) => { - const present = group.blocks.filter( - (block) => (summaries[block.type]?.count ?? 0) > 0, - ); - if (present.length === 0) return null; - return ( -
- - {group.subtitle} -

- } - /> -
- {present.map((block) => ( - - ))} -
-
- ); - })} + {presentBlocks.map((block) => ( + + ))}
); }