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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,3 +20,4 @@ ExportOptions.plist
|
|||||||
*.swp
|
*.swp
|
||||||
*.zip
|
*.zip
|
||||||
*.sha256
|
*.sha256
|
||||||
|
dist/
|
||||||
|
@@ -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;
|
||||||
|
@@ -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 source’s ID, drop it
|
// If IDs contain the source’s 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
18
CHANGELOG.md
Normal 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 user’s 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.
|
||||||
|
|
@@ -14,11 +14,19 @@ BusyMirror mirrors meetings between your calendars so your availability stays co
|
|||||||
Use one calendar’s confirmed meetings to block time in other calendars (e.g., corporate iPad vs. personal devices).
|
Use one calendar’s 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
24
ReleaseNotes-1.2.3.md
Normal 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
|
||||||
|
|
Reference in New Issue
Block a user