Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
72e3aa2
Align Codex token parsing with tokscale stale snapshots
Yuxin-Qiao Aug 21, 2026
c66f06e
Refresh Codex parser hash
Yuxin-Qiao Aug 21, 2026
3441186
Parse bare usage rows in Codex rollouts
Aug 21, 2026
66a0d8a
Fix stale reasoning and fallback cache parity
Aug 21, 2026
c69ac50
Fix Codex fallback test fixture line handling
Yuxin-Qiao Aug 23, 2026
14d9ab3
fix(antigravity): repair offline fallback proof and oauth fallback; f…
Yuxin-Qiao Aug 23, 2026
d0349b7
fix(lint): break long lines in offline fallback proof tests
Yuxin-Qiao Aug 23, 2026
990e742
fix(tests): update gatekeeper anchors for spend dashboard concurrency…
Yuxin-Qiao Aug 23, 2026
ec305bc
fix(tests): correct gatekeeper line anchors for concurrent dashboard fix
Yuxin-Qiao Aug 23, 2026
d6d281e
docs: update appcast for 0.54.1
steipete Aug 23, 2026
4b14ed9
chore: open 0.54.2 unreleased changelog section
steipete Aug 23, 2026
6ef5c5a
Stop re-merging the Codex plan-utilization history with itself on eve…
olddonkey Aug 23, 2026
07bd85a
Fix Codex day cost blanked by trace-only priority turns (#3150)
olddonkey Aug 23, 2026
63dc1cd
docs: credit #3141 and #3150 changelog entries
steipete Aug 23, 2026
c2701d2
fix(qwen-cloud): restore Brave browser support in cookie import (#3148)
umutkeltek Aug 23, 2026
b843190
Fix OpenRouter completed-day activity query (#3138)
kiranmagic7 Aug 23, 2026
0b715a4
Preserve unknown Grok period usage (#3159)
anupamchugh Aug 23, 2026
9b034df
fix: report non-writable CLI path conflicts (#3153)
yicone Aug 23, 2026
daa16fb
Fix single-quota icon scaling (#3155)
akshayprabhu200 Aug 23, 2026
87a16ff
docs: credit #3138 #3148 #3153 #3155 #3159 changelog entries
steipete Aug 23, 2026
6538ac3
fix(spend): silent refresh and invalidation coverage (#3106)
Yuxin-Qiao Aug 23, 2026
926f5b3
Improve Antigravity retrieval: retired Flash alias and offline fallba…
Yuxin-Qiao Aug 23, 2026
7427660
feat(spend): add tokscale-compatible local readers for Cursor and Ant…
Yuxin-Qiao Aug 23, 2026
ce4630b
Merge branch 'pr3120' into verify-3120
steipete Aug 23, 2026
90a1e26
test: repair gatekeeper anchors and regenerate parser hash on merged …
steipete Aug 23, 2026
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
147 changes: 147 additions & 0 deletions .github/pr-proof/3106-dashboard-snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
{
"generatedAt" : "2026-08-23T05:39:37Z",
"host" : {
"codexBarVersion" : null,
"refreshIntervalSeconds" : 0
},
"providers" : [
{
"cost" : null,
"credits" : null,
"display" : {
"accentColor" : "#49A3B0",
"priority" : "normal",
"sortKey" : 0
},
"enabled" : true,
"error" : {
"code" : 1,
"kind" : "provider",
"message" : "Network error: The operation couldn’t be completed. Operation not permitted"
},
"id" : "codex",
"identity" : null,
"name" : "Codex",
"source" : "auto",
"status" : null,
"updatedAt" : "2026-08-23T05:39:36Z",
"windows" : [

]
},
{
"cost" : null,
"credits" : null,
"display" : {
"accentColor" : "#3B82F6",
"priority" : "normal",
"sortKey" : 10
},
"enabled" : true,
"error" : null,
"id" : "opencodego",
"identity" : null,
"name" : "OpenCode Go",
"source" : "local",
"status" : null,
"updatedAt" : "2026-08-23T05:39:32Z",
"windows" : [
{
"kind" : "session",
"label" : "5-hour",
"remainingPercent" : 100,
"resetAt" : "2026-08-23T10:39:32Z",
"usedPercent" : 0
},
{
"kind" : "weekly",
"label" : "Weekly",
"remainingPercent" : 92.1,
"resetAt" : "2026-08-23T23:59:59Z",
"usedPercent" : 7.9
},
{
"kind" : "tertiary",
"label" : "Monthly",
"remainingPercent" : 96,
"resetAt" : "2026-09-20T04:11:23Z",
"usedPercent" : 4
}
]
},
{
"cost" : null,
"credits" : null,
"display" : {
"accentColor" : "#60BA7E",
"priority" : "normal",
"sortKey" : 20
},
"enabled" : true,
"error" : {
"code" : 1,
"kind" : "provider",
"message" : "antigravity usage timed out"
},
"id" : "antigravity",
"identity" : null,
"name" : "Antigravity",
"source" : "auto",
"status" : null,
"updatedAt" : "2026-08-23T05:39:37Z",
"windows" : [

]
},
{
"cost" : null,
"credits" : null,
"display" : {
"accentColor" : "#00BFA5",
"priority" : "normal",
"sortKey" : 30
},
"enabled" : true,
"error" : {
"code" : 1,
"kind" : "provider",
"message" : "The operation couldn’t be completed. Operation not permitted"
},
"id" : "cursor",
"identity" : null,
"name" : "Cursor",
"source" : "auto",
"status" : null,
"updatedAt" : "2026-08-23T05:39:37Z",
"windows" : [

]
},
{
"cost" : null,
"credits" : null,
"display" : {
"accentColor" : "#527DF0",
"priority" : "normal",
"sortKey" : 40
},
"enabled" : true,
"error" : {
"code" : 1,
"kind" : "provider",
"message" : "The operation couldn’t be completed. Operation not permitted"
},
"id" : "deepseek",
"identity" : null,
"name" : "DeepSeek",
"source" : "auto",
"status" : null,
"updatedAt" : "2026-08-23T05:39:37Z",
"windows" : [

]
}
],
"schemaVersion" : 1,
"staleAfterSeconds" : 180
}
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 0.54.2 — Unreleased

- Fixed single-quota menu-bar icons understating remaining usage: a provider's only meaningful quota now renders as one prominent meter instead of half an icon next to a reserved empty lane, including when it arrives in the secondary slot (#3154, #3155). Thanks @akshayprabhu200!
- Grok: report period-only CLI-proxy responses as unknown usage instead of 0% when Grok Build has hit its free limit, keeping identity and reset metadata (#3157, #3159). Thanks @anupamchugh!
- OpenRouter: request the latest completed UTC day from the Activity API instead of the current date, fixing the HTTP 400 that suppressed spend history (#3133, #3138). Thanks @kiranmagic7!
- Qwen Cloud: restored Brave browser support in cookie import (#3148). Thanks @umutkeltek!
- CLI install: report success even when unrelated PATH directories are non-writable, while still listing genuine `codexbar` conflicts from earlier PATH entries (#3153). Thanks @yicone!

- Fixed the Codex cost card blanking Today and recent days when priority processing is in use: row-ownership evidence now accepts the trace database's tier classification instead of discarding whole (day, model) groups (#3150). Thanks @olddonkey!
- Stopped re-merging the Codex plan-utilization history with itself on every refresh and menu open — the legacy-bucket migration now runs only when a non-canonical bucket actually exists (#3141). Thanks @olddonkey!

## 0.54.1 — 2026-08-21

### Highlights
Expand Down
70 changes: 61 additions & 9 deletions Sources/CodexBar/IconRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,23 @@ import CodexBarCore

// swiftlint:disable:next type_body_length
enum IconRenderer {
struct QuotaLayoutPolicy: Hashable {
let reservesMissingSecondaryLane: Bool
let treatsExhaustedSecondaryAsMissing: Bool

static func provider(_ provider: UsageProvider) -> Self {
let presentation = ProviderDescriptorRegistry.descriptor(for: provider).presentation
return Self(
reservesMissingSecondaryLane: presentation.reservesMissingSecondaryIconLane,
treatsExhaustedSecondaryAsMissing: presentation.treatsExhaustedSecondaryIconWindowAsMissing)
}

static func style(_ style: IconStyle) -> Self {
UsageProvider(rawValue: style.rawValue).map(self.provider)
?? Self(reservesMissingSecondaryLane: false, treatsExhaustedSecondaryAsMissing: false)
}
}

private static let creditsCap: Double = 1000
private static let baseSize = NSSize(width: 18, height: 18)
// Render to an 18×18 pt template (36×36 px at 2×) to match the system menu bar size.
Expand All @@ -28,6 +45,11 @@ enum IconRenderer {

private static let grid = PixelGrid(scale: outputScale)

static func fillWidthPixels(remaining: Double, rectWidth: Int) -> Int {
let clamped = max(0, min(remaining / 100, 1))
return max(0, min(rectWidth, Int((CGFloat(rectWidth) * CGFloat(clamped)).rounded())))
}

private struct IconCacheKey: Hashable {
let primary: Int
let weekly: Int
Expand All @@ -36,6 +58,7 @@ enum IconRenderer {
let style: Int
let indicator: Int
let hideCritters: Bool
let quotaLayoutPolicy: QuotaLayoutPolicy
}

private final class IconCacheStore: @unchecked Sendable {
Expand Down Expand Up @@ -120,8 +143,10 @@ enum IconRenderer {
wiggle: CGFloat = 0,
tilt: CGFloat = 0,
statusIndicator: ProviderStatusIndicator = .none,
hideCritters: Bool = false) -> NSImage
hideCritters: Bool = false,
quotaLayoutPolicy: QuotaLayoutPolicy? = nil) -> NSImage
{
let quotaLayoutPolicy = quotaLayoutPolicy ?? .style(style)
let shouldCache = blink <= 0.0001 && wiggle <= 0.0001 && tilt <= 0.0001
let render = {
self.renderImage {
Expand Down Expand Up @@ -178,8 +203,7 @@ enum IconRenderer {

// Fill: clip to the capsule and paint a left-to-right rect so the progress edge is straight.
if let remaining {
let clamped = max(0, min(remaining / 100, 1))
let fillWidthPx = max(0, min(rectPx.w, Int((CGFloat(rectPx.w) * CGFloat(clamped)).rounded())))
let fillWidthPx = Self.fillWidthPixels(remaining: remaining, rectWidth: rectPx.w)
if fillWidthPx > 0 {
NSGraphicsContext.current?.cgContext.saveGState()
trackPath.addClip()
Expand Down Expand Up @@ -638,8 +662,7 @@ enum IconRenderer {

let providerPresentation = UsageProvider(rawValue: style.rawValue)
.map { ProviderDescriptorRegistry.descriptor(for: $0).presentation }
let usesMissingSecondaryLayout =
providerPresentation?.treatsExhaustedSecondaryIconWindowAsMissing == true
let usesMissingSecondaryLayout = quotaLayoutPolicy.treatsExhaustedSecondaryAsMissing
let effectiveWeeklyRemaining: Double? = {
if usesMissingSecondaryLayout, let weeklyRemaining, weeklyRemaining <= 0 {
return nil
Expand Down Expand Up @@ -670,7 +693,22 @@ enum IconRenderer {
let twistFactory = decorations.contains(.factory)
let twistWarp = decorations.contains(.warp)

if weeklyAvailable {
if let bottomValue, bottomValue > 0, topValue == nil,
!quotaLayoutPolicy.reservesMissingSecondaryLane,
!usesMissingSecondaryLayout
{
// Some providers surface their only meaningful quota in the secondary slot.
drawBar(
rectPx: creditsRectPx,
remaining: bottomValue,
addNotches: twistNotches,
addFace: twistFace,
addGeminiTwist: twistGemini,
addAntigravityTwist: twistAntigravity,
addFactoryTwist: twistFactory,
addWarpTwist: twistWarp,
blink: blink)
} else if weeklyAvailable {
// Normal: top=primary, bottom=secondary (bonus/weekly).
drawBar(
rectPx: topRectPx,
Expand All @@ -693,8 +731,6 @@ enum IconRenderer {
blink: blink)
drawBar(rectPx: bottomRectPx, remaining: nil, alpha: 0.45)
} else {
// Weekly missing (e.g. Claude enterprise): keep normal layout but
// dim the bottom track to indicate N/A.
if topValue == nil, let ratio = creditsRatio {
// Credits-only: show credits prominently (e.g. credits loaded before usage).
drawBar(
Expand All @@ -709,7 +745,22 @@ enum IconRenderer {
addWarpTwist: twistWarp,
blink: blink)
drawBar(rectPx: creditsBottomRectPx, remaining: nil, alpha: 0.45)
} else if !quotaLayoutPolicy.reservesMissingSecondaryLane, let topValue {
// One meaningful quota should read as one meter. Reserving an unavailable second
// lane makes (for example) 46% remaining look like roughly 23% of the icon.
drawBar(
rectPx: creditsRectPx,
remaining: topValue,
addNotches: twistNotches,
addFace: twistFace,
addGeminiTwist: twistGemini,
addAntigravityTwist: twistAntigravity,
addFactoryTwist: twistFactory,
addWarpTwist: twistWarp,
blink: blink)
} else {
// Missing secondary (for example Claude Enterprise): preserve the normal two-lane
// layout and dim the unavailable lane.
drawBar(
rectPx: topRectPx,
remaining: topValue,
Expand Down Expand Up @@ -765,7 +816,8 @@ enum IconRenderer {
stale: stale,
style: self.styleKey(style),
indicator: self.indicatorKey(statusIndicator),
hideCritters: hideCritters)
hideCritters: hideCritters,
quotaLayoutPolicy: quotaLayoutPolicy)
if let cached = self.cachedIcon(for: key) {
return cached
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/CodexBar/MenuContent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ struct StatusIconView: View {
stale: self.store.isStale(provider: self.provider),
style: self.store.style(for: self.provider),
statusIndicator: self.store.statusIndicator(for: self.provider),
hideCritters: self.store.settings.menuBarHidesCritters)
hideCritters: self.store.settings.menuBarHidesCritters,
quotaLayoutPolicy: .provider(self.provider))
}
}
14 changes: 14 additions & 0 deletions Sources/CodexBar/PlanUtilizationHistoryStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ struct PlanUtilizationSeriesHistory: Codable, Equatable, Sendable {
let windowMinutes: Int
let entries: [PlanUtilizationHistoryEntry]

private enum CodingKeys: String, CodingKey {
case name
case windowMinutes
case entries
}

init(name: PlanUtilizationSeriesName, windowMinutes: Int, entries: [PlanUtilizationHistoryEntry]) {
self.name = name
self.windowMinutes = windowMinutes
Expand All @@ -56,6 +62,14 @@ struct PlanUtilizationSeriesHistory: Codable, Equatable, Sendable {
}
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let name = try container.decode(PlanUtilizationSeriesName.self, forKey: .name)
let windowMinutes = try container.decode(Int.self, forKey: .windowMinutes)
let entries = try container.decode([PlanUtilizationHistoryEntry].self, forKey: .entries)
self.init(name: name, windowMinutes: windowMinutes, entries: entries)
}

var latestCapturedAt: Date? {
self.entries.last?.capturedAt
}
Expand Down
Loading