From 6702d5534508c42f03302be32341dd00bbcb08b2 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Sun, 3 May 2026 11:48:44 -0700 Subject: [PATCH] Fix menubar provider view showing $0.00 after idle and refresh race condition CLI timeout increased from 20s to 45s to handle cold file-cache latency on provider-specific queries. Loading overlay now appears when the all-provider payload confirms a provider has spend but its dedicated data hasn't loaded yet. Manual refresh (force: true) bypasses the in-flight guard so users can always re-fetch. Tab strip prefers the provider-specific payload cost when available so it stays in sync with the hero section. --- mac/Sources/CodeBurnMenubar/AppStore.swift | 6 +----- .../CodeBurnMenubar/Data/DataClient.swift | 2 +- .../CodeBurnMenubar/Views/AgentTabStrip.swift | 3 +++ .../Views/MenuBarContent.swift | 19 +++++++++++++++---- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/mac/Sources/CodeBurnMenubar/AppStore.swift b/mac/Sources/CodeBurnMenubar/AppStore.swift index 935a70d..4374f4c 100644 --- a/mac/Sources/CodeBurnMenubar/AppStore.swift +++ b/mac/Sources/CodeBurnMenubar/AppStore.swift @@ -99,14 +99,10 @@ final class AppStore { private var inFlightKeys: Set = [] - /// Refresh the currently selected (period, provider) combination. Guards against concurrent - /// fetches for the same key so a slow initial request can't overwrite a newer one that - /// finished first (which would show stale numbers the user has already moved past). - /// When `force` is false (background timer), skips the CLI call if the cache is still fresh. func refresh(includeOptimize: Bool, force: Bool = false) async { let key = currentKey if !force, cache[key]?.isFresh == true { return } - guard !inFlightKeys.contains(key) else { return } + if !force, inFlightKeys.contains(key) { return } inFlightKeys.insert(key) let showedLoading = cache[key] == nil if showedLoading { diff --git a/mac/Sources/CodeBurnMenubar/Data/DataClient.swift b/mac/Sources/CodeBurnMenubar/Data/DataClient.swift index a6884be..d7e388a 100644 --- a/mac/Sources/CodeBurnMenubar/Data/DataClient.swift +++ b/mac/Sources/CodeBurnMenubar/Data/DataClient.swift @@ -6,7 +6,7 @@ import Foundation /// Pipe file descriptors pinned forever. private let maxPayloadBytes = 20 * 1024 * 1024 private let maxStderrBytes = 256 * 1024 -private let spawnTimeoutSeconds: UInt64 = 20 +private let spawnTimeoutSeconds: UInt64 = 45 enum DataClientError: Error { case spawn(String) diff --git a/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift b/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift index b54e4e9..33f7b15 100644 --- a/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift +++ b/mac/Sources/CodeBurnMenubar/Views/AgentTabStrip.swift @@ -46,6 +46,9 @@ struct AgentTabStrip: View { private func cost(for filter: ProviderFilter) -> Double? { let data = periodAll if filter == .all { return data.current.cost } + if filter == store.selectedProvider, store.hasCachedData { + return store.payload.current.cost + } let providers = Dictionary( data.current.providers.map { ($0.key.lowercased(), $0.value) }, uniquingKeysWith: + diff --git a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift index 64440ad..37befc3 100644 --- a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift +++ b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift @@ -41,7 +41,7 @@ struct MenuBarContent: View { } } - if store.isLoading { + if store.isLoading || (providerHasCostInAllPayload && !store.hasCachedData) { BurnLoadingOverlay(periodLabel: store.selectedPeriod.rawValue) .transition(.opacity) } @@ -57,11 +57,22 @@ struct MenuBarContent: View { } } - /// True when a specific provider tab is selected and that provider has no spend in the - /// currently selected period. The .all tab is exempt -- it always shows aggregated data. private var isFilteredEmpty: Bool { guard store.selectedProvider != .all else { return false } - return store.payload.current.cost <= 0 && store.payload.current.calls == 0 + if store.payload.current.cost > 0 || store.payload.current.calls > 0 { return false } + if providerHasCostInAllPayload { return false } + return true + } + + private var providerHasCostInAllPayload: Bool { + guard let allPayload = store.periodAllPayload else { return false } + let providers = Dictionary( + allPayload.current.providers.map { ($0.key.lowercased(), $0.value) }, + uniquingKeysWith: + + ) + return store.selectedProvider.providerKeys.contains { key in + (providers[key] ?? 0) > 0 + } } /// Show the tab row whenever the CLI detected at least one AI coding tool installed