From a30a16b18a0007ccc0177d234ea96f328b2916d3 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 5 Jun 2026 20:43:12 -0400 Subject: [PATCH] =?UTF-8?q?docs(03-12):=20complete=20EventForm=20gap-closu?= =?UTF-8?q?re=20plan=20=E2=80=94=20WR-03/05/07,=20IN-03/04,=20PWA=20assets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../03-12-SUMMARY.md | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 .planning/phases/03-event-write-back-pwa-install/03-12-SUMMARY.md diff --git a/.planning/phases/03-event-write-back-pwa-install/03-12-SUMMARY.md b/.planning/phases/03-event-write-back-pwa-install/03-12-SUMMARY.md new file mode 100644 index 0000000..50c4acb --- /dev/null +++ b/.planning/phases/03-event-write-back-pwa-install/03-12-SUMMARY.md @@ -0,0 +1,153 @@ +--- +phase: 03-event-write-back-pwa-install +plan: 12 +subsystem: pwa/EventForm +tags: [tdd, gap-closure, accessibility, pwa, calendar] +dependency_graph: + requires: [03-05, 03-06] + provides: [WR-03-fix, WR-05-fix, WR-07-fix, IN-03-fix, PWA-01-verified, PWA-02-verified] + affects: [apps/pwa/src/components/EventForm.tsx, apps/pwa/src/store/calendarStore.ts] +tech_stack: + added: [] + patterns: + - occurrence?.uid in reset effect deps (reactive re-population) + - local-accessor-only date extraction (parseDateTime WR-05) + - inline Tab/Shift+Tab focus trap on role=dialog (WR-07) + - exported todayIso single source of truth (IN-03) +key_files: + created: [] + modified: + - apps/pwa/src/components/EventForm.tsx + - apps/pwa/src/components/EventForm.test.tsx + - apps/pwa/src/store/calendarStore.ts + - apps/pwa/vitest.config.ts +decisions: + - TZ=UTC pinned globally in vitest.config.ts env block (not per-file beforeAll) for deterministic date assertions across all tests + - Focus trap implemented inline with dialogRef + onKeyDown — no new dependency added + - occurrence?.uid (not full occurrence) in reset effect deps to avoid deep-equality churn while still reacting to occurrence arrival + - vi.importActual used for IN-03 export test to bypass vi.mock() on calendarStore +metrics: + duration_minutes: 40 + completed_date: "2026-06-06T00:42:08Z" + tasks_completed: 2 + files_modified: 4 +--- + +# Phase 03 Plan 12: EventForm Gap Closure — Edit Mode, Focus Trap, PWA Assets Summary + +EventForm edit mode now pre-populates correctly from TanStack cache (even when occurrence arrives after form opens), preserves recurrence presets on edit, uses zone-consistent date extraction, and implements a real Tab/Shift+Tab focus trap. PWA install assets confirmed present. + +## Tasks Completed + +| Task | Type | Description | Commit | +|------|------|-------------|--------| +| 1 RED | test | WR-03 blank/recurrence, WR-05 zone, IN-03 export — failing tests | 02e312a | +| 1 GREEN | feat | WR-03 deps fix, WR-05 parseDateTime fix, IN-03 todayIso export | f0f1361 | +| 2 RED | test | WR-07 focus trap Tab/Shift+Tab cycle — failing tests | 4244e8c | +| 2 GREEN | feat | WR-07 inline focus trap on dialogRef + onKeyDown | e971e16 | + +## What Was Built + +### WR-03: Edit form re-populates when occurrence arrives after open + +The reset effect previously depended on `[eventFormOpen, eventFormMode, eventFormUid]` — not on `occurrence`. If the form opened before the `['events']` TanStack cache held the occurrence, the form stayed blank forever. + +**Fix:** Added `occurrence?.uid` to the reset effect dep array. The effect re-runs when the occurrence resolves in the cache, populating title/allDay/start/end/recurrence/location/description. + +**Recurrence fix (WR-03):** The effect previously hard-coded `setRecurrence('none')`. Now derives `occurrence?.recurrence` (cast via any since the CalendarOccurrence type doesn't expose it yet in v1). Defaults to `'none'` only when absent, with a comment documenting the v1 limitation. + +### WR-05: Zone-consistent parseDateTime + +The old implementation mixed `toISOString().slice(0,10)` (UTC date) with `getHours()` (local time) — the UTC date and local time can be in different day-boundaries at the edges. + +**Fix:** Replaced with consistent local-accessor family: `getFullYear/getMonth/getDate/getHours/getMinutes`. No `toISOString()` call in the timed branch. The all-day `^\d{4}-\d{2}-\d{2}$` branch is unchanged. + +**TZ=UTC pinned** in `vitest.config.ts` via `env: { TZ: 'UTC' }` so WR-05 assertions are deterministic on any CI runner. In UTC environment, a timed occurrence `'2026-06-10T23:30:00-04:00'` (UTC instant `2026-06-11T03:30:00Z`) renders date=`2026-06-11` and time=`03:30` — both consistent local-accessor values under UTC. + +### IN-03: todayIso exported from calendarStore + +`getDefaultStartDate()` and `getDefaultEndDate()` in EventForm.tsx had identical bodies duplicating the `todayIso()` function already in calendarStore. Exported `todayIso` from calendarStore (added `export` keyword) and imported it into EventForm, collapsing both helpers to `todayIso()` calls. + +### WR-07: Real focus trap on EventForm dialog + +The docblock claimed "Focus trap while open" but the implementation only called `.focus()` once on open. Tab escaped the modal to background content. + +**Fix:** Added `dialogRef` and `handleDialogKeyDown` handler on the dialog div. On Tab/Shift+Tab, queries all focusable elements inside `dialogRef.current` and wraps focus at the boundaries: +- Tab on last element → `first.focus()` + `preventDefault()` +- Shift+Tab on first element → `last.focus()` + `preventDefault()` + +No external library added. Existing focus-on-open (titleRef) and Escape-to-close unchanged. Docblock updated to accurately describe the focus trap. + +### PWA-01/PWA-02: Install assets confirmed present (IN-04) + +All three required PWA install assets exist in `apps/pwa/public/`: +- `icon-192.png` — 192×192 manifest icon +- `icon-512.png` — 512×512 manifest icon (+ maskable) +- `apple-touch-icon.png` — iOS Add-to-Home-Screen icon + +Referenced in `index.html` and `vite.config.ts` manifest. No code change needed; confirmed present for Gate 2. + +## TDD Gate Compliance + +| Gate | Commit | Status | +|------|--------|--------| +| Task 1 RED | 02e312a | test(03-12): failing tests added (3 failed) | +| Task 1 GREEN | f0f1361 | feat(03-12): 27 tests passing | +| Task 2 RED | 4244e8c | test(03-12): 2 failing focus trap tests | +| Task 2 GREEN | e971e16 | feat(03-12): 29 tests passing | + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 2 - Missing] Add todayIso to calendarStore vi.mock() in test file** +- **Found during:** Task 1 GREEN +- **Issue:** EventForm now imports `todayIso` from calendarStore, but the `vi.mock('../store/calendarStore.js')` factory in EventForm.test.tsx only exported `useCalendarStore`. Tests crashed with "No todayIso export is defined on the mock." +- **Fix:** Added `todayIso: () => new Date().toISOString().slice(0, 10)` to the mock factory so the mocked module matches the real module's export surface. +- **Files modified:** `apps/pwa/src/components/EventForm.test.tsx` + +**2. [Rule 2 - Missing] Use vi.importActual for IN-03 test** +- **Found during:** Task 1 GREEN +- **Issue:** The IN-03 test used `await import('../store/calendarStore.js')` which returns the mock (not the real module), so `actualModule.todayIso` was undefined. +- **Fix:** Changed to `await vi.importActual('../store/calendarStore.js')` to bypass the mock and test the real module export. +- **Files modified:** `apps/pwa/src/components/EventForm.test.tsx` + +## Verification + +``` +cd apps/pwa && npx vitest run src/components/EventForm.test.tsx +``` +**Result:** 29 passed (29) + +``` +cd apps/pwa && npm run build +``` +**Result:** Built successfully — 509.67 kB bundle, PWA service worker generated. + +## Issues Closed + +| ID | Description | Status | +|----|-------------|--------| +| WR-03 | Edit form blank when occurrence resolves after open | CLOSED | +| WR-03 | Editing recurring event resets recurrence to 'none' | CLOSED | +| WR-05 | parseDateTime mixes UTC date and local time | CLOSED | +| WR-07 | Focus trap claim without real trap implementation | CLOSED | +| IN-03 | Duplicate todayIso helpers | CLOSED | +| IN-04 | PWA install assets not verified | CLOSED (assets confirmed present) | + +## Self-Check: PASSED + +Files exist: +- [x] apps/pwa/src/components/EventForm.tsx — modified +- [x] apps/pwa/src/components/EventForm.test.tsx — modified +- [x] apps/pwa/src/store/calendarStore.ts — modified (todayIso exported) +- [x] apps/pwa/vitest.config.ts — modified (TZ=UTC) +- [x] apps/pwa/public/icon-192.png +- [x] apps/pwa/public/icon-512.png +- [x] apps/pwa/public/apple-touch-icon.png + +Commits exist: +- [x] 02e312a — RED Task 1 +- [x] f0f1361 — GREEN Task 1 +- [x] 4244e8c — RED Task 2 +- [x] e971e16 — GREEN Task 2