fix(bar-app): periodic background refresh so the glance self-heals

The menu only re-polled on open or after a mutation, so if the server briefly
dropped rows (e.g. a backend restart mid-session) the dropdown could stay stuck
showing stale/missing rows until reopened. Add a 60s background poll that
reconnects if the client was lost and reloads non-force (respecting server-side
caches, so it never hammers providers). As a bonus, alerts now evaluate every
interval instead of only on menu-open, making them genuinely proactive.
This commit is contained in:
Tam Nhu Tran
2026-06-09 18:32:26 -04:00
parent 7d7f033134
commit 28fef67a45
@@ -37,6 +37,12 @@ final class BarViewModel: ObservableObject {
private let home: String
private var client: CCSBarClient?
private var debouncer = RefreshDebouncer(interval: 15)
/// Periodic background refresh so the glance self-heals from a transient
/// server gap (e.g. a momentary backend restart that dropped the native rows)
/// without the user having to reopen the menu. Cheap + safe: it reads the
/// local server's caches and never hammers providers (native quota is
/// TTL-gated server-side).
private var pollTask: Task<Void, Never>?
private let prefs: BarPreferences
private let notifier: NotificationDelivering
@@ -58,6 +64,23 @@ final class BarViewModel: ObservableObject {
self.glanceMode = prefs.load().glanceMode
self.spendChartStyle = SpendChartStyleStore.load()
reconnect()
startBackgroundPolling()
}
/// Periodically re-poll for the app's lifetime so a transient empty/missing-row
/// state recovers on its own within one interval each tick reconnects if the
/// client/discovery was lost, then loads (non-force, so it respects the
/// server-side caches). This is what prevents the menu from getting stuck after
/// the server momentarily restarts.
private func startBackgroundPolling() {
pollTask?.cancel()
pollTask = Task { [weak self] in
while !Task.isCancelled {
try? await Task.sleep(for: .seconds(60))
guard let self else { return }
await self.load(force: false)
}
}
}
/// Re-read prefs after the preferences sheet writes through, so the next poll