- Sidebar-navigation redesign: NavigationSplitView with Routes / Schedule /
Activity Log replaces the 2x2 panel-card grid. Primary actions moved to
the toolbar (dry-run/write, sync status, Sync Now); Export/Import/Reveal
Log/Cleanup/Refresh/Recheck Permission moved to a toolbar overflow menu.
- Routes are now a collapsed-by-default list -- click a row to expand its
editor instead of always showing every field for every route.
- Schedule view leads with live auto-sync status; the manual launchd
schedule is demoted to an explicit optional section underneath.
- Activity Log is readable rows (status icon + text) with a search filter
and a Clear button, instead of a monospaced text dump.
- Menu bar dropdown got icons and real Cmd+,/Cmd+Q shortcuts.
- Explored as a design mockup first (published as an Artifact, light+dark),
direction confirmed before writing any SwiftUI.
Fixed along the way: routes were being silently dropped on most launches.
reloadCalendars() pruned routes against a freshly-created EKEventStore's
calendar fetch right after a permission grant, which can under-report
calendars for a moment (especially remote/Exchange accounts) before they
finish loading -- wrongly concluding a route's calendar was gone. Only
survived because of a routes.v1 legacy backup key that itself never got
updated after its first write, so any real route edit made in an affected
session would have been silently reverted on the next launch. Fixed by
skipping the prune specifically on the post-grant reload
(reloadCalendars(pruneRoutes: false)) and keeping the legacy backup current
on every save instead of frozen.
All unit tests pass. Verified on a real install: routes stopped
disappearing across repeated launches (checked via --status and reading
the UserDefaults plist directly).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Real Settings (Cmd+,) window via PreferencesView, hosting the pure
@AppStorage-backed defaults that used to live in the main window's
"General Settings" panel. Both windows share the same live values since
they're backed by the same UserDefaults keys.
- Menu bar icon reflects idle/syncing/error state; dropdown shows last-sync
time/result and whether auto-sync is armed, sourced from the same
lastRunAtISO/lastRunOK/lastRunSummary keys --status reads.
- ContentView.swift split from ~2000 lines into CalendarsSectionView,
RoutesSectionView, ScheduleSectionView, LogSectionView + a small shared
CalendarDisplay.swift. View-layer extraction only — state ownership and
settings persistence deliberately left alone.
- Fixed a latent revert-on-relaunch bug caught while building the Settings
window: moving preference controls out of ContentView meant they no longer
re-triggered saveSettingsToDefaults(), so settings.v2 could go stale and
the next launch would silently revert a just-changed preference via the
shared applySnapshot path. Split launch-time restore (routes + selection
only) from Import's full restore.
All 47 unit tests pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>