Files
familysync/.planning/phases/06-ux-polish/06-REVIEW.md
T

117 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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-01WR-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_