- 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>
31 lines
4.2 KiB
Markdown
31 lines
4.2 KiB
Markdown
# BusyMirror Roadmap
|
|
|
|
## Shipped (highlights)
|
|
- Route-driven mirroring (multi-source)
|
|
- Accepted-only filter (mirror your accepted meetings)
|
|
- Persistent settings with autosave/restore; Import/Export JSON
|
|
- Overlap modes (allow, skipCovered, fillGaps) and merge-gap
|
|
- Work Hours filter and title-based skip filters
|
|
- Privacy: placeholders with prefix + customizable title
|
|
- 1.3.0: Mark Private option (global + per-route)
|
|
- 1.3.4: persistent file logging, stale-calendar pruning on refresh, clickable top-bar mode toggle
|
|
- 1.3.6: in-app scheduling via `launchd` with hourly/daily/weekday modes
|
|
- 1.3.6: generated macOS app icon set and packaged release assets
|
|
- 1.4.0: unit-test suite (45 tests), Cancel button, progress indicator, sandbox LaunchAgent fix, mirror URL fix, engine refactor into `MirrorConfig`
|
|
- CLI diagnostics: `--help`, `--list-calendars [--json]`, `--status [--json]`, real exit codes (2 = no access, 3 = no saved routes), last-run tracking (time/ok/summary)
|
|
- 1.7.0: **Event-driven background sync**, replacing the hourly `launchd StartInterval` poll. Auto-sync logic lives in `BusyMirrorAppController` (an app-lifetime object, not tied to `ContentView`'s window lifecycle — closing the main window used to tear down the `EKEventStoreChanged` observer along with it, which would have made auto-sync a no-op whenever the window was closed). Once saved routes exist: registers as a login item (`SMAppService.mainApp`), removes any old `launchd` schedule, watches `EKEventStoreChanged` (debounced ~3s) and `NSWorkspace.didWakeNotification`, plus a 30-min fallback timer as a safety net. Auto-sync always writes (`writeEnabled: true`), independent of the interactive dry-run toggle.
|
|
- 1.8.0: **V2 UI polish.** `ContentView.swift` split from ~2000 lines into focused view files — `CalendarsSectionView`, `RoutesSectionView`, `ScheduleSectionView`, `LogSectionView` (state stays owned by `ContentView`/`@AppStorage`; these are view-layer extractions, not a full MVVM rewrite — the settings-persistence model didn't need touching and touching it is exactly how the 1.6.0/1.6.1 data-loss bug happened). Real `Settings { }` scene (⌘,) via `PreferencesView`, hosting the pure `@AppStorage`-backed defaults (time window, privacy/mirroring defaults, work hours, skip filters) that used to live in the main window; this surfaced a latent bug — moving fields out of `ContentView` meant they stopped re-triggering `saveSettingsToDefaults()`, so on next launch `applySnapshot` would have silently reverted a preference changed in the new Settings window using the stale blob. Fixed by splitting launch-time restore (`restoreLaunchState`, routes/selections only, since @AppStorage fields already self-restore) from Import's full restore (`applySnapshot`, unchanged). Menu bar icon now reflects idle/syncing/error state, and the dropdown shows last-sync time/result and whether auto-sync is armed.
|
|
|
|
## Next
|
|
1. **MCP server (thin external wrapper, not embedded in the app).** So agents driving BusyMirror don't have to shell out to the CLI and regex-parse log lines. A small standalone stdio-transport script (Node/Python) maps MCP tools 1:1 onto the CLI's `--json` output: `list_calendars`, `list_routes`, `run_route`, `run_saved_routes`, `get_status`. Deliberately kept out of the Swift app itself — no MCP SDK dependency in the signed binary (AGENTS.md's zero-external-packages rule stays intact), and MCP hosts spawn server processes on demand anyway, so there's no need for the app to run one persistently.
|
|
2. **Better server-side privacy mapping (per-provider heuristics).**
|
|
|
|
## Later
|
|
- Signed/notarized binaries and release pipeline.
|
|
- Smarter cleanup & conflict resolution.
|
|
- iOS/iPadOS helper (Shortcuts integration).
|
|
- Profiles & MDM/Managed Config support.
|
|
|
|
## Decided against
|
|
- **Direct Google/CalDAV/Exchange API integration** (OAuth flows, token storage, per-provider clients so BusyMirror can mirror a non-local calendar without it being added to macOS). EventKit already surfaces any calendar added via System Settings → Internet Accounts — macOS does the sync, auth, and refresh. Building a parallel integration would duplicate the OS for the narrow case of an account that can't or won't be added system-wide (e.g. MDM-restricted work accounts). Not worth the OAuth/Keychain/per-provider-quirk surface for that.
|