From 9481544a586f30bfe845758507adf6c124530123 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Sun, 14 Jun 2026 22:46:21 -0400 Subject: [PATCH] docs(18-04): complete admin timezone UI plan --- .planning/ROADMAP.md | 4 +- .planning/STATE.md | 15 ++- .../18-04-SUMMARY.md | 126 ++++++++++++++++++ 3 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 .planning/phases/18-auto-timezone-detection-and-ability-to-change-timezone/18-04-SUMMARY.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 4fb60f5..33a40f7 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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 diff --git a/.planning/STATE.md b/.planning/STATE.md index 43b9d3b..04ed418 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -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 diff --git a/.planning/phases/18-auto-timezone-detection-and-ability-to-change-timezone/18-04-SUMMARY.md b/.planning/phases/18-auto-timezone-detection-and-ability-to-change-timezone/18-04-SUMMARY.md new file mode 100644 index 0000000..717c3cb --- /dev/null +++ b/.planning/phases/18-auto-timezone-detection-and-ability-to-change-timezone/18-04-SUMMARY.md @@ -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 — 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; `` + `` 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, `
` 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. +- `` 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*