diff --git a/Sources/CodexBar/UsageStore+WidgetSnapshot.swift b/Sources/CodexBar/UsageStore+WidgetSnapshot.swift index e0808f9ce..4ee3d9e27 100644 --- a/Sources/CodexBar/UsageStore+WidgetSnapshot.swift +++ b/Sources/CodexBar/UsageStore+WidgetSnapshot.swift @@ -122,7 +122,7 @@ extension UsageStore { return metadata?.sessionLabel ?? "Session" }() - let rows: [WidgetSnapshot.WidgetUsageRowSnapshot] = [ + var rows: [WidgetSnapshot.WidgetUsageRowSnapshot] = [ WidgetSnapshot.WidgetUsageRowSnapshot( id: "primary", title: primaryTitle, @@ -132,6 +132,12 @@ extension UsageStore { title: metadata?.weeklyLabel ?? "Weekly", percentLeft: snapshot.secondary?.remainingPercent), ] + if metadata?.supportsOpus == true { + rows.append(WidgetSnapshot.WidgetUsageRowSnapshot( + id: "tertiary", + title: metadata?.opusLabel ?? "Opus", + percentLeft: snapshot.tertiary?.remainingPercent)) + } return rows.filter { $0.percentLeft != nil } } } diff --git a/Sources/CodexBarWidget/CodexBarWidgetViews.swift b/Sources/CodexBarWidget/CodexBarWidgetViews.swift index 17699f9e3..b42aabd79 100644 --- a/Sources/CodexBarWidget/CodexBarWidgetViews.swift +++ b/Sources/CodexBarWidget/CodexBarWidgetViews.swift @@ -492,7 +492,7 @@ struct WidgetUsageRow: Identifiable, Equatable { } let metadata = ProviderDefaults.metadata[entry.provider] - return [ + var rows = [ WidgetUsageRow( id: "primary", title: metadata?.sessionLabel ?? "Session", @@ -501,7 +501,14 @@ struct WidgetUsageRow: Identifiable, Equatable { id: "secondary", title: metadata?.weeklyLabel ?? "Weekly", percentLeft: entry.secondary?.remainingPercent), - ].filter { $0.percentLeft != nil } + ] + if metadata?.supportsOpus == true { + rows.append(WidgetUsageRow( + id: "tertiary", + title: metadata?.opusLabel ?? "Opus", + percentLeft: entry.tertiary?.remainingPercent)) + } + return rows.filter { $0.percentLeft != nil } } } diff --git a/Tests/CodexBarTests/CodexBarWidgetProviderTests.swift b/Tests/CodexBarTests/CodexBarWidgetProviderTests.swift index a496dfc0d..8d1754bc2 100644 --- a/Tests/CodexBarTests/CodexBarWidgetProviderTests.swift +++ b/Tests/CodexBarTests/CodexBarWidgetProviderTests.swift @@ -129,6 +129,27 @@ struct CodexBarWidgetProviderTests { #expect(rows == [WidgetUsageRow(id: "weekly", title: "Weekly", percentLeft: 75)]) } + @Test + func `legacy widget usage rows include tertiary slot when supported`() { + let now = Date(timeIntervalSince1970: 1_700_000_000) + let entry = WidgetSnapshot.ProviderEntry( + provider: .antigravity, + updatedAt: now, + primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + tertiary: RateWindow(usedPercent: 30, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + creditsRemaining: nil, + codeReviewRemainingPercent: nil, + tokenUsage: nil, + dailyUsage: []) + + let rows = WidgetUsageRow.rows(for: entry) + + #expect(rows.map(\.id) == ["primary", "secondary", "tertiary"]) + #expect(rows.map(\.title) == ["Claude", "Gemini Pro", "Gemini Flash"]) + #expect(rows.compactMap(\.percentLeft) == [90, 80, 70]) + } + @Test func `widget configuration intents default to codex and credits`() { let providerIntent = ProviderSelectionIntent() diff --git a/Tests/CodexBarTests/UsageStoreWidgetSnapshotTests.swift b/Tests/CodexBarTests/UsageStoreWidgetSnapshotTests.swift new file mode 100644 index 000000000..dcc797800 --- /dev/null +++ b/Tests/CodexBarTests/UsageStoreWidgetSnapshotTests.swift @@ -0,0 +1,50 @@ +import CodexBarCore +import Foundation +import Testing +@testable import CodexBar + +@MainActor +struct UsageStoreWidgetSnapshotTests { + @Test + func `widget snapshot includes antigravity tertiary usage row`() async throws { + let suite = "UsageStoreWidgetSnapshotTests-antigravity-tertiary" + 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 snapshot = UsageSnapshot( + primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + tertiary: RateWindow(usedPercent: 30, windowMinutes: nil, resetsAt: nil, resetDescription: nil), + updatedAt: Date(), + identity: ProviderIdentitySnapshot( + providerID: .antigravity, + accountEmail: nil, + accountOrganization: nil, + loginMethod: "Pro")) + + store._setSnapshotForTesting(snapshot, provider: .antigravity) + + var widgetSnapshots: [WidgetSnapshot] = [] + store._test_widgetSnapshotSaveOverride = { widgetSnapshots.append($0) } + defer { store._test_widgetSnapshotSaveOverride = nil } + + store.persistWidgetSnapshot(reason: "antigravity-tertiary-test") + await store.widgetSnapshotPersistTask?.value + + let entry = try #require(widgetSnapshots.last?.entries.first { $0.provider == .antigravity }) + #expect(entry.usageRows?.map(\.id) == ["primary", "secondary", "tertiary"]) + #expect(entry.usageRows?.map(\.title) == ["Claude", "Gemini Pro", "Gemini Flash"]) + #expect(entry.usageRows?.compactMap(\.percentLeft) == [90, 80, 70]) + } +}