From 39c5cba2377f4a48374d82b86dc4dc0b139e6e7a Mon Sep 17 00:00:00 2001 From: Tomas Kracmar Date: Wed, 26 Aug 2026 23:04:55 +0200 Subject: [PATCH] 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 --- BusyMirror.xcodeproj/project.pbxproj | 8 ++++---- BusyMirror/BusyMirrorApp.swift | 10 +++++++++- BusyMirror/ContentView.swift | 5 +++-- BusyMirror/MenuBarSupport.swift | 10 ++++++++++ CHANGELOG.md | 5 +++++ 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/BusyMirror.xcodeproj/project.pbxproj b/BusyMirror.xcodeproj/project.pbxproj index 14e740d..61a96d3 100644 --- a/BusyMirror.xcodeproj/project.pbxproj +++ b/BusyMirror.xcodeproj/project.pbxproj @@ -410,7 +410,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 24; + CURRENT_PROJECT_VERSION = 25; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BusyMirror/Info.plist; @@ -421,7 +421,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.8.1; 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 = 24; + CURRENT_PROJECT_VERSION = 25; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = BusyMirror/Info.plist; @@ -451,7 +451,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.8.0; + MARKETING_VERSION = 1.8.1; PRODUCT_BUNDLE_IDENTIFIER = com.cqrenet.BusyMirror; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; diff --git a/BusyMirror/BusyMirrorApp.swift b/BusyMirror/BusyMirrorApp.swift index 89a6894..4534134 100644 --- a/BusyMirror/BusyMirrorApp.swift +++ b/BusyMirror/BusyMirrorApp.swift @@ -23,9 +23,17 @@ struct BusyMirrorApp: App { .environmentObject(appController) } - Settings { + // A plain Window (not a Settings scene) — LSUIElement (accessory) + // apps don't get the standard app menu, so Cmd+, / the automatic + // "Settings…" command has no menu to live in and SettingsLink has + // nothing reliable to trigger. openWindow(id:) is the same mechanism + // that already reliably opens the main window from the menu bar, so + // reuse it here instead. + Window("Preferences", id: BusyMirrorSceneID.preferencesWindow) { PreferencesView() .environmentObject(appController) } + .defaultSize(width: 480, height: 560) + .windowResizability(.contentSize) } } diff --git a/BusyMirror/ContentView.swift b/BusyMirror/ContentView.swift index 2ce2cee..8c37170 100644 --- a/BusyMirror/ContentView.swift +++ b/BusyMirror/ContentView.swift @@ -61,6 +61,7 @@ struct Route: Identifiable, Hashable, Codable { struct ContentView: View { @EnvironmentObject private var appController: BusyMirrorAppController + @Environment(\.openWindow) private var openWindow @State private var store = EKEventStore() @State private var hasAccess = false @State private var calendars: [EKCalendar] = [] @@ -615,8 +616,8 @@ struct ContentView: View { Text("Mirroring defaults, filters, and work hours moved to Preferences.") .font(.footnote) .foregroundStyle(.secondary) - SettingsLink { - Text("Open Preferences…") + Button("Open Preferences…") { + appController.openPreferencesWindow(using: openWindow) } Spacer(minLength: 0) } diff --git a/BusyMirror/MenuBarSupport.swift b/BusyMirror/MenuBarSupport.swift index c767cfe..7d5f3c7 100644 --- a/BusyMirror/MenuBarSupport.swift +++ b/BusyMirror/MenuBarSupport.swift @@ -5,6 +5,7 @@ import ServiceManagement enum BusyMirrorSceneID { static let mainWindow = "main-window" + static let preferencesWindow = "preferences-window" } @MainActor @@ -66,6 +67,11 @@ final class BusyMirrorAppController: ObservableObject { openWindow(id: BusyMirrorSceneID.mainWindow) } + func openPreferencesWindow(using openWindow: OpenWindowAction) { + NSApp.activate(ignoringOtherApps: true) + openWindow(id: BusyMirrorSceneID.preferencesWindow) + } + // MARK: - Event-driven background sync // // Owned here (not by ContentView) because this controller lives for the @@ -280,6 +286,10 @@ struct BusyMirrorMenuBarView: View { appController.openMainWindow(using: openWindow) } + Button("Preferences…") { + appController.openPreferencesWindow(using: openWindow) + } + Divider() Button("Quit BusyMirror") { diff --git a/CHANGELOG.md b/CHANGELOG.md index 03f52e5..b7d6a09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to BusyMirror will be documented in this file. +## [1.8.1] - 2026-08-26 + +### Fixed +- **Preferences window unreachable**: 1.8.0's Settings scene (⌘,) and its `SettingsLink` button didn't work because BusyMirror is an `LSUIElement` (accessory) app — those don't get the standard app menu, so there's no menu for the automatic "Settings…" command or Cmd+, to live in. Replaced the `Settings { }` scene with a plain `Window(id: "preferences-window")`, opened via `openWindow(id:)` — the same mechanism that already reliably opens the main window from the menu bar. Also added a "Preferences…" item to the menu bar dropdown itself, so there are two direct routes in instead of one that depended on OS menu plumbing this app type doesn't get. ([BusyMirrorApp.swift](BusyMirror/BusyMirrorApp.swift), [MenuBarSupport.swift](BusyMirror/MenuBarSupport.swift), [ContentView.swift](BusyMirror/ContentView.swift)) + ## [1.8.0] - 2026-08-26 ### Added