fix: real-time refresh for menubar and TUI dashboard

Menubar: reduce cache TTL from 300s to 30s, background refresh from
60s to 15s, always fetch fresh data on tab switch instead of serving
stale cache. TUI: default auto-refresh to 30s (--refresh 0 to disable).

Closes #107
This commit is contained in:
iamtoruk 2026-04-19 08:55:48 -07:00 committed by AgentSeal
parent bd43b15342
commit fc576f44ba
3 changed files with 7 additions and 9 deletions

View file

@ -1,7 +1,7 @@
import Foundation
import Observation
private let cacheTTLSeconds: TimeInterval = 300
private let cacheTTLSeconds: TimeInterval = 30
struct CachedPayload {
let payload: MenubarPayload
@ -52,17 +52,15 @@ final class AppStore {
payload.optimize.findingCount
}
/// Switch to a period. Uses cached payload if fresh; otherwise fetches.
/// Switch to a period. Always fetches fresh data so the user never sees stale numbers.
func switchTo(period: Period) async {
selectedPeriod = period
if let cached = cache[currentKey], cached.isFresh { return }
await refresh(includeOptimize: true)
}
/// Switch to a provider filter. Uses cached payload if fresh; otherwise fetches.
/// Switch to a provider filter. Always fetches fresh data so the user never sees stale numbers.
func switchTo(provider: ProviderFilter) async {
selectedProvider = provider
if let cached = cache[currentKey], cached.isFresh { return }
await refresh(includeOptimize: true)
}

View file

@ -2,7 +2,7 @@ import SwiftUI
import AppKit
import Observation
private let refreshIntervalSeconds: UInt64 = 60
private let refreshIntervalSeconds: UInt64 = 15
private let nanosPerSecond: UInt64 = 1_000_000_000
private let refreshIntervalNanos: UInt64 = refreshIntervalSeconds * nanosPerSecond
/// Fixed so the popover's anchor point doesn't shift each time today's cost changes.

View file

@ -247,7 +247,7 @@ program
.option('--format <format>', 'Output format: tui, json', 'tui')
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
.option('--refresh <seconds>', 'Auto-refresh interval in seconds', parseInt)
.option('--refresh <seconds>', 'Auto-refresh interval in seconds (0 to disable)', parseInt, 30)
.action(async (opts) => {
let customRange: DateRange | null = null
try {
@ -502,7 +502,7 @@ program
.option('--format <format>', 'Output format: tui, json', 'tui')
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
.option('--refresh <seconds>', 'Auto-refresh interval in seconds', parseInt)
.option('--refresh <seconds>', 'Auto-refresh interval in seconds (0 to disable)', parseInt, 30)
.action(async (opts) => {
if (opts.format === 'json') {
await runJsonReport('today', opts.provider, opts.project, opts.exclude)
@ -518,7 +518,7 @@ program
.option('--format <format>', 'Output format: tui, json', 'tui')
.option('--project <name>', 'Show only projects matching name (repeatable)', collect, [])
.option('--exclude <name>', 'Exclude projects matching name (repeatable)', collect, [])
.option('--refresh <seconds>', 'Auto-refresh interval in seconds', parseInt)
.option('--refresh <seconds>', 'Auto-refresh interval in seconds (0 to disable)', parseInt, 30)
.action(async (opts) => {
if (opts.format === 'json') {
await runJsonReport('month', opts.provider, opts.project, opts.exclude)