5.5 KiB
phase, reviewed, depth, files_reviewed, files_reviewed_list, findings, status
| phase | reviewed | depth | files_reviewed | files_reviewed_list | findings | status | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-ux-polish | 2026-06-10T17:05:00Z | standard | 22 |
|
|
clean |
Phase 6: Code Review Report (re-review)
Reviewed: 2026-06-10 Depth: standard Files Reviewed: 22 Status: clean
Summary
This is the second adversarial pass over the Phase 6 UX-polish source set, focused on verifying the 12-commit fix batch (CR-01, WR-01–WR-08, IN-01/02/05/06) landed correctly and did not introduce regressions. IN-03 (single-member ColorLegend) and IN-04 (recurrenceCount default of 1) were intentionally deferred as scope questions and are out of remediation scope.
Verification result: all previously-flagged fixes are correctly applied, covered by
tests, and introduce no regressions. The full Phase 6 test surface is green —
49 API broker tests (expand/outboxWorker/vevent) and 95 PWA tests
(client/EventForm/eventDateTime).
No Critical or Warning findings on this pass. One Info note is carried forward unchanged (IN-04, the deferred count-default UX), recorded here only so it is not lost.
Fix verification detail
- CR-01 (RRULE UNTIL injection): Closed.
recurrenceUntilis nowz.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()at BOTH boundaries —eventFieldsSchema(events.ts:114) on ingress andoutboxPayloadSchema(outboxWorker.ts:86) on re-parse.until.replace(/-/g,'')is now guaranteed digits-only, andrecurrenceCountkeeps itsint().min(1)bound. The edit-as-move_preservedRrulecarry-through (route →.passthrough()schema → create branch) is the server's own stored RECUR, not attacker-controlled, and is unit-tested (outboxWorker.test.ts:311, 337). No new vector introduced. - WR-01 (all-day SQL over-select): Fixed. The all-day non-recurring branch now
gates
hasRrule = 0(events.ts:207), so a recurring all-day master in the window is carried only by the recurring branch — no double expansion. - WR-02 (blank "On date" → unbounded series): Fixed.
validate()now errors onrecurrenceBound === 'until' && !recurrenceUntil(EventForm.tsx:375). - WR-03 (NaN/0 recurrenceCount): Fixed.
onChangecoerces viaparseInt+Number.isFinite→ 0 (EventForm.tsx:929-930), andvalidate()requiresNumber.isInteger(recurrenceCount) && >= 1(EventForm.tsx:364). - WR-04 (UTC-slice anti-pattern): Fixed.
calendarStoreimports and uses the exportedlocalDateISOfor bothinitialCalendarRange()andtodayIso()(calendarStore.ts:27, 134-135, 142); notoISOString().slice(0,10)remains. - WR-05 (fragile instanceof): Fixed.
onGlobalErrornow also matches(error)?.name === 'SessionExpiredError'(main.tsx:35-38). - WR-06 (unbounded resync before commit): Fixed. Success path wraps
triggerTargetedResyncinPromise.racewith a 10s cap (outboxWorker.ts:711-714); the detached resync swallows its own errors and does not touch theisDrainingguard, so no post-finallyshared-state hazard. - WR-07 (string compare with blank start): Fixed. The bound-before-start compare
is now guarded on a non-empty
startDate(EventForm.tsx:377). - WR-08 (permissive new Date parse): Fixed.
parseDateTimerequires a^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}shape oncleanbefore trustingnew Date(EventForm.tsx:129); a partial value like2026-06now returnsok:false. - IN-01 (stale id doc comment): Fixed. client.ts:107 now documents the
ev-<sanitized-uid>-<epochMs>form matching expand.tsmakeOccurrenceId. - IN-02 (resolveDefaultView indirection): Addressed via clarifying doc comment (CalendarShell.tsx:61-68) documenting the SSR-only intent.
- IN-05 (duplicated focus trap): Fixed. Both EventForm and SeriesEditPrompt now
consume the shared
useFocusTraphook (hooks/useFocusTrap.ts). - IN-06 (unfolded ICS DESCRIPTION): Fixed. The fixture DESCRIPTION is now folded across two lines with a leading-space continuation (weekly-count3.ics:10-11).
Info
IN-04: recurrenceCount default of 1 is send-eligible the instant bound flips to "count"
File: apps/pwa/src/components/EventForm.tsx:225
Issue: Carried forward unchanged and explicitly deferred as a scope question.
recurrenceCount still defaults to 1, so selecting "After N times" and submitting
without touching the field creates a 1-occurrence "recurring" event (effectively
non-recurring). Not a bug; a confusing default. If addressed later, prefer an empty
initial value with the existing "e.g. 10" placeholder. No action required this pass.
Reviewed: 2026-06-10 Reviewer: Claude (gsd-code-reviewer) Depth: standard