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.
85 lines
6.4 KiB
Markdown
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_
|