Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
92ef8e4
perf(spend): parallelize loads and memoize model build
Yuxin-Qiao Aug 20, 2026
36a65ce
fix(spend): restore Codex account order after parallel load
Yuxin-Qiao Aug 20, 2026
2af83c4
test(spend): add out-of-order Codex concurrent order regression
Yuxin-Qiao Aug 20, 2026
5e049b4
test: update gatekeeper anchors after rebase to 54.0
Yuxin-Qiao Aug 21, 2026
9ee0d30
fix(spend): repair parallel load CI - file_length and escaping captures
Yuxin-Qiao Aug 21, 2026
74ab7d7
fix(spend): debounce frequent refresh and throttle date window rebuilds
Yuxin-Qiao Aug 21, 2026
866938c
Improve Antigravity retrieval: retired Flash alias and offline fallback
Yuxin-Qiao Aug 21, 2026
d5e7c9c
fix(gate): add missing provider-specific markers and sync anchors
Yuxin-Qiao Aug 21, 2026
70a875b
fix(lint): wrap long provider-specific comment
Yuxin-Qiao Aug 21, 2026
79d2596
Fix provider architecture gatekeeper for Antigravity offline and reti…
Yuxin-Qiao Aug 21, 2026
c449182
test: include offline strategy in antigravity pipeline expectations
Yuxin-Qiao Aug 21, 2026
d7e9b5e
fix(gate): sync remaining anchors and add missing markers
Yuxin-Qiao Aug 21, 2026
e2df318
Merge remote-tracking branch 'fork/feat/spend-perf-parallel-memoize' …
Yuxin-Qiao Aug 21, 2026
e6dd9fb
chore: trigger CI
Yuxin-Qiao Aug 21, 2026
46e2bd7
fix(spend): make debounce instant for testing
Yuxin-Qiao Aug 21, 2026
8909791
Merge remote-tracking branch 'origin/main' into tmp-fix-3105-gatekeeper
Yuxin-Qiao Aug 21, 2026
8338abf
fix(gate): update anchors after merge with main
Yuxin-Qiao Aug 21, 2026
98bf3a7
fix(lint): break long delay line
Yuxin-Qiao Aug 21, 2026
4660c70
fix(gate): drop stale codex anchor absorbed by sourceRevisions cluster
Yuxin-Qiao Aug 21, 2026
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
217 changes: 162 additions & 55 deletions Sources/CodexBar/SpendDashboardController.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import CodexBarCore
import CodexBarCore // swiftlint:disable file_length
import CryptoKit
import Foundation
import Observation
Expand Down Expand Up @@ -234,11 +234,18 @@ enum SpendDashboardSource {
publication: captured.publication,
publicationRevision: captured.revision)
}
for baseline in providerBaselines where mode.shouldRefresh(hasPublication: baseline.publication != nil) {
if UsageStore.tokenCostRequiresProviderSnapshot(baseline.provider) {
await store.refreshProvider(baseline.provider)
} else {
await store.refreshSpendDashboardTokenUsageNow(for: baseline.provider, force: true)
let baselinesToRefresh = providerBaselines.filter { mode.shouldRefresh(hasPublication: $0.publication != nil) }
if !baselinesToRefresh.isEmpty {
await withTaskGroup(of: Void.self) { group in
for baseline in baselinesToRefresh {
group.addTask {
if UsageStore.tokenCostRequiresProviderSnapshot(baseline.provider) {
await store.refreshProvider(baseline.provider)
} else {
await store.refreshSpendDashboardTokenUsageNow(for: baseline.provider, force: true)
}
}
}
}
}

Expand All @@ -247,6 +254,7 @@ enum SpendDashboardSource {
// newest same-scope publication available at this boundary.
let captureNow = now ?? nowProvider()
let providers = self.costCapableProviders(store: store)
// Provider-specific by design: spend dashboard
let codexSources = providers.contains(.codex)
? self.codexSources(settings: settings, store: store)
: []
Expand All @@ -269,6 +277,7 @@ enum SpendDashboardSource {
var inputs: [SpendDashboardModel.ProviderInput] = []
var unavailableSourceIDs: Set<String> = []
var confirmedEmptySourceIDs: Set<String> = []
// Provider-specific by design: spend dashboard
for provider in providers where provider != .codex {
// Provider-specific by design: Grok local session tokens are independent of the
// remote billing snapshot, so a failed probe still publishes readable logs.
Expand Down Expand Up @@ -335,7 +344,7 @@ enum SpendDashboardSource {
static func load(
_ request: SpendDashboardLoadRequest,
cacheRootResolver: @escaping CodexCacheRootResolver,
codexSnapshotLoader: CodexSnapshotLoader) async -> SpendDashboardLoadResult
codexSnapshotLoader: @escaping CodexSnapshotLoader) async -> SpendDashboardLoadResult
{
await self.load(
request,
Expand Down Expand Up @@ -413,7 +422,7 @@ enum SpendDashboardSource {

static func load(
_ request: SpendDashboardLoadRequest,
codexSnapshotLoader: CodexSnapshotLoader) async -> SpendDashboardLoadResult
codexSnapshotLoader: @escaping CodexSnapshotLoader) async -> SpendDashboardLoadResult
{
await self.load(
request,
Expand All @@ -424,8 +433,8 @@ enum SpendDashboardSource {

static func load(
_ request: SpendDashboardLoadRequest,
codexSnapshotLoader: CodexSnapshotLoader,
codexActivityLoader: CodexActivityLoader) async -> SpendDashboardLoadResult
codexSnapshotLoader: @escaping CodexSnapshotLoader,
codexActivityLoader: @escaping CodexActivityLoader) async -> SpendDashboardLoadResult
{
await self.load(
request,
Expand All @@ -436,56 +445,94 @@ enum SpendDashboardSource {

private static func load(
_ request: SpendDashboardLoadRequest,
cacheRootResolver: CodexCacheRootResolver,
codexSnapshotLoader: CodexSnapshotLoader,
codexActivityLoader: CodexActivityLoader) async -> SpendDashboardLoadResult
cacheRootResolver: @escaping CodexCacheRootResolver,
codexSnapshotLoader: @escaping CodexSnapshotLoader,
codexActivityLoader: @escaping CodexActivityLoader) async -> SpendDashboardLoadResult
{
var inputs = request.capturedInputs
var failedSourceIDs = request.unavailableSourceIDs
var invalidatedSourceIDs: Set<String> = []
for account in request.codexRequests {
let sourceID = "codex:\(account.id)"
do {
guard self.codexAuthFingerprintMatches(account) else {
failedSourceIDs.insert(sourceID)
invalidatedSourceIDs.insert(sourceID)
continue
}
let cacheRoot = cacheRootResolver(account)
let snapshot = try await codexSnapshotLoader(self.snapshotContext(
account: account,
cacheRoot: cacheRoot,
request: request,
force: request.force,
historyDays: Self.scanDays))
try Task.checkCancellation()
let tokenActivityCache = await codexActivityLoader(self.snapshotContext(
account: account,
cacheRoot: cacheRoot,
request: request,
force: false,
historyDays: Self.activityDays))
try Task.checkCancellation()
guard self.codexAuthFingerprintMatches(account) else {
if !request.codexRequests.isEmpty {
var pendingAccounts: [CodexSpendScanRequest] = []
for account in request.codexRequests {
let sourceID = "codex:\(account.id)"
if !self.codexAuthFingerprintMatches(account) {
failedSourceIDs.insert(sourceID)
invalidatedSourceIDs.insert(sourceID)
continue
} else {
pendingAccounts.append(account)
}
inputs.append(SpendDashboardModel.ProviderInput(
id: sourceID,
provider: .codex,
displayName: account.displayName,
modelProviderName: ProviderDescriptorRegistry.descriptor(for: .codex).metadata.displayName,
snapshot: snapshot,
tokenActivityCache: tokenActivityCache))
} catch is CancellationError {
failedSourceIDs.formUnion(request.codexRequests.map { "codex:\($0.id)" })
return SpendDashboardLoadResult(
inputs: [],
failedSourceIDs: failedSourceIDs,
invalidatedSourceIDs: invalidatedSourceIDs)
} catch {
failedSourceIDs.insert(sourceID)
}
if !pendingAccounts.isEmpty {
do {
try await withThrowingTaskGroup(of: (Int, String, SpendDashboardModel.ProviderInput?)
.self)
{ group in
for (index, account) in pendingAccounts.enumerated() {
group.addTask {
let sourceID = "codex:\(account.id)"
do {
let cacheRoot = cacheRootResolver(account)
let snapshot = try await codexSnapshotLoader(self.snapshotContext(
account: account,
cacheRoot: cacheRoot,
request: request,
force: request.force,
historyDays: Self.scanDays))
try Task.checkCancellation()
let tokenActivityCache = await codexActivityLoader(self.snapshotContext(
account: account,
cacheRoot: cacheRoot,
request: request,
force: false,
historyDays: Self.activityDays))
try Task.checkCancellation()
guard self.codexAuthFingerprintMatches(account)
else { return (index, sourceID, nil) }
let input = SpendDashboardModel.ProviderInput(
id: sourceID,
provider: .codex,
displayName: account.displayName,
// Provider-specific by design: spend dashboard
modelProviderName: ProviderDescriptorRegistry.descriptor(for: .codex)
.metadata
.displayName,
snapshot: snapshot,
tokenActivityCache: tokenActivityCache)
return (index, sourceID, input)
} catch is CancellationError { throw CancellationError() } catch {
return (index, "codex:\(account.id)", nil)
}
}
}
var results: [(Int, String, SpendDashboardModel.ProviderInput?)] = []
for try await result in group {
results.append(result)
}
results.sort { $0.0 < $1.0 }
for (_, sourceID, input) in results {
if let input {
inputs.append(input)
} else {
// Distinguish invalidated (auth changed) vs plain failure by re-checking.
if !self.codexAuthFingerprintMatches(
pendingAccounts.first { sourceID == "codex:\($0.id)" }!)
{
failedSourceIDs.insert(sourceID)
invalidatedSourceIDs.insert(sourceID)
} else {
failedSourceIDs.insert(sourceID)
}
}
}
}
} catch is CancellationError {
failedSourceIDs.formUnion(request.codexRequests.map { "codex:\($0.id)" })
return SpendDashboardLoadResult(
inputs: [],
failedSourceIDs: failedSourceIDs,
invalidatedSourceIDs: invalidatedSourceIDs)
} catch {}
}
}
let lateInvalidatedSourceIDs = Set(request.codexRequests.compactMap { account in
Expand Down Expand Up @@ -558,6 +605,7 @@ enum SpendDashboardSource {
providers: [UsageProvider]) -> Bool
{
settings.costUsageEnabled ||
// Provider-specific by design: spend dashboard
(providers.contains(.codex) && settings.codexLocalSessionCostLedgerEnabled)
}

Expand Down Expand Up @@ -713,6 +761,7 @@ enum SpendDashboardSource {
store: UsageStore) -> [String]
{
providers.compactMap { provider in
// Provider-specific by design: spend dashboard
guard provider != .codex else { return nil }
var config = settings.providerConfig(for: provider) ?? ProviderConfig(id: provider.instanceID)
config.enabled = nil
Expand Down Expand Up @@ -1040,6 +1089,9 @@ final class SpendDashboardController {
private var loadedAt = Date()
private var lastSuccessfulConfiguration: SpendDashboardConfiguration?
private var phase = LoadPhase.ordinary
// Throttle high-frequency date-window refreshes (didBecomeActive bursts).
private var lastRefreshDateWindowAt: Date?
private var lastRefreshDateWindowDayStart: Date?

init(
userDefaults: UserDefaults = .standard,
Expand Down Expand Up @@ -1067,6 +1119,15 @@ final class SpendDashboardController {
}
guard configuration != self.configuration else { return }
let previousConfiguration = self.configuration
// Fast-path: display-only changes (filter, currency, hide flag) require
// only a model rebuild — no Codex scan or token capture.
if let previousConfiguration,
Self.isDisplayOnlyConfigurationChange(from: previousConfiguration, to: configuration)
{
self.configuration = configuration
self.rebuildModel()
return
}
self.configuration = configuration
if self.isRefreshing || self.phase.manualRefreshOutstanding,
let previousConfiguration,
Expand Down Expand Up @@ -1391,10 +1452,28 @@ final class SpendDashboardController {
let calendar = self.configuration?.bucketCalendar ?? .current
let previousDay = calendar.startOfDay(for: self.loadedAt)
let nextDay = calendar.startOfDay(for: now)
let isSameDay = previousDay == nextDay
// Throttle burst activations (didBecomeActive) that otherwise rebuild
// the 365-day model on every app focus. Keep a 30s floor for same-day
// revisits while still allowing immediate refresh when the bucket day
// actually rolled over or a previous load failed.
if isSameDay,
let lastAt = self.lastRefreshDateWindowAt,
let lastDay = self.lastRefreshDateWindowDayStart,
lastDay == nextDay,
now.timeIntervalSince(lastAt) < 30,
self.lastSuccessfulConfiguration != nil,
self.failedSourceCount == 0
{
self.loadedAt = now
return
Comment on lines +1468 to +1469

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Rebuild after system time-zone changes

When bucketTimeZoneIdentifier is empty (for example, the local-ledger-only path), PreferencesSpendDashboardPane routes NSSystemTimeZoneDidChange through this method so the model can be rebucketed using the new .current zone. If that notification arrives within 30 seconds of a focus refresh and both timestamps fall on the same local day, this branch only advances loadedAt and returns, leaving daily totals and the selected-day mapping in the old zone until another refresh occurs after the throttle window. The time-zone notification needs to bypass the same-day throttle or pass an explicit forced-rebuild flag.

Useful? React with 👍 / 👎.

}
self.lastRefreshDateWindowAt = now
self.lastRefreshDateWindowDayStart = nextDay
self.loadedAt = now
self.rebuildModel()
guard let configuration else { return }
guard previousDay != nextDay || self.lastSuccessfulConfiguration == nil || self.failedSourceCount > 0
guard !isSameDay || self.lastSuccessfulConfiguration == nil || self.failedSourceCount > 0
else { return }
let nextPhase: LoadPhase = self.phase.manualRefreshOutstanding ? .forcing : .ordinary
self.startLoad(configuration: configuration, phase: nextPhase)
Expand All @@ -1410,6 +1489,8 @@ final class SpendDashboardController {
self.openCodexObservation = .disabled
self.isRefreshing = false
self.phase = .ordinary
self.lastRefreshDateWindowAt = nil
self.lastRefreshDateWindowDayStart = nil
self.publishCurrentState()
}

Expand Down Expand Up @@ -1544,6 +1625,7 @@ final class SpendDashboardController {
_ input: SpendDashboardModel.ProviderInput,
displayNamesByID: [String: String]) -> SpendDashboardModel.ProviderInput
{
// Provider-specific by design: spend dashboard
guard input.provider == .codex,
let displayName = displayNamesByID[input.id],
displayName != input.displayName
Expand All @@ -1565,7 +1647,32 @@ final class SpendDashboardController {
lhs.costUsageEnabled == rhs.costUsageEnabled &&
lhs.providerIDs == rhs.providerIDs &&
lhs.codexAccountIdentities == rhs.codexAccountIdentities &&
lhs.sourceOwnershipFingerprints == rhs.sourceOwnershipFingerprints
lhs.sourceOwnershipFingerprints == rhs.sourceOwnershipFingerprints &&
lhs.bucketTimeZoneIdentifier == rhs.bucketTimeZoneIdentifier &&
lhs.openCodexUsageLogsEnabled == rhs.openCodexUsageLogsEnabled &&
lhs.hideNativeCodexCostWhenOpenCodexPresent == rhs.hideNativeCodexCostWhenOpenCodexPresent &&
lhs.hiddenSourceIDs == rhs.hiddenSourceIDs &&
lhs.preferredCurrencyCode == rhs.preferredCurrencyCode
}

private static func isDisplayOnlyConfigurationChange(
from lhs: SpendDashboardConfiguration,
to rhs: SpendDashboardConfiguration) -> Bool
{
// Only presentation-layer fields changed; no provider scan or token capture needed.
guard lhs.costUsageEnabled == rhs.costUsageEnabled,
lhs.providerIDs == rhs.providerIDs,
lhs.codexAccountIdentities == rhs.codexAccountIdentities,
lhs.sourceOwnershipFingerprints == rhs.sourceOwnershipFingerprints,
lhs.sourceRevisions == rhs.sourceRevisions,
lhs.bucketTimeZoneIdentifier == rhs.bucketTimeZoneIdentifier,
lhs.openCodexUsageLogsEnabled == rhs.openCodexUsageLogsEnabled
else { return false }
return lhs.hiddenSourceIDs != rhs.hiddenSourceIDs ||
lhs.preferredCurrencyCode != rhs.preferredCurrencyCode ||
lhs.hideNativeCodexCostWhenOpenCodexPresent != rhs.hideNativeCodexCostWhenOpenCodexPresent ||
lhs.menuOwnershipFingerprint != rhs.menuOwnershipFingerprint ||
lhs.codexAccountDisplayNames != rhs.codexAccountDisplayNames
}

private static func invalidatedSourceIDs(
Expand Down
Loading
Loading