From b2d5129a6fc3ae3d61d725079a3d2ebf48693936 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 5 Jun 2026 12:22:50 -0700 Subject: [PATCH] fix: skip unchanged quota indicator constraints --- CHANGELOG.md | 1 + .../StatusItemController+SwitcherViews.swift | 19 +++++--- .../StatusMenuSwitcherRefreshTests.swift | 44 +++++++++++++++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c47b38be2c..741bb1c7ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Sources/CodexBar/StatusItemController+SwitcherViews.swift b/Sources/CodexBar/StatusItemController+SwitcherViews.swift index a7fe4c684e..f0c006e034 100644 --- a/Sources/CodexBar/StatusItemController+SwitcherViews.swift +++ b/Sources/CodexBar/StatusItemController+SwitcherViews.swift @@ -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) } @@ -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 { diff --git a/Tests/CodexBarTests/StatusMenuSwitcherRefreshTests.swift b/Tests/CodexBarTests/StatusMenuSwitcherRefreshTests.swift index 4d71f58e1e..bd2da02ecf 100644 --- a/Tests/CodexBarTests/StatusMenuSwitcherRefreshTests.swift +++ b/Tests/CodexBarTests/StatusMenuSwitcherRefreshTests.swift @@ -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)!