---
phase: 06-ux-polish
reviewed: 2026-06-10T17:05:00Z
depth: standard
files_reviewed: 22
files_reviewed_list:
  - apps/api/src/broker/expand.ts
  - apps/api/src/broker/outboxWorker.ts
  - apps/api/src/routes/events.ts
  - apps/api/tests/broker/expand.test.ts
  - apps/api/tests/broker/outboxWorker.test.ts
  - apps/api/tests/broker/vevent.test.ts
  - apps/api/tests/fixtures/weekly-count3.ics
  - apps/pwa/src/api/client.test.ts
  - apps/pwa/src/api/client.ts
  - apps/pwa/src/components/AuthSplash.tsx
  - apps/pwa/src/components/CalendarShell.tsx
  - apps/pwa/src/components/EventForm.test.tsx
  - apps/pwa/src/components/EventForm.tsx
  - apps/pwa/src/components/PushPermissionPrompt.tsx
  - apps/pwa/src/components/SeriesEditPrompt.tsx
  - apps/pwa/src/lib/eventDateTime.test.ts
  - apps/pwa/src/lib/eventDateTime.ts
  - apps/pwa/src/main.tsx
  - apps/pwa/src/store/calendarStore.ts
  - apps/pwa/src/styles/index.css
  - apps/pwa/src/styles/tokens.css
  - apps/pwa/src/hooks/useFocusTrap.ts
findings:
  critical: 0
  warning: 0
  info: 1
  total: 1
status: 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. `recurrenceUntil` is now
  `z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional()` at BOTH boundaries —
  `eventFieldsSchema` (events.ts:114) on ingress and `outboxPayloadSchema`
  (outboxWorker.ts:86) on re-parse. `until.replace(/-/g,'')` is now guaranteed
  digits-only, and `recurrenceCount` keeps its `int().min(1)` bound. The
  edit-as-move `_preservedRrule` carry-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 on
  `recurrenceBound === 'until' && !recurrenceUntil` (EventForm.tsx:375).
- **WR-03 (NaN/0 recurrenceCount):** Fixed. `onChange` coerces via
  `parseInt` + `Number.isFinite` → 0 (EventForm.tsx:929-930), and `validate()`
  requires `Number.isInteger(recurrenceCount) && >= 1` (EventForm.tsx:364).
- **WR-04 (UTC-slice anti-pattern):** Fixed. `calendarStore` imports and uses the
  exported `localDateISO` for both `initialCalendarRange()` and `todayIso()`
  (calendarStore.ts:27, 134-135, 142); no `toISOString().slice(0,10)` remains.
- **WR-05 (fragile instanceof):** Fixed. `onGlobalError` now also matches
  `(error)?.name === 'SessionExpiredError'` (main.tsx:35-38).
- **WR-06 (unbounded resync before commit):** Fixed. Success path wraps
  `triggerTargetedResync` in `Promise.race` with a 10s cap (outboxWorker.ts:711-714);
  the detached resync swallows its own errors and does not touch the `isDraining`
  guard, so no post-`finally` shared-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. `parseDateTime` requires a
  `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}` shape on `clean` before trusting `new Date`
  (EventForm.tsx:129); a partial value like `2026-06` now returns `ok:false`.
- **IN-01 (stale id doc comment):** Fixed. client.ts:107 now documents the
  `ev-<sanitized-uid>-<epochMs>` form matching expand.ts `makeOccurrenceId`.
- **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 `useFocusTrap` hook (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_
