Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixed
- Menu bar: defer merged-menu close rebuilds and cache repeated menu-card height measurements so dismissing or rapidly switching the merged dropdown avoids rebuilding SwiftUI-backed cards on the main thread (#1274, #1286). Thanks @hhh2210!
- Menu bar: observe a compact icon-state signature so merged status icons no longer redraw for provider snapshot changes that cannot affect the visible icon (#1297). Thanks @hhh2210!
- Menu bar: keep provider-switcher quota bars from replacing Auto Layout constraints when the visible ratio is unchanged, making tab switches responsive with many providers enabled (#1303, #1315). Thanks @juanjoseluisgarcia!

## 0.32.4 — 2026-06-02

Expand Down
19 changes: 14 additions & 5 deletions Sources/CodexBar/StatusItemController+SwitcherViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -613,11 +613,14 @@ final class ProviderSwitcherView: NSView {
let key = ObjectIdentifier(button)
if let remaining {
if var indicator = self.quotaIndicators[key] {
Self.updateQuotaIndicatorFill(
indicator: &indicator,
remainingPercent: remaining,
selection: segment.selection)
self.quotaIndicators[key] = indicator
let newRatio = Self.quotaIndicatorRatio(remainingPercent: remaining)
if newRatio != indicator.fillRatio {
Self.updateQuotaIndicatorFill(
indicator: &indicator,
remainingPercent: remaining,
selection: segment.selection)
self.quotaIndicators[key] = indicator
}
} else {
self.addQuotaIndicator(to: button, selection: segment.selection, remainingPercent: remaining)
}
Expand Down Expand Up @@ -691,6 +694,12 @@ final class ProviderSwitcherView: NSView {
self.quotaIndicators[ObjectIdentifier(button)]?.fill.frame
}
}

func _test_quotaIndicatorConstraintIdentifiers() -> [ObjectIdentifier] {
self.buttons.compactMap { button in
self.quotaIndicators[ObjectIdentifier(button)].map { ObjectIdentifier($0.fillWidthConstraint) }
}
}
#endif

private func isLightMode() -> Bool {
Expand Down
44 changes: 44 additions & 0 deletions Tests/CodexBarTests/StatusMenuSwitcherRefreshTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,50 @@ struct StatusMenuSwitcherRefreshTests {
#expect(Self.switcherButtons(in: menu).first { $0.tag == nextProviderButton.tag }?.state == .on)
}

@Test
func `tab switch does not replace quota indicator constraints`() {
let switcher = ProviderSwitcherView(
providers: [.codex, .claude],
selected: .provider(.codex),
includesOverview: false,
width: 310,
showsIcons: false,
iconProvider: { _ in NSImage() },
weeklyRemainingProvider: { _ in 75.0 },
onSelect: { _ in })

let initialConstraints = switcher._test_quotaIndicatorConstraintIdentifiers()
#expect(initialConstraints.count == 2, "both providers should have quota indicators")

switcher.updateQuotaIndicators()

let afterFirstCall = switcher._test_quotaIndicatorConstraintIdentifiers()
#expect(afterFirstCall == initialConstraints, "same ratio: constraints must not be replaced")
}

@Test
func `quota indicator constraints are replaced when ratio changes`() {
var currentRemaining = 75.0
let switcher = ProviderSwitcherView(
providers: [.codex, .claude],
selected: .provider(.codex),
includesOverview: false,
width: 310,
showsIcons: false,
iconProvider: { _ in NSImage() },
weeklyRemainingProvider: { _ in currentRemaining },
onSelect: { _ in })

let initialConstraints = switcher._test_quotaIndicatorConstraintIdentifiers()
#expect(initialConstraints.count == 2)

currentRemaining = 40.0
switcher.updateQuotaIndicators()

let afterDataChange = switcher._test_quotaIndicatorConstraintIdentifiers()
#expect(afterDataChange != initialConstraints, "changed ratio: constraints should be replaced")
}

private static func makeSettings() -> SettingsStore {
let suite = "StatusMenuSwitcherRefreshTests-\(UUID().uuidString)"
let defaults = UserDefaults(suiteName: suite)!
Expand Down