Skip to content
Closed
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
13 changes: 13 additions & 0 deletions Sources/CodexBar/UsageStore+WidgetSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@ extension UsageStore {
window: window)
}
}
if provider == .claude,
let spendLimit = MenuBarMetricWindowResolver.claudeSpendLimitWindow(snapshot: snapshot)
{
let period = snapshot.providerCost?.period?.trimmingCharacters(in: .whitespacesAndNewlines)
let title = period.flatMap { $0.isEmpty ? nil : $0 } ?? "Extra usage"
return [
WidgetSnapshot.WidgetUsageRowSnapshot(
id: "extraUsage",
title: title,
percentLeft: spendLimit.remainingPercent,
window: spendLimit),
]
}
if provider == .antigravity,
let rows = Self.antigravityQuotaSummaryWidgetRows(snapshot: snapshot),
!rows.isEmpty
Expand Down
10 changes: 10 additions & 0 deletions Tests/CodexBarTests/SpendDashboardClockRolloverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import Testing
struct SpendDashboardClockRolloverTests {
@Test
func `reporting window advances and rescans source inputs`() async throws {
let suiteName = "SpendDashboardClockRolloverTests-reporting-\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: suiteName))
defaults.removePersistentDomain(forName: suiteName)
defer { defaults.removePersistentDomain(forName: suiteName) }
let loadedAt = try #require(ISO8601DateFormatter().date(from: "2026-07-16T12:00:00Z"))
let afterRollover = try #require(ISO8601DateFormatter().date(from: "2026-07-22T12:00:00Z"))
let loadCount = LockIsolated(0)
Expand All @@ -15,6 +19,7 @@ struct SpendDashboardClockRolloverTests {
let initialInput = Self.input(day: "2026-07-15", cost: 4, updatedAt: loadedAt)
let rolloverInput = Self.input(day: "2026-07-22", cost: 6, updatedAt: afterRollover)
let controller = SpendDashboardController(
userDefaults: defaults,
requestBuilder: { mode in
SpendDashboardLoadRequest(
configuration: configuration,
Expand Down Expand Up @@ -51,6 +56,10 @@ struct SpendDashboardClockRolloverTests {

@Test
func `rollover replaces an in flight load instead of dropping the rescan`() async throws {
let suiteName = "SpendDashboardClockRolloverTests-in-flight-\(UUID().uuidString)"
let defaults = try #require(UserDefaults(suiteName: suiteName))
defaults.removePersistentDomain(forName: suiteName)
defer { defaults.removePersistentDomain(forName: suiteName) }
let loadedAt = try #require(ISO8601DateFormatter().date(from: "2026-07-16T12:00:00Z"))
let afterRollover = try #require(ISO8601DateFormatter().date(from: "2026-07-22T12:00:00Z"))
let clock = LockIsolated(loadedAt)
Expand All @@ -59,6 +68,7 @@ struct SpendDashboardClockRolloverTests {
let freshInput = Self.input(day: "2026-07-22", cost: 6, updatedAt: afterRollover)
let gate = SpendDashboardRolloverGate()
let controller = SpendDashboardController(
userDefaults: defaults,
requestBuilder: { mode in
SpendDashboardLoadRequest(
configuration: configuration,
Expand Down
44 changes: 20 additions & 24 deletions Tests/CodexBarTests/SpendDashboardControllerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,7 @@ struct SpendDashboardControllerTests {
let snapshot = Self.input(id: "claude", provider: .claude, cost: 3).snapshot
store._setTokenSnapshotForTesting(snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = makeSpendDashboardController(settings: settings, store: store)

let baselineConfiguration = SpendDashboardSource.configuration(settings: settings, store: store)
controller.update(configuration: baselineConfiguration)
Expand Down Expand Up @@ -408,9 +406,7 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 3).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = makeSpendDashboardController(settings: settings, store: store)

let firstConfiguration = SpendDashboardSource.configuration(settings: settings, store: store)
controller.update(configuration: firstConfiguration)
Expand All @@ -431,9 +427,7 @@ struct SpendDashboardControllerTests {
#expect(controller.failedSourceCount == 1)
#expect(store.tokenSnapshot(for: .claude)?.last30DaysCostUSD == 3)

let reopenedController = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let reopenedController = makeSpendDashboardController(settings: settings, store: store)
reopenedController.update(configuration: replacementConfiguration)
await Self.waitUntil { !reopenedController.isRefreshing }
#expect(reopenedController.model.groups.isEmpty)
Expand Down Expand Up @@ -487,9 +481,7 @@ struct SpendDashboardControllerTests {

store._setTokenSnapshotForTesting(Self.input(provider: .mistral, cost: 3).snapshot, provider: .mistral)
store._test_providerRefreshOverride = { _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = makeSpendDashboardController(settings: settings, store: store)
controller.update(configuration: selectedBackupConfiguration)
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 3)
Expand Down Expand Up @@ -527,9 +519,7 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 4).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = makeSpendDashboardController(settings: settings, store: store)
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 4)
Expand Down Expand Up @@ -557,9 +547,7 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 5).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = makeSpendDashboardController(settings: settings, store: store)
let firstConfiguration = SpendDashboardSource.configuration(settings: settings, store: store)
controller.update(configuration: firstConfiguration)
await Self.waitUntil { !controller.isRefreshing }
Expand Down Expand Up @@ -626,9 +614,7 @@ struct SpendDashboardControllerTests {
environmentBase: [:])
store._setTokenSnapshotForTesting(Self.input(provider: .claude, cost: 5).snapshot, provider: .claude)
store._test_tokenUsageRefreshOverride = { _, _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = makeSpendDashboardController(settings: settings, store: store)
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 5)
Expand All @@ -644,9 +630,7 @@ struct SpendDashboardControllerTests {
#expect(controller.model.groups.isEmpty)
#expect(controller.failedSourceCount == 1)

let reopenedController = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let reopenedController = makeSpendDashboardController(settings: settings, store: store)
reopenedController.update(configuration: reenabledConfiguration)
await Self.waitUntil { !reopenedController.isRefreshing }
#expect(reopenedController.model.groups.isEmpty)
Expand Down Expand Up @@ -876,6 +860,18 @@ struct SpendDashboardControllerTests {
}
}

@MainActor
private func makeSpendDashboardController(
settings: SettingsStore,
store: UsageStore) -> SpendDashboardController
{
SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
}

@MainActor
struct SpendDashboardRequestTimeTests {
@Test
Expand Down
24 changes: 15 additions & 9 deletions Tests/CodexBarTests/SpendDashboardTokenProvenanceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ struct SpendDashboardTokenProvenanceTests {
store.activateCachedTokenAccountSnapshot(provider: .mistral, accountID: account.id)
#expect(store.tokenSnapshotPublicationRevision(for: .mistral) == baselineRevision)
store._test_providerRefreshOverride = { _ in }
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 3)
Expand All @@ -148,9 +150,11 @@ struct SpendDashboardTokenProvenanceTests {
return loadCount == 1 ? Self.tokenSnapshot(cost: 4) : Self.emptyTokenSnapshot()
}
await store.refreshTokenUsageNow(for: .bedrock, force: true)
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
#expect(controller.model.groups.first?.totalCost == 4)
Expand All @@ -177,9 +181,11 @@ struct SpendDashboardTokenProvenanceTests {
}
await store.refreshTokenUsageNow(for: .bedrock, force: true)
let publicationRevision = store.tokenSnapshotPublicationRevision(for: .bedrock)
let controller = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let controller = SpendDashboardController(
userDefaults: settings.userDefaults,
requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})

controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
await Self.waitUntil { !controller.isRefreshing }
Expand Down
57 changes: 57 additions & 0 deletions Tests/CodexBarTests/UsageStoreWidgetSnapshotTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,63 @@ struct UsageStoreWidgetSnapshotTests {
#expect(entry.tokenUsage?.last30DaysTokens == 42000)
}

@Test
func `widget snapshot uses Claude enterprise spend limit instead of placeholder quota`() async throws {
let suite = "UsageStoreWidgetSnapshotTests-claude-enterprise-spend-limit"
let defaults = try #require(UserDefaults(suiteName: suite))
defaults.removePersistentDomain(forName: suite)

let settings = SettingsStore(
userDefaults: defaults,
configStore: testConfigStore(suiteName: suite),
zaiTokenStore: NoopZaiTokenStore(),
syntheticTokenStore: NoopSyntheticTokenStore())
settings.statusChecksEnabled = false

let store = UsageStore(
fetcher: UsageFetcher(environment: [:]),
browserDetection: BrowserDetection(cacheTTL: 0),
settings: settings)
let updatedAt = Date(timeIntervalSince1970: 1_800_000_000)
store._setSnapshotForTesting(
UsageSnapshot(
primary: RateWindow(
usedPercent: 0,
windowMinutes: 300,
resetsAt: nil,
resetDescription: nil,
isSyntheticPlaceholder: true),
secondary: nil,
providerCost: ProviderCostSnapshot(
used: 25545.63,
limit: 30000,
currencyCode: "USD",
period: "Monthly cap",
updatedAt: updatedAt),
updatedAt: updatedAt,
identity: ProviderIdentitySnapshot(
providerID: .claude,
accountEmail: nil,
accountOrganization: nil,
loginMethod: nil)),
provider: .claude)

var widgetSnapshots: [WidgetSnapshot] = []
store._test_widgetSnapshotSaveOverride = { widgetSnapshots.append($0) }
defer { store._test_widgetSnapshotSaveOverride = nil }

store.persistWidgetSnapshot(reason: "claude-enterprise-spend-limit-test")
await store.widgetSnapshotPersistTask?.value

let entry = try #require(widgetSnapshots.last?.entries.first { $0.provider == .claude })
let row = try #require(entry.usageRows?.first)
#expect(entry.usageRows?.count == 1)
#expect(row.id == "extraUsage")
#expect(row.title == "Monthly cap")
#expect(abs((row.percentLeft ?? 0) - 14.8479) < 0.0001)
#expect(row.window?.isSyntheticPlaceholder == false)
}

@Test(arguments: [true, false])
func `widget snapshot respects extra usage visibility for Devin`(_ showsExtraUsage: Bool) async throws {
let suite = "UsageStoreWidgetSnapshotTests-devin-extra-usage-\(showsExtraUsage)"
Expand Down