Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7321949
feat: add model-centric usage & spend view across providers
Yuxin-Qiao Jul 19, 2026
2089450
feat: elevate usage & spend dashboard — cost coverage, tool scanners,…
Yuxin-Qiao Jul 26, 2026
3d1b64f
feat: refine usage and spend attribution
Yuxin-Qiao Jul 26, 2026
2cf1d84
perf: bound local usage scans
Yuxin-Qiao Jul 26, 2026
a7164a7
refactor: register dashboard history sources
Yuxin-Qiao Jul 27, 2026
2be387e
fix: preserve merged subscription history coverage
Yuxin-Qiao Jul 27, 2026
5603bb3
refine model chart branding
Yuxin-Qiao Jul 27, 2026
702ea23
prewarm spend dashboard and dedupe quota alerts
Yuxin-Qiao Jul 27, 2026
833d746
improve spend model and tool readability
Yuxin-Qiao Jul 27, 2026
7459bb2
use official provider icon assets
Yuxin-Qiao Jul 27, 2026
3b1749a
improve spend chart insights and tool attribution
Yuxin-Qiao Jul 27, 2026
4281acc
feat: enhance spend dashboard UX — collapsible detail panels, model i…
Yuxin-Qiao Jul 28, 2026
344ee70
Merge remote-tracking branch 'origin/main' into pr-2322
Yuxin-Qiao Jul 28, 2026
23f84a9
test: update intentionallyUnchanged list in LocalizationLanguageCatal…
Yuxin-Qiao Jul 28, 2026
a587488
fix: restore persistedDepletion check to prevent suppressing fresh qu…
Yuxin-Qiao Jul 28, 2026
9902729
Fix session quota depletion and quota warning state cleanup when wind…
Yuxin-Qiao Jul 28, 2026
96e911d
Re-trigger CI after transient network glitch on musl static SDK download
Yuxin-Qiao Jul 28, 2026
01b2b9f
fix: scope rateWindow nil cleanup to target key in quota warning tran…
Yuxin-Qiao Jul 28, 2026
243d573
Merge remote-tracking branch 'origin/main' into pr-2322
Yuxin-Qiao Jul 29, 2026
b8beac4
Generalize usage and spend adapters
Yuxin-Qiao Jul 29, 2026
efef540
Merge latest main and preserve generalized spend adapters
Yuxin-Qiao Jul 29, 2026
82e8c86
fix: detect in-place same-size Codex session rewrites via sampling fi…
Yuxin-Qiao Jul 31, 2026
dcdd22f
Merge remote-tracking branch 'origin/main' into fix/pr-2322-mainstrea…
Yuxin-Qiao Jul 31, 2026
365c346
fix: correct Qwen/OpenCode usage accounting edge cases
Yuxin-Qiao Jul 31, 2026
efbba1e
Close remaining spend review threads
Yuxin-Qiao Jul 31, 2026
8e4629e
Split SpendDashboardModel aggregation helpers into their own file
Yuxin-Qiao Jul 31, 2026
15d3eaf
fix: address review batch - scanner integrity, partial pricing, chart…
Yuxin-Qiao Aug 1, 2026
c49af6d
fix(core): propagate scanner read failures and withhold partially pri…
Yuxin-Qiao Aug 1, 2026
d41f830
fix(core): uniform partial-pricing withholding, routing evidence, and…
Yuxin-Qiao Aug 1, 2026
a316207
fix(core): guard overflow and propagate scan failures across scanners…
Yuxin-Qiao Aug 1, 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
37 changes: 36 additions & 1 deletion Sources/CodexBar/CodexbarApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ struct CodexBarApp: App {
@State private var store: UsageStore
@State private var managedCodexAccountCoordinator: ManagedCodexAccountCoordinator
@State private var codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator
@State private var spendDashboardController: SpendDashboardController
private let preferencesSelection: PreferencesSelection
private let account: AccountInfo

Expand Down Expand Up @@ -78,6 +79,9 @@ struct CodexBarApp: App {
let browserDetection = BrowserDetection(cacheTTL: BrowserDetection.defaultCacheTTL)
let account = fetcher.loadAccountInfo()
let store = UsageStore(fetcher: fetcher, browserDetection: browserDetection, settings: settings)
let spendDashboardController = SpendDashboardController(requestBuilder: { mode in
await SpendDashboardSource.makeRequest(settings: settings, store: store, mode: mode)
})
let codexAccountPromotionCoordinator = CodexAccountPromotionCoordinator(
settingsStore: settings,
usageStore: store,
Expand All @@ -87,6 +91,7 @@ struct CodexBarApp: App {
_store = State(wrappedValue: store)
_managedCodexAccountCoordinator = State(wrappedValue: managedCodexAccountCoordinator)
_codexAccountPromotionCoordinator = State(wrappedValue: codexAccountPromotionCoordinator)
_spendDashboardController = State(wrappedValue: spendDashboardController)
self.account = account
CodexBarLog.setLogLevel(settings.debugLogLevel)
self.appDelegate.configure(.init(
Expand All @@ -95,7 +100,8 @@ struct CodexBarApp: App {
account: account,
selection: preferencesSelection,
managedCodexAccountCoordinator: managedCodexAccountCoordinator,
codexAccountPromotionCoordinator: codexAccountPromotionCoordinator))
codexAccountPromotionCoordinator: codexAccountPromotionCoordinator,
spendDashboardController: spendDashboardController))
}

@SceneBuilder
Expand All @@ -112,6 +118,7 @@ struct CodexBarApp: App {
PreferencesView(
settings: self.settings,
store: self.store,
spendDashboardController: self.spendDashboardController,
updater: self.appDelegate.updaterController,
selection: self.preferencesSelection,
managedCodexAccountCoordinator: self.managedCodexAccountCoordinator,
Expand Down Expand Up @@ -374,6 +381,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let selection: PreferencesSelection
let managedCodexAccountCoordinator: ManagedCodexAccountCoordinator
let codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator
let spendDashboardController: SpendDashboardController?
}

let updaterController: UpdaterProviding = makeUpdaterController()
Expand All @@ -390,6 +398,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private var preferencesSelection: PreferencesSelection?
private var managedCodexAccountCoordinator: ManagedCodexAccountCoordinator?
private var codexAccountPromotionCoordinator: CodexAccountPromotionCoordinator?
private var spendDashboardController: SpendDashboardController?
private var spendDashboardWarmupTask: Task<Void, Never>?
private var hasInstalledLimitResetObservers = false
#if DEBUG
private var debugMemoryPressureObserver: NSObjectProtocol?
Expand All @@ -405,6 +415,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
self.preferencesSelection = dependencies.selection
self.managedCodexAccountCoordinator = dependencies.managedCodexAccountCoordinator
self.codexAccountPromotionCoordinator = dependencies.codexAccountPromotionCoordinator
self.spendDashboardController = dependencies.spendDashboardController
}

func applicationWillFinishLaunching(_ notification: Notification) {
Expand All @@ -417,6 +428,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
self.installDebugMemoryPressureObserverIfNeeded()
#endif
self.ensureStatusController()
self.scheduleSpendDashboardWarmup()
Task { @MainActor [weak self] in
await Task.yield()
guard let settings = self?.settings else { return }
Expand Down Expand Up @@ -452,6 +464,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
}

func applicationWillTerminate(_ notification: Notification) {
self.spendDashboardWarmupTask?.cancel()
self.spendDashboardWarmupTask = nil
self.spendDashboardController?.stop()
self.memoryPressureMonitor.stop()
#if DEBUG
self.removeDebugMemoryPressureObserver()
Expand All @@ -462,6 +477,26 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
self.terminateActiveProcessesForAppShutdown()
}

private func scheduleSpendDashboardWarmup() {
guard self.spendDashboardWarmupTask == nil,
let controller = self.spendDashboardController,
let settings = self.settings,
let store = self.store
else {
return
}
self.spendDashboardWarmupTask = Task(priority: .utility) { @MainActor [weak self] in
defer { self?.spendDashboardWarmupTask = nil }
do {
try await Task.sleep(for: .seconds(5))
} catch {
return
}
guard !Task.isCancelled, settings.costUsageEnabled else { return }
controller.update(configuration: SpendDashboardSource.configuration(settings: settings, store: store))
}
}

func runProviderLoginFlow(_ provider: UsageProvider) async {
self.ensureStatusController()
guard let statusController else { return }
Expand Down
Loading