Pre-existing bug (predates the Khmer i18n work), surfaced by the i18n Gate-2 sweep.
Verified on demo.staging 6/6 + i18n suite 6/6 + staging smoke 5/5.
lib/utils.ts formatDate() / formatTime() called
toLocaleDateString/TimeString("en-US", …) with no timeZone, so they used the
runtime's zone — the Cloudflare Worker renders in UTC while the browser renders in the user's
local zone (ICT, UTC+7). Server HTML and client HTML therefore disagreed on every rendered time →
React #418 ("Text content does not match server-rendered HTML"). Reproduced on the POS landing,
whose register cards show session start/close times.
APP_TIME_ZONE = "Asia/Phnom_Penh" (NIX Cafe is a Cambodia-only POS —
times should be the shop's zone, not the viewer's browser). Applied to both helpers, which covers
32 call sites across 14 files, plus the remaining tz-less date renders (receipts, cash-movement dialog,
kitchen live-clock, subscription, Odoo month label) and the dashboard weekday label (UTC-parsed + UTC-formatted, since
it's a calendar date). 8 files, no migration.
Cambodia users see no change — the browser already rendered ICT. Only the server's output changed (UTC → ICT) so the two now agree. Verified the same instant formats identically under three runtime zones:
TZ=UTC → 06:30 AM | Jul 20, 2026 (the CF Worker / SSR) TZ=Asia/Phnom_Penh → 06:30 AM | Jul 20, 2026 (a Cambodia browser) TZ=America/New_York → 06:30 AM | Jul 20, 2026 (a non-ICT browser)





| POS landing — zero hydration errors (reproduced #418 before the fix) | pass |
| POS sessions / Orders / Team / Customers — zero hydration errors | pass |
| Same instant identical across UTC / ICT / non-ICT runtimes | pass |
| i18n suite re-run (no regression; hydration warnings now 0) | 6/6 |
| Staging smoke | 5/5 |
nix-cafe staging ca4dadc
(Worker nix-cafe-staging a614e798). If NIX ever goes multi-region, APP_TIME_ZONE
is the single place to make per-tenant. Parked for the scheduled prod promote.