13 Commits
v1.0.0 ... main

Author SHA1 Message Date
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
ae40b42e6f Bump version 2025-09-29 17:52:18 +02:00
691575c554 Adding support to save routes 2025-09-29 15:31:11 +02:00
b931f3ba2c Fixed building 2025-09-29 15:18:42 +02:00
53f21492da Fixes 2025-09-29 13:30:45 +02:00
11 changed files with 836 additions and 179 deletions

2
.gitignore vendored
View File

@@ -19,3 +19,5 @@ ExportOptions.plist
# Misc # Misc
*.swp *.swp
*.zip *.zip
*.sha256
dist/

View File

@@ -410,7 +410,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 9;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BusyMirror/Info.plist; INFOPLIST_FILE = BusyMirror/Info.plist;
@@ -421,7 +421,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.3.1;
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror; PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -440,7 +440,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 9;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = BusyMirror/Info.plist; INFOPLIST_FILE = BusyMirror/Info.plist;
@@ -451,7 +451,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.3.1;
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror; PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
REGISTER_APP_GROUPS = YES; REGISTER_APP_GROUPS = YES;
@@ -465,7 +465,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5; MACOSX_DEPLOYMENT_TARGET = 15.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
@@ -482,7 +482,7 @@
buildSettings = { buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)"; BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.5; MACOSX_DEPLOYMENT_TARGET = 15.5;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
@@ -498,7 +498,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirrorUITests; PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirrorUITests;
@@ -513,7 +513,7 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 2;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0; MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirrorUITests; PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirrorUITests;

File diff suppressed because it is too large Load Diff

28
CHANGELOG.md Normal file
View File

@@ -0,0 +1,28 @@
# Changelog
# Changelog
All notable changes to BusyMirror will be documented in this file.
## [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.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.

46
Makefile Normal file
View File

@@ -0,0 +1,46 @@
# 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 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
app: build-release
@# Ensure the app exists
@test -d "$(APP_PATH)" && echo "Built: $(APP_PATH)" || (echo "App not found at $(APP_PATH)" && exit 1)
@echo "Version: $(VERSION)"
@echo "OK"
package: app
@echo "Packaging BusyMirror $(VERSION)"
@zip -qry "BusyMirror-$(VERSION)-macOS.zip" "$(APP_PATH)"
@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,43 @@
BusyMirror mirrors meetings between your calendars so your availability stays consistent across accounts/devices. BusyMirror mirrors meetings between your calendars so your availability stays consistent across accounts/devices.
## What it does (current checkpoint) ## What it does (current)
- Manual “Run” to mirror events across selected routes (Source → Targets). - Route-driven mirroring (multi-source): define Source → Targets routes and run them in one go.
- DRY-RUN mode shows what would happen. - Manual selection mirroring: pick a source and targets in the UI and run.
- Prefix-based tagging of mirrored events. - Two privacy modes:
- Cleanup of placeholders (with confirmation). - Private (hide details): mirrors placeholders with prefix + placeholder title (e.g., "🪞 Busy").
- Loop/duplicate guards so mirrors dont replicate themselves. - Mark Private: mirrors prefix + real title, but marks events Private on supported servers (best-effort).
- Time window and merge-gap settings. - DRY-RUN mode: see what would be created/updated/deleted without writing.
- 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.
- Prefix-based tagging and loop guards to prevent re-mirroring mirrors.
- Settings: autosave/restore, Import/Export JSON.
## Why ## Why
Use one calendars confirmed meetings to block time in other calendars (e.g., corporate iPad vs. personal devices). Use one calendars confirmed meetings to block time in other calendars (e.g., corporate iPad vs. personal devices).
## Build (macOS) ## Build (macOS)
Option A — Xcode
1. Open `BusyMirror.xcodeproj` in Xcode. 1. Open `BusyMirror.xcodeproj` in Xcode.
2. Select the BusyMirror scheme → My Mac. 2. Select the BusyMirror scheme → My Mac.
3. Product → Build. 3. Product → Build.
4. Product → Archive → Distribute App → Copy App (no notarization) to export a `.app` (or ZIP it for sharing). 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`
- Flags exist for privacy, all-day, merge gap, days window, overlap mode, and cleanup.
## Roadmap ## Roadmap
See [ROADMAP.md](ROADMAP.md) See [ROADMAP.md](ROADMAP.md)

View File

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