From c706cd2de26c6652e3ca386fb670278f263c7ba2 Mon Sep 17 00:00:00 2001 From: iamtoruk Date: Mon, 4 May 2026 23:11:42 -0700 Subject: [PATCH] Strip optimize from menubar, fix stuck loading spinner The menubar ran --optimize on every 30-second CLI invocation. As sessions accumulated throughout the day, optimize got heavier until it exceeded the 45-second timeout. When the fetch failed with no cached data, the loading overlay had no escape hatch and stayed forever. - Never pass includeOptimize from the menubar (background loop, forceRefresh, tab/period switches, manual refresh button) - On fetch failure with empty cache, retry without optimize as fallback so the spinner always clears - refreshQuietly also skips optimize --- mac/Sources/CodeBurnMenubar/AppStore.swift | 24 ++++++++++++++----- mac/Sources/CodeBurnMenubar/CodeBurnApp.swift | 4 ++-- .../Views/MenuBarContent.swift | 2 +- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/mac/Sources/CodeBurnMenubar/AppStore.swift b/mac/Sources/CodeBurnMenubar/AppStore.swift index 8dd40a1..27ef0a4 100644 --- a/mac/Sources/CodeBurnMenubar/AppStore.swift +++ b/mac/Sources/CodeBurnMenubar/AppStore.swift @@ -71,9 +71,9 @@ final class AppStore { switchTask?.cancel() switchTask = Task { if selectedProvider == .all { - await refresh(includeOptimize: true, force: true) + await refresh(includeOptimize: false, force: true) } else { - async let main: Void = refresh(includeOptimize: true, force: true) + async let main: Void = refresh(includeOptimize: false, force: true) async let all: Void = refreshQuietly(period: period) _ = await (main, all) } @@ -88,9 +88,9 @@ final class AppStore { switchTask?.cancel() switchTask = Task { if provider == .all { - await refresh(includeOptimize: true, force: true) + await refresh(includeOptimize: false, force: true) } else { - async let main: Void = refresh(includeOptimize: true, force: true) + async let main: Void = refresh(includeOptimize: false, force: true) async let all: Void = refreshQuietly(period: selectedPeriod) _ = await (main, all) } @@ -119,8 +119,20 @@ final class AppStore { lastError = nil } catch { if Task.isCancelled { return } - lastError = String(describing: error) NSLog("CodeBurn: fetch failed for \(key.period.rawValue)/\(key.provider.rawValue): \(error)") + if includeOptimize, cache[key] == nil { + do { + let fallback = try await DataClient.fetch(period: key.period, provider: key.provider, includeOptimize: false) + guard !Task.isCancelled else { return } + cache[key] = CachedPayload(payload: fallback, fetchedAt: Date()) + lastError = nil + return + } catch { + if Task.isCancelled { return } + NSLog("CodeBurn: fallback fetch also failed: \(error)") + } + } + lastError = String(describing: error) } let allKey = PayloadCacheKey(period: selectedPeriod, provider: .all) @@ -134,7 +146,7 @@ final class AppStore { /// Always uses the .all provider since the menubar badge shows total spend. func refreshQuietly(period: Period) async { do { - let fresh = try await DataClient.fetch(period: period, provider: .all, includeOptimize: true) + let fresh = try await DataClient.fetch(period: period, provider: .all, includeOptimize: false) cache[PayloadCacheKey(period: period, provider: .all)] = CachedPayload(payload: fresh, fetchedAt: Date()) } catch { NSLog("CodeBurn: quiet refresh failed for \(period.rawValue): \(error)") diff --git a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift index c9db46b..dbc2e66 100644 --- a/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift +++ b/mac/Sources/CodeBurnMenubar/CodeBurnApp.swift @@ -171,7 +171,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate { lastRefreshTime = now Task { - async let main: Void = store.refresh(includeOptimize: true, force: true) + async let main: Void = store.refresh(includeOptimize: false, force: true) async let today: Void = store.refreshQuietly(period: .today) _ = await (main, today) refreshStatusButton() @@ -207,7 +207,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate { if self.store.selectedPeriod != .today || self.store.selectedProvider != .all { await self.store.refreshQuietly(period: .today) } - await self.store.refresh(includeOptimize: true, force: true) + await self.store.refresh(includeOptimize: false, force: true) self.refreshStatusButton() try? await Task.sleep(nanoseconds: refreshIntervalNanos) } diff --git a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift index 892d0a1..59ffdbb 100644 --- a/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift +++ b/mac/Sources/CodeBurnMenubar/Views/MenuBarContent.swift @@ -407,7 +407,7 @@ struct FooterBar: View { .fixedSize() Button { - Task { await store.refresh(includeOptimize: true, force: true) } + Task { await store.refresh(includeOptimize: false, force: true) } } label: { Image(systemName: store.isLoading ? "arrow.triangle.2.circlepath" : "arrow.clockwise") .font(.system(size: 11, weight: .medium))