mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-05-17 03:56:45 +00:00
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.
This commit is contained in:
parent
8cf68e7a16
commit
6702d55345
4 changed files with 20 additions and 10 deletions
|
|
@ -99,14 +99,10 @@ final class AppStore {
|
|||
|
||||
private var inFlightKeys: Set<PayloadCacheKey> = []
|
||||
|
||||
/// 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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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: +
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue