---
status: complete
phase: 03-event-write-back-pwa-install
mode: mvp
source:
  - 03-05-SUMMARY.md (Event Write UI)
  - 03-06-SUMMARY.md (Edit/Delete + SyncStateToast)
  - 03-07-SUMMARY.md (PWA Install)
  - 03-08-SUMMARY.md (Gate 2 Live Verification)
  - 03-12-SUMMARY.md (EventForm gap closure)
  - 03-REVIEW.md / 03-REVIEW-FIX.md (code-review fix cycle, this session)
scope: regression-focused (post code-review-fix)
method: playwright-cli desktop drive (local dev-bypass stack, no real Fastmail writes) + green test suites + Gate 2 record
started: 2026-06-09T15:20:00Z
updated: 2026-06-09T15:30:00Z
---

## Current Test

[testing complete]

## Context

Gate 2 (Plan 03-08) already operator-verified the full event write-back + iOS-install user
story **live** against real Authelia/Fastmail on desktop and the wife's iPhone (A1–A3, B1–B4,
D1–D6). This UAT pass is **regression-focused**: it re-confirms the behaviours touched by the
code-review fix cycle run this session (CR-01/CR-02 member-scoped lookups, CR-03 all-day
inclusive/exclusive, WR-01/move-path RRULE preservation, WR-04 sync-status ranking, IN-03
payload re-validation), which landed _after_ Gate 2.

Browser drive used a local dev-bypass stack (MariaDB + API + PWA) as the credential-less dev
user, so no event ever reached a real Fastmail calendar. Seeded test data (one dev user, one
`uat.local` calendar, one recurring event) was removed after the run; DB restored to original
state (real users 2/3 and their 538 events untouched).

## Tests

### 1. Cold-start smoke — app boots and renders after the fixes

expected: PWA loads, calendar shell renders (nav, Calendars legend, New Event control), no real console errors.
result: pass
evidence: Loaded http://localhost:5173 in real Chromium. Title "FamilySync"; nav + "New Event" + Schedule-X month grid (June 2026) rendered; legend showed **distinct** member colours (Dev User #4A90D9, Family #F25C7A). Only console error was a benign favicon.ico 404.

### 2. Create-event UI flow → enqueue → sync feedback

expected: New Event → fill form → Save → event enqueues (202) and SyncStateToast shows pending state.
result: pass
evidence: Opened EventForm (all UI-SPEC fields, focus on Title). Filled title, clicked "Create Event"; dialog closed, `calendar_outbox` row id=20 created (operation=create, pending), and SyncStateToast rendered `role="status"` "Syncing…". (Dispatch intentionally cannot complete — dev user has no Fastmail credential — so nothing hit a real calendar; the done/Saved transition is covered by outboxWorker tests + Gate 2 D1.)

### 3. All-day toggle hides time inputs

expected: Toggling All day on removes the start/end time fields; off restores them.
result: pass
evidence: Toggled the all-day switch → `[checked]`; the 09:00 / 10:00 time textboxes disappeared, Start/End showed date-only.

### 4. Edit mode pre-fill + recurrence preserved (WR-01 / WR-02 fix)

expected: Editing an event pre-populates fields; recurrence picker is disabled in edit mode with copy explaining the schedule is kept.
result: pass
evidence: Clicked a recurring occurrence → EventDetailPopover (live Edit/Delete footer) → Edit. "Edit Event" dialog pre-populated (title, dates 2026-06-10, times 10:00/11:00). Recurrence combobox rendered **`[disabled]`** with helper text **"Repeat can't be changed yet — edits keep the existing schedule."** — the exact preserve-on-edit guidance the WR-01/WR-02 fix added. Footer button correctly labelled "Save Changes".

### 5. Member-scoped read (CR-01 GET path)

expected: A member sees only events from calendars in their writable set.
result: pass
evidence: As dev user 1 (owns only the seeded UAT calendar), GET /api/events returned only that calendar's occurrences and `writable-calendars` returned only it — never the 538 events on user 2's calendars. Confirms the member-scoped query.

### 6. CR-01/CR-02 member-scoped edit/delete + freshest-etag (byte/SQL level)

expected: Edit/delete resolve the acting member's row (not an arbitrary shared-account duplicate); worker re-reads the writing member's etag.
result: pass
evidence: Certified by green API integration tests re-run this session (events.test.ts member-scoping + 503-join regression; outboxWorker freshest-etag WR-02 cases) — api 108 passed. Live-verified at Gate 2 D4/D5. Not UI-observable without a two-member shared-account dataset.

### 7. CR-03 all-day inclusive/exclusive round-trip (byte level)

expected: All-day events write exclusive DTEND, pre-fill inclusive on edit; span does not grow on re-edit.
result: pass
evidence: Certified by vevent.test.ts (inclusive→exclusive write) + EventForm.test.tsx (exclusive→inclusive pre-fill) — green. The all-day off-by-one was also fixed and confirmed live at Gate 2.

### 8. WR-01 + move-path RRULE preservation (byte level)

expected: Editing a recurring event keeps its RRULE, including edit-as-move to another calendar (worker create branch re-applies the source rule).
result: pass
evidence: Certified by the iteration-2 regression tests (events.test.ts \_preservedRrule forwarding + outboxWorker create-branch RRULE re-apply) — green. UI half (disabled picker + helper) browser-verified in Test 4. Recurring round-trip live-verified at Gate 2 D3.

### 9. WR-04 sync-status ranking + IN-03 payload re-validation

expected: sync-status ranks a failed/dead row above an older done row; worker hard-fails malformed outbox payloads before any CalDAV call.
result: pass
evidence: Certified by green API integration tests (sync-status priority CASE; outbox payload safeParse hard-fail) re-run this session.

### 10. Coverage check (goal-backward against the phase user story)

expected: Members can create/edit/delete events written to the correct Fastmail calendar; app installable to iPhone & Android home screens with guided onboarding.
result: pass (with documented deferrals)
evidence: Create/edit/delete → correct Fastmail calendar: Gate 2 D1–D6 (live). iPhone install + standalone OIDC login + onboarding walkthrough: Gate 2 B1–B4 (live, load-bearing). Code paths present: EventForm/Edit/Delete + outbox worker, VitePWA manifest/SW + InstallPrompt walkthrough. **Deferred (not failures):** B5 Android install walkthrough (device check), C SSE smoke (Phase 4 entry gate per D-14).

## Summary

total: 10
passed: 10
issues: 0
pending: 0
skipped: 0

## Gaps

[none — 0 UAT issues]

## Accepted limitations (carried forward, not UAT failures)

- **WR-01 (code-review Warning):** a missing cached etag still produces an unconditional PUT/DELETE; has a `console.warn`, but true conflict prevention needs a deeper D-08 change. v1-accepted.
- **WR-02 (code-review Warning):** edit cannot _change/remove_ an RRULE — "omitted recurrence" means "keep existing"; surfaced to the user via the helper text verified in Test 4. Deferred to the recurrence-editing milestone.
- **Gate 2 deferrals:** B5 Android install walkthrough (device-only human check); C SSE 5-min smoke (Phase 4 entry gate); backlog 999.3–999.9 (session-timeout redirect, VALARM reminders, first-login app-password setup, all-day visual distinction, recurrence bound, edit-recurring-series).
