Three reliability bugs made data inconsistent:
1. Empty-overwrite: a failed or partial fetch returned an empty
UsageData that the worker/app saved unconditionally, wiping the last
good reading and blanking the widget. Added UsageData.mergedWith()
so a fetch that returns nothing usable keeps the previous snapshot,
and a partial fetch falls back per-metric. Never blank again.
2. No in-app auto-refresh: onResume only refreshed when the cache was
>5 min old and there was no live timer. Replaced with a foreground
lifecycle loop that refreshes on open and every 30s while visible,
always painting cached data first. Manual button keeps the spinner;
the loop is silent. App refresh now also pushes the widget update.
3. Spurious logout: a single transient 401/403 (e.g. a Cloudflare
challenge) called clearSession() immediately, logging the user out
and showing "Not signed in". Now clears only after 3 consecutive
auth failures; the counter resets on any successful read.
Battery-friendly: no foreground service. Background widget refresh
stays on the existing alarm + 15-min WorkManager, but with the merge
fix the widget always shows the last data it pulled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>