Files
familysync/.planning/phases/03-event-write-back-pwa-install/03-12-SUMMARY.md
T

154 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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