Skip to content

Commit 3c2aab2

Browse files
committed
fix(menubar): prefetch periods and align dashboard dates with local timezone
Loading overlay no longer flashes on every 15s poll. isLoading now only toggles when the cache is cold, and all periods prefetch once on launch so tab switching is instant. Heatmap tooltip, trend bars, forecast, and all-time stats were computing on UTC dates while the CLI reports on local dates, so the two disagreed at day boundaries. Switched every date formatter and calendar in these paths to .current so the menubar matches codeburn today output.
1 parent 988060c commit 3c2aab2

3 files changed

Lines changed: 35 additions & 21 deletions

File tree

mac/Sources/CodeBurnMenubar/AppStore.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,11 @@ final class AppStore {
7373
let key = currentKey
7474
guard !inFlightKeys.contains(key) else { return }
7575
inFlightKeys.insert(key)
76-
isLoading = true
76+
let showLoading = cache[key] == nil
77+
if showLoading { isLoading = true }
7778
defer {
7879
inFlightKeys.remove(key)
79-
isLoading = false
80+
if showLoading { isLoading = false }
8081
}
8182
do {
8283
let fresh = try await DataClient.fetch(period: key.period, provider: key.provider, includeOptimize: includeOptimize)
@@ -88,6 +89,15 @@ final class AppStore {
8889
}
8990
}
9091

92+
/// Prefetch all periods so tab switching is instant. Skips any period already cached.
93+
func prefetchAll() async {
94+
for period in Period.allCases {
95+
let key = PayloadCacheKey(period: period, provider: .all)
96+
if cache[key] != nil { continue }
97+
await refreshQuietly(period: period)
98+
}
99+
}
100+
91101
/// Background refresh for a period other than the visible one (e.g. keeping today fresh for the menubar badge).
92102
/// Does not toggle isLoading, so the popover's loading overlay is unaffected.
93103
/// Always uses the .all provider since the menubar badge shows total spend.

mac/Sources/CodeBurnMenubar/CodeBurnApp.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,21 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
7171

7272
private func startRefreshLoop() {
7373
refreshTask = Task { [weak self] in
74+
guard let s = self else { return }
75+
// First cycle: fetch current view, then prefetch all periods in background
76+
await s.store.refreshQuietly(period: .today)
77+
s.refreshStatusButton()
78+
await s.store.refresh(includeOptimize: true)
79+
s.refreshStatusButton()
80+
await s.store.prefetchAll()
81+
7482
while !Task.isCancelled {
75-
guard let self else { return }
76-
// Always keep the (today, all) payload warm. The menubar title and the
77-
// agent tab strip both read from it, so it has to refresh every cycle
78-
// regardless of whether the user is currently viewing Today or a
79-
// different period / provider.
80-
await self.store.refreshQuietly(period: .today)
81-
self.refreshStatusButton()
82-
await self.store.refresh(includeOptimize: true)
83-
self.refreshStatusButton()
8483
try? await Task.sleep(nanoseconds: refreshIntervalNanos)
84+
guard let s = self else { return }
85+
await s.store.refreshQuietly(period: .today)
86+
s.refreshStatusButton()
87+
await s.store.refresh(includeOptimize: true)
88+
s.refreshStatusButton()
8589
}
8690
}
8791
}

mac/Sources/CodeBurnMenubar/Views/HeatmapSection.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ private struct BarTooltipCard: View {
344344
private func prettyDate(_ ymd: String) -> String {
345345
let parser = DateFormatter()
346346
parser.dateFormat = "yyyy-MM-dd"
347-
parser.timeZone = TimeZone(identifier: "UTC")
347+
parser.timeZone = .current
348348
guard let date = parser.date(from: ymd) else { return ymd }
349349
let display = DateFormatter()
350350
display.dateFormat = "EEE MMM d"
@@ -392,11 +392,11 @@ private struct TrendStats {
392392

393393
private func buildTrendBars(from days: [DailyHistoryEntry]) -> [TrendBar] {
394394
var calendar = Calendar(identifier: .gregorian)
395-
calendar.timeZone = TimeZone(identifier: "UTC")!
395+
calendar.timeZone = .current
396396
let formatter: DateFormatter = {
397397
let f = DateFormatter()
398398
f.dateFormat = "yyyy-MM-dd"
399-
f.timeZone = TimeZone(identifier: "UTC")
399+
f.timeZone = .current
400400
return f
401401
}()
402402
let entryByDate = Dictionary(uniqueKeysWithValues: days.map { ($0.date, $0) })
@@ -427,11 +427,11 @@ private func computeTrendStats(bars: [TrendBar], allDays: [DailyHistoryEntry]) -
427427
let peak = bars.filter { $0.cost > 0 }.max(by: { $0.cost < $1.cost })
428428

429429
var calendar = Calendar(identifier: .gregorian)
430-
calendar.timeZone = TimeZone(identifier: "UTC")!
430+
calendar.timeZone = .current
431431
let formatter: DateFormatter = {
432432
let f = DateFormatter()
433433
f.dateFormat = "yyyy-MM-dd"
434-
f.timeZone = TimeZone(identifier: "UTC")
434+
f.timeZone = .current
435435
return f
436436
}()
437437
let today = calendar.startOfDay(for: Date())
@@ -547,11 +547,11 @@ private struct ForecastStats {
547547

548548
private func computeForecast(days: [DailyHistoryEntry]) -> ForecastStats {
549549
var calendar = Calendar(identifier: .gregorian)
550-
calendar.timeZone = TimeZone(identifier: "UTC")!
550+
calendar.timeZone = .current
551551
let formatter: DateFormatter = {
552552
let f = DateFormatter()
553553
f.dateFormat = "yyyy-MM-dd"
554-
f.timeZone = TimeZone(identifier: "UTC")
554+
f.timeZone = .current
555555
return f
556556
}()
557557
let now = Date()
@@ -798,17 +798,17 @@ private func computeAllStats(payload: MenubarPayload) -> AllStats {
798798
let favoriteModel = payload.current.topModels.first?.name ?? ""
799799

800800
var calendar = Calendar(identifier: .gregorian)
801-
calendar.timeZone = TimeZone(identifier: "UTC")!
801+
calendar.timeZone = .current
802802
let formatter: DateFormatter = {
803803
let f = DateFormatter()
804804
f.dateFormat = "yyyy-MM-dd"
805-
f.timeZone = TimeZone(identifier: "UTC")
805+
f.timeZone = .current
806806
return f
807807
}()
808808
let displayFormatter: DateFormatter = {
809809
let f = DateFormatter()
810810
f.dateFormat = "MMM d"
811-
f.timeZone = TimeZone(identifier: "UTC")
811+
f.timeZone = .current
812812
return f
813813
}()
814814

0 commit comments

Comments
 (0)