Standard app instead of menu-bar-only: removed LSUIElement from Info.plist. Dock icon, Cmd+Tab, and the standard app menu (Cmd+Q) are back. The accessory-app design was making the app hard to quit reliably -- no Dock icon and no accessible app menu meant the only way to quit was finding the menu bar dropdown's "Quit BusyMirror" item, which was blocking updates (old process holds the bundle open, "app in use" on replace). Menu bar extra stays as a secondary quick-access point. Verified via `lsappinfo` (type="Foreground", was "UIElement") since this is an app-type change a screenshot wouldn't show either way. All unit tests pass. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.8 KiB
4.8 KiB
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
launchdwith 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 StartIntervalpoll. Auto-sync logic lives inBusyMirrorAppController(an app-lifetime object, not tied toContentView's window lifecycle — closing the main window used to tear down theEKEventStoreChangedobserver 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 oldlaunchdschedule, watchesEKEventStoreChanged(debounced ~3s) andNSWorkspace.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.swiftsplit from ~2000 lines into focused view files —CalendarsSectionView,RoutesSectionView,ScheduleSectionView,LogSectionView(state stays owned byContentView/@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). A preferences window 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 ofContentViewmeant they stopped re-triggeringsaveSettingsToDefaults(), so on next launchapplySnapshotwould have silently reverted a preference changed in the new 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. - 1.8.1: the preferences window (originally a
Settings{}scene) didn't actually open —LSUIElementapps get no standard app menu, so Cmd+,/SettingsLinkhad no menu to hook into. Replaced with a plainWindowopened viaopenWindow(id:), plus a "Preferences…" menu bar item as a second entry point. - 1.8.2: standard app, not menu-bar-only. Removed
LSUIElement— Dock icon, Cmd+Tab, standard app menu (Cmd+Q) are back. The accessory-app design (no Dock icon) turned out to make the app hard to quit reliably, which blocked replacing the bundle during updates. Menu bar extra stays as a secondary quick-access point.
Next
- 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
--jsonoutput: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. - 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.