Skip to content

Commit 4f0ac06

Browse files
authored
fix: cap session row by binding quotas (#2841)
Co-authored-by: Yuxin Qiao <104957188+Yuxin-Qiao@users.noreply.github.com>
1 parent 63eb5bf commit 4f0ac06

19 files changed

Lines changed: 548 additions & 29 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 0.49.2 — 2026-08-10
44

55
### Fixed
6+
- Menu cards: show session quota as exhausted until the final reset of every binding weekly/monthly plan lane, while preserving session-specific details and independent purchased credits (#2840). Thanks @Yuxin-Qiao!
67
- Plugins: honor the “Usage bars fill” remaining/used setting in user-installed provider cards, keeping percentage labels and bar direction aligned (#2749). Thanks @RyloRiz!
78
- Sub2API: localize and group menu-card quota labels and request, token, and cost totals into a compact usage summary (#2835). Thanks @weirdo-adam!
89
- Codex: avoid repeatedly converting historical token snapshots during cost-cache refreshes, preventing sustained CPU usage on large session histories.

Sources/CodexBar/MenuCardView+ModelHelpers.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,29 @@ import CodexBarCore
22
import SwiftUI
33

44
extension UsageMenuCardView.Model {
5+
/// Resolves the displayed primary percentage/reset through the provider's binding quotas.
6+
/// Primary-owned detail text remains sourced from the raw primary window.
7+
static func bindingQuotaProjection(
8+
input: Input,
9+
primary: RateWindow,
10+
snapshot: UsageSnapshot) -> RateWindowBindingQuotaProjection?
11+
{
12+
let lanes = ProviderDescriptorRegistry.descriptor(for: input.provider)
13+
.presentation.primaryBindingQuotaLanes
14+
let bindingWindows = lanes.compactMap { lane -> RateWindow? in
15+
switch lane {
16+
case .primary: nil
17+
case .secondary: snapshot.secondary
18+
case .tertiary: snapshot.tertiary
19+
}
20+
}
21+
guard !bindingWindows.isEmpty else { return nil }
22+
return RateWindow.bindingQuotaProjection(
23+
primary: primary,
24+
bindingLanes: bindingWindows,
25+
now: input.now)
26+
}
27+
528
struct PaceDetail {
629
let leftLabel: String
730
let rightLabel: String?

Sources/CodexBar/MenuCardView.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,10 @@ extension UsageMenuCardView.Model {
12361236
metrics.append(Self.primaryMetric(
12371237
input: input,
12381238
primary: primary,
1239+
bindingProjection: Self.bindingQuotaProjection(
1240+
input: input,
1241+
primary: primary,
1242+
snapshot: snapshot),
12391243
percentStyle: percentStyle,
12401244
title: labels.primary))
12411245
}
@@ -1317,6 +1321,7 @@ extension UsageMenuCardView.Model {
13171321
private static func primaryMetric(
13181322
input: Input,
13191323
primary: RateWindow,
1324+
bindingProjection: RateWindowBindingQuotaProjection? = nil,
13201325
percentStyle: PercentStyle,
13211326
title: String? = nil) -> Metric
13221327
{
@@ -1329,13 +1334,26 @@ extension UsageMenuCardView.Model {
13291334
primary: primary)
13301335
Self.applyPrimaryBalancePresentation(&presentation, input: input, primary: primary)
13311336
Self.applyPrimaryResetPresentation(&presentation, input: input, primary: primary)
1332-
Self.applyPrimaryPacePresentation(&presentation, input: input, primary: primary)
1337+
if bindingProjection == nil {
1338+
Self.applyPrimaryPacePresentation(&presentation, input: input, primary: primary)
1339+
}
13331340
Self.applyPrimaryFinalOverrides(&presentation, input: input, primary: primary)
1341+
if let bindingProjection {
1342+
let resetWindow = RateWindow(
1343+
usedPercent: bindingProjection.usedPercent,
1344+
windowMinutes: primary.windowMinutes,
1345+
resetsAt: bindingProjection.resetsAt,
1346+
resetDescription: bindingProjection.resetDescription)
1347+
presentation.resetText = Self.resetText(
1348+
for: resetWindow,
1349+
style: input.resetTimeDisplayStyle,
1350+
now: input.now)
1351+
}
1352+
let displayedUsedPercent = bindingProjection?.usedPercent ?? primary.usedPercent
13341353
return Metric(
13351354
id: "primary",
13361355
title: title ?? L(input.metadata.sessionLabel),
1337-
percent: Self.clamped(
1338-
input.usageBarsShowUsed ? primary.usedPercent : primary.remainingPercent),
1356+
percent: Self.clamped(input.usageBarsShowUsed ? displayedUsedPercent : 100 - displayedUsedPercent),
13391357
percentStyle: percentStyle,
13401358
statusText: presentation.statusText,
13411359
resetText: presentation.resetText,

Sources/CodexBarCore/Providers/Alibaba/AlibabaCodingPlanProviderDescriptor.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,9 @@ public enum AlibabaCodingPlanProviderDescriptor {
8484
supportsTokenCost: false,
8585
noDataMessage: { "Alibaba Coding Plan cost summary is not supported." }),
8686
pace: .calendarMonthResetWindow,
87-
presentation: ProviderUsagePresentation(menuCard: ProviderMenuCardPresentation(
88-
showsPrimaryBalanceDescription: true)),
87+
presentation: ProviderUsagePresentation(
88+
primaryBindingQuotaLanes: [.secondary, .tertiary],
89+
menuCard: ProviderMenuCardPresentation(showsPrimaryBalanceDescription: true)),
8990
fetchPlan: ProviderFetchPlan(
9091
sourceModes: [.auto, .web, .api],
9192
pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),

Sources/CodexBarCore/Providers/Alibaba/AlibabaTokenPlanProviderDescriptor.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ public enum AlibabaTokenPlanProviderDescriptor {
9595
supportsTokenCost: false,
9696
noDataMessage: { "Alibaba Token Plan cost summary is not supported." }),
9797
pace: .calendarMonthResetWindow,
98-
presentation: ProviderUsagePresentation(menuCard: ProviderMenuCardPresentation(
99-
showsPrimaryBalanceDescription: true)),
98+
presentation: ProviderUsagePresentation(
99+
primaryBindingQuotaLanes: [.secondary],
100+
menuCard: ProviderMenuCardPresentation(showsPrimaryBalanceDescription: true)),
100101
fetchPlan: ProviderFetchPlan(
101102
sourceModes: [.auto, .web],
102103
pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),

Sources/CodexBarCore/Providers/Chutes/ChutesProviderDescriptor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public enum ChutesProviderDescriptor {
4444
supportsTokenCost: false,
4545
noDataMessage: { "Chutes cost history is not available from CodexBar." }),
4646
presentation: ProviderUsagePresentation(
47+
primaryBindingQuotaLanes: [.secondary],
4748
menuCard: ProviderMenuCardPresentation(
4849
showsPrimaryBalanceDescription: true,
4950
hidesPrimaryResetWithoutDate: true),

Sources/CodexBarCore/Providers/Claude/ClaudeProviderDescriptor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ public enum ClaudeProviderDescriptor {
196196
return series
197197
},
198198
secondaryGloballyCapsPrimary: true,
199+
primaryBindingQuotaLanes: [.secondary],
199200
menuCard: ProviderMenuCardPresentation(
200201
costVisibilityResolver: { context in
201202
context.showOptionalUsage || context.snapshot?.loginMethod(for: .claude) == "Admin API"

Sources/CodexBarCore/Providers/ClinePass/ClinePassProviderDescriptor.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public enum ClinePassProviderDescriptor {
4242
tokenCost: ProviderTokenCostConfig(
4343
supportsTokenCost: false,
4444
noDataMessage: { "ClinePass cost history is not available via the usage-limits API." }),
45+
presentation: ProviderUsagePresentation(
46+
primaryBindingQuotaLanes: [.secondary, .tertiary]),
4547
fetchPlan: self.fetchPlan(),
4648
cli: ProviderCLIConfig(
4749
name: "clinepass",

Sources/CodexBarCore/Providers/CommandCode/CommandCodeProviderDescriptor.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ public enum CommandCodeProviderDescriptor {
4444
supportsTokenCost: false,
4545
noDataMessage: { "Command Code cost summary is not yet supported." }),
4646
pace: .calendarMonthResetWindow,
47+
presentation: ProviderUsagePresentation(
48+
primaryBindingQuotaLanes: [.secondary]),
4749
fetchPlan: ProviderFetchPlan(
4850
sourceModes: [.auto, .web],
4951
pipeline: ProviderFetchPipeline(resolveStrategies: { _ in [CommandCodeWebFetchStrategy()] })),

Sources/CodexBarCore/Providers/Doubao/DoubaoProviderDescriptor.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ public enum DoubaoProviderDescriptor {
6363
supportsTokenCost: false,
6464
noDataMessage: { "Doubao cost summary is not available." }),
6565
pace: .calendarMonthResetWindow,
66+
presentation: ProviderUsagePresentation(
67+
primaryBindingQuotaLanes: [.secondary, .tertiary]),
6668
fetchPlan: ProviderFetchPlan(
6769
sourceModes: [.auto, .cli, .api],
6870
pipeline: ProviderFetchPipeline(resolveStrategies: self.resolveStrategies)),

0 commit comments

Comments
 (0)