15 Commits

Author SHA1 Message Date
fe9e813583 Release 1.3.9 2026-04-09 15:55:09 +02:00
cdf82b99cc Release 1.3.8 2026-04-08 11:56:01 +02:00
2912d2f52a Release 1.3.7 2026-03-24 10:36:44 +01:00
a838e021a1 Docs: refresh README and roadmap 2026-03-13 09:12:19 +01:00
f81403745c Release 1.3.6 2026-03-13 09:08:31 +01:00
58d88e9fa5 Release 1.3.4 2026-03-13 06:56:46 +01:00
3ecf29f499 1.3.1: fix auto-delete of missing-source mirrors; bump version; add release notes 2025-10-13 11:43:01 +02:00
eb643ac74d Version update 2025-10-10 10:00:57 +02:00
df06564434 BusyMirror 1.3.0: add Mark Private option (global + per-route); version bump and release notes 2025-10-10 09:58:05 +02:00
74b9949610 BusyMirror 1.2.6: always enable Mirror Now when calendars accessible; route/manual decided at runtime 2025-10-10 09:08:26 +02:00
6676e62889 BusyMirror 1.2.5: Mirror Now enables for routes or manual; add computed canRunMirrorNow; version bump 2025-10-10 08:59:59 +02:00
d1fbd4c81f BusyMirror 1.2.4: enable Mirror Now when routes exist; version bump 2025-10-10 08:52:41 +02:00
6ef0feecc1 BusyMirror 1.2.3: reliable settings autosave/restore; remember source/target; @MainActor fixes; reinit EKEventStore after grant; Makefile; changelog + release notes 2025-10-10 08:34:07 +02:00
aac4de3fb3 BusyMirror 1.2.1: fix calendar loading after grant, attendee status filter, main-actor; add Makefile 2025-10-10 07:45:59 +02:00
8f80a5f672 Adding source events filtering 2025-09-29 18:06:34 +02:00
29 changed files with 2114 additions and 403 deletions

2
.gitignore vendored
View File

@@ -18,5 +18,7 @@ ExportOptions.plist
# Misc
*.swp
*.profraw
*.zip
*.sha256
dist/

View File

@@ -410,7 +410,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 17;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BusyMirror/Info.plist;
@@ -421,7 +421,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.1.0;
MARKETING_VERSION = 1.3.9;
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;
@@ -440,7 +440,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 17;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BusyMirror/Info.plist;
@@ -451,7 +451,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.1.0;
MARKETING_VERSION = 1.3.9;
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES;

View File

