From 42fb21e2317a13dc54a8add3970265bcf0194bb2 Mon Sep 17 00:00:00 2001 From: Gamitha Samarasingha Date: Thu, 19 Mar 2026 01:08:51 +0530 Subject: [PATCH 1/5] Add dynamic menu bar icon based on active AI app Co-Authored-By: MiniMax M2.5 --- Sources/CodexBar/ActiveAppDetector.swift | 62 +++++++++++++++++++ Sources/CodexBar/PreferencesDisplayPane.swift | 6 ++ Sources/CodexBar/SettingsStore+Defaults.swift | 26 ++++++++ Sources/CodexBar/SettingsStore.swift | 2 + Sources/CodexBar/SettingsStoreState.swift | 1 + .../StatusItemController+Animation.swift | 23 +++++++ Sources/CodexBar/StatusItemController.swift | 45 ++++++++++++++ 7 files changed, 165 insertions(+) create mode 100644 Sources/CodexBar/ActiveAppDetector.swift diff --git a/Sources/CodexBar/ActiveAppDetector.swift b/Sources/CodexBar/ActiveAppDetector.swift new file mode 100644 index 000000000..468dac4fd --- /dev/null +++ b/Sources/CodexBar/ActiveAppDetector.swift @@ -0,0 +1,62 @@ +import AppKit +import CodexBarCore + +/// Maps active application bundle identifiers to their corresponding UsageProvider. +/// Only providers with desktop apps can be detected; CLI-only and web-only providers return nil. +struct ActiveAppDetector { + /// Maps bundle identifier prefixes to their corresponding provider. + /// Order matters: more specific prefixes should come first. + private static let bundleIdToProvider: [(prefix: String, provider: UsageProvider)] = [ + // Desktop AI apps + ("com.openai.codex", .codex), + ("com.anthropic.claude", .claude), + ("com.cursor.sh", .cursor), + ("com.opencodeos.opencode", .opencode), + ("com.google.antigravity", .antigravity), + ("com.augmentcode.augment", .augment), + ("com.minimax.agent", .minimax), + ("dev.warp.Warp-Stable", .warp), + + // VS Code (GitHub Copilot - must check before generic JetBrains) + ("com.microsoft.VSCode", .copilot), + + // JetBrains IDEs with Copilot support + ("com.jetbrains.", .copilot), + + // Local AI servers (ollama runs locally, so it may show as active window) + ("com.ollama", .ollama), + ] + + /// Detects the provider associated with the currently active frontmost application. + /// - Returns: The provider for the active AI app, or nil if no relevant AI app is active. + static func activeProvider() -> UsageProvider? { + guard let frontmostApp = NSWorkspace.shared.frontmostApplication, + let bundleId = frontmostApp.bundleIdentifier + else { + return nil + } + + return provider(for: bundleId) + } + + /// Looks up the provider for a given bundle identifier. + /// - Parameter bundleId: The application's bundle identifier. + /// - Returns: The corresponding provider, or nil if no match found. + static func provider(for bundleId: String) -> UsageProvider? { + // Try exact match first for special cases + for (prefix, provider) in Self.bundleIdToProvider { + if bundleId == prefix { + return provider + } + } + + // Then try prefix match (handles versioned bundle IDs like com.anthropic.claude-2) + for (prefix, provider) in Self.bundleIdToProvider { + if bundleId.hasPrefix(prefix) { + return provider + } + } + + return nil + } +} \ No newline at end of file diff --git a/Sources/CodexBar/PreferencesDisplayPane.swift b/Sources/CodexBar/PreferencesDisplayPane.swift index 04050b3bb..8b60cb8df 100644 --- a/Sources/CodexBar/PreferencesDisplayPane.swift +++ b/Sources/CodexBar/PreferencesDisplayPane.swift @@ -33,6 +33,12 @@ struct DisplayPane: View { binding: self.$settings.menuBarShowsHighestUsage) .disabled(!self.settings.mergeIcons) .opacity(self.settings.mergeIcons ? 1 : 0.5) + PreferenceToggleRow( + title: "Show active provider", + subtitle: "Auto-show the provider for the active AI app window.", + binding: self.$settings.menuBarShowsActiveProvider) + .disabled(!self.settings.mergeIcons) + .opacity(self.settings.mergeIcons ? 1 : 0.5) PreferenceToggleRow( title: "Menu bar shows percent", subtitle: "Replace critter bars with provider branding icons and a percentage.", diff --git a/Sources/CodexBar/SettingsStore+Defaults.swift b/Sources/CodexBar/SettingsStore+Defaults.swift index 44d83a023..047106ffe 100644 --- a/Sources/CodexBar/SettingsStore+Defaults.swift +++ b/Sources/CodexBar/SettingsStore+Defaults.swift @@ -280,6 +280,14 @@ extension SettingsStore { } } + var menuBarShowsActiveProvider: Bool { + get { self.defaultsState.menuBarShowsActiveProvider } + set { + self.defaultsState.menuBarShowsActiveProvider = newValue + self.userDefaults.set(newValue, forKey: "menuBarShowsActiveProvider") + } + } + var switcherShowsIcons: Bool { get { self.defaultsState.switcherShowsIcons } set { @@ -474,6 +482,24 @@ extension SettingsStore { } } + /// The last provider that was shown in the menu bar when an active AI app was detected. + /// Used as fallback when no relevant AI app is active. + var lastActiveProviderRaw: String? { + get { self.userDefaults.string(forKey: "lastActiveProvider") } + set { + if let newValue { + self.userDefaults.set(newValue, forKey: "lastActiveProvider") + } else { + self.userDefaults.removeObject(forKey: "lastActiveProvider") + } + } + } + + var lastActiveProvider: UsageProvider? { + get { self.lastActiveProviderRaw.flatMap(UsageProvider.init(rawValue:)) } + set { self.lastActiveProviderRaw = newValue?.rawValue } + } + var debugLoadingPattern: LoadingPattern? { get { self.debugLoadingPatternRaw.flatMap(LoadingPattern.init(rawValue:)) } set { self.debugLoadingPatternRaw = newValue?.rawValue } diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index 09f3e3caa..bb19a8f24 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -216,6 +216,7 @@ extension SettingsStore { if openAIWebAccessDefault == nil { userDefaults.set(true, forKey: "openAIWebAccessEnabled") } let jetbrainsIDEBasePath = userDefaults.string(forKey: "jetbrainsIDEBasePath") ?? "" let mergeIcons = userDefaults.object(forKey: "mergeIcons") as? Bool ?? true + let menuBarShowsActiveProvider = userDefaults.object(forKey: "menuBarShowsActiveProvider") as? Bool ?? false let switcherShowsIcons = userDefaults.object(forKey: "switcherShowsIcons") as? Bool ?? true let mergedMenuLastSelectedWasOverview = userDefaults.object( forKey: "mergedMenuLastSelectedWasOverview") as? Bool ?? false @@ -253,6 +254,7 @@ extension SettingsStore { openAIWebAccessEnabled: openAIWebAccessEnabled, jetbrainsIDEBasePath: jetbrainsIDEBasePath, mergeIcons: mergeIcons, + menuBarShowsActiveProvider: menuBarShowsActiveProvider, switcherShowsIcons: switcherShowsIcons, mergedMenuLastSelectedWasOverview: mergedMenuLastSelectedWasOverview, mergedOverviewSelectedProvidersRaw: mergedOverviewSelectedProvidersRaw, diff --git a/Sources/CodexBar/SettingsStoreState.swift b/Sources/CodexBar/SettingsStoreState.swift index 98e01406d..0f195a34d 100644 --- a/Sources/CodexBar/SettingsStoreState.swift +++ b/Sources/CodexBar/SettingsStoreState.swift @@ -29,6 +29,7 @@ struct SettingsDefaultsState { var openAIWebAccessEnabled: Bool var jetbrainsIDEBasePath: String var mergeIcons: Bool + var menuBarShowsActiveProvider: Bool var switcherShowsIcons: Bool var mergedMenuLastSelectedWasOverview: Bool var mergedOverviewSelectedProvidersRaw: [String] diff --git a/Sources/CodexBar/StatusItemController+Animation.swift b/Sources/CodexBar/StatusItemController+Animation.swift index 5f422862f..275ea82dc 100644 --- a/Sources/CodexBar/StatusItemController+Animation.swift +++ b/Sources/CodexBar/StatusItemController+Animation.swift @@ -481,6 +481,17 @@ extension StatusItemController { } private func primaryProviderForUnifiedIcon() -> UsageProvider { + // When "show active provider" is enabled, use the active AI app's provider + if self.settings.menuBarShowsActiveProvider, + self.shouldMergeIcons, + let activeProvider = ActiveAppDetector.activeProvider(), + self.store.isEnabled(activeProvider) || self.store.enabledProviders().isEmpty + { + // Update the last active provider when we detect an active AI app + self.settings.lastActiveProvider = activeProvider + return activeProvider + } + // When "show highest usage" is enabled, auto-select the provider closest to rate limit. if self.settings.menuBarShowsHighestUsage, self.shouldMergeIcons, @@ -488,12 +499,24 @@ extension StatusItemController { { return highest.provider } + + // Use manually selected provider if self.shouldMergeIcons, let selected = self.selectedMenuProvider, self.store.isEnabled(selected) { return selected } + + // Fall back to last active provider if available + if self.settings.menuBarShowsActiveProvider, + self.shouldMergeIcons, + let lastProvider = self.settings.lastActiveProvider, + self.store.isEnabled(lastProvider) + { + return lastProvider + } + for provider in UsageProvider.allCases { if self.store.isEnabled(provider), self.store.snapshot(for: provider) != nil { return provider diff --git a/Sources/CodexBar/StatusItemController.swift b/Sources/CodexBar/StatusItemController.swift index a83420ee9..3d920ba61 100644 --- a/Sources/CodexBar/StatusItemController.swift +++ b/Sources/CodexBar/StatusItemController.swift @@ -80,6 +80,7 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin private var lastConfigRevision: Int private var lastProviderOrder: [UsageProvider] private var lastMergeIcons: Bool + private var lastMenuBarShowsActiveProvider: Bool private var lastSwitcherShowsIcons: Bool private var lastObservedUsageBarsShowUsed: Bool /// Tracks which `usageBarsShowUsed` mode the provider switcher was built with. @@ -98,6 +99,8 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin set { self.settings.selectedMenuProvider = newValue } } + private var activeAppObserver: NSObjectProtocol? + struct BlinkState { var nextBlink: Date var blinkStart: Date? @@ -167,6 +170,7 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin self.lastConfigRevision = settings.configRevision self.lastProviderOrder = settings.providerOrder self.lastMergeIcons = settings.mergeIcons + self.lastMenuBarShowsActiveProvider = settings.menuBarShowsActiveProvider self.lastSwitcherShowsIcons = settings.switcherShowsIcons self.lastObservedUsageBarsShowUsed = settings.usageBarsShowUsed self.lastSwitcherUsageBarsShowUsed = settings.usageBarsShowUsed @@ -195,6 +199,35 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin selector: #selector(self.handleProviderConfigDidChange), name: .codexbarProviderConfigDidChange, object: nil) + + // Observe active app changes for dynamic menu bar icon + self.activeAppObserver = NSWorkspace.shared.notificationCenter.addObserver( + forName: NSWorkspace.didActivateApplicationNotification, + object: nil, + queue: .main) { [weak self] _ in + Task { @MainActor [weak self] in + self?.handleActiveAppChanged() + } + } + } + + @objc private func handleActiveAppChanged() { + guard self.settings.menuBarShowsActiveProvider, self.shouldMergeIcons else { return } + self.updateIcons() + } + + private func updateActiveProviderTracking() { + guard self.settings.menuBarShowsActiveProvider, self.shouldMergeIcons else { return } + + if let activeProvider = ActiveAppDetector.activeProvider() { + // Update last active provider when switching to an AI app + self.settings.lastActiveProvider = activeProvider + // Update the selected menu provider to reflect active app + self.selectedMenuProvider = activeProvider + } else if self.settings.lastActiveProvider != nil { + // When no AI app is active, keep showing the last active provider + // (don't change selectedMenuProvider here - let primaryProviderForUnifiedIcon handle fallback) + } } private func wireBindings() { @@ -321,6 +354,17 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin let configChanged = self.settings.configRevision != self.lastConfigRevision let orderChanged = self.settings.providerOrder != self.lastProviderOrder let shouldRefreshOpenMenus = self.shouldRefreshOpenMenusForProviderSwitcher() + + // Track setting changes for menuBarShowsActiveProvider + let showActiveProviderChanged = self.settings.menuBarShowsActiveProvider != self.lastMenuBarShowsActiveProvider + if showActiveProviderChanged { + self.lastMenuBarShowsActiveProvider = self.settings.menuBarShowsActiveProvider + // When enabled, immediately check for active app + if self.settings.menuBarShowsActiveProvider, self.shouldMergeIcons { + self.updateActiveProviderTracking() + } + } + self.invalidateMenus() if orderChanged || configChanged { self.rebuildProviderStatusItems() @@ -484,6 +528,7 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin deinit { self.blinkTask?.cancel() self.loginTask?.cancel() + // Note: activeAppObserver will be cleaned up by the system when the app terminates NotificationCenter.default.removeObserver(self) } } From b61359a20ac2eb5610673735917620e72f583fed Mon Sep 17 00:00:00 2001 From: Gamitha Samarasingha Date: Fri, 20 Mar 2026 23:55:03 +0530 Subject: [PATCH 2/5] Select provider when auto-select triggers, not just update icon When an AI app became active, the menu bar icon updated but the underlying provider wasn't set as selected, so clicking the menu would show the previously selected provider instead. Co-Authored-By: MiniMax M2.7 --- Sources/CodexBar/StatusItemController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/CodexBar/StatusItemController.swift b/Sources/CodexBar/StatusItemController.swift index 3d920ba61..5ac43149c 100644 --- a/Sources/CodexBar/StatusItemController.swift +++ b/Sources/CodexBar/StatusItemController.swift @@ -213,6 +213,7 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin @objc private func handleActiveAppChanged() { guard self.settings.menuBarShowsActiveProvider, self.shouldMergeIcons else { return } + self.updateActiveProviderTracking() self.updateIcons() } From b3b0c795a08deb11ba6061ddb2c834885d064cb5 Mon Sep 17 00:00:00 2001 From: Gamitha Samarasingha Date: Sat, 21 Mar 2026 00:06:49 +0530 Subject: [PATCH 3/5] Add support for Electron Ollama provider in ActiveAppDetector --- Sources/CodexBar/ActiveAppDetector.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/CodexBar/ActiveAppDetector.swift b/Sources/CodexBar/ActiveAppDetector.swift index 468dac4fd..f24d9f9c2 100644 --- a/Sources/CodexBar/ActiveAppDetector.swift +++ b/Sources/CodexBar/ActiveAppDetector.swift @@ -16,6 +16,7 @@ struct ActiveAppDetector { ("com.augmentcode.augment", .augment), ("com.minimax.agent", .minimax), ("dev.warp.Warp-Stable", .warp), + ("com.electron.ollama", .ollama), // VS Code (GitHub Copilot - must check before generic JetBrains) ("com.microsoft.VSCode", .copilot), From 9e58b5bb991227bda2803f9c144e35288c70d6ac Mon Sep 17 00:00:00 2001 From: Gamitha Samarasingha Date: Sat, 21 Mar 2026 02:01:03 +0530 Subject: [PATCH 4/5] Add active provider mapping and settings persistence tests --- Sources/CodexBar/ActiveAppDetector.swift | 18 ++-- Sources/CodexBar/StatusItemController.swift | 9 +- .../ActiveAppDetectorTests.swift | 36 ++++++++ .../PreferencesPaneSmokeTests.swift | 1 + .../SettingsStoreActiveProviderTests.swift | 83 +++++++++++++++++++ 5 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 Tests/CodexBarTests/ActiveAppDetectorTests.swift create mode 100644 Tests/CodexBarTests/SettingsStoreActiveProviderTests.swift diff --git a/Sources/CodexBar/ActiveAppDetector.swift b/Sources/CodexBar/ActiveAppDetector.swift index f24d9f9c2..02d5eccad 100644 --- a/Sources/CodexBar/ActiveAppDetector.swift +++ b/Sources/CodexBar/ActiveAppDetector.swift @@ -3,7 +3,7 @@ import CodexBarCore /// Maps active application bundle identifiers to their corresponding UsageProvider. /// Only providers with desktop apps can be detected; CLI-only and web-only providers return nil. -struct ActiveAppDetector { +enum ActiveAppDetector { /// Maps bundle identifier prefixes to their corresponding provider. /// Order matters: more specific prefixes should come first. private static let bundleIdToProvider: [(prefix: String, provider: UsageProvider)] = [ @@ -37,7 +37,7 @@ struct ActiveAppDetector { return nil } - return provider(for: bundleId) + return self.provider(for: bundleId) } /// Looks up the provider for a given bundle identifier. @@ -45,19 +45,15 @@ struct ActiveAppDetector { /// - Returns: The corresponding provider, or nil if no match found. static func provider(for bundleId: String) -> UsageProvider? { // Try exact match first for special cases - for (prefix, provider) in Self.bundleIdToProvider { - if bundleId == prefix { - return provider - } + for (prefix, provider) in self.bundleIdToProvider where bundleId == prefix { + return provider } // Then try prefix match (handles versioned bundle IDs like com.anthropic.claude-2) - for (prefix, provider) in Self.bundleIdToProvider { - if bundleId.hasPrefix(prefix) { - return provider - } + for (prefix, provider) in self.bundleIdToProvider where bundleId.hasPrefix(prefix) { + return provider } return nil } -} \ No newline at end of file +} diff --git a/Sources/CodexBar/StatusItemController.swift b/Sources/CodexBar/StatusItemController.swift index 5ac43149c..bd354ae30 100644 --- a/Sources/CodexBar/StatusItemController.swift +++ b/Sources/CodexBar/StatusItemController.swift @@ -204,11 +204,12 @@ final class StatusItemController: NSObject, NSMenuDelegate, StatusItemControllin self.activeAppObserver = NSWorkspace.shared.notificationCenter.addObserver( forName: NSWorkspace.didActivateApplicationNotification, object: nil, - queue: .main) { [weak self] _ in - Task { @MainActor [weak self] in - self?.handleActiveAppChanged() - } + queue: .main) + { [weak self] _ in + Task { @MainActor [weak self] in + self?.handleActiveAppChanged() } + } } @objc private func handleActiveAppChanged() { diff --git a/Tests/CodexBarTests/ActiveAppDetectorTests.swift b/Tests/CodexBarTests/ActiveAppDetectorTests.swift new file mode 100644 index 000000000..b6b10af8a --- /dev/null +++ b/Tests/CodexBarTests/ActiveAppDetectorTests.swift @@ -0,0 +1,36 @@ +import CodexBarCore +import Testing +@testable import CodexBar + +struct ActiveAppDetectorTests { + @Test + func `provider maps expected exact and prefix bundle identifiers`() { + let cases: [(bundleID: String, expected: UsageProvider?)] = [ + // Codex + ("com.openai.codex", .codex), + ("com.openai.codex.desktop", .codex), + // Claude + ("com.anthropic.claude", .claude), + ("com.anthropic.claude-2", .claude), + // Copilot (VS Code and JetBrains) + ("com.microsoft.VSCode", .copilot), + ("com.microsoft.VSCodeInsiders", .copilot), + ("com.jetbrains.intellij", .copilot), + // Ollama + ("com.electron.ollama", .ollama), + ("com.ollama", .ollama), + ("com.ollama.desktop", .ollama), + // Unknown + ("com.apple.Safari", nil), + ] + + for testCase in cases { + #expect(ActiveAppDetector.provider(for: testCase.bundleID) == testCase.expected) + } + } + + @Test + func `provider returns nil for unknown bundle identifier`() { + #expect(ActiveAppDetector.provider(for: "com.example.unknown-ai-app") == nil) + } +} diff --git a/Tests/CodexBarTests/PreferencesPaneSmokeTests.swift b/Tests/CodexBarTests/PreferencesPaneSmokeTests.swift index b71448ef1..6bbdcefd9 100644 --- a/Tests/CodexBarTests/PreferencesPaneSmokeTests.swift +++ b/Tests/CodexBarTests/PreferencesPaneSmokeTests.swift @@ -25,6 +25,7 @@ struct PreferencesPaneSmokeTests { let settings = Self.makeSettingsStore(suite: "PreferencesPaneSmokeTests-toggled") settings.menuBarShowsBrandIconWithPercent = true settings.menuBarShowsHighestUsage = true + settings.menuBarShowsActiveProvider = true settings.showAllTokenAccountsInMenu = true settings.hidePersonalInfo = true settings.resetTimesShowAbsolute = true diff --git a/Tests/CodexBarTests/SettingsStoreActiveProviderTests.swift b/Tests/CodexBarTests/SettingsStoreActiveProviderTests.swift new file mode 100644 index 000000000..3c13badc5 --- /dev/null +++ b/Tests/CodexBarTests/SettingsStoreActiveProviderTests.swift @@ -0,0 +1,83 @@ +import CodexBarCore +import Foundation +import Testing +@testable import CodexBar + +@MainActor +struct SettingsStoreActiveProviderTests { + @Test + func `menu bar shows active provider defaults to false`() { + let settings = Self.makeSettingsStore(suite: "SettingsStoreActiveProviderTests-default") + + #expect(settings.menuBarShowsActiveProvider == false) + } + + @Test + func `menu bar shows active provider persists across instances`() throws { + let suite = "SettingsStoreActiveProviderTests-persist-active-toggle" + let defaultsA = try #require(UserDefaults(suiteName: suite)) + defaultsA.removePersistentDomain(forName: suite) + let configStore = testConfigStore(suiteName: suite) + let storeA = Self.makeSettingsStore(userDefaults: defaultsA, configStore: configStore) + + storeA.menuBarShowsActiveProvider = true + + let defaultsB = try #require(UserDefaults(suiteName: suite)) + let storeB = Self.makeSettingsStore(userDefaults: defaultsB, configStore: configStore) + + #expect(storeB.menuBarShowsActiveProvider == true) + } + + @Test + func `last active provider persists across instances`() throws { + let suite = "SettingsStoreActiveProviderTests-persist-last-provider" + let defaultsA = try #require(UserDefaults(suiteName: suite)) + defaultsA.removePersistentDomain(forName: suite) + let configStore = testConfigStore(suiteName: suite) + let storeA = Self.makeSettingsStore(userDefaults: defaultsA, configStore: configStore) + + storeA.lastActiveProvider = .claude + + let defaultsB = try #require(UserDefaults(suiteName: suite)) + let storeB = Self.makeSettingsStore(userDefaults: defaultsB, configStore: configStore) + + #expect(storeB.lastActiveProvider == .claude) + } + + @Test + func `last active provider invalid raw resolves nil and clearing removes persisted value`() throws { + let suite = "SettingsStoreActiveProviderTests-invalid-and-clear" + let defaults = try #require(UserDefaults(suiteName: suite)) + defaults.removePersistentDomain(forName: suite) + defaults.set("invalid-provider", forKey: "lastActiveProvider") + + let configStore = testConfigStore(suiteName: suite) + let store = Self.makeSettingsStore(userDefaults: defaults, configStore: configStore) + + #expect(store.lastActiveProvider == nil) + + store.lastActiveProvider = .codex + #expect(defaults.string(forKey: "lastActiveProvider") == UsageProvider.codex.rawValue) + + store.lastActiveProvider = nil + #expect(defaults.string(forKey: "lastActiveProvider") == nil) + } + + private static func makeSettingsStore(suite: String) -> SettingsStore { + let defaults = UserDefaults(suiteName: suite)! + defaults.removePersistentDomain(forName: suite) + let configStore = testConfigStore(suiteName: suite) + return Self.makeSettingsStore(userDefaults: defaults, configStore: configStore) + } + + private static func makeSettingsStore( + userDefaults: UserDefaults, + configStore: CodexBarConfigStore) -> SettingsStore + { + SettingsStore( + userDefaults: userDefaults, + configStore: configStore, + zaiTokenStore: NoopZaiTokenStore(), + syntheticTokenStore: NoopSyntheticTokenStore()) + } +} From 79dcb0955e0f191f792dfa9bd8adc93de9fd2762 Mon Sep 17 00:00:00 2001 From: Gamitha Samarasingha Date: Sat, 21 Mar 2026 17:30:37 +0530 Subject: [PATCH 5/5] fix: sync selectedMenuProvider with icon in primaryProviderForUnifiedIcon When the active-app branch is taken on app launch (no didActivateApplication notification received first), the icon updated to the active provider but selectedMenuProvider remained stale, causing the menu to resolve from the wrong provider until the next activation event. Co-Authored-By: MiniMax M2.7 --- Sources/CodexBar/StatusItemController+Animation.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/CodexBar/StatusItemController+Animation.swift b/Sources/CodexBar/StatusItemController+Animation.swift index 275ea82dc..d7d5f2659 100644 --- a/Sources/CodexBar/StatusItemController+Animation.swift +++ b/Sources/CodexBar/StatusItemController+Animation.swift @@ -489,6 +489,8 @@ extension StatusItemController { { // Update the last active provider when we detect an active AI app self.settings.lastActiveProvider = activeProvider + // Keep menu in sync with icon when no didActivateApplication notification was received + self.selectedMenuProvider = activeProvider return activeProvider }