▍ humdrum codex / soft

TUI: send fresh/force on refresh; capture sports source id + live flag

cdf325854b27c6238fe7e2e92b91147ed8903966
humdrum <me@humdrum.me> · 2026-07-08 16:15

parent 845a465f

TUI: send fresh/force on refresh; capture sports source id + live flag

Bare GET /api/sources returned cached snapshots only, so pressing r in
the TUI re-read the same cache. fetch_sources now sends fresh=1 (auto
refresh) or force=1 (manual r), and refresh_data captures the sports
source id + a live-game flag on every refresh for later consumers.

1 files changed

tui/dashboard.py +24 −6
@@ -159,10 +159,14 @@
 
 # ── Fetching ──────────────────────────────────────────────────────────────────
 
-async def fetch_sources() -> list[dict]:
+async def fetch_sources(force: bool = False) -> list[dict]:
+    # Bare /api/sources returns cached snapshots only. fresh=1 refreshes stale
+    # sources first; force=1 refreshes every source. Without a param, pressing
+    # "r" would just re-read the same cache — the old refresh bug.
+    q = "force=1" if force else "fresh=1"
     try:
-        async with httpx.AsyncClient(timeout=15) as c:
-            r = await c.get(f"{BASE_URL}/api/sources")
+        async with httpx.AsyncClient(timeout=20) as c:
+            r = await c.get(f"{BASE_URL}/api/sources?{q}")
             r.raise_for_status()
             return r.json().get("sources", [])
     except Exception:
@@ -1051,6 +1055,8 @@
     _theme_idx: int = 0
     _page_id: str = "page-personal"
     _connected: bool = False
+    _sports_id: str | None = None
+    _sports_live: bool = False
 
     def __init__(self, **kwargs):
         super().__init__(**kwargs)
@@ -1121,8 +1127,8 @@         yield CommandBar(id="cmd-bar")
         yield Footer()
 
     @work(exclusive=True)
-    async def refresh_data(self) -> None:
-        sources = await fetch_sources()
+    async def refresh_data(self, force: bool = False) -> None:
+        sources = await fetch_sources(force)
         connected = bool(sources)
         no_data = Text("No data.", style="dim italic")
 
@@ -1174,6 +1180,18 @@         )
         panel("nowplaying").set_content(
             render_nowplaying(payload(sources, "cider"))
         )
+        sports_src = next(
+            (s for s in sources if (s.get("source") or {}).get("kind") == "sports"),
+            None,
+        )
+        if sports_src:
+            self._sports_id = (sports_src.get("source") or {}).get("id")
+            snap = sports_src.get("snapshot") or {}
+            games = (snap.get("payload") or {}).get("games", []) if snap.get("ok") else []
+            self._sports_live = any(g.get("state") == "in" for g in games)
+        else:
+            self._sports_id = None
+            self._sports_live = False
         self._connected = connected
         self._update_subtitle()
 
@@ -1404,7 +1422,7 @@
     # ── Actions ──────────────────────────────────────────────────────────────
 
     def action_refresh(self) -> None:
-        self.refresh_data()
+        self.refresh_data(force=True)
         if self._page_id == "page-system":
             self.refresh_system()