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
7 changes: 7 additions & 0 deletions Sources/CodingBar/SelfTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ enum SelfTest {
check("codex usage → 2 windows", codexWindows.count == 2)
check("codex secondary labelled 7d", codexWindows.last?.label == "7d")
check("codex 7d remaining ~0.26", abs((codexWindows.last?.remaining ?? 0) - 0.26) < 0.0001)
check("codex menu quota uses weekly window",
codexWindows.menuWindow(preferring: .codex)?.label == "7d")
let codexFiveHourOnly = [QuotaWindow(provider: .codex, label: "5h", remaining: 0.91, resetAt: nil)]
let canonicalFallback = [QuotaWindow(provider: .claude, label: "5h", remaining: 0.72, resetAt: nil)] + codexFiveHourOnly
check("codex menu quota never falls back to 5h",
codexFiveHourOnly.menuWindow(preferring: .codex) == nil
&& canonicalFallback.menuWindow(preferring: .codex)?.provider == .claude)

let mixed = claudeWindows + codexWindows
check("tightestRemaining picks most-depleted", abs((mixed.tightestRemaining ?? 1) - 0.26) < 0.0001)
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodingBar/Views/MenuBarItemView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ struct MenuBarReadout: View {
.fixedSize()
}

// `pct` is the remaining fraction of the menu window (Claude 5h). We render it
// `pct` is the remaining fraction of the selected menu-bar quota window. We render it
// as *used %* per user preference; the 4-cell meter lights up with usage and is
// colored by health (low usage = green, high usage = red). The Spacer expands
// to fill the shared width, so the meter stays flush with the number's edge.
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodingBarCore/Aggregator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ public enum Aggregator {
primaryText = "\(totalTodayTokens)"
}

// Menu bar shows one window (the user's preferred provider, else Claude 5h).
// Menu bar shows the selected provider's canonical window, with a cross-provider fallback.
// quotaPercent is the *remaining* fraction (drives bar fill + color); the view
// renders it as "used %".
let quotaPercent: Double? = quota.menuWindow(preferring: menuQuotaProvider)?.remaining
Expand Down
25 changes: 13 additions & 12 deletions Sources/CodingBarCore/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,25 @@ public extension Array where Element == QuotaWindow {
/// The most-depleted window's remaining fraction, nil when there is no data.
var tightestRemaining: Double? { map(\.remaining).min() }

/// The single window surfaced in the menu bar: Claude 5h preferred, else
/// Codex 5h, else the most-depleted window. Keeps the menu bar meaning one
/// fixed, predictable thing ("how much of my Claude 5h window is used").
/// The single window surfaced in the menu bar: Claude 5h preferred, else Codex
/// 7d. Only these canonical limits are eligible because the menu bar has no label
/// that could honestly explain an arbitrary fallback window.
var menuWindow: QuotaWindow? {
first { $0.provider == .claude && $0.label == "5h" }
?? first { $0.provider == .codex && $0.label == "5h" }
?? self.min { $0.remaining < $1.remaining }
?? first { $0.provider == .codex && $0.label == "7d" }
}

/// Menu-bar window honoring the user's preferred provider: that provider's 5h
/// window, else its most-depleted window. Falls back to the default `menuWindow`
/// chain when the preferred provider has no data (so a Claude-only or Codex-only
/// user still sees something). `nil` preference → the default chain.
/// Menu-bar window honoring the user's preferred provider: Claude's 5h or Codex's
/// 7d window. If that canonical window is unavailable, falls back to the other
/// provider's canonical window; `nil` preference uses the default chain.
func menuWindow(preferring provider: Provider?) -> QuotaWindow? {
guard let provider else { return menuWindow }
let preferred = first { $0.provider == provider && $0.label == "5h" }
?? filter { $0.provider == provider }.min { $0.remaining < $1.remaining }
return preferred ?? menuWindow
let label: String
switch provider {
case .claude: label = "5h"
case .codex: label = "7d"
}
return first { $0.provider == provider && $0.label == label } ?? menuWindow
}
}

Expand Down
21 changes: 21 additions & 0 deletions Tests/CodingBarCoreTests/SmokeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,27 @@ final class SmokeTests: XCTestCase {
XCTAssertEqual(unlabelable.map(\.label), ["7d"])
}

func testCodexMenuQuotaUsesWeeklyWindow() {
let windows = [
QuotaWindow(provider: .codex, label: "5h", remaining: 0.91, resetAt: nil),
QuotaWindow(provider: .codex, label: "7d", remaining: 0.26, resetAt: nil),
]

XCTAssertEqual(windows.menuWindow(preferring: .codex)?.label, "7d")
}

func testCodexMenuQuotaNeverFallsBackToFiveHourWindow() {
let codexFiveHourOnly = [
QuotaWindow(provider: .codex, label: "5h", remaining: 0.91, resetAt: nil),
]
XCTAssertNil(codexFiveHourOnly.menuWindow(preferring: .codex))

let withClaude = [
QuotaWindow(provider: .claude, label: "5h", remaining: 0.72, resetAt: nil),
] + codexFiveHourOnly
XCTAssertEqual(withClaude.menuWindow(preferring: .codex)?.provider, .claude)
}

/// A model-scoped weekly cap burns on its own curve — for a model priced above the plan
/// average it usually empties well before the overall week — so it needs its own
/// depletion line, keyed by window id rather than the shared provider key.
Expand Down
9 changes: 9 additions & 0 deletions release-notes/v1.1.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
- The menu-bar quota provider setting now uses each provider's actual primary
limit: Claude shows the 5-hour window, while Codex shows the weekly window.
Selecting Codex previously still preferred a 5-hour-shaped window, so the
number could represent the wrong allowance.
- Codex no longer silently falls back to a 5-hour window when weekly data is
unavailable. CodingBar falls back to Claude's 5-hour limit when possible, or
leaves the quota readout hidden rather than displaying a misleading metric.

**Full Changelog**: https://github.com/Gnonymous/CodingBar/compare/v1.1.7...v1.1.8
Loading