From 357abf14533bb73928301002f9c1cf3b4d3910ef Mon Sep 17 00:00:00 2001 From: Nicolas0315 <40846197+Nicolas0315@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:10:54 +0900 Subject: [PATCH 1/2] Reduce idle menu power usage --- Sources/CodexBar/SettingsStore.swift | 4 ++-- .../CodexBar/StatusItemController+Menu.swift | 2 +- .../StatusItemController+MenuTracking.swift | 20 +++++++++++++++++++ .../ProviderSettingsDescriptorTests.swift | 4 ++-- Tests/CodexBarTests/SettingsStoreTests.swift | 14 ++++++------- .../StatusMenuOpenRefreshTests.swift | 14 +++++++++++-- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index a38c5ba549..54caafd81d 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -375,9 +375,9 @@ extension SettingsStore { userDefaults.set(false, forKey: "openAIWebAccessEnabled") } let openAIWebBatterySaverDefault = userDefaults.object(forKey: "openAIWebBatterySaverEnabled") as? Bool - let openAIWebBatterySaverEnabled = openAIWebBatterySaverDefault ?? false + let openAIWebBatterySaverEnabled = openAIWebBatterySaverDefault ?? true if Self.isRunningTests, openAIWebBatterySaverDefault == nil { - userDefaults.set(false, forKey: "openAIWebBatterySaverEnabled") + userDefaults.set(true, forKey: "openAIWebBatterySaverEnabled") } let providerStorageFootprintsDefault = userDefaults.object(forKey: "providerStorageFootprintsEnabled") as? Bool let providerStorageFootprintsEnabled = providerStorageFootprintsDefault ?? false diff --git a/Sources/CodexBar/StatusItemController+Menu.swift b/Sources/CodexBar/StatusItemController+Menu.swift index 225331c5b2..fa2367fb25 100644 --- a/Sources/CodexBar/StatusItemController+Menu.swift +++ b/Sources/CodexBar/StatusItemController+Menu.swift @@ -165,7 +165,7 @@ extension StatusItemController { if !isPersistentMenu { self.menuProviders.removeValue(forKey: key) self.menuVersions.removeValue(forKey: key) - } else if self.menuNeedsRefresh(menu) { + } else if Self.isClosedMenuPreparationEnabled, self.menuNeedsRefresh(menu) { self.rebuildClosedMenuIfNeeded(menu) } self.parentMenuRebuildsDeferredDuringTracking.remove(key) diff --git a/Sources/CodexBar/StatusItemController+MenuTracking.swift b/Sources/CodexBar/StatusItemController+MenuTracking.swift index 4922f9cc6a..c5f7004df3 100644 --- a/Sources/CodexBar/StatusItemController+MenuTracking.swift +++ b/Sources/CodexBar/StatusItemController+MenuTracking.swift @@ -3,9 +3,12 @@ import CodexBarCore extension StatusItemController { private static let defaultClosedMenuPreparationDelay: Duration = .milliseconds(350) + private static let defaultClosedMenuPreparationEnabled = false // Avoid idle SwiftUI layout work. #if DEBUG private static var closedMenuPreparationDelayForTesting: Duration = defaultClosedMenuPreparationDelay + private static var closedMenuPreparationEnabledForTesting = defaultClosedMenuPreparationEnabled + static func setClosedMenuPreparationDelayForTesting(_ delay: Duration) { self.closedMenuPreparationDelayForTesting = delay } @@ -13,6 +16,14 @@ extension StatusItemController { static func resetClosedMenuPreparationDelayForTesting() { self.closedMenuPreparationDelayForTesting = self.defaultClosedMenuPreparationDelay } + + static func setClosedMenuPreparationEnabledForTesting(_ enabled: Bool) { + self.closedMenuPreparationEnabledForTesting = enabled + } + + static func resetClosedMenuPreparationEnabledForTesting() { + self.closedMenuPreparationEnabledForTesting = self.defaultClosedMenuPreparationEnabled + } #endif private static var closedMenuPreparationDelay: Duration { @@ -23,6 +34,14 @@ extension StatusItemController { #endif } + static var isClosedMenuPreparationEnabled: Bool { + #if DEBUG + closedMenuPreparationEnabledForTesting + #else + defaultClosedMenuPreparationEnabled + #endif + } + func invalidateMenus( refreshOpenMenus: Bool = false, deferOpenParentMenuRebuild: Bool = false, @@ -49,6 +68,7 @@ extension StatusItemController { func prepareAttachedClosedMenusIfNeeded() { guard self.isMenuRefreshEnabled else { return } + guard Self.isClosedMenuPreparationEnabled else { return } guard self.openMenus.isEmpty else { return } guard !self.isMenuDataRefreshInFlight else { return } for menu in self.attachedMenusForClosedPreparation() { diff --git a/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift b/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift index 43ae2fe0e4..7e422a0e2c 100644 --- a/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift +++ b/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift @@ -63,7 +63,7 @@ struct ProviderSettingsDescriptorTests { } @Test - func `codex exposes open AI web extras toggle as default off opt in`() throws { + func `codex exposes open AI web extras toggle as default off with battery saver on`() throws { let fixture = try self.makeSettingsFixture(suite: "ProviderSettingsDescriptorTests-codex-openai-toggle") let context = fixture.settingsContext(provider: .codex) @@ -74,7 +74,7 @@ struct ProviderSettingsDescriptorTests { #expect(extrasToggle.subtitle.contains("Turn this on")) let batterySaverToggle = try #require(toggles.first(where: { $0.id == "codex-openai-web-battery-saver" })) - #expect(batterySaverToggle.binding.wrappedValue == false) + #expect(batterySaverToggle.binding.wrappedValue == true) #expect(batterySaverToggle.isVisible?() == false) fixture.settings.openAIWebAccessEnabled = true diff --git a/Tests/CodexBarTests/SettingsStoreTests.swift b/Tests/CodexBarTests/SettingsStoreTests.swift index 7c3b70790d..b0fe71da78 100644 --- a/Tests/CodexBarTests/SettingsStoreTests.swift +++ b/Tests/CodexBarTests/SettingsStoreTests.swift @@ -880,8 +880,8 @@ struct SettingsStoreTests { #expect(store.openAIWebAccessEnabled == false) #expect(defaults.bool(forKey: "openAIWebAccessEnabled") == false) - #expect(store.openAIWebBatterySaverEnabled == false) - #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) + #expect(store.openAIWebBatterySaverEnabled == true) + #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == true) #expect(store.codexCookieSource == .off) } @@ -905,8 +905,8 @@ struct SettingsStoreTests { #expect(store.openAIWebAccessEnabled == true) #expect(defaults.bool(forKey: "openAIWebAccessEnabled") == true) - #expect(store.openAIWebBatterySaverEnabled == false) - #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) + #expect(store.openAIWebBatterySaverEnabled == true) + #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == true) #expect(store.codexCookieSource == .auto) } @@ -953,8 +953,8 @@ struct SettingsStoreTests { #expect(store.openAIWebAccessEnabled == true) #expect(defaults.bool(forKey: "openAIWebAccessEnabled") == true) - #expect(store.openAIWebBatterySaverEnabled == false) - #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) + #expect(store.openAIWebBatterySaverEnabled == true) + #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == true) #expect(store.codexCookieSource == .auto) } @@ -998,7 +998,7 @@ struct SettingsStoreTests { zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) - #expect(store.openAIWebBatterySaverEnabled == false) + #expect(store.openAIWebBatterySaverEnabled == true) store.openAIWebBatterySaverEnabled = false #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) diff --git a/Tests/CodexBarTests/StatusMenuOpenRefreshTests.swift b/Tests/CodexBarTests/StatusMenuOpenRefreshTests.swift index 3d622dd3d0..c47952ffb5 100644 --- a/Tests/CodexBarTests/StatusMenuOpenRefreshTests.swift +++ b/Tests/CodexBarTests/StatusMenuOpenRefreshTests.swift @@ -169,7 +169,7 @@ extension StatusMenuTests { } @Test - func `closed attached menu is prepared before next open after invalidation`() async { + func `closed attached menu stays stale after invalidation until next open`() async { self.disableMenuCardsForTesting() let settings = self.makeSettings() settings.statusChecksEnabled = false @@ -200,11 +200,17 @@ extension StatusMenuTests { let openedVersion = controller.menuVersions[key] controller.invalidateMenus() - for _ in 0..<40 where controller.menuVersions[key] == openedVersion { + for _ in 0..<40 { await Task.yield() } #expect(controller.openMenus.isEmpty) + #expect(controller.closedMenuRebuildTasks[key] == nil) + #expect(controller.menuVersions[key] == openedVersion) + + controller.menuWillOpen(menu) + defer { controller.menuDidClose(menu) } + #expect(controller.menuVersions[key] == controller.menuContentVersion) } @@ -228,6 +234,8 @@ extension StatusMenuTests { defer { controller.releaseStatusItemsForTesting() } StatusItemController.setClosedMenuPreparationDelayForTesting(.zero) defer { StatusItemController.resetClosedMenuPreparationDelayForTesting() } + StatusItemController.setClosedMenuPreparationEnabledForTesting(true) + defer { StatusItemController.resetClosedMenuPreparationEnabledForTesting() } controller.menuRefreshEnabledOverrideForTesting = true let menu = controller.makeMenu() @@ -261,6 +269,8 @@ extension StatusMenuTests { func `closed attached menu preparation waits for token refresh to finish`() async { StatusItemController.setClosedMenuPreparationDelayForTesting(.zero) defer { StatusItemController.resetClosedMenuPreparationDelayForTesting() } + StatusItemController.setClosedMenuPreparationEnabledForTesting(true) + defer { StatusItemController.resetClosedMenuPreparationEnabledForTesting() } self.disableMenuCardsForTesting() let settings = self.makeSettings() From 3f9070117d350e73c6e90c784c670a7bbdafe953 Mon Sep 17 00:00:00 2001 From: Nicolas0315 <40846197+Nicolas0315@users.noreply.github.com> Date: Thu, 4 Jun 2026 00:46:29 +0900 Subject: [PATCH 2/2] Preserve battery saver upgrade behavior --- Sources/CodexBar/SettingsStore.swift | 4 ++-- .../ProviderSettingsDescriptorTests.swift | 4 ++-- Tests/CodexBarTests/SettingsStoreTests.swift | 20 +++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/CodexBar/SettingsStore.swift b/Sources/CodexBar/SettingsStore.swift index 54caafd81d..a38c5ba549 100644 --- a/Sources/CodexBar/SettingsStore.swift +++ b/Sources/CodexBar/SettingsStore.swift @@ -375,9 +375,9 @@ extension SettingsStore { userDefaults.set(false, forKey: "openAIWebAccessEnabled") } let openAIWebBatterySaverDefault = userDefaults.object(forKey: "openAIWebBatterySaverEnabled") as? Bool - let openAIWebBatterySaverEnabled = openAIWebBatterySaverDefault ?? true + let openAIWebBatterySaverEnabled = openAIWebBatterySaverDefault ?? false if Self.isRunningTests, openAIWebBatterySaverDefault == nil { - userDefaults.set(true, forKey: "openAIWebBatterySaverEnabled") + userDefaults.set(false, forKey: "openAIWebBatterySaverEnabled") } let providerStorageFootprintsDefault = userDefaults.object(forKey: "providerStorageFootprintsEnabled") as? Bool let providerStorageFootprintsEnabled = providerStorageFootprintsDefault ?? false diff --git a/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift b/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift index 7e422a0e2c..a5ab477812 100644 --- a/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift +++ b/Tests/CodexBarTests/ProviderSettingsDescriptorTests.swift @@ -63,7 +63,7 @@ struct ProviderSettingsDescriptorTests { } @Test - func `codex exposes open AI web extras toggle as default off with battery saver on`() throws { + func `codex exposes open AI web extras toggle as default off with battery saver off`() throws { let fixture = try self.makeSettingsFixture(suite: "ProviderSettingsDescriptorTests-codex-openai-toggle") let context = fixture.settingsContext(provider: .codex) @@ -74,7 +74,7 @@ struct ProviderSettingsDescriptorTests { #expect(extrasToggle.subtitle.contains("Turn this on")) let batterySaverToggle = try #require(toggles.first(where: { $0.id == "codex-openai-web-battery-saver" })) - #expect(batterySaverToggle.binding.wrappedValue == true) + #expect(batterySaverToggle.binding.wrappedValue == false) #expect(batterySaverToggle.isVisible?() == false) fixture.settings.openAIWebAccessEnabled = true diff --git a/Tests/CodexBarTests/SettingsStoreTests.swift b/Tests/CodexBarTests/SettingsStoreTests.swift index b0fe71da78..bffbc6a802 100644 --- a/Tests/CodexBarTests/SettingsStoreTests.swift +++ b/Tests/CodexBarTests/SettingsStoreTests.swift @@ -880,8 +880,8 @@ struct SettingsStoreTests { #expect(store.openAIWebAccessEnabled == false) #expect(defaults.bool(forKey: "openAIWebAccessEnabled") == false) - #expect(store.openAIWebBatterySaverEnabled == true) - #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == true) + #expect(store.openAIWebBatterySaverEnabled == false) + #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) #expect(store.codexCookieSource == .off) } @@ -905,8 +905,8 @@ struct SettingsStoreTests { #expect(store.openAIWebAccessEnabled == true) #expect(defaults.bool(forKey: "openAIWebAccessEnabled") == true) - #expect(store.openAIWebBatterySaverEnabled == true) - #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == true) + #expect(store.openAIWebBatterySaverEnabled == false) + #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) #expect(store.codexCookieSource == .auto) } @@ -953,8 +953,8 @@ struct SettingsStoreTests { #expect(store.openAIWebAccessEnabled == true) #expect(defaults.bool(forKey: "openAIWebAccessEnabled") == true) - #expect(store.openAIWebBatterySaverEnabled == true) - #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == true) + #expect(store.openAIWebBatterySaverEnabled == false) + #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) #expect(store.codexCookieSource == .auto) } @@ -998,13 +998,13 @@ struct SettingsStoreTests { zaiTokenStore: NoopZaiTokenStore(), syntheticTokenStore: NoopSyntheticTokenStore()) - #expect(store.openAIWebBatterySaverEnabled == true) + #expect(store.openAIWebBatterySaverEnabled == false) - store.openAIWebBatterySaverEnabled = false - #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == false) + store.openAIWebBatterySaverEnabled = true + #expect(defaults.bool(forKey: "openAIWebBatterySaverEnabled") == true) store.openAIWebAccessEnabled = true - #expect(store.openAIWebBatterySaverEnabled == false) + #expect(store.openAIWebBatterySaverEnabled == true) } @Test