Files
familysync/.planning/phases/03-event-write-back-pwa-install/03-REVIEW-FIX.md
T
Lucas Berger 7c687ea413 docs(03): add code review fix report (--auto, 2 fix passes)
Auto-fix loop converged after 2 fix iterations + a final verifying re-review:
- Pass 1: 13/14 findings fixed (3 Critical, 6 Warning, 4 Info).
- Re-review surfaced 1 new Critical (move-path RRULE data loss) + 4 lower.
- Pass 2: 8/8 fixed, including the move-path RRULE forwarding.
- Final re-review: 0 Critical. Remaining 2 Warning / 2 Info are documented
  v1 scope cuts (recurrence-editing deferred), not defects.

Test suites green throughout: api 108, pwa 145; both tsc --noEmit clean.
Per-iteration REVIEW/REVIEW-FIX snapshots retained as audit trail.
2026-06-09 11:12:09 -04:00

85 lines
6.4 KiB
Markdown

---
phase: 03-event-write-back-pwa-install
fixed_at: 2026-06-09T15:06:11Z
review_path: .planning/phases/03-event-write-back-pwa-install/03-REVIEW.md
iteration: 2
findings_in_scope: 8
fixed: 8
skipped: 0
status: all_fixed
---
# Phase 3: Code Review Fix Report (Iteration 2)
**Fixed at:** 2026-06-09T15:06:11Z
**Source review:** .planning/phases/03-event-write-back-pwa-install/03-REVIEW.md
**Iteration:** 2
**Summary:**
- Findings in scope: 8 (fix_scope: all — Critical + Warning + Info)
- Fixed: 8
- Skipped: 0
All fixes verified with `tsc --noEmit` AND the full vitest suite in BOTH apps:
- `apps/api`: 108 tests pass (was 103 baseline; +5 new regression tests)
- `apps/pwa`: 145 tests pass (was 141 baseline; +4 new regression tests)
- Typecheck clean in both packages.
## Fixed Issues
### CR-01: Edit-as-move silently strips a recurring series' RRULE
**Files modified:** `apps/api/src/routes/events.ts`, `apps/api/src/broker/outboxWorker.ts`, `apps/api/tests/routes/events.test.ts`, `apps/api/tests/broker/outboxWorker.test.ts`
**Commit:** 5168920
**Applied fix:** Used the review's approach 1 (forward the RRULE from the route). The PATCH edit lookup now also selects `calendarEvents.rawVevent`. In the edit-as-move branch, when the edit payload carries no explicit `recurrence`, the route extracts the source RRULE via `extractRruleString()` and stashes it on the create outbox payload as `_preservedRrule`. The worker's `create` branch now mirrors the `update` branch's recurrence logic: it re-applies `_preservedRrule` when the payload omits `recurrence`, while an explicit `recurrence` (including `'none'`) still wins. Added two worker regression tests (moved event → emitted ICS contains `RRULE:`; explicit `recurrence:'none'` suppresses RRULE even when `_preservedRrule` present) and one route regression test (move stashes the source `FREQ=WEEKLY;BYDAY=MO` on the create row).
**Note:** Logic-sensitive fix. Backed by direct regression tests asserting the RRULE survives the move on both the route side (payload stash) and the worker side (ICS re-apply), so behavior is locked rather than relying on syntax verification alone.
### WR-01: Edit form provides no indication recurrence is locked
**Files modified:** `apps/pwa/src/components/EventForm.tsx`, `apps/pwa/src/components/EventForm.test.tsx`
**Commit:** eed178f
**Applied fix:** Additive helper text only (no logic change). In edit mode, explanatory copy renders beneath the disabled recurrence select: "Repeat can't be changed yet — edits keep the existing schedule." Added two tests (text present in edit mode; absent in create mode).
### WR-02: `handleAllDayToggle` can leave end-date inconsistent
**Files modified:** `apps/pwa/src/components/EventForm.tsx`, `apps/pwa/src/components/EventForm.test.tsx`
**Commit:** eed178f
**Applied fix:** On toggle-on, `endDate` is clamped to `max(startDate, endDate)` deterministically (snaps a behind-end up to the start day) and any stale end-time error from the timed view is cleared. Added a test toggling all-day ON with end behind start, asserting the clamp and clean validation. Per the review's prescribed `max(startDate, endDate)` fix, a genuinely midnight-spanning event (end day after start day) still yields a 2-day all-day span — the clamp only repairs the behind-case, matching the review's suggested fix exactly.
### WR-03: Missing cached etag becomes an unconditional PUT/DELETE
**Files modified:** `apps/api/src/broker/write.ts`
**Commit:** 5b720ff
**Applied fix:** Took the review's minimum (observability). `updateCalendarEvent` and `deleteCalendarEvent` now `console.warn` when dispatched with a null/empty etag, making the unconditional-write (conflict-detection-disabled) path observable instead of silent. The write is not blocked (blocking would strand the user's edit).
### WR-04: `sync-status` masks an earlier failure behind the newest row
**Files modified:** `apps/api/src/routes/events.ts`, `apps/api/tests/routes/events.test.ts`
**Commit:** fd13852
**Applied fix:** The `sync-status` query now orders by a status-priority CASE (`failed`/`dead` rank 0, `pending` rank 1, `done` rank 2) before `createdAt DESC`, so any failed/dead row for the uid is surfaced ahead of a later `done` row. Added a test seeding a dead row, asserting the handler returns `dead` + its error and that the ORDER BY contains the priority CASE expression (scanned via the Drizzle sql `queryChunks` to avoid the circular-structure JSON.stringify pitfall).
### IN-01: `RRULE_PRESETS` round-trip is lossy for parameterized RRULEs
**Files modified:** `apps/api/src/broker/vevent.ts`
**Commit:** f95760e
**Applied fix:** Documentation only. Added a v1-limitation note at `RRULE_PRESETS` explaining that applying a bare preset to a previously-rich rule drops BYDAY/INTERVAL/UNTIL/COUNT, and that recurrence editing must modify the parsed RECUR in place rather than replacing it with a preset.
### IN-02: `parseDateTime` silently rewrites a malformed edit value to today/09:00
**Files modified:** `apps/pwa/src/components/EventForm.tsx`, `apps/pwa/src/components/EventForm.test.tsx`
**Commit:** eed178f
**Applied fix:** `parseDateTime` now returns an `ok` flag. A new `initFormDateTime()` helper falls back to today/09:00 only on the CREATE path (benign default for a new event); in EDIT mode a parse failure leaves the field blank. `validate()` blocks submit when start/end (or time for non-all-day) is blank, surfacing "Couldn't read this event's date — re-open it from the calendar." Added a test: edit mode with an unparseable start leaves the date blank and blocks `updateEvent`.
**Note:** Logic-sensitive (changes validation flow). Backed by a regression test asserting the blank field + blocked submit; CREATE-mode defaults remain covered by existing tests.
### IN-03: Unchecked `as` casts on JSON-parsed outbox payload fields
**Files modified:** `apps/api/src/broker/outboxWorker.ts`, `apps/api/tests/broker/outboxWorker.test.ts`
**Commit:** b8c1864
**Applied fix:** Added a worker-local zod schema (`outboxPayloadSchema`, mirroring `eventFieldsSchema` and `.passthrough()`-ing the CR-01 `_preservedRrule` field). Both the `update` and `create` branches now `safeParse` the JSON payload after parsing and hard-fail the row (no retry) on validation error, so a schema-invalid row can never dispatch `SUMMARY:undefined`/Invalid Date. Added a test: a create row missing `title` is hard-failed and never dispatched.
---
_Fixed: 2026-06-09T15:06:11Z_
_Fixer: Claude (gsd-code-fixer)_
_Iteration: 2_