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