diff --git a/messages/de.json b/messages/de.json index 741542e11..d1bfa9bd9 100644 --- a/messages/de.json +++ b/messages/de.json @@ -3030,6 +3030,9 @@ "baselineWithin": "Dein üblicher Bereich liegt bei {low}–{high} {unit}; heute liegt {value} darin.", "baselineAbove": "Dein üblicher Bereich liegt bei {low}–{high} {unit}; heute liegt {value} darüber.", "baselineBelow": "Dein üblicher Bereich liegt bei {low}–{high} {unit}; heute liegt {value} darunter.", + "steadyWithin": "Deine Werte liegen meist bei {low} {unit}; heute passt {value} dazu.", + "steadyAbove": "Deine Werte liegen meist bei {low} {unit}; heute liegt {value} darüber.", + "steadyBelow": "Deine Werte liegen meist bei {low} {unit}; heute liegt {value} darunter.", "insufficient": "Lerne noch deinen üblichen Bereich — protokolliere noch ein paar Tage weiter.", "driver": "{note}" }, diff --git a/messages/en.json b/messages/en.json index 931af8495..d193ae5ec 100644 --- a/messages/en.json +++ b/messages/en.json @@ -3030,6 +3030,9 @@ "baselineWithin": "Your usual range is {low}–{high} {unit}; today's {value} sits within it.", "baselineAbove": "Your usual range is {low}–{high} {unit}; today's {value} sits above it.", "baselineBelow": "Your usual range is {low}–{high} {unit}; today's {value} sits below it.", + "steadyWithin": "Your readings usually sit at {low} {unit}; today's {value} matches.", + "steadyAbove": "Your readings usually sit at {low} {unit}; today's {value} is above that.", + "steadyBelow": "Your readings usually sit at {low} {unit}; today's {value} is below that.", "insufficient": "Still learning your usual range — keep logging for a few more days.", "driver": "{note}" }, diff --git a/messages/es.json b/messages/es.json index 18069350a..7892f3e77 100644 --- a/messages/es.json +++ b/messages/es.json @@ -3030,6 +3030,9 @@ "baselineWithin": "Tu rango habitual es {low}–{high} {unit}; hoy {value} se sitúa dentro de él.", "baselineAbove": "Tu rango habitual es {low}–{high} {unit}; hoy {value} se sitúa por encima.", "baselineBelow": "Tu rango habitual es {low}–{high} {unit}; hoy {value} se sitúa por debajo.", + "steadyWithin": "Tus valores suelen estar en {low} {unit}; hoy {value} coincide.", + "steadyAbove": "Tus valores suelen estar en {low} {unit}; hoy {value} está por encima.", + "steadyBelow": "Tus valores suelen estar en {low} {unit}; hoy {value} está por debajo.", "insufficient": "Todavía estoy aprendiendo tu rango habitual — sigue registrando unos días más.", "driver": "{note}" }, diff --git a/messages/fr.json b/messages/fr.json index 35008a2ac..1d67565f3 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -3030,6 +3030,9 @@ "baselineWithin": "Ta plage habituelle est de {low}–{high} {unit} ; aujourd'hui, {value} s'y situe.", "baselineAbove": "Ta plage habituelle est de {low}–{high} {unit} ; aujourd'hui, {value} se situe au-dessus.", "baselineBelow": "Ta plage habituelle est de {low}–{high} {unit} ; aujourd'hui, {value} se situe en dessous.", + "steadyWithin": "Tes valeurs sont habituellement à {low} {unit} ; aujourd'hui, {value} correspond.", + "steadyAbove": "Tes valeurs sont habituellement à {low} {unit} ; aujourd'hui, {value} se situe au-dessus.", + "steadyBelow": "Tes valeurs sont habituellement à {low} {unit} ; aujourd'hui, {value} se situe en dessous.", "insufficient": "J'apprends encore ta plage habituelle — continue à enregistrer quelques jours de plus.", "driver": "{note}" }, diff --git a/messages/it.json b/messages/it.json index b4ec05570..b39391e9c 100644 --- a/messages/it.json +++ b/messages/it.json @@ -3030,6 +3030,9 @@ "baselineWithin": "Il tuo intervallo abituale è {low}–{high} {unit}; oggi {value} rientra al suo interno.", "baselineAbove": "Il tuo intervallo abituale è {low}–{high} {unit}; oggi {value} si colloca al di sopra.", "baselineBelow": "Il tuo intervallo abituale è {low}–{high} {unit}; oggi {value} si colloca al di sotto.", + "steadyWithin": "I tuoi valori sono di solito {low} {unit}; oggi {value} corrisponde.", + "steadyAbove": "I tuoi valori sono di solito {low} {unit}; oggi {value} è al di sopra.", + "steadyBelow": "I tuoi valori sono di solito {low} {unit}; oggi {value} è al di sotto.", "insufficient": "Sto ancora imparando il tuo intervallo abituale — continua a registrare per qualche giorno in più.", "driver": "{note}" }, diff --git a/messages/ko.json b/messages/ko.json index be44f2329..3365b5585 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -3030,6 +3030,9 @@ "baselineWithin": "평소 범위: {low}–{high} {unit}. 오늘 값 {value} — 범위 안이에요.", "baselineAbove": "평소 범위: {low}–{high} {unit}. 오늘 값 {value} — 범위 위예요.", "baselineBelow": "평소 범위: {low}–{high} {unit}. 오늘 값 {value} — 범위 아래예요.", + "steadyWithin": "평소 값: {low} {unit}. 오늘 값 {value} — 평소와 같아요.", + "steadyAbove": "평소 값: {low} {unit}. 오늘 값 {value} — 평소보다 높아요.", + "steadyBelow": "평소 값: {low} {unit}. 오늘 값 {value} — 평소보다 낮아요.", "insufficient": "아직 평소 범위를 배우는 중이에요 — 며칠 더 기록해 주세요.", "driver": "{note}" }, diff --git a/messages/pl.json b/messages/pl.json index dfc1646b7..9602d2454 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -3030,6 +3030,9 @@ "baselineWithin": "Twój zwykły zakres to {low}–{high} {unit}; dzisiejsza wartość {value} mieści się w nim.", "baselineAbove": "Twój zwykły zakres to {low}–{high} {unit}; dzisiejsza wartość {value} jest powyżej niego.", "baselineBelow": "Twój zwykły zakres to {low}–{high} {unit}; dzisiejsza wartość {value} jest poniżej niego.", + "steadyWithin": "Twoje pomiary zwykle wynoszą {low} {unit}; dzisiejsza wartość {value} jest taka sama.", + "steadyAbove": "Twoje pomiary zwykle wynoszą {low} {unit}; dzisiejsza wartość {value} jest wyższa.", + "steadyBelow": "Twoje pomiary zwykle wynoszą {low} {unit}; dzisiejsza wartość {value} jest niższa.", "insufficient": "Wciąż poznaję twój zwykły zakres — rejestruj jeszcze przez kilka dni.", "driver": "{note}" }, diff --git a/src/components/insights/derived/__tests__/coach-read-strip-steady-band.test.tsx b/src/components/insights/derived/__tests__/coach-read-strip-steady-band.test.tsx new file mode 100644 index 000000000..9023a1c9f --- /dev/null +++ b/src/components/insights/derived/__tests__/coach-read-strip-steady-band.test.tsx @@ -0,0 +1,72 @@ +import { describe, it, expect, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +import { I18nProvider } from "@/lib/i18n/context"; +import { queryKeys } from "@/lib/query-keys"; +import type { CoachReadStripData } from "@/lib/insights/derived/coach-read-shape"; + +/** + * A band whose two ends format to the same figure is a steady value. + * + * With many identical daily readings the robust band collapses, and the strip + * read "Your usual range is 61–61 bpm". It now names the one value instead, + * and keeps the placement of today's reading. + */ + +vi.mock("@/hooks/use-auth", () => ({ + useAuth: () => ({ + user: { timezone: "UTC", dateOfBirth: null, gender: null }, + isAuthenticated: true, + }), +})); + +vi.mock("@/hooks/use-mounted", () => ({ useMounted: () => true })); + +import { CoachReadStrip } from "../coach-read-strip"; + +const METRIC = "RESTING_HEART_RATE"; + +function render(data: CoachReadStripData): string { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: Infinity } }, + }); + client.setQueryData(queryKeys.insightsCoachRead(METRIC, "en"), data); + return renderToStaticMarkup( + + + + + , + ); +} + +function band( + low: number, + high: number, + latest: number, + placement: "within" | "above" | "below", +): CoachReadStripData { + return { + baseline: { low, high, latest, placement, sampleDays: 30 }, + learning: false, + driver: null, + }; +} + +describe("CoachReadStrip — steady band", () => { + it("names one value when both ends format alike", () => { + const markup = render(band(61, 61.04, 61, "within")); + expect(markup).not.toContain("61–61"); + expect(markup).toContain("usually sit at 61"); + }); + + it("keeps the placement for a steady value", () => { + expect(render(band(61, 61, 66, "above"))).toContain("is above that"); + expect(render(band(61, 61, 57, "below"))).toContain("is below that"); + }); + + it("still shows a real range as a range", () => { + expect(render(band(58, 64, 61, "within"))).toContain("58–64"); + }); +}); diff --git a/src/components/insights/derived/coach-read-strip.tsx b/src/components/insights/derived/coach-read-strip.tsx index 1558ecbbd..c529823b8 100644 --- a/src/components/insights/derived/coach-read-strip.tsx +++ b/src/components/insights/derived/coach-read-strip.tsx @@ -106,8 +106,16 @@ export function CoachReadStrip({ return t("insights.coach.readStrip.insufficient"); } const { low, high, latest, placement } = data.baseline; - const key = - placement === "above" + // A band whose ends format to the same figure ("61–61 bpm") is a steady + // value, not a range; say it as one. + const steady = fmt(low) === fmt(high); + const key = steady + ? placement === "above" + ? "insights.coach.readStrip.steadyAbove" + : placement === "below" + ? "insights.coach.readStrip.steadyBelow" + : "insights.coach.readStrip.steadyWithin" + : placement === "above" ? "insights.coach.readStrip.baselineAbove" : placement === "below" ? "insights.coach.readStrip.baselineBelow"