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

6.4 KiB

phase, fixed_at, review_path, iteration, findings_in_scope, fixed, skipped, status
phase fixed_at review_path iteration findings_in_scope fixed skipped status
03-event-write-back-pwa-install 2026-06-09T15:06:11Z .planning/phases/03-event-write-back-pwa-install/03-REVIEW.md 2 8 8 0 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