34 Commits
Author SHA1 Message Date
tomas.kracmarandClaude Sonnet 5 9e3dd93939 Release 1.9.2
Fix Sync Now toolbar button showing icon-only, no text label. Confirmed
via screenshot that 1.9.1's sidebar/toolbar fixes worked; this was the
remaining clarity issue -- forced .labelStyle(.titleAndIcon) so the label
always shows.

All unit tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.9.2
2026-08-27 17:48:42 +02:00
tomas.kracmarandClaude Sonnet 5 622a85f4fd Release 1.9.1
Fix sidebar not clickable: List(SidebarSection.allCases, selection:) ties
selection to Identifiable's id (String) while the binding was typed
SidebarSection? -- rebuilt with the standard List(selection:) { ForEach {
.tag(section) } } pattern instead, plus .listStyle(.sidebar).

Fix "Write to calendars" hard to find: still present as the Dry Run/Write
toolbar segmented control, but likely lost to toolbar overflow alongside a
status readout, Sync Now, and the overflow menu all in one
ToolbarItemGroup. Moved status out to .navigationSubtitle (native, never
overflows), toolbar now just Dry Run/Write, Sync Now/Cancel, overflow menu.

All unit tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.9.1
2026-08-27 17:44:44 +02:00
tomas.kracmarandClaude Sonnet 5 77e3eeaf7b Release 1.9.0
- 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>
v1.9.0
2026-08-27 09:37:21 +02:00
tomas.kracmarandClaude Sonnet 5 4e29b3716c Release 1.8.3
Fix Preferences missing from the standard app menu: 1.8.1's workaround for
LSUIElement (plain Window + openWindow) was never swapped back for a real
Settings{} scene after 1.8.2 removed LSUIElement, so there was still no
Preferences item in the app's own menu or Cmd+, binding, only a button in
the main window and a menu-bar-dropdown item. Restored Settings{} now that
the app menu exists to host it; menu bar dropdown and main window button
both use the standard openSettings()/SettingsLink instead of a custom
openWindow(id:). Net simplification -- removes the now-redundant window ID
and controller method.

All unit tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.8.3
2026-08-27 09:16:29 +02:00
tomas.kracmarandClaude Sonnet 5 d40bc986c5 Release 1.8.2
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>
v1.8.2
2026-08-27 07:20:19 +02:00
tomas.kracmarandClaude Sonnet 5 39c5cba237 Release 1.8.1
Fix Preferences window being unreachable in 1.8.0: BusyMirror is LSUIElement
(accessory), so it never gets the standard app menu that the automatic
Settings scene's Cmd+, / SettingsLink depend on. Replaced Settings{} with a
plain Window opened via openWindow(id:) -- the same mechanism already used
for the main window -- and added a "Preferences..." item to the menu bar
dropdown as a second, more discoverable entry point.

All unit tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.8.1
2026-08-26 23:04:55 +02:00
tomas.kracmarandClaude Sonnet 5 edddf65288 Release 1.8.0
- 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>
v1.8.0
2026-08-26 21:26:07 +02:00
tomas.kracmarandClaude Sonnet 5 08e9fe5325 Release 1.7.0
- Event-driven background sync, replacing the hourly launchd StartInterval
  poll (only fired if the Mac happened to be awake at that instant, and got
  throttled/coalesced by macOS). Once saved routes exist: registers as a
  login item (SMAppService.mainApp), removes any old launchd schedule, and
  reacts to EKEventStoreChanged (debounced ~3s), NSWorkspace.didWakeNotification,
  plus a 30-min fallback timer as a safety net. Auto-sync always writes,
  matching what the old scheduled runs did.
- This lives in BusyMirrorAppController (app-lifetime), not ContentView —
  ContentView's own EKEventStoreChanged observer is torn down when the main
  window closes, which would make auto-sync a no-op exactly when it needs to
  keep working (window closed, menu-bar-only). Confirmed via MenuBarSupport's
  existing "Sync Now" handler, which already has to reopen the window before
  it can run anything.

All 47 unit tests pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.7.0
2026-08-26 18:37:23 +02:00
tomas.kracmarandClaude Sonnet 5 61cea918a6 Release 1.6.1
- Sync reminders: new "Sync reminders when mirroring" option (global and
  per-route) copies source event alarms into mirrored placeholders. CLI flag
  --sync-reminders.
- CLI polish: --help, --list-calendars, --status (all support --json).
  Scheduled/headless runs record last-run time/result/summary. Failure paths
  now exit with distinct nonzero codes (2 = no calendar access, 3 = no saved
  routes) instead of always exiting 0.
- Fix settings silently wiped on upgrade: SettingsPayload's auto-synthesized
  Codable threw on any settings blob missing a field added since (e.g. the
  new syncReminders key), failing the whole decode and letting the next
  autosave persist an empty state over real routes/filters. Custom decode
  now falls back per-field like Route already did, and loadSettingsFromDefaults
  recovers routes from the legacy routes.v1 key if settings.v2 comes back
  empty, repairing installs already hit by this.
- Roadmap: event-driven background sync (SMAppService + EKEventStoreChanged +
  wake notification) and an external MCP wrapper around the CLI queued next;
  decided against direct Google/CalDAV API integration since EventKit already
  covers it via System Settings accounts.

