d2ef26296a
* fix(codex): show Business monthly credit on stacked cards Stacked account fetches skipped the spend-controls credits path, so Enterprise/Business cards showed Limits not available instead of used vs cap. * docs: credit #3112 changelog entry * fix(codex): keep cached monthly limits after spend-controls failures Transient enrichment errors were persisted as missing credits, so stacked Business cards fell back to “Limits not available.” Restore the unreleased changelog heading as well. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): clear published credits when the monthly cap is gone Successful spend-controls absence skipped publication and left the old cap on the selected account. Retarget gatekeeper fingerprints after that cluster split. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): keep monthly-only usage out of reset-credit rescue A transient spend-controls failure still produced a success with no windows, and OAuth reset-credit rescue converted that into noRateLimitsFound before the cached monthly cap could be preserved. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): treat unmappable monthly limits as enrichment failures A nested spend-controls limit that is present but not numeric is no longer treated as confirmed absence, so a cached Business cap is preserved. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): honor disabled caps and unmappable monthly usage Inactive enforcement still confirms absence even if limit JSON is malformed, and a non-numeric current_month_usage is treated as enrichment failure instead of zero used. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): let an absent monthly cap win over malformed usage Malformed current_month_usage only counts as enrichment failure when an active numeric limit actually needs that used amount. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): hide monthly credit when optional credits are off Override-derived monthly lanes now follow Show credits + extra usage, so stacked Business cards no longer keep a credit meter the user asked to hide. * fix(codex): include monthly credits in compact account planning Inactive compact rows now see per-account monthly caps so an exhausted Business limit cannot hide behind a window-less usage snapshot. * fix(codex): preserve only the monthly cap after enrichment failure When spend-controls fails and usage confirmed there is no generic credit balance, keep the cached monthly limit without republishing stale remaining/events. * fix(codex): fail monthly enrichment when account id or enforcement is unknown A gated spend-controls fetch without an account id, or a non-string enforcement_mode, is now inconclusive so a cached Business cap is not cleared or published as active. * fix(codex): hide menu-bar monthly credit when optional credits are off Menu-bar projections now omit the optional monthly cap when the credits toggle is off, and a mappable nonpositive limit confirms cap removal even if enforcement is unreadable. * fix(codex): reject non-finite monthly credit values NaN and Infinity usage or limit strings are treated as mapping failures so they cannot zero out cached usage or persist invalid credit snapshots. * fix(codex): keep monthly cap through standalone credits refresh When spend-controls enrichment fails, the follow-up credits refresh now merges the prior monthly limit instead of publishing a generic-only snapshot. * fix(codex): clear generic credits when monthly enrichment fails A standalone credits refresh that confirms the generic balance is gone now publishes nil instead of throwing, so stale credits are not restored while a monthly cap is still preserved when one exists. * fix(codex): retarget snapshot-store architecture suppressions The monthly-cap helper shifted the Codex identity anchors, so the provider-architecture allowlist now points at the new lines. * fix(codex): fetch monthly credits when plan type is unknown Spend-control accounts with a missing or unmapped plan type now query the monthly endpoint, so a cached Business cap is not treated as confirmed removal. * fix(codex): hydrate cached monthly credits and clear stale generic balance Offline startup left the menu-bar projection without the disk-cached cap, and enrichment failure kept a generic balance when the monthly cap was already absent. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): persist standalone credits onto account snapshots Usage refresh stored the selected account snapshot before the credits pass, so a later cap add or removal never reached disk and offline hydration replayed the stale value. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(codex): persist credits using workspace identity Same-email workspace accounts made the snapshot write a no-op; match the published cap with the scoped refresh identity so only the selected workspace row is stored. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: retrigger CI Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
86 lines
3.6 KiB
Swift
86 lines
3.6 KiB
Swift
import CodexBarCore
|
|
|
|
extension UsageMenuCardView.Model {
|
|
static func sessionEquivalentDetail(
|
|
input: Input,
|
|
weeklyWindow: RateWindow,
|
|
weeklyWindowID: String?) -> UsagePaceText.SessionEquivalentDetail?
|
|
{
|
|
guard let forecast = input.sessionEquivalentForecast,
|
|
forecast.applies(to: weeklyWindow, windowID: weeklyWindowID)
|
|
else {
|
|
return nil
|
|
}
|
|
return UsagePaceText.sessionEquivalentDetail(forecast: forecast)
|
|
}
|
|
|
|
static func codexRateMetrics(
|
|
input: Input,
|
|
projection: CodexConsumerProjection,
|
|
percentStyle: PercentStyle) -> [Metric]
|
|
{
|
|
projection.displayedRateLanes(
|
|
showOptionalCreditsAndExtraUsage: input.showOptionalCreditsAndExtraUsage).compactMap { lane in
|
|
guard let window = projection.rateWindow(for: lane) else { return nil }
|
|
|
|
let title = CodexConsumerProjection.rateTitle(
|
|
lane: lane,
|
|
windowMinutes: window.windowMinutes,
|
|
sessionLabel: input.metadata.sessionLabel,
|
|
weeklyLabel: input.metadata.weeklyLabel)
|
|
let id: String
|
|
let paceDetail: PaceDetail?
|
|
switch lane {
|
|
case .session:
|
|
id = "primary"
|
|
// UsagePaceText.sessionPace suppresses weekly/monthly durations centrally;
|
|
// unknown durations in the session lane keep their existing pace.
|
|
paceDetail = Self.sessionPaceDetail(
|
|
provider: input.provider,
|
|
window: window,
|
|
now: input.now,
|
|
showUsed: input.usageBarsShowUsed)
|
|
case .weekly:
|
|
id = "secondary"
|
|
paceDetail = Self.weeklyPaceDetail(
|
|
provider: input.provider,
|
|
window: window,
|
|
now: input.now,
|
|
pace: Self.standardWeeklyPace(input: input, window: window),
|
|
showUsed: input.usageBarsShowUsed)
|
|
case .monthly:
|
|
id = "monthly"
|
|
paceDetail = nil
|
|
}
|
|
let workdayMarkerPercents: [Double] = if lane == .weekly, input.workdayTickAppearance != .hidden {
|
|
workDayMarkerPercents(
|
|
workDays: input.workDaysPerWeek,
|
|
windowMinutes: window.windowMinutes)
|
|
} else {
|
|
[]
|
|
}
|
|
|
|
return Metric(
|
|
id: id,
|
|
title: title,
|
|
percent: Self.clamped(input.usageBarsShowUsed ? window.usedPercent : window.remainingPercent),
|
|
percentStyle: percentStyle,
|
|
resetText: Self.resetText(for: window, style: input.resetTimeDisplayStyle, now: input.now),
|
|
detailText: nil,
|
|
detailLeftText: paceDetail?.leftLabel,
|
|
detailRightText: paceDetail?.rightLabel,
|
|
pacePercent: paceDetail?.pacePercent,
|
|
detailIsPaceDerived: paceDetail?.isPaceDerived ?? false,
|
|
paceOnTop: paceDetail?.paceOnTop ?? true,
|
|
warningMarkerPercents: Self.warningMarkerPercents(
|
|
thresholds: lane.quotaWarningWindow.flatMap { input.quotaWarningThresholds[$0] },
|
|
showUsed: input.usageBarsShowUsed),
|
|
workdayMarkerPercents: workdayMarkerPercents,
|
|
workdayTickAppearance: input.workdayTickAppearance,
|
|
sessionEquivalentDetail: lane == .weekly
|
|
? Self.sessionEquivalentDetail(input: input, weeklyWindow: window, weeklyWindowID: nil)
|
|
: nil)
|
|
}
|
|
}
|
|
}
|