docs(18-04): complete admin timezone UI plan

This commit is contained in:
Lucas Berger
2026-06-14 22:46:21 -04:00
parent 3013b53b19
commit 9481544a58
3 changed files with 136 additions and 9 deletions
+2 -2
View File
@@ -636,7 +636,7 @@ Plans:
**Goal:** Make the household timezone an explicit, stored, user-changeable setting — auto-detected from the browser at first run, changeable from the role-gated /admin Settings — and route the server-side all-day "9 AM local" reminder computation through it (replacing the implicit `process.env.TZ` fallback), without touching the already-correct browser-local display/timed-write path.
**Requirements**: TBD (decision contract D-01..D-07 from 18-CONTEXT.md)
**Depends on:** Phase 10 (admin role + `/admin` Settings + `app_config`); Phase 11 (all-day reminder computation this rewires). Independent of Phase 17. Phase 12 (setup wizard) not required — seeding is self-contained.
**Plans:** 3/4 plans executed
**Plans:** 4/4 plans complete
Plans:
**Wave 1**
@@ -650,6 +650,6 @@ Plans:
**Wave 3** *(blocked on Wave 2 completion)*
- [ ] 18-04-PLAN.md — PWA Timezone section in /admin Settings (searchable IANA picker + detected-zone seed) + client fns (D-02/D-04)
- [x] 18-04-PLAN.md — PWA Timezone section in /admin Settings (searchable IANA picker + detected-zone seed) + client fns (D-02/D-04)
**UI hint**: yes
+8 -7
View File
@@ -2,16 +2,16 @@
gsd_state_version: 1.0
milestone: v1.1
milestone_name: Operability & Polish
status: executing
status: verifying
stopped_at: Phase 18 Plan 03 complete — broker rewire done; plan 4 of 4 is next
last_updated: "2026-06-15T02:33:25.687Z"
last_updated: "2026-06-15T02:46:09.819Z"
last_activity: 2026-06-15
progress:
total_phases: 23
completed_phases: 9
completed_phases: 10
total_plans: 36
completed_plans: 35
percent: 39
completed_plans: 36
percent: 43
---
# Project State
@@ -27,7 +27,7 @@ See: .planning/PROJECT.md (updated 2026-06-10)
Phase: 18 (auto-timezone-detection-and-ability-to-change-timezone) — EXECUTING
Plan: 4 of 4
Status: Ready to execute
Status: Phase complete — ready for verification
Last activity: 2026-06-15
### ✅ Resolved Checkpoint — Phase 15 Plan 15-03 Task 2 (human-action)
@@ -110,6 +110,7 @@ _Updated after each plan completion_
| Phase 18-auto-timezone-detection-and-ability-to-change-timezone P01 | 2 | 2 tasks | 2 files |
| Phase 18 P02 | 3 | 2 tasks | 2 files |
| Phase 18 P03 | 28 | 2 tasks | 4 files |
| Phase 18 P04 | 15 | 3 tasks | 3 files |
## Accumulated Context
@@ -256,7 +257,7 @@ Recent decisions affecting current work:
## Session Continuity
Last session: 2026-06-15T02:33:25.669Z
Last session: 2026-06-15T02:46:09.800Z
Stopped at: Phase 18 Plan 03 complete — broker rewire done; plan 4 of 4 is next
Resume file: None
@@ -0,0 +1,126 @@
---
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 <input type="text" list="..."> — 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*