v2 Phase 4: decouple Session (local usage) from the rate-limit API

The Session screen's tokens/cost are computed locally from transcripts and need
no network, but they were gated behind a successful rate-limit API call — so an
expired OAuth token or API outage froze the whole watch.

Daemon: always compute and send local usage every cycle; rate-limit utilization
is now best-effort and merged on top when available, with ok=true only when it's
fresh (ok=false on expired token / API down). poll_api returns just the
rate-limit fields; connect_and_run owns the payload + ok flag.

Firmware: ui_update always refreshes the Session screen; the rate-limit bars and
the freshness clock update only when data->ok, so the usage view falls back to
its existing idle "Zzz" state instead of showing stale or zeroed percentages.

Verified: with the token expired (HTTP 401) the daemon still sends
tk/tc/tn/to + ok:false and the Session screen keeps updating.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wenil
2026-06-20 18:49:34 +03:00
co-authored by Claude Opus 4.8
parent 4c1b63e645
commit b188349262
2 changed files with 65 additions and 46 deletions
+23 -14
View File
@@ -785,16 +785,36 @@ void ui_init(void) {
void ui_update(const UsageData* data) {
if (!data->valid) return;
last_data_ms = lv_tick_get(); // a valid usage update just landed → dot goes green
char buf[48];
// Session screen — local token data, computed by the daemon from transcripts
// with no API/token needed. Always refresh it so it stays live even when the
// rate-limit data below is unavailable (data->ok == false).
if (sess_cost_lbl) {
lv_label_set_text_fmt(sess_cost_lbl, "$%d.%02d",
data->cost_cents_today / 100, data->cost_cents_today % 100);
format_tokens(data->tokens_today, buf, sizeof(buf));
lv_label_set_text_fmt(sess_tokens_lbl, "%s tokens", buf);
format_tokens(data->output_today, buf, sizeof(buf));
lv_label_set_text_fmt(sess_gen_lbl, "%s generated", buf);
lv_label_set_text_fmt(sess_msgs_lbl, "%d requests", data->messages_today);
}
// Rate-limit utilization (5h / 7d). The daemon sets ok=false when this data
// is unavailable (expired OAuth token, API down). Skip the bars and DON'T
// bump the freshness clock then, so the usage view falls back to its idle
// "Zzz" state instead of showing stale or zeroed percentages.
if (!data->ok) return;
last_data_ms = lv_tick_get(); // fresh limit data just landed → dot goes green
data_received = true;
int s_pct = (int)(data->session_pct + 0.5f);
lv_label_set_text_fmt(lbl_session_pct, "%d%%", s_pct);
lv_bar_set_value(bar_session, s_pct, LV_ANIM_ON);
lv_obj_set_style_bg_color(bar_session, pct_color(data->session_pct), LV_PART_INDICATOR);
char buf[48];
format_reset_time(data->session_reset_mins, buf, sizeof(buf));
lv_label_set_text(lbl_session_reset, buf);
@@ -805,17 +825,6 @@ void ui_update(const UsageData* data) {
format_reset_time(data->weekly_reset_mins, buf, sizeof(buf));
lv_label_set_text(lbl_weekly_reset, buf);
// Session screen — refreshed here so it's current whenever it's opened.
if (sess_cost_lbl) {
lv_label_set_text_fmt(sess_cost_lbl, "$%d.%02d",
data->cost_cents_today / 100, data->cost_cents_today % 100);
format_tokens(data->tokens_today, buf, sizeof(buf));
lv_label_set_text_fmt(sess_tokens_lbl, "%s tokens", buf);
format_tokens(data->output_today, buf, sizeof(buf));
lv_label_set_text_fmt(sess_gen_lbl, "%s generated", buf);
lv_label_set_text_fmt(sess_msgs_lbl, "%d requests", data->messages_today);
}
}
// Pick the usage-view sub-screen: pairing hint (BLE down), the idle "Zzz" screen