diff --git a/messages/de.json b/messages/de.json index 67e3f9651..a2d2e6d9e 100644 --- a/messages/de.json +++ b/messages/de.json @@ -2748,8 +2748,8 @@ "last7days": "letzte 7 Tage", "last30days": "letzte 30 Tage", "last90days": "letzte 90 Tage", - "lastYear": "Jahresrückblick", - "allTime": "gesamter Zeitraum" + "lastYear": "letzte 12 Monate", + "allTime": "gesamter Zeitraum (12 Monate im Detail)" }, "windowLabel": "Zeitraum", "settingsAriaLabel": "Coach-Einstellungen", @@ -3047,7 +3047,7 @@ "loadError": "Diese Messgröße konnte gerade nicht geladen werden.", "blutdruckDescription": "Dein systolisch-/diastolisch-Verlauf mit altersangepassten Zielbereichen und einer textbasierten Einschätzung.", "gewichtDescription": "Gewichtsverlauf gegen das aus deiner Größe abgeleitete gesunde Band, mit Einschätzung.", - "pulsDescription": "Pulsverlauf gegenüber dem persönlichen Karvonen-Zielband, mit Einschätzung.", + "pulsDescription": "Ruhepuls im Vergleich zu einem alters- und geschlechtsabhängigen Referenzband aus CDC/NCHS-Populationsperzentilen, mit Einschätzung.", "stimmungDescription": "Tägliche Stimmungswerte im Zeitverlauf mit einer textbasierten Einschätzung.", "medikamenteDescription": "Einnahme-Kalender pro Medikament plus eine textbasierte Einschätzung.", "bmiDescription": "BMI aus deinem Gewicht und deiner Größe abgeleitet, mit Einschätzung.", diff --git a/messages/en.json b/messages/en.json index 4d689f6bf..ba0a5ac87 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2748,8 +2748,8 @@ "last7days": "last 7 days", "last30days": "last 30 days", "last90days": "last 90 days", - "lastYear": "year so far", - "allTime": "all time" + "lastYear": "last 12 months", + "allTime": "all time (12 months of detail)" }, "windowLabel": "Window", "settingsAriaLabel": "Coach settings", @@ -3047,7 +3047,7 @@ "loadError": "Could not load this metric right now.", "blutdruckDescription": "Your systolic / diastolic trend with age-personalised target zones and a written assessment.", "gewichtDescription": "Weight trend mapped against your height-derived healthy band, plus a written assessment.", - "pulsDescription": "Pulse trend against the personalised Karvonen target band, plus a written assessment.", + "pulsDescription": "Resting pulse against an age- and sex-based reference band from CDC/NCHS population percentiles, plus a written assessment.", "stimmungDescription": "Daily mood scores over time with a written assessment.", "medikamenteDescription": "Per-medication adherence calendars and a written assessment.", "bmiDescription": "BMI derived from your weight and height, with a written assessment.", diff --git a/messages/es.json b/messages/es.json index da064100b..632a9f9ed 100644 --- a/messages/es.json +++ b/messages/es.json @@ -2748,8 +2748,8 @@ "last7days": "Últimos 7 días", "last30days": "Últimos 30 días", "last90days": "Últimos 90 días", - "lastYear": "lo que va de año", - "allTime": "Todo el tiempo" + "lastYear": "últimos 12 meses", + "allTime": "todo el tiempo (12 meses en detalle)" }, "windowLabel": "Periodo", "settingsAriaLabel": "Ajustes del coach", @@ -3047,7 +3047,7 @@ "loadError": "No se pudo cargar esta métrica ahora mismo.", "blutdruckDescription": "Tu evolución sistólica/diastólica con rangos objetivo ajustados a la edad y una valoración en texto.", "gewichtDescription": "Evolución del peso frente a la banda saludable derivada de tu altura, con valoración.", - "pulsDescription": "Resting pulse vs. the personalised Karvonen target band, plus a written assessment.", + "pulsDescription": "Pulso en reposo frente a una banda de referencia por edad y sexo basada en percentiles poblacionales de CDC/NCHS, con una valoración escrita.", "stimmungDescription": "Valores diarios de estado de ánimo en el tiempo, con una valoración en texto.", "medikamenteDescription": "Calendario de tomas por medicamento, más una valoración en texto.", "bmiDescription": "IMC derivado de tu peso y altura, con valoración.", diff --git a/messages/fr.json b/messages/fr.json index 19ff3c528..5e03401f1 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -2748,8 +2748,8 @@ "last7days": "7 derniers jours", "last30days": "30 derniers jours", "last90days": "90 derniers jours", - "lastYear": "depuis le début de l'année", - "allTime": "Toute la période" + "lastYear": "12 derniers mois", + "allTime": "toute la période (12 mois en détail)" }, "windowLabel": "Période", "settingsAriaLabel": "Paramètres du coach", @@ -3047,7 +3047,7 @@ "loadError": "Impossible de charger cette mesure pour le moment.", "blutdruckDescription": "Ton évolution systolique/diastolique avec des plages cibles adaptées à l’âge et une évaluation textuelle.", "gewichtDescription": "Évolution du poids face à la bande saine dérivée de ta taille, avec évaluation.", - "pulsDescription": "Resting pulse vs. the personalised Karvonen target band, plus a written assessment.", + "pulsDescription": "Pouls au repos comparé à une plage de référence selon l'âge et le sexe, issue des percentiles de population CDC/NCHS, avec une évaluation rédigée.", "stimmungDescription": "Valeurs d’humeur quotidiennes dans le temps, avec une évaluation textuelle.", "medikamenteDescription": "Calendrier des prises par médicament, ainsi qu’une évaluation textuelle.", "bmiDescription": "IMC dérivé de ton poids et de ta taille, avec évaluation.", diff --git a/messages/it.json b/messages/it.json index eb36f3640..eb02d4511 100644 --- a/messages/it.json +++ b/messages/it.json @@ -2748,8 +2748,8 @@ "last7days": "Ultimi 7 giorni", "last30days": "Ultimi 30 giorni", "last90days": "Ultimi 90 giorni", - "lastYear": "anno in corso", - "allTime": "Tutto il tempo" + "lastYear": "ultimi 12 mesi", + "allTime": "tutto il tempo (12 mesi in dettaglio)" }, "windowLabel": "Periodo", "settingsAriaLabel": "Impostazioni del coach", @@ -3047,7 +3047,7 @@ "loadError": "Impossibile caricare questa metrica al momento.", "blutdruckDescription": "Il tuo andamento sistolico/diastolico con range obiettivo adattati all’età e una valutazione testuale.", "gewichtDescription": "Andamento del peso rispetto alla fascia sana derivata dalla tua altezza, con valutazione.", - "pulsDescription": "Resting pulse vs. the personalised Karvonen target band, plus a written assessment.", + "pulsDescription": "Battito a riposo confrontato con una fascia di riferimento per età e sesso, basata sui percentili di popolazione CDC/NCHS, con una valutazione scritta.", "stimmungDescription": "Valori giornalieri di umore nel tempo, con una valutazione testuale.", "medikamenteDescription": "Calendario delle assunzioni per farmaco e una valutazione testuale.", "bmiDescription": "BMI derivato dal tuo peso e dalla tua altezza, con valutazione.", diff --git a/messages/pl.json b/messages/pl.json index 8976add7f..18514d304 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -2748,8 +2748,8 @@ "last7days": "Ostatnie 7 dni", "last30days": "Ostatnie 30 dni", "last90days": "Ostatnie 90 dni", - "lastYear": "od początku roku", - "allTime": "Cały okres" + "lastYear": "ostatnie 12 miesięcy", + "allTime": "cały okres (12 miesięcy szczegółowo)" }, "windowLabel": "Okres", "settingsAriaLabel": "Ustawienia coacha", @@ -3047,7 +3047,7 @@ "loadError": "Nie udało się teraz wczytać tej metryki.", "blutdruckDescription": "Twój przebieg skurczowego/rozkurczowego z zakresami docelowymi dopasowanymi do wieku i oceną tekstową.", "gewichtDescription": "Przebieg wagi na tle zdrowego pasma wyliczonego z twojego wzrostu, z oceną.", - "pulsDescription": "Resting pulse vs. the personalised Karvonen target band, plus a written assessment.", + "pulsDescription": "Tętno spoczynkowe na tle zakresu referencyjnego według wieku i płci, opartego na percentylach populacyjnych CDC/NCHS, wraz z oceną opisową.", "stimmungDescription": "Dzienne wartości nastroju w czasie wraz z oceną tekstową.", "medikamenteDescription": "Kalendarz przyjęć dla każdego leku plus ocena tekstowa.", "bmiDescription": "BMI obliczone z twojej wagi i wzrostu, z oceną.", diff --git a/src/components/insights/__tests__/hero-strip.test.tsx b/src/components/insights/__tests__/hero-strip.test.tsx index 7f59ad7b3..c13e4e984 100644 --- a/src/components/insights/__tests__/hero-strip.test.tsx +++ b/src/components/insights/__tests__/hero-strip.test.tsx @@ -241,12 +241,35 @@ describe("", () => { ); }); - it("renders the personal-baseline meta line", () => { - const html = render(); + it("renders the personal-baseline meta line under a real briefing", () => { + const html = render( + , + ); expect(html).toMatch(/data-slot="insights-hero-strip-baseline"/); expect(html).toContain("Based on your last 90 days"); }); + /** + * Inverted from the previous assertion, which rendered with + * `briefing={null}` and still expected the baseline line. + * + * That pinned the defect. The line claims the subtitle above it was drawn + * from the reader's own last 90 days, but with no briefing the subtitle is + * `heroFallbackSubtitle` — fixed copy computed from nothing. Both siblings in + * the same meta row (the freshness caption, the no-provider hint) already + * gate on their own data; this one did not, so an account with no briefing at + * all was told its generic sentence rested on a personal baseline. + */ + it("withholds the personal-baseline meta line when there is no briefing", () => { + const html = render(); + expect(html).not.toMatch(/data-slot="insights-hero-strip-baseline"/); + expect(html).not.toContain("Based on your last 90 days"); + // The fallback subtitle still renders — only the provenance claim goes. + expect(html).toContain( + "A daily read of your trends, drawn straight from the numbers you've logged.", + ); + }); + it("renders the freshness caption when updatedAt is supplied", () => { // v1.22 — the hero freshness line now uses `formatUpdatedLabel` for parity // with the briefing + per-metric cards: a same-day timestamp reads diff --git a/src/components/insights/hero-strip.tsx b/src/components/insights/hero-strip.tsx index 3ba16e578..ef92e9184 100644 --- a/src/components/insights/hero-strip.tsx +++ b/src/components/insights/hero-strip.tsx @@ -199,14 +199,24 @@ export function HeroStrip({
- - {t("insights.heroPersonalBaseline")} - + {/* The baseline line describes where the SUBTITLE came from, so it + may only show when the subtitle IS a briefing. Without one the + subtitle falls back to generic copy derived from nothing, and + this line went on claiming a personal baseline underneath it — + the one part of the row that was ungated while both its + siblings already checked for their own data first. */} + {briefing && ( + + {t("insights.heroPersonalBaseline")} + + )} {generatedLine && ( <> - + {briefing && ( + + )} {generatedLine} diff --git a/src/components/insights/metric-target-summary.tsx b/src/components/insights/metric-target-summary.tsx index 199711035..81fdc0277 100644 --- a/src/components/insights/metric-target-summary.tsx +++ b/src/components/insights/metric-target-summary.tsx @@ -28,8 +28,8 @@ import { apiGet } from "@/lib/api/api-fetch"; * nothing and a cold sub-page warms the cache its siblings reuse. We * never recompute the ranges here: the route is the single source of * truth for the age-based ESH blood-pressure band, the WHO weight / BMI - * band, the Karvonen pulse band, the AASM sleep band, the ADA / DDG - * glucose bands, and the medication / mood targets. + * band, the CDC/NCHS percentile pulse band, the AASM sleep band, the + * ADA / DDG glucose bands, and the medication / mood targets. * * The panel is a compact reference panel carrying the full target * context: diff --git a/src/lib/analytics/pulse-targets.ts b/src/lib/analytics/pulse-targets.ts index 4acc0ad56..fb3420941 100644 --- a/src/lib/analytics/pulse-targets.ts +++ b/src/lib/analytics/pulse-targets.ts @@ -68,14 +68,19 @@ export function getPersonalizedPulseTarget( age: number | null, gender: PulseGender, ): PersonalizedPulseTarget { - // AHA fallback for adults when profile context is missing. + // No age to personalise against (missing profile, or under 20 where the + // percentile table does not apply), so this arm returns a fixed band that is + // the same for everyone. The source label says so in as many words: the + // surrounding UI describes the pulse band as age- and sex-based, and a reader + // who falls into this arm needs to know theirs is not. if (age == null || age < 20) { return { greenMin: 60, greenMax: 100, orangeMin: 55, orangeMax: 105, - source: "AHA (adults, resting pulse 60-100 bpm)", + source: + "AHA general adult range (resting pulse 60-100 bpm) — not personalised", }; }