BusyMirror 1.2.3: reliable settings autosave/restore; remember source/target; @MainActor fixes; reinit EKEventStore after grant; Makefile; changelog + release notes

This commit is contained in:
2025-10-10 08:34:07 +02:00
parent aac4de3fb3
commit 6ef0feecc1
6 changed files with 80 additions and 4 deletions

1
.gitignore vendored
View File

@@ -20,3 +20,4 @@ ExportOptions.plist
*.swp *.swp
*.zip *.zip
*.sha256 *.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 = 4; CURRENT_PROJECT_VERSION = 5;
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.2.1; MARKETING_VERSION = 1.2.3;
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 = 4; CURRENT_PROJECT_VERSION = 5;
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.2.1; MARKETING_VERSION = 1.2.3;
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;

View File

@@ -656,18 +656,33 @@ struct ContentView: View {
tryRunCLIIfPresent() tryRunCLIIfPresent()
enforceNoSourceInTargets() enforceNoSourceInTargets()
} }
// Persist key settings whenever they change, to ensure restore between runs
.onChange(of: daysBack) { _ in saveSettingsToDefaults() }
.onChange(of: daysForward) { _ in saveSettingsToDefaults() }
.onChange(of: mergeGapHours) { _ in saveSettingsToDefaults() }
.onChange(of: hideDetails) { _ in saveSettingsToDefaults() }
.onChange(of: copyDescription) { _ in saveSettingsToDefaults() }
.onChange(of: mirrorAllDay) { _ in saveSettingsToDefaults() }
.onChange(of: mirrorAcceptedOnly) { _ in saveSettingsToDefaults() }
.onChange(of: overlapModeRaw) { _ in saveSettingsToDefaults() }
.onChange(of: titlePrefix) { _ in saveSettingsToDefaults() }
.onChange(of: placeholderTitle) { _ in saveSettingsToDefaults() }
.onChange(of: autoDeleteMissing) { _ in saveSettingsToDefaults() }
.onChange(of: sourceIndex) { newValue in .onChange(of: sourceIndex) { newValue in
// Track selected source by persistent ID and ensure it is not a target // Track selected source by persistent ID and ensure it is not a target
if newValue < calendars.count { sourceID = calendars[newValue].calendarIdentifier } if newValue < calendars.count { sourceID = calendars[newValue].calendarIdentifier }
enforceNoSourceInTargets() enforceNoSourceInTargets()
saveSettingsToDefaults()
} }
.onChange(of: targetSelections) { _ in .onChange(of: targetSelections) { _ in
// If the new source is accidentally included, drop it // If the new source is accidentally included, drop it
enforceNoSourceInTargets() enforceNoSourceInTargets()
saveSettingsToDefaults()
} }
.onChange(of: targetIDs) { _ in .onChange(of: targetIDs) { _ in
// If IDs contain the sources ID, drop it // If IDs contain the sources ID, drop it
enforceNoSourceInTargets() enforceNoSourceInTargets()
saveSettingsToDefaults()
} }
.onChange(of: routes) { _ in .onChange(of: routes) { _ in
saveSettingsToDefaults() saveSettingsToDefaults()
@@ -1165,6 +1180,9 @@ private struct SettingsPayload: Codable {
var placeholderTitle: String var placeholderTitle: String
var autoDeleteMissing: Bool var autoDeleteMissing: Bool
var routes: [Route] var routes: [Route]
// UI selections (optional for backward compatibility)
var selectedSourceID: String? = nil
var selectedTargetIDs: [String]? = nil
// optional metadata // optional metadata
var appVersion: String? var appVersion: String?
var exportedAt: Date = Date() var exportedAt: Date = Date()
@@ -1188,6 +1206,8 @@ private struct SettingsPayload: Codable {
placeholderTitle: placeholderTitle, placeholderTitle: placeholderTitle,
autoDeleteMissing: autoDeleteMissing, autoDeleteMissing: autoDeleteMissing,
routes: routes, routes: routes,
selectedSourceID: sourceID,
selectedTargetIDs: Array(targetIDs).sorted(),
appVersion: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String, appVersion: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String,
exportedAt: Date() exportedAt: Date()
) )
@@ -1211,7 +1231,12 @@ private struct SettingsPayload: Codable {
placeholderTitle = s.placeholderTitle placeholderTitle = s.placeholderTitle
autoDeleteMissing = s.autoDeleteMissing autoDeleteMissing = s.autoDeleteMissing
routes = s.routes routes = s.routes
// Restore UI selections if provided
if let selSrc = s.selectedSourceID { sourceID = selSrc }
if let selTgts = s.selectedTargetIDs { targetIDs = Set(selTgts) }
clampWorkHours() clampWorkHours()
// Rebuild indices from IDs after restoring selections
rebuildSelectionsFromIDs()
} }
private func exportSettings() { private func exportSettings() {

18
CHANGELOG.md Normal file
View File

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

View File

@@ -14,11 +14,19 @@ BusyMirror mirrors meetings between your calendars so your availability stays co
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.
## Roadmap ## Roadmap
See [ROADMAP.md](ROADMAP.md) See [ROADMAP.md](ROADMAP.md)

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