Whole-branch review — fix/r1-native-keep-events-on-error @ b99d284 vs origin/merge/native-testflight 9ebcd95 (stacked on #13)
Reviewed by Claude Fable 5.1 (claude-fable-5-1), session 21a32889, as the AGENTS.md whole-branch reviewer. Diffed against its parent as told; scratch worktree; branch not edited.
VERDICT: APPROVE-WITH-NITS
What I checked and how
- Diff is one commit, four Swift files, nothing outside
native/apple.CalendarModel.swift,CalendarWorkspace.swift,CalendarMenuView.swift, newLoadFailureTests.swift. No Go, no migrations. - B11 fixed at the root. The
catchno longer assignsevents = []; it only setserror(still guarded bygeneration == token, so a stale load cannot write over a newer one).hasLoadedis set only after BOTH requests succeed, cleared byclear()on sign-out, and isprivate(set). - Views. Window: never-loaded + error → the existing
ContentUnavailableView; loaded + error → a one-line banner (staleBanner) with the error text and Try again, ABOVE the day section, which is now shown whenevererror.isEmpty || hasLoaded. Menu panel: same rule for the count, the "No events on this day" line and the agenda. Copy uses the same curly apostrophe as the existing string. Colour is not the only signal (icon + words). - Test exercises the fix — verified.
swift test: 6/6 (LoadFailureTests, CivilDateTests, SessionLifecycleTests). I putevents = []back into the catch andswift test --filter LoadFailureTestsfails with 2 assertions ("a failed refresh blanked the events on screen" and the dayEvents check); restored, it passes. The fixture (pythonThreadingHTTPServer,/fail//recover) follows the SessionLifecycleTests pattern. - Not visually verified in a running app — same as the discussion says. Given the
staleBanneraccessibility identifier is in place, a UI test could pick it up later.
Findings (none blocking)
- Nit —
CalendarModel.swift:60-70.loadfetches the session first and assignscalendars/weekStart/defaultCalendarbefore the events request. If the session call succeeds and the events call fails, the model shows FRESH calendars beside STALE events under a banner saying "showing what was last loaded". Harmless for one person on one team, but if you want the banner to be literally true, stage both responses into locals and commit them together after the secondguard. - Nit —
CalendarModel.swift:107(save). After a successful save, a failed reload leaves the banner up with the just-saved event absent from the list. That is honest, butnoticeis set fromsaved.warning(usually empty), so nothing says "saved". Considernotice = saved.warning ?? "Saved."or leavingquickSavedNotice-style feedback whenerroris non-empty. - Merge order. After #13 is squash-merged, merge origin/main into this branch and confirm
git diff --stat origin/main HEADshows only these four files before its own-squash -expect(R1-BRIEF, stacked branches).