Skip to content

Commit db99319

Browse files
authored
Fix menubar ghost status item on macOS Tahoe
Set accessory activation policy in willFinishLaunching before the focus chain forms. Debounce observation tracking to coalesce rapid property changes into a single status bar refresh.
1 parent 3ea4a62 commit db99319

1 file changed

Lines changed: 17 additions & 14 deletions

File tree

mac/Sources/CodeBurnMenubar/CodeBurnApp.swift

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
3030
let updateChecker = UpdateChecker()
3131
/// Held for the lifetime of the app to opt out of App Nap and Automatic Termination.
3232
private var backgroundActivity: NSObjectProtocol?
33+
private var pendingRefreshWork: DispatchWorkItem?
3334

34-
func applicationDidFinishLaunching(_ notification: Notification) {
35-
// On macOS Tahoe (26.x), accessory apps may fail to render their status item
36-
// if the activation policy is set before the status item is created. Starting
37-
// as a regular app and switching to accessory after setup works around this.
38-
NSApp.setActivationPolicy(.regular)
39-
NSApp.activate(ignoringOtherApps: true)
35+
func applicationWillFinishLaunching(_ notification: Notification) {
36+
// Set accessory policy before the app's focus chain forms. On macOS Tahoe
37+
// (26.x), setting it after didFinishLaunching causes ghost status items
38+
// because the policy gets baked into the initial focus chain.
39+
NSApp.setActivationPolicy(.accessory)
40+
}
4041

42+
func applicationDidFinishLaunching(_ notification: Notification) {
4143
ProcessInfo.processInfo.automaticTerminationSupportEnabled = false
4244
ProcessInfo.processInfo.disableSuddenTermination()
4345
backgroundActivity = ProcessInfo.processInfo.beginActivity(
@@ -48,11 +50,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
4850
restorePersistedCurrency()
4951
setupStatusItem()
5052
setupPopover()
51-
52-
// Switch to accessory policy after status item is set up to hide from Dock
53-
DispatchQueue.main.async {
54-
NSApp.setActivationPolicy(.accessory)
55-
}
5653
observeStore()
5754
startRefreshLoop()
5855
setupWakeObservers()
@@ -222,9 +219,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
222219
_ = store.payload
223220
_ = store.todayPayload
224221
} onChange: { [weak self] in
225-
Task { @MainActor in
226-
self?.refreshStatusButton()
227-
self?.observeStore()
222+
DispatchQueue.main.async {
223+
guard let self else { return }
224+
self.pendingRefreshWork?.cancel()
225+
let work = DispatchWorkItem { [weak self] in
226+
self?.refreshStatusButton()
227+
self?.observeStore()
228+
}
229+
self.pendingRefreshWork = work
230+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05, execute: work)
228231
}
229232
}
230233
}

0 commit comments

Comments
 (0)