- 06-04-SUMMARY: pulse keyframe checkpoint PASS (playwright-cli); CP-04.3 iOS device-only noted - 06-05-SUMMARY: TDD RED/GREEN + dead-end guard follow-up fix; playwright-cli cold-load + 401 PASS - 06-06-SUMMARY: end-tracking/recurrence-bound/series-prompt/all-day-pill; Schedule-X selector fix noted - ROADMAP: mark 06-04/05/06 complete; phase 6 row updated to 6/6 Complete 2026-06-10 - STATE: phase 06 position/status updated; 3 new metric rows; 6 new decisions; phase-level UX fixes (AppNav/BottomTabBar) documented; residual device-only items added to Blockers
10 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 |
|
|
|
|
|
|
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: callscomputeNewAllDayEnd(all-day mode) orcomputeNewTimedEnd(timed mode) to recompute end before setting start — end never strands behind start. - Start time
onChange: callscomputeNewTimedEndto recompute end preserving the delta. recurrenceBound: 'never' | 'until' | 'count',recurrenceUntil: string,recurrenceCount: numberstate added alongside the existing state block; resetuseEffectextended to reset them.- "Ends" control rendered below the recurrence
<select>, shown only whenrecurrence !== 'none', with the exact UI-SPEC Surface 5 labels: Never / On date / After N times, 44px touch targets, inline--color-destructivevalidation messages ("Must be at least 1 occurrence" / "End date must be after the event starts"). - Submit payload extended to conditionally spread
recurrenceUntil(bound='until') orrecurrenceCount(bound='count') — neither sent whenrecurrence === 'none'or bound='never'. exclusiveEndToInclusiveDatepre-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 opensSeriesEditPromptinstead 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:
- End-tracking (D-04): Moving a timed start → end follows, 1h duration preserved, end never before start. All-day: day-span preserved. PASS.
- Recurrence bound (D-06): Weekly recurrence → "Ends" control appears. "After N times" N=3 → server expansion returned exactly 3 occurrences (
COUNT=3). "On date" →UNTILbounded, occurrences stop. "Never" → unbounded. PASS. - FREQ persistence (D-07): Created a Daily recurrence → occurrences render daily (not weekly). PASS.
- 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/editfired; change reflected across occurrences. PASS. - 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).
- 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-eventfor week/day;.sx__month-grid-event:not(:has(.sx__month-grid-event-time))for month view). Also remapped--sx-color-primary-containerCSS 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: booleanas a required field onCalendarOccurrence; the existingEventDetailPopover.test.tsxfixtures omitted it, failing TypeScript compilation. - Fix: Added
hasRrule: false(andtruewhere the test exerciseshasRrule-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,recurrenceCountpresent)apps/pwa/src/styles/index.css— FOUND (sx__date-grid-eventall-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)