From 7c7ee221fdfdc0e39e33e8f211ce7a3fe7026aba Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Thu, 20 Aug 2026 17:36:10 -0700 Subject: [PATCH 1/4] feat: add dock icon visibility toggle to General settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hidden from dock by default. Toggle in Settings > General > System features applies NSApp.setActivationPolicy at runtime — no relaunch needed. --- buddi/buddiApp.swift | 9 +++++++++ buddi/components/Settings/SettingsView.swift | 8 ++++++++ buddi/models/Constants.swift | 1 + 3 files changed, 18 insertions(+) diff --git a/buddi/buddiApp.swift b/buddi/buddiApp.swift index e222c21..4d7a43b 100644 --- a/buddi/buddiApp.swift +++ b/buddi/buddiApp.swift @@ -373,6 +373,12 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + NotificationCenter.default.addObserver( + forName: Notification.Name.showInDockChanged, object: nil, queue: .main + ) { _ in + NSApp.setActivationPolicy(Defaults[.showInDock] ? .regular : .accessory) + } + // Use closure-based observers for DistributedNotificationCenter and keep tokens for removal screenLockedObserver = DistributedNotificationCenter.default().addObserver( forName: NSNotification.Name(rawValue: "com.apple.screenIsLocked"), @@ -475,6 +481,8 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } + NSApp.setActivationPolicy(Defaults[.showInDock] ? .regular : .accessory) + previousScreens = NSScreen.screens HookInstaller.installIfNeeded() BuddiSessionBridge.shared.startMonitoring() @@ -659,6 +667,7 @@ extension Notification.Name { static let showOnAllDisplaysChanged = Notification.Name("showOnAllDisplaysChanged") static let automaticallySwitchDisplayChanged = Notification.Name("automaticallySwitchDisplayChanged") static let expandedDragDetectionChanged = Notification.Name("expandedDragDetectionChanged") + static let showInDockChanged = Notification.Name("showInDockChanged") } extension CGRect: @retroactive Hashable { diff --git a/buddi/components/Settings/SettingsView.swift b/buddi/components/Settings/SettingsView.swift index 15c13c3..4332a51 100644 --- a/buddi/components/Settings/SettingsView.swift +++ b/buddi/components/Settings/SettingsView.swift @@ -151,6 +151,7 @@ struct GeneralSettings: View { @Default(.automaticallySwitchDisplay) var automaticallySwitchDisplay @Default(.enableGestures) var enableGestures @Default(.openNotchOnHover) var openNotchOnHover + @Default(.showInDock) var showInDock var body: some View { @@ -163,6 +164,13 @@ struct GeneralSettings: View { Text("Show menu bar icon") } .tint(.effectiveAccent) + Defaults.Toggle(key: .showInDock) { + Text("Show in Dock") + } + .onChange(of: showInDock) { + NotificationCenter.default.post( + name: Notification.Name.showInDockChanged, object: nil) + } LaunchAtLogin.Toggle("Launch at login") Defaults.Toggle(key: .showOnAllDisplays) { Text("Show on all displays") diff --git a/buddi/models/Constants.swift b/buddi/models/Constants.swift index 960607b..d0dda58 100644 --- a/buddi/models/Constants.swift +++ b/buddi/models/Constants.swift @@ -70,6 +70,7 @@ enum OptionKeyAction: String, CaseIterable, Identifiable, Defaults.Serializable extension Defaults.Keys { // MARK: General static let menubarIcon = Key("menubarIcon", default: true) + static let showInDock = Key("showInDock", default: false) static let showOnAllDisplays = Key("showOnAllDisplays", default: false) static let automaticallySwitchDisplay = Key("automaticallySwitchDisplay", default: true) static let releaseName = Key("releaseName", default: "Uncommon Robot (Target Wizard)") From 4da242cbcb79d56fa1f84456b8cb7d229c762ed6 Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Thu, 20 Aug 2026 21:45:05 -0700 Subject: [PATCH 2/4] feat: add default view picker to General settings Picker in Settings > General > Notch behavior lets user choose which view opens first (Buddy, Widgets, or Shelf). Disabled when Remember last tab is on. Defaults to Buddy. --- buddi/components/Settings/SettingsView.swift | 9 +++++++++ buddi/enums/generic.swift | 8 ++++---- buddi/models/BuddiViewModel.swift | 2 +- buddi/models/Constants.swift | 1 + 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/buddi/components/Settings/SettingsView.swift b/buddi/components/Settings/SettingsView.swift index 4332a51..3fdc3ce 100644 --- a/buddi/components/Settings/SettingsView.swift +++ b/buddi/components/Settings/SettingsView.swift @@ -340,6 +340,15 @@ struct GeneralSettings: View { Text("Enable haptic feedback") } Toggle("Remember last tab", isOn: $coordinator.openLastTabByDefault) + Picker("Default view", selection: Binding( + get: { Defaults[.defaultView] }, + set: { Defaults[.defaultView] = $0 } + )) { + Text("Buddy").tag(NotchViews.buddy) + Text("Widgets").tag(NotchViews.home) + Text("Shelf").tag(NotchViews.shelf) + } + .disabled(coordinator.openLastTabByDefault) if openNotchOnHover { Slider(value: $minimumHoverDuration, in: 0...1, step: 0.1) { HStack { diff --git a/buddi/enums/generic.swift b/buddi/enums/generic.swift index f06b34d..771e74e 100644 --- a/buddi/enums/generic.swift +++ b/buddi/enums/generic.swift @@ -23,10 +23,10 @@ public enum NotchState { case open } -public enum NotchViews { - case buddy - case home - case shelf +public enum NotchViews: String, CaseIterable, Defaults.Serializable { + case buddy = "buddy" + case home = "home" + case shelf = "shelf" } enum SettingsEnum { diff --git a/buddi/models/BuddiViewModel.swift b/buddi/models/BuddiViewModel.swift index 242c628..19efcf0 100644 --- a/buddi/models/BuddiViewModel.swift +++ b/buddi/models/BuddiViewModel.swift @@ -240,7 +240,7 @@ class BuddiViewModel: NSObject, ObservableObject { if !ShelfStateViewModel.shared.isEmpty && Defaults[.openShelfByDefault] { coordinator.currentView = .shelf } else if !coordinator.openLastTabByDefault { - coordinator.currentView = .buddy + coordinator.currentView = Defaults[.defaultView] } } diff --git a/buddi/models/Constants.swift b/buddi/models/Constants.swift index d0dda58..428d7a0 100644 --- a/buddi/models/Constants.swift +++ b/buddi/models/Constants.swift @@ -71,6 +71,7 @@ extension Defaults.Keys { // MARK: General static let menubarIcon = Key("menubarIcon", default: true) static let showInDock = Key("showInDock", default: false) + static let defaultView = Key("defaultView", default: .buddy) static let showOnAllDisplays = Key("showOnAllDisplays", default: false) static let automaticallySwitchDisplay = Key("automaticallySwitchDisplay", default: true) static let releaseName = Key("releaseName", default: "Uncommon Robot (Target Wizard)") From 22addaa9d207c25ce5b8b380fa95b4c2c2038e68 Mon Sep 17 00:00:00 2001 From: --global <--global> Date: Thu, 20 Aug 2026 21:52:06 -0700 Subject: [PATCH 3/4] fix: restore dock policy from preference when Settings closes relinquishFocus hardcoded .accessory, overriding showInDock toggle. Now reads Defaults[.showInDock] to restore correct policy. --- buddi/components/Settings/SettingsWindowController.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/buddi/components/Settings/SettingsWindowController.swift b/buddi/components/Settings/SettingsWindowController.swift index 0c4749d..ca86562 100644 --- a/buddi/components/Settings/SettingsWindowController.swift +++ b/buddi/components/Settings/SettingsWindowController.swift @@ -98,9 +98,7 @@ class SettingsWindowController: NSWindowController { private func relinquishFocus() { window?.orderOut(nil) - - // Set app back to accessory mode immediately - NSApp.setActivationPolicy(.accessory) + NSApp.setActivationPolicy(Defaults[.showInDock] ? .regular : .accessory) } } From d8d16c76357bb562075c9fa93188eb3468e3a84b Mon Sep 17 00:00:00 2001 From: qwelpl Date: Fri, 21 Aug 2026 09:20:10 -0700 Subject: [PATCH 4/4] fix: address grayash review - dock toggle cleanup and partial wiring - Remove showInDockChanged observer + notification; relinquishFocus and launch-time apply already cover all policy transitions - Fix BuddiViewModel camera-permission alert hardcoding .accessory, breaking dock icon for users with Show in Dock enabled - Add applicationShouldHandleReopen to open Settings on dock icon click - Drop default view picker: partially wired (coordinator hardcodes .buddy at multiple sites), conflicts with openShelfByDefault; revert to .buddy default and remove defaultView key and NotchViews Serializable conformance --- buddi/buddiApp.swift | 12 +++++------- buddi/components/Settings/SettingsView.swift | 16 +--------------- buddi/enums/generic.swift | 8 ++++---- buddi/models/BuddiViewModel.swift | 4 ++-- buddi/models/Constants.swift | 1 - 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/buddi/buddiApp.swift b/buddi/buddiApp.swift index 4d7a43b..60fc888 100644 --- a/buddi/buddiApp.swift +++ b/buddi/buddiApp.swift @@ -77,6 +77,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { return false } + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + SettingsWindowController.shared.showWindow() + return true + } + func applicationWillTerminate(_ notification: Notification) { NotificationCenter.default.removeObserver(self) if let observer = screenLockedObserver { @@ -373,12 +378,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - NotificationCenter.default.addObserver( - forName: Notification.Name.showInDockChanged, object: nil, queue: .main - ) { _ in - NSApp.setActivationPolicy(Defaults[.showInDock] ? .regular : .accessory) - } - // Use closure-based observers for DistributedNotificationCenter and keep tokens for removal screenLockedObserver = DistributedNotificationCenter.default().addObserver( forName: NSNotification.Name(rawValue: "com.apple.screenIsLocked"), @@ -667,7 +666,6 @@ extension Notification.Name { static let showOnAllDisplaysChanged = Notification.Name("showOnAllDisplaysChanged") static let automaticallySwitchDisplayChanged = Notification.Name("automaticallySwitchDisplayChanged") static let expandedDragDetectionChanged = Notification.Name("expandedDragDetectionChanged") - static let showInDockChanged = Notification.Name("showInDockChanged") } extension CGRect: @retroactive Hashable { diff --git a/buddi/components/Settings/SettingsView.swift b/buddi/components/Settings/SettingsView.swift index 3fdc3ce..5016bdb 100644 --- a/buddi/components/Settings/SettingsView.swift +++ b/buddi/components/Settings/SettingsView.swift @@ -151,8 +151,7 @@ struct GeneralSettings: View { @Default(.automaticallySwitchDisplay) var automaticallySwitchDisplay @Default(.enableGestures) var enableGestures @Default(.openNotchOnHover) var openNotchOnHover - @Default(.showInDock) var showInDock - + var body: some View { Form { @@ -167,10 +166,6 @@ struct GeneralSettings: View { Defaults.Toggle(key: .showInDock) { Text("Show in Dock") } - .onChange(of: showInDock) { - NotificationCenter.default.post( - name: Notification.Name.showInDockChanged, object: nil) - } LaunchAtLogin.Toggle("Launch at login") Defaults.Toggle(key: .showOnAllDisplays) { Text("Show on all displays") @@ -340,15 +335,6 @@ struct GeneralSettings: View { Text("Enable haptic feedback") } Toggle("Remember last tab", isOn: $coordinator.openLastTabByDefault) - Picker("Default view", selection: Binding( - get: { Defaults[.defaultView] }, - set: { Defaults[.defaultView] = $0 } - )) { - Text("Buddy").tag(NotchViews.buddy) - Text("Widgets").tag(NotchViews.home) - Text("Shelf").tag(NotchViews.shelf) - } - .disabled(coordinator.openLastTabByDefault) if openNotchOnHover { Slider(value: $minimumHoverDuration, in: 0...1, step: 0.1) { HStack { diff --git a/buddi/enums/generic.swift b/buddi/enums/generic.swift index 771e74e..f06b34d 100644 --- a/buddi/enums/generic.swift +++ b/buddi/enums/generic.swift @@ -23,10 +23,10 @@ public enum NotchState { case open } -public enum NotchViews: String, CaseIterable, Defaults.Serializable { - case buddy = "buddy" - case home = "home" - case shelf = "shelf" +public enum NotchViews { + case buddy + case home + case shelf } enum SettingsEnum { diff --git a/buddi/models/BuddiViewModel.swift b/buddi/models/BuddiViewModel.swift index 19efcf0..2c8ea99 100644 --- a/buddi/models/BuddiViewModel.swift +++ b/buddi/models/BuddiViewModel.swift @@ -184,7 +184,7 @@ class BuddiViewModel: NSObject, ObservableObject { } } - NSApp.setActivationPolicy(.accessory) + NSApp.setActivationPolicy(Defaults[.showInDock] ? .regular : .accessory) NSApp.deactivate() } @@ -240,7 +240,7 @@ class BuddiViewModel: NSObject, ObservableObject { if !ShelfStateViewModel.shared.isEmpty && Defaults[.openShelfByDefault] { coordinator.currentView = .shelf } else if !coordinator.openLastTabByDefault { - coordinator.currentView = Defaults[.defaultView] + coordinator.currentView = .buddy } } diff --git a/buddi/models/Constants.swift b/buddi/models/Constants.swift index 428d7a0..d0dda58 100644 --- a/buddi/models/Constants.swift +++ b/buddi/models/Constants.swift @@ -71,7 +71,6 @@ extension Defaults.Keys { // MARK: General static let menubarIcon = Key("menubarIcon", default: true) static let showInDock = Key("showInDock", default: false) - static let defaultView = Key("defaultView", default: .buddy) static let showOnAllDisplays = Key("showOnAllDisplays", default: false) static let automaticallySwitchDisplay = Key("automaticallySwitchDisplay", default: true) static let releaseName = Key("releaseName", default: "Uncommon Robot (Target Wizard)")