Files
T
2026-06-18 22:21:38 -04:00

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 AdminTimezoneResponse interface + fetchAdminTimezone() + setAdminTimezone() to apps/pwa/src/api/client.ts, following the exact pattern of fetchAdminCalendars / setSharedCalendar (credentials: include, redirect: manual, handleAuthResponse)
  • Added Timezone section to AdminPage.tsx after the Shared Calendar section (with marginBottom on the preceding section for spacing)
  • Timezone section features: 4-state loading/error/data pattern; <input type="text" list="iana-zones"> + <datalist> from Intl.supportedValuesOf('timeZone') (guarded); "Using system default" notice when isExplicitlySet=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.ts with 6 desktop Playwright tests — all 6 pass against the live 18-02 API endpoints, including the save + persist across reload flow

Task Commits

  1. Task 1: Add fetchAdminTimezone + setAdminTimezone to PWA API client - 57424e6 (feat)
  2. Task 2: Add Timezone section to AdminPage - 43d6689 (feat)
  3. Task 3: Playwright timezone round-trip e2e spec - 3013b53 (test)

Files Created/Modified

  • apps/pwa/src/api/client.ts — added AdminTimezoneResponse interface + fetchAdminTimezone() + setAdminTimezone() in the /api/admin/* section
  • apps/pwa/src/routes/AdminPage.tsx — added timezoneQuery, timezoneMutation, timezoneInput state, detectedTz, computed values, <section aria-label="Timezone"> with IANA picker + affordances + save button
  • apps/pwa/e2e/timezone-verify.spec.ts — new 6-test Playwright spec verifying the full round-trip

Decisions Made

  • AdminTimezoneResponse type import removed from AdminPage.tsx@typescript-eslint/no-unused-vars flagged it (type is inferred from useQuery return value, not used as an explicit annotation). ESLint clean.
  • <input type="text" list="iana-zones"> has ARIA combobox role (not textbox) in Chromium — discovered via Playwright page snapshot. Updated e2e locators to getByRole('combobox').
  • The timezoneInput state variable is null when the user hasn't typed anything; effectiveTimezoneInput = timezoneInput ?? storedTimezone ensures 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 AdminTimezoneResponse was imported but not used as an explicit type annotation; ESLint no-unused-vars reported 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.js baked in — the timezone endpoints returned 404. The --watch dist/index.js watcher only restarts on index.js change, not sub-module changes.
  • Fix: Rebuilt the API (pnpm --filter @familysync/api build), then docker cp the updated dist/routes/admin.js, dist/lib/householdTimezone.js, and dist/index.js into 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, contains aria-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