@@ -1,55 +1,15 @@
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
{ "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" },
{ "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" },
{ "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" },
{ "filename" : "icon_64x64.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" },
{ "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" },
{ "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" },
{ "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" },
{ "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" },
{ "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" },
{ "filename" : "icon_1024x1024.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" }
],
"info" : {
"author" : "xcode",

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@@ -2,10 +2,19 @@ import SwiftUI
@main
struct BusyMirrorApp: App {
@StateObject private var appController = BusyMirrorAppController()
var body: some Scene {
WindowGroup {
Window("BusyMirror", id: BusyMirrorSceneID.mainWindow) {
ContentView()
.environmentObject(appController)
.frame(minWidth: 720, minHeight: 520)
}
.defaultSize(width: 1120, height: 760)
MenuBarExtra("BusyMirror", systemImage: appController.isSyncing ? "arrow.triangle.2.circlepath.circle.fill" : "calendar.badge.clock") {
BusyMirrorMenuBarView()
.environmentObject(appController)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>LSUIElement</key>
<true/>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>BusyMirror needs access to your calendars to create busy placeholders.</string>
<key>NSRemindersFullAccessUsageDescription</key>

View File

@@ -0,0 +1,75 @@
import SwiftUI
import AppKit
enum BusyMirrorSceneID {
static let mainWindow = "main-window"
}
@MainActor
final class BusyMirrorAppController: ObservableObject {
@Published private(set) var isSyncing = false
@Published private(set) var hasPendingSyncRequest = false
@Published private(set) var syncRequestToken = UUID()
@Published private(set) var isMainWindowVisible = false
func requestSync() {
hasPendingSyncRequest = true
syncRequestToken = UUID()
}
func clearPendingSyncRequest() {
hasPendingSyncRequest = false
}
func setSyncing(_ syncing: Bool) {
isSyncing = syncing
}
func setMainWindowVisible(_ visible: Bool) {
isMainWindowVisible = visible
}
func openMainWindow(using openWindow: OpenWindowAction) {
NSApp.activate(ignoringOtherApps: true)
openWindow(id: BusyMirrorSceneID.mainWindow)
}
}
struct BusyMirrorMenuBarView: View {
@Environment(\.openWindow) private var openWindow
@EnvironmentObject private var appController: BusyMirrorAppController
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("BusyMirror")
.font(.headline)
Text(appController.isSyncing ? "Sync in progress." : "Use your saved routes or current selection.")
.font(.subheadline)
.foregroundStyle(.secondary)
Divider()
Button(appController.isSyncing ? "Syncing…" : "Sync Now") {
let shouldOpenWindow = !appController.isMainWindowVisible
appController.requestSync()
if shouldOpenWindow {
appController.openMainWindow(using: openWindow)
}
}
.disabled(appController.isSyncing)
Button("Open BusyMirror") {
appController.openMainWindow(using: openWindow)
}
Divider()
Button("Quit BusyMirror") {
NSApp.terminate(nil)
}
}
.padding(12)
.frame(width: 240, alignment: .leading)
}
}

62
CHANGELOG.md Normal file
View File

@@ -0,0 +1,62 @@
# Changelog
All notable changes to BusyMirror will be documented in this file.
## [1.3.9] - 2026-04-09
- New: add a macOS menu bar extra with `Sync Now`, `Open BusyMirror`, and `Quit BusyMirror`.
- UX: menu bar sync requests reuse the existing mirror flow and can open the main window automatically when needed.
- UX: BusyMirror now runs as a menu bar-only app and no longer appears in the Dock.
- Build: bump version to 1.3.9 (build 17).
## [1.3.8] - 2026-04-08
- Fix: release ZIPs now package `BusyMirror.app` at the archive root instead of embedding the full build path.
- Fix: release builds now apply an ad-hoc bundle signature before packaging so downloaded artifacts pass `codesign --verify --deep --strict`.
- Build: suppress resource fork sidecars in release ZIPs via `ditto --norsrc --keepParent`.
- Build: bump version to 1.3.8 (build 16).
## [1.3.7] - 2026-03-24
- Fix: mirror reconciliation now survives target providers that strip BusyMirror's custom event URL metadata.
- Fix: moved and deleted source events are tracked via stable EventKit identifiers and a persisted local mirror index, so target placeholders update reliably.
- Fix: mirror updates now detect title and notes changes, not just start/end time changes.
- Build: bump version to 1.3.7 (build 15).
## [1.3.6] - 2026-03-13
- Scheduling: add in-app `Scheduled runs` controls to install or remove a user `launchd` LaunchAgent from BusyMirror itself.
- Scheduling: support `Hourly`, `Daily`, and `Weekdays` schedules; hourly mode runs saved routes via `StartInterval`.
- UX: generate and ship a proper macOS app icon set for BusyMirror.
- Build: bump version to 1.3.6 (build 14).
## [1.3.4] - 2026-03-13
- Fix: route-scoped cleanup no longer deletes placeholders created by other source routes during the same multi-route run.
- Fix: stale calendars are pruned from saved selections and routes during refresh, and refresh now recreates `EKEventStore` for a hard reload.
- UX: the top bar `DRY RUN` / `WRITE` status pill is clickable, the left column keeps its own height on desktop, and the app can reveal its log file from the UI.
- Logging: mirror activity is persisted to `~/Library/Logs/BusyMirror/BusyMirror.log` with simple rotation to `BusyMirror.previous.log`.
- CLI: add `--run-saved-routes` so scheduled `launchd` runs can use the saved UI routes instead of fragile index-based route definitions.
## [1.3.1] - 2025-10-13
- Fix: auto-delete of mirrored placeholders when the source is removed now works even if no source instances remain in the window. Also cleans legacy mirrors without URLs by matching exact times.
## [1.3.2] - 2025-10-13
- New: Organizer filters — skip events by organizer (name/email/URL). UI under Options and persisted in settings.
- CLI: add `--exclude-organizers` (and `--exclude-titles`) flags to control filters when running headless.
## [1.2.4] - 2025-10-10
- Fix: enable “Mirror Now” when Routes are defined even if no Source/Targets are checked in the main window. Button now enables if either routes exist or a manual selection is present.
## [1.3.0] - 2025-10-10
- New: Mark Private option to mirror with prefix + real title and set event privacy on supported servers; available globally and per-route; persisted.
- Misc: calendar access fixes, concurrency annotations, acceptedonly filter, settings autosave/restore, Mirror Now enablement.
## [1.2.3] - 2025-10-10
- Fix: reliably save and restore settings between runs via autosave of key options and restoration of source/target selections by persistent IDs.
- UX: persist Source and Target selections; rebuild indices on launch so UI matches saved IDs.
- Build: bump version to 1.2.3 (build 5).
## [1.2.1] - 2025-10-10
- Fix: reinitialize EKEventStore after permission grant to avoid “Loaded 0 calendars” right after approval.
- Fix: attendee status filter uses current users attendee `participantStatus == .accepted` instead of unavailable APIs.
- Concurrency: mark `requestAccess()` and `reloadCalendars()` as `@MainActor` to satisfy strict concurrency checks.
- Dev: add Makefile with `build-debug`, `build-release`, and `package` targets; produce versioned ZIP + SHA-256.
## [1.2.0] - 2024-09-29
- Feature: multi-route mirroring, overlap modes, merge gaps, work hours filter, CLI support, export/import settings.

55
Makefile Normal file
View File

@@ -0,0 +1,55 @@
# Simple build and package helpers for BusyMirror
SCHEME ?= BusyMirror
PROJECT ?= BusyMirror.xcodeproj
DERIVED ?= build/DerivedData
DEST := platform=macOS
# Extract marketing version from project settings
VERSION := $(shell sed -n 's/.*MARKETING_VERSION = \([0-9.]*\);.*/\1/p' $(PROJECT)/project.pbxproj | head -n1)
.PHONY: all clean build-debug build-release sign-app open app package
all: build-release
clean:
@echo "Cleaning derived data…"
xcodebuild -scheme $(SCHEME) -project $(PROJECT) -derivedDataPath $(DERIVED) -destination '$(DEST)' CODE_SIGNING_ALLOWED=NO clean >/dev/null
@echo "Done."
build-debug:
@echo "Building Debug…"
xcodebuild -scheme $(SCHEME) -project $(PROJECT) -configuration Debug -destination '$(DEST)' -derivedDataPath $(DERIVED) CODE_SIGNING_ALLOWED=NO build
build-release:
@echo "Building Release…"
xcodebuild -scheme $(SCHEME) -project $(PROJECT) -configuration Release -destination '$(DEST)' -derivedDataPath $(DERIVED) CODE_SIGNING_ALLOWED=NO build
# Convenience to open the built app in Finder
open: app
@open "$<"
# Path to built app (Release)
APP_PATH := $(DERIVED)/Build/Products/Release/BusyMirror.app
SIGNED_APP_PATH := build/ReleaseSigned/BusyMirror.app
sign-app: build-release
@echo "Preparing signed release app…"
@rm -rf "$(SIGNED_APP_PATH)"
@mkdir -p "$(dir $(SIGNED_APP_PATH))"
@ditto "$(APP_PATH)" "$(SIGNED_APP_PATH)"
@xattr -rc "$(SIGNED_APP_PATH)"
@codesign --force --deep --sign - "$(SIGNED_APP_PATH)"
@codesign --verify --deep --strict --verbose=2 "$(SIGNED_APP_PATH)"
app: sign-app
@# Ensure the app exists
@test -d "$(SIGNED_APP_PATH)" && echo "Built: $(SIGNED_APP_PATH)" || (echo "App not found at $(SIGNED_APP_PATH)" && exit 1)
@echo "Version: $(VERSION)"
@echo "OK"
package: app
@echo "Packaging BusyMirror $(VERSION)"
@ditto --norsrc -c -k --keepParent "$(SIGNED_APP_PATH)" "BusyMirror-$(VERSION)-macOS.zip"
@shasum -a 256 "BusyMirror-$(VERSION)-macOS.zip" | awk '{print $$1}' > "BusyMirror-$(VERSION)-macOS.zip.sha256"
@echo "Created BusyMirror-$(VERSION)-macOS.zip and .sha256"

View File

@@ -2,23 +2,71 @@
BusyMirror mirrors meetings between your calendars so your availability stays consistent across accounts/devices.
## What it does (current checkpoint)
- Manual “Run” to mirror events across selected routes (Source → Targets).
- DRY-RUN mode shows what would happen.
- Prefix-based tagging of mirrored events.
- Cleanup of placeholders (with confirmation).
- Loop/duplicate guards so mirrors dont replicate themselves.
- Time window and merge-gap settings.
On macOS, BusyMirror now runs as a menu bar app. Use the menu bar icon to sync manually or open the main window; it no longer appears in the Dock.
## What it does (current)
- Route-driven mirroring (multi-source): define Source → Targets routes and run them in one go.
- Manual selection mirroring: pick a source and targets in the UI and run.
- Two privacy modes:
- Private (hide details): mirrors placeholders with prefix + placeholder title (e.g., "🪞 Busy").
- Mark Private: mirrors prefix + real title, but marks events Private on supported servers (best-effort).
- DRY-RUN mode: see what would be created/updated/deleted without writing.
- Activity Log in the app plus persistent file logging on disk.
- In-app scheduling: install or remove a `launchd` LaunchAgent from the `Scheduled runs` section.
- Menu bar controls: trigger `Sync Now`, open the main window, or quit without keeping a Dock icon around.
- Overlap modes: `allow`, `skipCovered`, `fillGaps`.
- Merge adjacent events with a configurable gap.
- Time window controls (days back/forward) and Work Hours filter.
- Accepted-only filter (mirror your accepted meetings only).
- Cleanup of placeholders, including auto-delete of mirrors whose source disappeared.
- Refresh Calendars prunes stale saved calendars and routes when calendars are removed from the system.
- Prefix-based tagging and loop guards to prevent re-mirroring mirrors.
- Settings: autosave/restore, Import/Export JSON, saved routes for scheduled/headless runs.
## Why
Use one calendars confirmed meetings to block time in other calendars (e.g., corporate iPad vs. personal devices).
## Build (macOS)
Option A — Xcode
1. Open `BusyMirror.xcodeproj` in Xcode.
2. Select the BusyMirror scheme → My Mac.
3. Product → Build.
4. Product → Archive → Distribute App → Copy App (no notarization) to export a `.app` (or ZIP it for sharing).
Option B — Makefile (reproducible)
- Build Release: `make build-release`
- Package ZIP: `make package` (creates `BusyMirror-<version>-macOS.zip` + `.sha256`)
- Built app: `build/DerivedData/Build/Products/Release/BusyMirror.app`
See `CHANGELOG.md` for notable changes.
## CLI (optional)
- Run from Terminal with `--routes` to mirror without the UI. Example:
- `BusyMirror.app/Contents/MacOS/BusyMirror --routes "1->2,3; 4->5" --write 1 --days-forward 7 --mode allow --exit`
- Run the routes already saved in the app settings:
- `BusyMirror.app/Contents/MacOS/BusyMirror --run-saved-routes --write 1 --exit`
- Flags exist for privacy, all-day, merge gap, days window, overlap mode, cleanup, and filters.
- Filters:
- `--exclude-titles "token1, token2"`
- `--exclude-organizers "alice@example.com, Example Org"`
- Tokens are comma or newline separated; matching is case-insensitive.
## Logs
- BusyMirror now writes a persistent log file to `~/Library/Logs/BusyMirror/BusyMirror.log`.
- When the file grows large, the previous file is rotated to `~/Library/Logs/BusyMirror/BusyMirror.previous.log`.
- `launchd` stdout/stderr for scheduled runs are also written in the same folder.
- In the UI, use `Reveal Log File` to open the current log directly in Finder.
## Scheduling
- BusyMirror can create its own schedule from the app UI in `Scheduled runs`.
- Choose `Hourly`, `Daily`, or `Weekdays`, then click `Install Schedule`.
- The installed LaunchAgent runs:
- `/Applications/BusyMirror.app/Contents/MacOS/BusyMirror --run-saved-routes --write 1 --exit`
- This is more stable than index-based `--routes`, because it uses the routes and per-route options you already configured in the UI.
- Hourly schedules use `launchd` `StartInterval`; daily and weekday schedules use `StartCalendarInterval`.
- You can remove the job from the same UI with `Remove Schedule`, and inspect the generated plist with `Reveal LaunchAgent`.
- Note: scheduled headless runs depend on Calendar permission being granted to the installed app. Because these local builds are unsigned, macOS may require re-granting permission after replacing the app bundle with a new build.
## Roadmap
See [ROADMAP.md](ROADMAP.md)

View File

@@ -1,17 +1,29 @@
# 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
## Next
- Source filters (name patterns like `[HOLD]`, `#nomirror`)
- Mirror only **Accepted** meetings (exclude tentative/declined)
- Persistent settings (routes, window, prefix)
- Import/Export settings (.busymirror.json)
- Auto-refresh calendars on `EKEventStoreChanged` (live refresh button-less)
- Better scheduled-run diagnostics in the UI (last run / last error / next run)
- Better server-side privacy mapping (per-provider heuristics)
## Then
- iOS/iPadOS app (Run Now, Shortcuts, iCloud sync)
- UI: route editor & clearer toggles
- Signed/notarized binaries and release pipeline
- CLI quality: friendlier `--routes` parsing and help flag
- “Dry-run by default” preference
## Later
- Background monitoring (macOS)
- Smarter cleanup & conflict resolution
- iOS/iPadOS helper (Shortcuts integration)
- Profiles & MDM/Managed Config support

24
ReleaseNotes-1.2.3.md Normal file
View File

@@ -0,0 +1,24 @@
# BusyMirror 1.2.3 — 2025-10-10
This release focuses on reliable settings persistence and quality-of-life fixes from the 1.2.1 hotfix.
Highlights
- Settings persist between runs: autosave key options on change; restore on launch.
- Source/Target selection is remembered using calendar IDs and rehydrated into UI indices.
Fixes and improvements
- Save on change for: days back/forward, default merge gap, privacy/copy notes, all-day, accepted-only, overlap mode, title/placeholder prefixes, auto-delete.
- Restore saved `selectedSourceID` and `selectedTargetIDs` and rebuild index selections.
- Keep backward compatibility with older saved payloads.
- Version bump to 1.2.3 (build 5).
Included from 1.2.1
- Reinitialize `EKEventStore` after permission grant to avoid “Loaded 0 calendars”.
- Use attendee `participantStatus == .accepted` for accepted-only filter.
- Mark `requestAccess()` and `reloadCalendars()` as `@MainActor`.
- Makefile for reproducible builds and packaging.
Build
- `make build-release`
- `make package` → BusyMirror-1.2.3-macOS.zip and .sha256

11
ReleaseNotes-1.2.4.md Normal file
View File

@@ -0,0 +1,11 @@
# BusyMirror 1.2.4 — 2025-10-10
Bugfix release improving route-driven mirroring.
Fixes
- Mirror Now is enabled when routes are defined, even if nothing is checked in the main window. This allows fully route-driven runs without requiring a temporary manual selection.
Build
- `make build-release`
- `make package` → BusyMirror-1.2.4-macOS.zip and .sha256

17
ReleaseNotes-1.3.0.md Normal file
View File

@@ -0,0 +1,17 @@
# BusyMirror 1.3.0 — 2025-10-10
New
- Mark Private option: mirror events with your prefix + real title while marking them Private on supported servers (e.g., Exchange). Coworkers see the time block but not the details.
- Per-route and global toggles for Mark Private; persists in settings and export/import.
Fixes & improvements
- More reliable calendar loading after permission grant (reinit EKEventStore).
- Concurrency: `@MainActor` on permission/refresh methods.
- Acceptedonly filter via current user attendee `participantStatus`.
- Settings autosave and restore (including source/target selections by IDs).
- Mirror Now enabled when calendars available; routes or manual selection used as appropriate.
Build
- `make build-release`
- `make package` → BusyMirror-1.3.0-macOS.zip and .sha256

6
ReleaseNotes-1.3.1.md Normal file
View File

@@ -0,0 +1,6 @@
BusyMirror 1.3.1 — Bugfix Release
- Fix: Auto-delete mirrored placeholders when the source event is removed.
- Triggers even if no source instances remain in the selected window.
- Also cleans legacy mirrors without mirror URLs by matching exact times.

11
ReleaseNotes-1.3.2.md Normal file
View File

@@ -0,0 +1,11 @@
BusyMirror 1.3.2 — 2025-10-13
Changes
- Organizer filters: skip mirroring events whose organizer matches a name, email, or URL token. Case-insensitive. Configure in Options.
- CLI flags: `--exclude-organizers` and `--exclude-titles` accept comma/newline separated tokens. Example:
- `--routes "1->2" --write 1 --exclude-organizers "alice@example.com, Example Org" --exit`
Notes
- Export/Import settings now includes organizer filters (backwards compatible).
- No changes to event URL format; feature is fully optional.

9
ReleaseNotes-1.3.3.md Normal file
View File

@@ -0,0 +1,9 @@
BusyMirror 1.3.3 — 2025-10-13
Changes
- UI: Options panel is scrollable to ensure new filters are always visible on smaller windows.
- Organizer filter: skip by organizer name/email/URL; settings persisted; usable via CLI with `--exclude-organizers`.
Build
- Version bump to 1.3.3 (build stays 11).

11
ReleaseNotes-1.3.4.md Normal file
View File

@@ -0,0 +1,11 @@
BusyMirror 1.3.4 - 2026-03-13
Changes
- Fix multi-route cleanup so one source route no longer deletes mirrored placeholders created by another route.
- Persist activity logs to `~/Library/Logs/BusyMirror/BusyMirror.log` and expose a `Reveal Log File` action in the app.
- Add `--run-saved-routes` for headless runs using the routes configured in the UI, which makes `launchd` scheduling practical.
- Improve calendar refresh by pruning stale saved identifiers and recreating the EventKit store.
- Keep the left column from stretching to match the routes/log column on desktop layouts.
Build
- Version bump to 1.3.4 (build 12).

9
ReleaseNotes-1.3.6.md Normal file
View File

@@ -0,0 +1,9 @@
BusyMirror 1.3.6 - 2026-03-13
Changes
- Add in-app scheduling controls so BusyMirror can install and remove its own `launchd` LaunchAgent.
- Support hourly saved-route runs in addition to daily and weekday schedules.
- Ship a generated macOS app icon set for the app bundle and exported releases.
Build
- Version bump to 1.3.6 (build 14).

9
ReleaseNotes-1.3.7.md Normal file
View File

@@ -0,0 +1,9 @@
BusyMirror 1.3.7 - 2026-03-24
Changes
- Fix mirrored event tracking on providers that do not preserve BusyMirror's custom event URL metadata.
- Track source events using stable EventKit identifiers and a local mirror index so moved and deleted source events update target calendars reliably.
- Detect title and notes changes during reconciliation instead of only updating mirrors when times change.
Build
- Version bump to 1.3.7 (build 15).

9
ReleaseNotes-1.3.8.md Normal file
View File

@@ -0,0 +1,9 @@
BusyMirror 1.3.8 - 2026-04-08
Changes
- Fix release packaging so the ZIP contains `BusyMirror.app` at the archive root.
- Apply an ad-hoc bundle signature before packaging so the distributed app bundle verifies correctly after unzip.
- Strip resource fork sidecars from release archives to avoid malformed download contents.
Build
- Version bump to 1.3.8 (build 16).

9
ReleaseNotes-1.3.9.md Normal file
View File

@@ -0,0 +1,9 @@
BusyMirror 1.3.9 - 2026-04-09
Changes
- Add a menu bar extra with `Sync Now`, `Open BusyMirror`, and `Quit BusyMirror`.
- Route menu bar sync requests through the same mirroring flow as the main window, opening the window automatically when needed.
- Run BusyMirror as a menu bar-only app so it no longer appears in the Dock.
Build
- Version bump to 1.3.9 (build 17).