All 47 unit tests pass (45 existing + 2 new SettingsPayloadTests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v1.6.1
2026-08-26 13:10:23 +02:00
tomas.kracmarandClaude Sonnet 4.6 ad6ae396da Release 1.5.1
Bug fixes and code quality improvements:

- Fix mirror index dirtied on every sync (MirrorRecord.updatedAt in equality)
- Fix mirror URL corruption: encode calendar/source IDs before joining with ';'
  and use percentEncodedPath to prevent double-encoding
- Fix cleanup route mutating UI calendar picker selection unnecessarily
- Fix --exit flag redundancy (isCLIRun no longer implies termination)
- Remove dead SKIP_ALL_DAY_DEFAULT constant
- Replace deprecated FileHandle(forWritingAtPath:) with throwing variant
- Add EKEventStoreChanged observer for live calendar list refresh
- Extract AppLogStore into its own file (AppLogStore.swift)
- Add Block.span(start🔚) factory; replace verbose nil-field constructions
- Remove redundant MainActor.run{} wrappers inside @MainActor MirrorEngine
- Fix SettingsPayload indentation inside ContentView

All 45 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v1.5.1
2026-05-27 15:48:08 +02:00
tomas.kracmar 2c319808c2 Release 1.5.0
- Remove non-functional markPrivate feature and Objective-C runtime hacks
- Extract mirror engine into MirrorEngine.swift
- Move calLabel to MirrorUtils.swift
- Update AGENTS.md architecture documentation
- Bump version to 1.5.0 (build 19)
v1.5.0
2026-05-27 12:51:22 +02:00
tomas.kracmar f625ecc263 Release 1.4.0
Fixes:
- Sandbox: add LaunchAgent temporary-exception entitlement
- Mirror URL: fix broken buildMirrorURL (URLComponents with ; separator)
- Cleanup: add bounds check to prevent crash on missing source
- State safety: pass MirrorConfig instead of mutating global @State
- KVC: remove misleading do-catch around setValue:forKey:
- Log cap: limit in-memory log to 2000 lines
- CLI: fix race with calendar loading
- launchCtl: separate stdout/stderr pipes

Features:
- Cancel button for long-running mirrors
- Progress indicator for multi-route runs (Route X of Y)
- Target event cache across routes

Code quality:
- Extract BlockMath, MirrorUtils, EventFilters, MirrorConfig
- Add 45 unit tests across 3 test files
- Refactor mergeGapMin to computed property
- Make log editor read-only

Build:
- Bump version to 1.4.0 (build 18)
- Add LSMinimumSystemVersion 15.5
v1.4.0
2026-05-27 11:00:18 +02:00
tomas.kracmar fe9e813583 Release 1.3.9 v1.3.9 2026-04-09 15:55:09 +02:00
tomas.kracmar cdf82b99cc Release 1.3.8 v1.3.8 2026-04-08 11:56:01 +02:00
tomas.kracmar 2912d2f52a Release 1.3.7 v1.3.7 2026-03-24 10:36:44 +01:00
tomas.kracmar a838e021a1 Docs: refresh README and roadmap 2026-03-13 09:12:19 +01:00
tomas.kracmar f81403745c Release 1.3.6 v1.3.6 2026-03-13 09:08:31 +01:00
tomas.kracmar 58d88e9fa5 Release 1.3.4 v1.3.4 2026-03-13 06:56:46 +01:00
tomas.kracmar 3ecf29f499 1.3.1: fix auto-delete of missing-source mirrors; bump version; add release notes v1.3.1 2025-10-13 11:43:01 +02:00
tomas.kracmar eb643ac74d Version update 2025-10-10 10:00:57 +02:00
tomas.kracmar df06564434 BusyMirror 1.3.0: add Mark Private option (global + per-route); version bump and release notes v1.3.0 2025-10-10 09:58:05 +02:00
tomas.kracmar 74b9949610 BusyMirror 1.2.6: always enable Mirror Now when calendars accessible; route/manual decided at runtime v1.2.6 2025-10-10 09:08:26 +02:00
tomas.kracmar 6676e62889 BusyMirror 1.2.5: Mirror Now enables for routes or manual; add computed canRunMirrorNow; version bump v1.2.5 2025-10-10 08:59:59 +02:00
tomas.kracmar d1fbd4c81f BusyMirror 1.2.4: enable Mirror Now when routes exist; version bump v1.2.4 2025-10-10 08:52:41 +02:00
tomas.kracmar 6ef0feecc1 BusyMirror 1.2.3: reliable settings autosave/restore; remember source/target; @MainActor fixes; reinit EKEventStore after grant; Makefile; changelog + release notes v1.2.3 2025-10-10 08:34:07 +02:00
tomas.kracmar aac4de3fb3 BusyMirror 1.2.1: fix calendar loading after grant, attendee status filter, main-actor; add Makefile v1.2.1 2025-10-10 07:45:59 +02:00
tomas.kracmar 8f80a5f672 Adding source events filtering 2025-09-29 18:06:34 +02:00
tomas.kracmar ae40b42e6f Bump version v1.2.0 2025-09-29 17:52:18 +02:00
tomas.kracmar 691575c554 Adding support to save routes v1.1.0 2025-09-29 15:31:11 +02:00
tomas.kracmar b931f3ba2c Fixed building 2025-09-29 15:18:42 +02:00
tomas.kracmar 53f21492da Fixes 2025-09-29 13:30:45 +02:00
tomas.kracmar 37328eeae2 Added XCode project files v1.0.0 2025-08-23 15:43:03 +02:00
tomas.kracmar 3ac1faa3dc Initial commit 2025-08-23 15:39:57 +02:00
tomas.kracmar d6c04b9d90 Initial commit 2025-08-23 13:32:37 +00:00