--- phase: 03-event-write-back-pwa-install plan: 05 type: execute wave: 3 depends_on: ["03-03"] files_modified: - apps/pwa/src/api/client.ts - apps/pwa/src/store/calendarStore.ts - apps/pwa/src/components/EventForm.tsx - apps/pwa/src/components/CalendarShell.tsx autonomous: true requirements: [CAL-04, CAL-05, CAL-07] user_setup: [] must_haves: truths: - "A member can tap 'New Event', fill the form, and save — POST /api/events/create fires and the form closes" - "The form supports timed and all-day events, a recurrence preset (None/Daily/Weekly/Monthly/Yearly), title/location/description" - "The calendar picker is hidden when the member has exactly one writable calendar (D-02)" - "Edit mode pre-populates the form and calls PATCH /api/events/:uid/edit" artifacts: - path: "apps/pwa/src/components/EventForm.tsx" provides: "create/edit modal form (bottom sheet on phone, dialog on desktop)" min_lines: 80 - path: "apps/pwa/src/api/client.ts" provides: "createEvent, updateEvent, fetchWritableCalendars typed calls" exports: ["createEvent", "updateEvent", "fetchWritableCalendars"] key_links: - from: "apps/pwa/src/components/EventForm.tsx" to: "/api/events/create" via: "createEvent mutation" pattern: "createEvent" - from: "apps/pwa/src/api/client.ts" to: "/api/events/writable-calendars" via: "fetchWritableCalendars GET" pattern: "writable-calendars" - from: "apps/pwa/src/components/CalendarShell.tsx" to: "EventForm" via: "New Event FAB toggles eventFormOpen" pattern: "eventFormOpen" --- Build the create/edit event UI: the typed write client calls, the Zustand form-state keys, the `EventForm` modal (timed/all-day/recurring fields, conditional calendar picker), and the "New Event" FAB/toolbar entry on the calendar shell. This is the front half of the create and edit vertical slices — after this plan a member can open the form and submit a write (delete + sync feedback land in Plan 06). Purpose: CAL-04 (create timed/all-day) and CAL-07 (create recurring) become user-reachable. Built against the UI Design Contract (03-UI-SPEC.md) for fields, copy, tokens, and interaction; reuses the Phase 2 EventDetailPopover overlay/focus-trap/responsive pattern (D-10). The calendar picker is populated from the authoritative `GET /api/events/writable-calendars` endpoint (added in Plan 03) — the writable set (D-03) is owned by the server, not derived on the client. Output: EventForm + client write calls + store keys + FAB, all wired to the Plan 03 API. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/phases/03-event-write-back-pwa-install/03-UI-SPEC.md @.planning/phases/03-event-write-back-pwa-install/03-PATTERNS.md @apps/pwa/src/api/client.ts @apps/pwa/src/store/calendarStore.ts @apps/pwa/src/components/EventDetailPopover.tsx @apps/pwa/src/components/CalendarShell.tsx Task 1: Typed write client calls + Zustand form-state keys apps/pwa/src/api/client.ts, apps/pwa/src/store/calendarStore.ts - apps/pwa/src/api/client.ts (existing — fetch function + interface-first pattern; CalendarOccurrence shape) - apps/pwa/src/store/calendarStore.ts (existing — CalendarStore interface + create() pattern) - .planning/phases/03-event-write-back-pwa-install/03-UI-SPEC.md (§State Management Contract — Zustand keys; §EventForm fields → request shape) - .planning/phases/03-event-write-back-pwa-install/03-PATTERNS.md (§client.ts — POST/PATCH fetch shape; §Zustand UI state) - .planning/phases/03-event-write-back-pwa-install/03-03-PLAN.md (Task 3 — GET /api/events/writable-calendars response shape `{ calendars: [{ url, displayName, color, isShared }] }`) Tests (extend pwa test suite where one exists, else add a small client unit test): - createEvent posts to /api/events/create with credentials:'include' and JSON body; returns { uid } on 202. - updateEvent PATCHes /api/events/:uid/edit. - fetchWritableCalendars GETs /api/events/writable-calendars and returns the WritableCalendar[] from the response's `calendars` array. - The Zustand store exposes the new keys with correct defaults. In client.ts add exported interfaces `CreateEventPayload` (title, allDay, start, end, optional location, description, recurrence: 'none'|'daily'|'weekly'|'monthly'|'yearly', calendarUrl?), `CreateEventResponse` ({ uid }), `WritableCalendar` ({ url, displayName, color, isShared }). Add `createEvent(payload): Promise` (POST), `updateEvent(uid, payload): Promise` (PATCH `/api/events/${uid}/edit`), and `fetchWritableCalendars(): Promise` (GET `/api/events/writable-calendars`, added by Plan 03 Task 3 — call it unconditionally; parse the JSON `{ calendars }` envelope and return `body.calendars`). The server is the authoritative owner of the D-03 writable set; do NOT derive the writable set on the client. All follow the existing fetch shape with credentials:'include' and `if (!res.ok) throw`. In calendarStore.ts extend `CalendarStore` with `eventFormOpen: boolean`, `eventFormMode: 'create'|'edit'`, `eventFormUid: string|null`, plus setters `setEventForm(open, mode?, uid?)`. Defaults: closed, mode 'create', uid null. Keep all server data out of Zustand (D — server state stays in TanStack Query). cd /home/luc/Projects/familysync && pnpm --filter @familysync/pwa exec tsc --noEmit && grep -q "createEvent" apps/pwa/src/api/client.ts && grep -q "writable-calendars" apps/pwa/src/api/client.ts && grep -q "eventFormOpen" apps/pwa/src/store/calendarStore.ts && pnpm --filter @familysync/pwa test - `grep -Eq "createEvent|updateEvent" apps/pwa/src/api/client.ts`. - `grep -q "writable-calendars" apps/pwa/src/api/client.ts` (calls the Plan 03 endpoint; no client-side derivation). - `grep -q "eventFormOpen" apps/pwa/src/store/calendarStore.ts`. - PWA tsc --noEmit passes; existing PWA tests stay green. Write client calls (including fetchWritableCalendars against the Plan 03 endpoint) and form-state Zustand keys exist and type-check. Task 2: EventForm modal (create + edit) per UI Design Contract apps/pwa/src/components/EventForm.tsx - .planning/phases/03-event-write-back-pwa-install/03-UI-SPEC.md (§EventForm — field order/types/required; §CalendarPicker D-02; §Recurrence picker; §Copywriting Contract; §Interaction Contract all-day toggle + recurrence + keyboard; §Spacing/Typography/Color tokens) - apps/pwa/src/components/EventDetailPopover.tsx (analog — backdrop+dialog structure ~202-221, Escape+focus-trap useEffect ~143-159, responsive isPhone/dialogStyle ~165-199, design tokens, XSS plain-text rule) - .planning/phases/03-event-write-back-pwa-install/03-PATTERNS.md (§EventForm.tsx — modal/overlay, focus trap, TanStack mutation, Zustand) - apps/pwa/src/api/client.ts (createEvent/updateEvent/fetchWritableCalendars from Task 1) Tests (EventForm.test.tsx): renders title/all-day/start/end/recurrence/location/description fields; toggling "All day" hides time inputs; calendar picker is absent when fetchWritableCalendars returns one calendar and present when it returns two (D-02); empty title shows "Title is required"; end-before-start shows "End time must be after start"; submitting calls the createEvent mutation in create mode and updateEvent in edit mode; Escape and backdrop close the form. Implement `EventForm.tsx` as a modal overlay reusing the EventDetailPopover backdrop+dialog+focus-trap+responsive pattern (bottom sheet on phone, centered 480px dialog on desktop). Fields and order exactly per UI-SPEC §EventForm. All-day toggle (`role="switch"`) hides start/end time inputs and applies the auto-advance rule; defaults start 09:00/end 10:00 when toggled off. Recurrence as a segmented select (`role="radiogroup"` or `