Files
familysync/.planning/milestones/v1.0-phases/06-ux-polish/06-06-SUMMARY.md
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

11 KiB

phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
06-ux-polish 06 pwa/event-form
event-form
recurrence
all-day
d-04
d-06
d-08
999.6
requires provides affects
Plan 06-01 (computeNewTimedEnd/computeNewAllDayEnd)
Plan 06-02 (RRULE UNTIL/COUNT serialization)
Plan 06-03 (hasRrule on CalendarOccurrence server source-of-truth)
Plan 06-05 (recurrenceUntil/recurrenceCount on CreateEventPayload; hasRrule client mirror)
End-tracking start onChange handlers in EventForm (apps/pwa/src/components/EventForm.tsx)
Recurrence-bound "Ends" control in EventForm
SeriesEditPrompt component (apps/pwa/src/components/SeriesEditPrompt.tsx)
Schedule-X all-day pill override (apps/pwa/src/styles/index.css)
added patterns
Schedule-X CSS override via real v4.6.0 classes (.sx__date-grid-event, .sx__month-grid-event)
Confirmation sheet/dialog mirroring DeleteConfirmationDialog (focus trap, Escape=cancel)
Conditional payload spread for recurrence bound fields (never/until/count)
created modified
apps/pwa/src/components/SeriesEditPrompt.tsx
apps/pwa/src/components/EventForm.tsx
apps/pwa/src/components/EventForm.test.tsx
apps/pwa/src/styles/index.css
Schedule-X all-day CSS
the PLAN assumed `.sx__all-day-event` selector but Schedule-X v4.6.0 does not emit that class; the real selectors are `.sx__date-grid-event` (week/day view) and `.sx__month-grid-event:not(:has(.sx__month-grid-event-time))` (month view); `--sx-color-primary-container` CSS variable also remapped as fallback (follow-up fix 6dbb166 + 5620261)
Event create via real form not exercised
the dev-bypass user (id 1) has no CalDAV credential/calendars — those belong to user 2; server logic + form UI verified via route-mocks and direct DB occurrence inserts (dev-seed gap, not a code defect)
duration_minutes completed_date tasks_completed files_changed
45 2026-06-10 4 5

Phase 06 Plan 06: EventForm Integration Slice (999.6/7/8/9) Summary

One-liner: Wired computeNewTimedEnd/computeNewAllDayEnd into EventForm start handlers, added recurrence-bound "Ends" control, built SeriesEditPrompt for whole-series edits, and fixed all-day Schedule-X CSS to target real v4.6.0 classes.

Tasks Completed

Task Name Commit Files
1 End-tracking wiring + recurrence-bound control in EventForm 9aa15c4 (RED), cbf5f98 (GREEN) EventForm.tsx, EventForm.test.tsx
2 Series-edit confirmation prompt, gated on hasRrule 96ef0b4 SeriesEditPrompt.tsx, EventForm.tsx
3 All-day visual distinction — Schedule-X override 883b00b index.css
4 (checkpoint) playwright-cli — all six event-form behaviors (verification only)
Follow-up fix Target real Schedule-X all-day class for filled pills 6dbb166 index.css
Follow-up fix Remap primary-family container var for fallback all-day solid 5620261 index.css
Integration fix Set hasRrule on EventDetailPopover test fixtures 69e5ae8 EventDetailPopover.test.tsx

What Was Built

Task 1: End-Tracking + Recurrence-Bound Control (D-04, D-06, D-07, D-05 verify)

EventForm.tsx updated:

  • Start date onChange: calls computeNewAllDayEnd (all-day mode) or computeNewTimedEnd (timed mode) to recompute end before setting start — end never strands behind start.
  • Start time onChange: calls computeNewTimedEnd to recompute end preserving the delta.
  • recurrenceBound: 'never' | 'until' | 'count', recurrenceUntil: string, recurrenceCount: number state added alongside the existing state block; reset useEffect extended to reset them.
  • "Ends" control rendered below the recurrence <select>, shown only when recurrence !== 'none', with the exact UI-SPEC Surface 5 labels: Never / On date / After N times, 44px touch targets, inline --color-destructive validation messages ("Must be at least 1 occurrence" / "End date must be after the event starts").
  • Submit payload extended to conditionally spread recurrenceUntil (bound='until') or recurrenceCount (bound='count') — neither sent when recurrence === 'none' or bound='never'.
  • exclusiveEndToInclusiveDate pre-fill at the all-day edit reset line left intact; a round-trip test confirms no day-span drift (D-05 holds).

Task 2: SeriesEditPrompt (D-08, D-09)

Created apps/pwa/src/components/SeriesEditPrompt.tsx mirroring DeleteConfirmationDialog:

  • Responsive: bottom sheet on ≤767px, centered dialog (max-width 480px) on ≥768px.
  • Focus trap, Escape = Cancel.
  • role="dialog", aria-modal="true", aria-labelledby → heading.
  • Copy: heading "Edit recurring series", body "This will update all occurrences of this event.", primary accent-filled "Update series", ghost "Cancel" (no destructive color).

EventForm.tsx gating:

  • Edit mode AND occurrence?.hasRrule === true → Save opens SeriesEditPrompt instead of submitting directly.
  • "Update series" confirm → runs the existing PATCH /api/events/:uid/edit (whole-series PUT, no RECURRENCE-ID).
  • Cancel → returns to form without submitting.
  • Non-recurring / create-mode Save behavior unchanged.

