Files
steipete--codexbar/Sources/CodexBar/StatusItemController+MenuBarLayout.swift
Giuseppe Bisemi 83e7deb356 feat: add provider usage lanes to menu bar layouts (#3039)
* feat: add provider usage lanes to menu bar layouts

* Address PR review feedback (#3039)

- honor snapshot-gated direct lane capabilities
- carry snapshot-aware provider lane labels into the editor and renderer

Note: pre-existing failure in MiniMaxMenuCardBillingTests not addressed by this PR.

* fix(ci): refresh provider architecture allowlist anchors

* Address Codex lane normalization feedback (#3039)

- route direct Codex lanes through the consumer projection
- cover expired resets and weekly binding caps

Note: pre-existing failure in MiniMaxMenuCardBillingTests not addressed by this PR.

* fix(ci): refresh provider architecture allowlist anchors

The Codex lane-normalization commit shifted the two `if provider == .codex` clusters past the two-line allowlist tolerance, which failed the macOS shard.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: add Cursor lane menubar proof for #3039

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 08:15:25 -07:00

252 lines
11 KiB
Swift

import AppKit
import CodexBarCore
import Foundation
struct MenuBarLayoutWindows {
let primary: RateWindow?
let secondary: RateWindow?
let tertiary: RateWindow?
let session: RateWindow?
let weekly: RateWindow?
let automatic: RateWindow?
}
extension StatusItemController {
func applyStoredMenuBarLayoutIfNeeded(
provider: UsageProvider,
snapshot: UsageSnapshot?,
icon: NSImage?,
warningFlash: Bool,
statusItem: NSStatusItem,
now: Date = .init())
-> Bool?
{
let resolution = self.settings.menuBarLayoutResolution(for: provider)
guard !resolution.usesLegacyRendering,
self.settings.menuBarIconStyle == .iconAndPercent,
let button = statusItem.button
else {
statusItem.length = NSStatusItem.variableLength
return nil
}
let renderedIcon = icon.map { warningFlash ? Self.quotaWarningFlashImage(base: $0) : $0 }
let data = self.menuBarLayoutRenderData(
provider: provider,
snapshot: snapshot,
warningFlash: warningFlash,
now: now)
let appearanceName = button.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua])?.rawValue ?? "default"
let options = MenuBarLayoutRenderOptions(
size: self.settings.menuBarLayoutSize,
highContrast: self.shouldUseHighContrastStatusItemContent,
showUsed: self.settings.usageBarsShowUsed,
appearanceName: appearanceName,
isDebugApp: Self.isDebugApp(bundleIdentifier: Bundle.main.bundleIdentifier),
isStale: self.store.isStale(provider: provider),
now: now,
verticalAdjustment: self.settings.menuBarLayoutVerticalAdjustment)
let rendered = self.menuBarLayoutRenderer.render(
layout: resolution.layout,
data: data,
icon: renderedIcon,
options: options)
let expectedImagePosition: NSControl.ImagePosition = if rendered.leadingIcon != nil {
rendered.attributedTitle.length > 0 ? .imageLeft : .imageOnly
} else {
.noImage
}
let wasCached = button.image === rendered.leadingIcon
&& button.imagePosition == expectedImagePosition
&& button.attributedTitle.isEqual(to: rendered.attributedTitle)
self.setButtonLayoutContent(rendered, for: button, statusItem: statusItem)
return wasCached
}
func menuBarLayoutRenderData(
provider: UsageProvider,
snapshot: UsageSnapshot?,
warningFlash: Bool,
now: Date = .init())
-> MenuBarLayoutRenderData
{
let windows = self.menuBarLayoutWindows(provider: provider, snapshot: snapshot, now: now)
let scopedNamed = MenuBarLayoutSemanticWindowResolver.scopedWeeklyNamedWindow(snapshot: snapshot)
let paceWindow = windows.weekly ?? windows.automatic
let runsOut = paceWindow
.flatMap {
self.store.weeklyPace(
provider: provider,
window: $0,
now: now)
}
.flatMap { UsagePaceText.weeklyDetail(provider: provider, pace: $0, now: now).rightLabel }
let costStrings = self.menuBarLayoutCostStrings(provider: provider, now: now)
let providerName = L(self.store.metadata(for: provider).displayName)
let accountLabel = self.menuBarLayoutAccountLabel(provider: provider, snapshot: snapshot)
let automatic = MenuBarLayoutRenderWindow(windows.automatic)
return MenuBarLayoutRenderData(
provider: provider,
iconKey: "\(provider.rawValue):\(warningFlash ? "warning" : "normal")",
providerName: providerName,
accountLabel: accountLabel,
laneLabels: MenuBarLayoutLaneLabels(provider: provider, snapshot: snapshot),
primary: MenuBarLayoutRenderWindow(windows.primary),
secondary: MenuBarLayoutRenderWindow(windows.secondary),
tertiary: MenuBarLayoutRenderWindow(windows.tertiary),
session: MenuBarLayoutRenderWindow(windows.session),
weekly: MenuBarLayoutRenderWindow(windows.weekly),
scopedWeekly: MenuBarLayoutRenderWindow(scopedNamed?.window),
scopedWeeklyTitle: scopedNamed?.title,
automatic: automatic,
// Provider-specific by design: Mistral uses spend text when its automatic lane has no percentage window.
automaticText: provider == .mistral && automatic == nil
? Self.mistralSpendDisplayText(snapshot: snapshot)
: nil,
sessionPace: self.store.menuBarLayoutPaceText(
provider: provider,
window: windows.session,
now: now),
weeklyPace: self.store.menuBarLayoutPaceText(
provider: provider,
window: windows.weekly,
now: now,
minimumElapsedPercent: 1),
automaticPace: self.store.menuBarLayoutPaceText(
provider: provider,
window: windows.automatic,
now: now),
runsOut: runsOut,
balance: MenuBarLayoutBalanceResolver.balance(provider: provider, snapshot: snapshot),
costToday: costStrings.today,
cost30d: costStrings.last30Days)
}
func menuBarLayoutAccountLabel(provider: UsageProvider, snapshot: UsageSnapshot?) -> String? {
let rawAccountLabel = snapshot?.accountEmail(for: provider)?
.trimmingCharacters(in: .whitespacesAndNewlines)
return self.settings.hidePersonalInfo || rawAccountLabel?.isEmpty != false
? nil
: rawAccountLabel
}
func menuBarLayoutCostStrings(
provider: UsageProvider,
now: Date = .init())
-> (today: String?, last30Days: String?)
{
let snapshot = self.store.tokenSnapshotForCurrentProviderConfig(for: provider)?.snapshot
let sourceCurrencyCode = snapshot?.currencyCode ?? "USD"
let preferredCurrencyCode = self.settings.preferredCurrencyCode
let today = MenuBarLayoutCostResolver.todayCostUSD(snapshot: snapshot, now: now).map {
UsageFormatter.convertedCostString(
$0,
preferredCurrency: preferredCurrencyCode,
providerCurrency: sourceCurrencyCode)
}
let last30Days = snapshot?.last30DaysCostUSD.map {
UsageFormatter.convertedCostString(
$0,
preferredCurrency: preferredCurrencyCode,
providerCurrency: sourceCurrencyCode)
}
return (today, last30Days)
}
func menuBarLayoutWindows(
provider: UsageProvider,
snapshot: UsageSnapshot?,
now: Date)
-> MenuBarLayoutWindows
{
if provider == .codex,
let projection = self.store.codexConsumerProjectionIfNeeded(
for: provider,
surface: .menuBar,
snapshotOverride: snapshot,
now: now)
{
let session = projection.menuBarSelectableRateWindow(for: .session)
let weekly = projection.menuBarSelectableRateWindow(for: .weekly)
let automatic = projection.automaticMenuBarWindow()
return MenuBarLayoutWindows(
primary: session,
secondary: weekly,
tertiary: snapshot?.tertiary,
session: session,
weekly: weekly,
automatic: automatic)
}
let semanticWindows = MenuBarLayoutSemanticWindowResolver.windows(
provider: provider,
snapshot: snapshot)
// Provider-specific by design: Mistral's automatic lane can explicitly select its Monthly Plan window.
let automaticPreference = provider == .mistral
? self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot)
: .automatic
let automatic = MenuBarMetricWindowResolver.rateWindow(
preference: automaticPreference,
provider: provider,
snapshot: snapshot,
supportsAverage: self.settings.menuBarMetricSupportsAverage(for: provider),
antigravityPrioritizeExhaustedQuotas: self.settings.antigravityPrioritizeExhaustedQuotas,
now: now)
return MenuBarLayoutWindows(
primary: snapshot?.primary,
secondary: snapshot?.secondary,
tertiary: snapshot?.tertiary,
session: semanticWindows.session,
weekly: semanticWindows.weekly,
automatic: MenuBarLayoutAutomaticWindowDisplayNormalizer.normalized(
provider: provider,
snapshot: snapshot,
window: automatic))
}
private func setButtonLayoutContent(
_ rendered: MenuBarLayoutRenderedTitle,
for button: NSStatusBarButton,
statusItem: NSStatusItem)
{
// A leading icon token is surfaced as the status item image so AppKit applies the
// system's inactive-display tinting to it, matching how other menu bar icons behave.
// Text tokens keep rendering through the attributed title.
if let icon = rendered.leadingIcon {
if button.image !== icon {
button.image = icon
}
let position: NSControl.ImagePosition = rendered.attributedTitle.length > 0 ? .imageLeft : .imageOnly
if button.imagePosition != position {
button.imagePosition = position
}
} else {
if button.image != nil {
button.image = nil
}
if button.imagePosition != .noImage {
button.imagePosition = .noImage
}
}
if !button.attributedTitle.isEqual(to: rendered.attributedTitle) {
button.attributedTitle = rendered.attributedTitle
}
if button.accessibilityTitle() != rendered.accessibilityLabel {
button.setAccessibilityTitle(rendered.accessibilityLabel)
}
// AppKit exposes no content-inset API on NSStatusBarButton. Explicit item length is the actual
// status-item padding mechanism: tight removes most edge space; regular keeps the native breathing room.
var bounds = rendered.attributedTitle.boundingRect(
with: NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading])
if let icon = rendered.leadingIcon {
bounds.size.width += icon.size.width
}
let horizontalPadding: CGFloat = self.settings.menuBarLayoutGap == .tight ? 3 : 10
statusItem.length = max(18, ceil(bounds.width) + horizontalPadding)
}
}