Skip to content

Commit dfcee77

Browse files
willytop8claude
andcommitted
feat: stop OpenRouter from replacing menu-bar meter with logo
OpenRouter has a brand logo, but the logo suppresses usage information that is more useful in the menu bar. This change makes OpenRouter always render its usage meter (the same capsule bar all other providers use) regardless of whether a brand image is available. - Removes the logo-fallback branch from applyIcon(for:phase:) for OpenRouter; the meter path is now unconditional - Adds pixel-level tests confirming the meter renders (not the logo) both when key data is available with no configured limit and when key data has not been fetched yet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 63e473f commit dfcee77

3 files changed

Lines changed: 134 additions & 105 deletions

File tree

Sources/CodexBar/StatusItemController+Animation.swift

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ extension StatusItemController {
223223
return false
224224
}
225225

226-
// swiftlint:disable function_body_length
227226
@discardableResult
228227
func applyIcon(phase: Double?) -> Bool {
229228
guard let button = self.statusItem.button else { return false }
@@ -336,30 +335,6 @@ extension StatusItemController {
336335
return false
337336
}
338337

339-
if Self.shouldUseOpenRouterBrandFallback(provider: primaryProvider, snapshot: snapshot),
340-
let brand = ProviderBrandIcon.image(for: primaryProvider)
341-
{
342-
let signature = [
343-
"mode=openRouterFallback",
344-
"provider=\(primaryProvider.rawValue)",
345-
"style=\(String(describing: style))",
346-
"primary=\(debugDouble(primary))",
347-
"weekly=\(debugDouble(weekly))",
348-
"credits=\(debugDouble(credits))",
349-
"stale=\(stale ? "1" : "0")",
350-
"status=\(statusIndicator.rawValue)",
351-
"anim=\(needsAnimation ? "1" : "0")",
352-
].joined(separator: "|")
353-
if self.shouldSkipMergedIconRender(signature) {
354-
return true
355-
}
356-
self.setButtonTitle(nil, for: button)
357-
self.setButtonImage(
358-
Self.brandImageWithStatusOverlay(brand: brand, statusIndicator: statusIndicator),
359-
for: button)
360-
return false
361-
}
362-
363338
self.setButtonTitle(nil, for: button)
364339
if let morphProgress {
365340
let signature = [
@@ -408,8 +383,6 @@ extension StatusItemController {
408383
return false
409384
}
410385

411-
// swiftlint:enable function_body_length
412-
413386
private func shouldSkipMergedIconRender(_ signature: String) -> Bool {
414387
guard self.shouldMergeIcons else {
415388
self.lastAppliedMergedIconRenderSignature = signature
@@ -440,17 +413,7 @@ extension StatusItemController {
440413
return
441414
}
442415

443-
if Self.shouldUseOpenRouterBrandFallback(provider: provider, snapshot: snapshot),
444-
let brand = ProviderBrandIcon.image(for: provider)
445-
{
446-
self.setButtonTitle(nil, for: button)
447-
self.setButtonImage(
448-
Self.brandImageWithStatusOverlay(
449-
brand: brand,
450-
statusIndicator: self.store.statusIndicator(for: provider)),
451-
for: button)
452-
return
453-
}
416+
// OpenRouter always gets a meter here — the brand-logo fallback was removed on purpose.
454417
let resolved = snapshot.map {
455418
IconRemainingResolver.resolvedPercents(
456419
snapshot: $0,
@@ -775,18 +738,6 @@ extension StatusItemController {
775738
}
776739
}
777740

778-
nonisolated static func shouldUseOpenRouterBrandFallback(
779-
provider: UsageProvider,
780-
snapshot: UsageSnapshot?) -> Bool
781-
{
782-
guard provider == .openrouter,
783-
let openRouterUsage = snapshot?.openRouterUsage
784-
else {
785-
return false
786-
}
787-
return openRouterUsage.keyQuotaStatus == .noLimitConfigured
788-
}
789-
790741
nonisolated static func brandImageWithStatusOverlay(
791742
brand: NSImage,
792743
statusIndicator: ProviderStatusIndicator) -> NSImage

Tests/CodexBarTests/StatusItemAnimationTests.swift

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Testing
55

66
@MainActor
77
@Suite(.serialized)
8+
// swiftlint:disable:next type_body_length
89
struct StatusItemAnimationTests {
910
private func maxAlpha(in rep: NSBitmapImageRep) -> CGFloat {
1011
var maxAlpha: CGFloat = 0
@@ -247,6 +248,138 @@ struct StatusItemAnimationTests {
247248
#expect(alpha < 0.6)
248249
}
249250

251+
@Test
252+
func `open router without key limit uses meter icon when brand percent is disabled`() {
253+
let settings = SettingsStore(
254+
configStore: testConfigStore(suiteName: "StatusItemAnimationTests-openrouter-no-limit-meter"),
255+
zaiTokenStore: NoopZaiTokenStore(),
256+
syntheticTokenStore: NoopSyntheticTokenStore())
257+
settings.statusChecksEnabled = false
258+
settings.refreshFrequency = .manual
259+
settings.mergeIcons = false
260+
settings.menuBarShowsBrandIconWithPercent = false
261+
262+
let registry = ProviderRegistry.shared
263+
if let openRouterMeta = registry.metadata[.openrouter] {
264+
settings.setProviderEnabled(provider: .openrouter, metadata: openRouterMeta, enabled: true)
265+
}
266+
settings.openRouterAPIToken = "or-token"
267+
268+
let fetcher = UsageFetcher()
269+
let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)
270+
let controller = StatusItemController(
271+
store: store,
272+
settings: settings,
273+
account: fetcher.loadAccountInfo(),
274+
updater: DisabledUpdaterController(),
275+
preferencesSelection: PreferencesSelection(),
276+
statusBar: self.makeStatusBarForTesting())
277+
defer { controller.releaseStatusItemsForTesting() }
278+
279+
let snapshot = OpenRouterUsageSnapshot(
280+
totalCredits: 50,
281+
totalUsage: 45,
282+
balance: 5,
283+
usedPercent: 90,
284+
keyDataFetched: true,
285+
keyLimit: nil,
286+
keyUsage: nil,
287+
rateLimit: nil,
288+
updatedAt: Date()).toUsageSnapshot()
289+
290+
store._setSnapshotForTesting(snapshot, provider: .openrouter)
291+
store._setErrorForTesting(nil, provider: .openrouter)
292+
293+
controller.applyIcon(for: .openrouter, phase: nil)
294+
295+
guard let image = controller.statusItems[.openrouter]?.button?.image else {
296+
#expect(Bool(false))
297+
return
298+
}
299+
300+
#expect(image.size.width == 18)
301+
#expect(image.size.height == 18)
302+
#expect(snapshot.openRouterUsage?.keyQuotaStatus == .noLimitConfigured)
303+
#expect(controller.statusItems[.openrouter]?.button?.title.isEmpty == true)
304+
#expect(MenuBarDisplayText.percentText(window: snapshot.primary, showUsed: false) == nil)
305+
306+
// With no key limit, the primary bar has no fill — just the dim track.
307+
// A brand logo would be fully opaque here; the track is not.
308+
let rep = image.representations.compactMap { $0 as? NSBitmapImageRep }.first(where: {
309+
$0.pixelsWide == 36 && $0.pixelsHigh == 36
310+
})
311+
#expect(rep != nil)
312+
if let rep {
313+
let alpha = (rep.colorAt(x: 8, y: 25) ?? .clear).alphaComponent
314+
#expect(alpha < 0.5)
315+
}
316+
}
317+
318+
@Test
319+
func `open router key data not fetched still uses meter icon when brand percent is disabled`() {
320+
let settings = SettingsStore(
321+
configStore: testConfigStore(suiteName: "StatusItemAnimationTests-openrouter-no-fetch-meter"),
322+
zaiTokenStore: NoopZaiTokenStore(),
323+
syntheticTokenStore: NoopSyntheticTokenStore())
324+
settings.statusChecksEnabled = false
325+
settings.refreshFrequency = .manual
326+
settings.mergeIcons = false
327+
settings.menuBarShowsBrandIconWithPercent = false
328+
329+
let registry = ProviderRegistry.shared
330+
if let openRouterMeta = registry.metadata[.openrouter] {
331+
settings.setProviderEnabled(provider: .openrouter, metadata: openRouterMeta, enabled: true)
332+
}
333+
settings.openRouterAPIToken = "or-token"
334+
335+
let fetcher = UsageFetcher()
336+
let store = UsageStore(fetcher: fetcher, browserDetection: BrowserDetection(cacheTTL: 0), settings: settings)
337+
let controller = StatusItemController(
338+
store: store,
339+
settings: settings,
340+
account: fetcher.loadAccountInfo(),
341+
updater: DisabledUpdaterController(),
342+
preferencesSelection: PreferencesSelection(),
343+
statusBar: self.makeStatusBarForTesting())
344+
defer { controller.releaseStatusItemsForTesting() }
345+
346+
let snapshot = OpenRouterUsageSnapshot(
347+
totalCredits: 50,
348+
totalUsage: 45,
349+
balance: 5,
350+
usedPercent: 90,
351+
keyDataFetched: false,
352+
keyLimit: nil,
353+
keyUsage: nil,
354+
rateLimit: nil,
355+
updatedAt: Date()).toUsageSnapshot()
356+
357+
store._setSnapshotForTesting(snapshot, provider: .openrouter)
358+
store._setErrorForTesting(nil, provider: .openrouter)
359+
360+
controller.applyIcon(for: .openrouter, phase: nil)
361+
362+
guard let image = controller.statusItems[.openrouter]?.button?.image else {
363+
#expect(Bool(false))
364+
return
365+
}
366+
367+
#expect(image.size.width == 18)
368+
#expect(image.size.height == 18)
369+
#expect(snapshot.openRouterUsage?.keyQuotaStatus == .unavailable)
370+
371+
// Even with no key data, OpenRouter still renders a meter rather than the brand logo.
372+
// A brand logo would be fully opaque here; the unfilled track is not.
373+
let rep = image.representations.compactMap { $0 as? NSBitmapImageRep }.first(where: {
374+
$0.pixelsWide == 36 && $0.pixelsHigh == 36
375+
})
376+
#expect(rep != nil)
377+
if let rep {
378+
let alpha = (rep.colorAt(x: 8, y: 25) ?? .clear).alphaComponent
379+
#expect(alpha < 0.5)
380+
}
381+
}
382+
250383
@Test
251384
func `menu bar percent uses configured metric`() {
252385
let settings = SettingsStore(

Tests/CodexBarTests/StatusItemControllerMenuTests.swift

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -96,61 +96,6 @@ struct StatusItemControllerMenuTests {
9696
#expect(percent == 76)
9797
}
9898

99-
@Test
100-
func `open router brand fallback enabled when no key limit configured`() {
101-
let snapshot = OpenRouterUsageSnapshot(
102-
totalCredits: 50,
103-
totalUsage: 45,
104-
balance: 5,
105-
usedPercent: 90,
106-
keyDataFetched: true,
107-
keyLimit: nil,
108-
keyUsage: nil,
109-
rateLimit: nil,
110-
updatedAt: Date()).toUsageSnapshot()
111-
112-
#expect(StatusItemController.shouldUseOpenRouterBrandFallback(
113-
provider: .openrouter,
114-
snapshot: snapshot))
115-
#expect(MenuBarDisplayText.percentText(window: snapshot.primary, showUsed: false) == nil)
116-
}
117-
118-
@Test
119-
func `open router brand fallback disabled when key quota fetch unavailable`() {
120-
let snapshot = OpenRouterUsageSnapshot(
121-
totalCredits: 50,
122-
totalUsage: 45,
123-
balance: 5,
124-
usedPercent: 90,
125-
keyDataFetched: false,
126-
keyLimit: nil,
127-
keyUsage: nil,
128-
rateLimit: nil,
129-
updatedAt: Date()).toUsageSnapshot()
130-
131-
#expect(!StatusItemController.shouldUseOpenRouterBrandFallback(
132-
provider: .openrouter,
133-
snapshot: snapshot))
134-
}
135-
136-
@Test
137-
func `open router brand fallback disabled when key quota available`() {
138-
let snapshot = OpenRouterUsageSnapshot(
139-
totalCredits: 50,
140-
totalUsage: 45,
141-
balance: 5,
142-
usedPercent: 90,
143-
keyLimit: 20,
144-
keyUsage: 2,
145-
rateLimit: nil,
146-
updatedAt: Date()).toUsageSnapshot()
147-
148-
#expect(!StatusItemController.shouldUseOpenRouterBrandFallback(
149-
provider: .openrouter,
150-
snapshot: snapshot))
151-
#expect(snapshot.primary?.usedPercent == 10)
152-
}
153-
15499
@Test
155100
@MainActor
156101
func `menu card width stays at base width when menu accessories are present`() {

0 commit comments

Comments
 (0)