From e1ca6d9da22755b0c5cc4be2bae4e943d0227e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 31 Jul 2026 07:55:20 +0200 Subject: [PATCH] Work out the checkup due phrase on the same clock as the date beside it The phrase read the device clock while the date printed under it read the profile clock. Set a laptop to another zone and the card could say a checkup was tomorrow above a date that was today. The zone is now something a caller has to state, and both screens state the one their dates are printed in. --- ...sorge-due-bucket-single-home-guard.test.ts | 45 +++++ .../vorsorge-dashboard-card.tsx | 7 +- .../vorsorge-section.tsx | 5 +- .../__tests__/due-day.test.ts | 159 ++++++++++-------- src/lib/measurement-reminders/due-day.ts | 30 +++- 5 files changed, 163 insertions(+), 83 deletions(-) diff --git a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts index e65751449..21e0dec33 100644 --- a/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts +++ b/src/__tests__/vorsorge-due-bucket-single-home-guard.test.ts @@ -52,6 +52,9 @@ const MINTED_ONLY_BY_THE_BUCKET = [ "measurementReminders.nextDue.none", ]; +/** The hook that yields the zone the surrounding dates are printed in. */ +const DISPLAY_ZONE_HOOK = "useDisplayTimezone"; + const FIX_IT = [ `The due-day bucket lives in src/${HOME} and nowhere else.`, `Import { relativeDueKey } ${HOME_IMPORT} instead of writing a second one.`, @@ -59,6 +62,14 @@ const FIX_IT = [ "and the dashboard card read that body, and they must agree.", ].join(" "); +const FIX_THE_ZONE = [ + "The due phrase has to be worked out in the same zone as the date printed", + `beside it. Read ${DISPLAY_ZONE_HOOK}() and pass it as the third argument to`, + "relativeDueKey. Do not reach for the device clock and do not give the", + "parameter a default: a caller that stays silent is exactly how the phrase", + "and the date drifted a day apart in the first place.", +].join(" "); + function sourceFiles(): string[] { return globSync("**/*.{ts,tsx}", { cwd: SRC }) .map((p) => p.split(sep).join("/")) @@ -121,3 +132,37 @@ describe("the checkup due-day bucket has one home", () => { } }); }); + +describe("the due-day bucket asks the same clock as the date beside it", () => { + it("never floors a day against the device clock", () => { + // `startOfLocalDayInTz(instant, undefined)` means "whatever zone this + // machine is set to". That is the reading the phrase used to take while + // the date next to it took the profile zone. + const home = read(HOME); + + expect(home, FIX_THE_ZONE).not.toMatch( + /startOfLocalDayInTz\([^)]*undefined/, + ); + }); + + it("takes the zone as an argument with no default to fall back on", () => { + const home = read(HOME); + const signature = home.slice( + home.indexOf("export function relativeDueKey"), + home.indexOf("): RelativeDue"), + ); + + expect(signature, FIX_THE_ZONE).toContain("timeZone: string"); + // `timeZone: string = …` or `timeZone = …` would let a caller stay silent. + expect(signature, FIX_THE_ZONE).not.toMatch(/timeZone[^,)]*=/); + }); + + it("is called by surfaces that read the printed date's zone", () => { + const silent = sourceFiles() + .filter((rel) => rel !== HOME) + .filter((rel) => read(rel).includes("relativeDueKey(")) + .filter((rel) => !read(rel).includes(DISPLAY_ZONE_HOOK)); + + expect(silent, FIX_THE_ZONE).toEqual([]); + }); +}); diff --git a/src/components/measurement-reminders/vorsorge-dashboard-card.tsx b/src/components/measurement-reminders/vorsorge-dashboard-card.tsx index 50d820432..3db90c565 100644 --- a/src/components/measurement-reminders/vorsorge-dashboard-card.tsx +++ b/src/components/measurement-reminders/vorsorge-dashboard-card.tsx @@ -19,7 +19,7 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { CalendarClock, CheckCircle2, Plus } from "lucide-react"; -import { useTranslations } from "@/lib/i18n/context"; +import { useTranslations, useDisplayTimezone } from "@/lib/i18n/context"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -50,6 +50,9 @@ function resolveLabel( export function VorsorgeDashboardCard() { const { t } = useTranslations(); + // Issue #490 — the same profile zone the checkups page derives its due + // phrase in, so the two screens still agree when the device sits elsewhere. + const displayTz = useDisplayTimezone(); const router = useRouter(); const { data: reminders, isLoading } = useMeasurementReminders(); const { satisfy } = useMeasurementReminderMutations(); @@ -112,7 +115,7 @@ export function VorsorgeDashboardCard() { ) : (