chore(release): v1.28.35 — timestamps honor the profile timezone

Client-rendered times fell back to the hardcoded display zone
(Europe/Berlin) because neither useFormatters() nor the legacy format.ts
helpers ever received users.timezone. A third localStorage mirror
(timezone-mirror.ts, following the time-format/date-format pattern)
carries the validated profile zone from /api/auth/me into both read
paths; the fallback chain is strictly profile → Europe/Berlin — never
the browser zone, so screen, PDF, and export cannot disagree.
makeFormatters validates its zone before Intl (a poison value falls back
instead of throwing inside every render).

Same pass, per the two-document audit: formatUpdatedLabel boundary and
clock now resolve one zone in every state (the undefined-tz browser hole
is closed); @db.Date calendar fields render UTC-pinned so profiles west
of UTC cannot shift a day; dose-history day headings and GLP-1 weekday
labels derive from the display zone; chart day/month labels are
encoding-derived and UTC-pinned (bucket math untouched, byte-identical
for Berlin profiles); the clinician share view renders in the patient's
timezone instead of the container's; Playwright pins Europe/Berlin.
Server day-key semantics, streak anchoring, insight cache keys, and
verbatim wall-clock strings are untouched by construction and pinned by
the new Auckland/New_York/Manila + DST test matrix.
This commit is contained in:
Marc-André Bombeck committed 2026-07-14 20:19:23 +02:00
1 parent 136653e3d7
commit 9bbd8a89dc
41 files changed
+958 -90

No files matched your search

+7
View File
@@ -18,6 +18,7 @@ import type {
} from "@/lib/format-locale";
import { isTimeFormatPreference, storeTimeFormat } from "@/lib/time-format";
import { isDateFormatPreference, storeDateFormat } from "@/lib/date-format";
import { storeTimezone } from "@/lib/timezone-mirror";
import type { ModuleKey } from "@/lib/modules/registry";
import type { TourProgress } from "@/lib/onboarding/tour-progress";
import { clearOfflineCachesForSessionEnd } from "@/lib/pwa/query-persister";
@@ -241,6 +242,12 @@ async function fetchMe(): Promise<AuthUser> {
? data.dateFormat
: "AUTO";
storeDateFormat(dateFormat);
// Profile timezone (issue #490): mirror into localStorage so
// `useFormatters()` and the legacy format helpers render timestamps in
// the user's zone. `storeTimezone` validates the IANA name and CLEARS
// the mirror on garbage, so a poison value falls back to Berlin instead
// of reaching `Intl.DateTimeFormat` (RangeError).
storeTimezone(typeof data.timezone === "string" ? data.timezone : "");
// A confirmed session: refresh the non-sensitive offline-relaunch marker.
storeWasAuthenticated();
return {