Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.0 KiB
phase: 18-auto-timezone-detection-and-ability-to-change-timezone plan: "04" subsystem: pwa tags: [timezone, iana, admin, react, tanstack-query, playwright, ui]
Dependency graph
requires:
- phase: 18-02 provides: GET/PUT /api/admin/config/timezone (consumed by fetchAdminTimezone/setAdminTimezone)
provides:
- fetchAdminTimezone() — GET /api/admin/config/timezone in PWA API client
- setAdminTimezone(tz) — PUT /api/admin/config/timezone in PWA API client
- AdminTimezoneResponse interface (timezone: string, isExplicitlySet: boolean)
- Timezone section in AdminPage (/admin) — searchable IANA picker, save, detected-zone affordance
affects:
- apps/pwa/src/api/client.ts
- apps/pwa/src/routes/AdminPage.tsx
- apps/pwa/e2e/timezone-verify.spec.ts (new verification spec)
Tech tracking
tech-stack: added: [] patterns: - Intl.supportedValuesOf('timeZone') guarded for runtime availability (datalist population) - ARIA combobox role for — Playwright locator uses getByRole('combobox') not getByRole('textbox') - useQuery + useMutation with invalidateQueries on success (same pattern as calendars section)
key-files: created: - apps/pwa/e2e/timezone-verify.spec.ts modified: - apps/pwa/src/api/client.ts - apps/pwa/src/routes/AdminPage.tsx
key-decisions:
- "Removed AdminTimezoneResponse from AdminPage.tsx import list — ESLint no-unused-vars; type is inferred from useQuery return"
- "Input with list= attribute has ARIA combobox role in Chromium, not textbox — Playwright e2e uses getByRole('combobox')"
- "timezoneInput state is null when the user has not interacted — effectiveTimezoneInput = timezoneInput ?? storedTimezone preserves stored value as pre-fill"
- "setTimezoneInput(null) on mutation success resets local override so save button re-disables to match new stored value"
requirements-completed: [D-02, D-04]
Metrics
duration: 15min completed: 2026-06-15
Phase 18 Plan 04: Admin Timezone UI Summary
Searchable IANA timezone picker in /admin Settings — fetchAdminTimezone/setAdminTimezone in client.ts + Timezone section in AdminPage.tsx — verified end-to-end via Playwright with the real 18-02 endpoints
Performance
- Duration: ~15 min
- Started: 2026-06-15T02:30:00Z
- Completed: 2026-06-15T02:45:03Z
- Tasks: 3 (2 auto + 1 playwright-verified checkpoint)
- Files modified/created: 3
Accomplishments
- Added
AdminTimezoneResponseinterface +fetchAdminTimezone()+setAdminTimezone()toapps/pwa/src/api/client.ts, following the exact pattern offetchAdminCalendars/setSharedCalendar(credentials: include, redirect: manual, handleAuthResponse) - Added Timezone section to
AdminPage.tsxafter the Shared Calendar section (withmarginBottomon the preceding section for spacing) - Timezone section features: 4-state loading/error/data pattern;
<input type="text" list="iana-zones">+<datalist>fromIntl.supportedValuesOf('timeZone')(guarded); "Using system default" notice whenisExplicitlySet=false; "Use detected: {zone}" affordance (D-02); disabled Save when pending or unchanged;timezoneMutation.mutate(tz)on save;invalidateQueries(['admin','timezone'])on success - Created
apps/pwa/e2e/timezone-verify.spec.tswith 6 desktop Playwright tests — all 6 pass against the live 18-02 API endpoints, including the save + persist across reload flow
Task Commits
- Task 1: Add fetchAdminTimezone + setAdminTimezone to PWA API client -
57424e6(feat) - Task 2: Add Timezone section to AdminPage -
43d6689(feat) - Task 3: Playwright timezone round-trip e2e spec -
3013b53(test)
Files Created/Modified
apps/pwa/src/api/client.ts— addedAdminTimezoneResponseinterface +fetchAdminTimezone()+setAdminTimezone()in the/api/admin/*sectionapps/pwa/src/routes/AdminPage.tsx— addedtimezoneQuery,timezoneMutation,timezoneInputstate,detectedTz, computed values,<section aria-label="Timezone">with IANA picker + affordances + save buttonapps/pwa/e2e/timezone-verify.spec.ts— new 6-test Playwright spec verifying the full round-trip
Decisions Made
AdminTimezoneResponsetype import removed fromAdminPage.tsx—@typescript-eslint/no-unused-varsflagged it (type is inferred fromuseQueryreturn value, not used as an explicit annotation). ESLint clean.<input type="text" list="iana-zones">has ARIAcomboboxrole (nottextbox) in Chromium — discovered via Playwright page snapshot. Updated e2e locators togetByRole('combobox').- The
timezoneInputstate variable isnullwhen the user hasn't typed anything;effectiveTimezoneInput = timezoneInput ?? storedTimezoneensures the input shows the stored value on load without the save button enabling prematurely. - After mutation success,
setTimezoneInput(null)resets the local override so the save button re-disables (effectiveTimezoneInput collapses back to the now-updated storedTimezone).
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Removed unused AdminTimezoneResponse import
- Found during: Task 2 (ESLint check)
- Issue:
type AdminTimezoneResponsewas imported but not used as an explicit type annotation; ESLintno-unused-varsreported it as an error - Fix: Removed from the import line; the type is inferred via
useQuery - Files modified:
apps/pwa/src/routes/AdminPage.tsx - Commit:
43d6689
2. [Rule 3 - Blocking] API dev container serving stale compiled code
- Found during: Task 3 (playwright verification)
- Issue: The Docker container running the API had the pre-Plan-18-02
dist/routes/admin.jsbaked in — the timezone endpoints returned 404. The--watch dist/index.jswatcher only restarts onindex.jschange, not sub-module changes. - Fix: Rebuilt the API (
pnpm --filter @familysync/api build), thendocker cpthe updateddist/routes/admin.js,dist/lib/householdTimezone.js, anddist/index.jsinto the container to trigger a restart. - Files modified: container-only (not tracked in git)
- Commit: N/A (operational, not code)
Known Stubs
None — the Timezone section is fully wired to the real GET /api/admin/config/timezone and PUT /api/admin/config/timezone endpoints (Plan 18-02). No placeholder data.
Threat Flags
No new threat surface beyond the plan's threat model. T-18-11 (client isAdmin gate is UX only), T-18-12 (server validates IANA via Plan 02 Zod refine), T-18-13 (plain-text JSX children, no dangerouslySetInnerHTML).
Self-Check: PASSED
apps/pwa/src/api/client.ts— FOUND (modified, exports fetchAdminTimezone + setAdminTimezone + AdminTimezoneResponse)apps/pwa/src/routes/AdminPage.tsx— FOUND (modified, containsaria-label="Timezone")apps/pwa/e2e/timezone-verify.spec.ts— FOUND (created, 6 desktop tests all pass)- Commit
57424e6— FOUND - Commit
43d6689— FOUND - Commit
3013b53— FOUND
Phase: 18-auto-timezone-detection-and-ability-to-change-timezone Completed: 2026-06-15