117 lines
5.5 KiB
Markdown
117 lines
5.5 KiB
Markdown
---
|
||
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_
|