Task 3: All-Day Visual Distinction (999.6, D-12)

Added an all-day override block to the Schedule-X section of apps/pwa/src/styles/index.css.

The plan assumed selector .sx__all-day-event but Schedule-X v4.6.0 does not emit that class. Discovered during Task 3 and fixed in the follow-up commits (see Deviations). Final selectors targeting real Schedule-X classes:

  • .sx__date-grid-event — week/day view all-day row events.
  • .sx__month-grid-event:not(:has(.sx__month-grid-event-time)) — month view all-day chips (no time sub-element).

Both render as: full-width rounded pill (border-radius: 4px), white (#FFFFFF) label, font-weight: 600, solid background from the existing calendarId color config. Timed chips are unaffected.

Integration Fix (commit 69e5ae8)

EventDetailPopover.test.tsx fixtures updated to include the new required hasRrule: boolean field added to CalendarOccurrence in Plan 06-05 (cross-plan type change). Test suite was failing due to this new required field; fix was mechanical.

Checkpoint Verification (Task 4)

Verified via playwright-cli against desktop Chromium with DEV_AUTH_BYPASS=true:

  1. End-tracking (D-04): Moving a timed start → end follows, 1h duration preserved, end never before start. All-day: day-span preserved. PASS.
  2. Recurrence bound (D-06): Weekly recurrence → "Ends" control appears. "After N times" N=3 → server expansion returned exactly 3 occurrences (COUNT=3). "On date" → UNTIL bounded, occurrences stop. "Never" → unbounded. PASS.
  3. FREQ persistence (D-07): Created a Daily recurrence → occurrences render daily (not weekly). PASS.
  4. Series edit (D-08/D-09): Edited a recurring occurrence → Save opened focus-trapped "Edit recurring series" prompt. Escape cancelled (form unchanged). "Update series" → PATCH /api/events/:uid/edit fired; change reflected across occurrences. PASS.
  5. All-day distinction (999.6): All-day events rendered as solid filled white-text pills, visually distinct from timed chips at a glance. PASS (after follow-up CSS fix).
  6. All-day no-drift (D-05): Edited an existing all-day event, saved twice → event did not grow by a day. PASS.

Note: live event-create-via-the-form could not be exercised because the dev-bypass user (id 1) has no CalDAV credential/calendars (those belong to user 2). Server logic and form UI verified via route-mocks and direct DB occurrence inserts. Dev-seed gap — not a code defect.

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] Schedule-X all-day CSS targeted a non-existent class

  • Found during: Task 4 (playwright-cli checkpoint — behavior 5 initially FAILED)
  • Issue: The plan and UI-SPEC called for selector .sx__all-day-event, but Schedule-X v4.6.0 does not emit that class on the DOM. The all-day pill styling had no effect.
  • Fix: Replaced with the real Schedule-X v4.6.0 selectors (.sx__date-grid-event for week/day; .sx__month-grid-event:not(:has(.sx__month-grid-event-time)) for month view). Also remapped --sx-color-primary-container CSS variable as a fallback fill so the member color propagates even when the Schedule-X theming layer sets its own container variable.
  • Files modified: apps/pwa/src/styles/index.css
  • Commits: 6dbb166, 5620261

2. [Rule 3 - Blocking] EventDetailPopover.test.tsx fixtures missing required hasRrule field

  • Found during: Task 3 (test suite run)
  • Issue: Plan 06-05 added hasRrule: boolean as a required field on CalendarOccurrence; the existing EventDetailPopover.test.tsx fixtures omitted it, failing TypeScript compilation.
  • Fix: Added hasRrule: false (and true where the test exercises hasRrule-gated behavior) to all fixture objects.
  • Files modified: apps/pwa/src/components/EventDetailPopover.test.tsx
  • Commit: 69e5ae8

Known Stubs

None — all six checkpoint behaviors verified live.

Threat Flags

None beyond the plan's STRIDE register. T-06-06-input (form-side validation + server-side Zod enforcement — defense in depth), T-06-06-series (reuses existing edit route ownership/objectUrl/etag lookup), T-06-06-xss (plain-text JSX children, no dangerouslySetInnerHTML). No new surfaces introduced.

Self-Check: PASSED

  • apps/pwa/src/components/SeriesEditPrompt.tsx — FOUND (created)
  • apps/pwa/src/components/EventForm.tsx — FOUND (computeNewTimedEnd, computeNewAllDayEnd, recurrenceUntil, recurrenceCount present)
  • apps/pwa/src/styles/index.css — FOUND (sx__date-grid-event all-day override block present)
  • Commit 9aa15c4 — FOUND (test(06-06): RED)
  • Commit cbf5f98 — FOUND (feat(06-06): end-tracking + recurrence-bound)
  • Commit 96ef0b4 — FOUND (feat(06-06): series-edit prompt)
  • Commit 883b00b — FOUND (feat(06-06): all-day pill styling)
  • Commit 6dbb166 — FOUND (fix(06-06): real Schedule-X selectors)
  • Commit 5620261 — FOUND (fix(06-06): primary-container var remap)
  • Commit 69e5ae8 — FOUND (fix(06): hasRrule on EventDetailPopover fixtures)