From a0373c1eecea2b90720bb6b8e758627dd8a80a15 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 26 Feb 2026 16:18:09 +0100 Subject: [PATCH] dashboard: move live indicator to TOC sidebar on all pages; add live-data docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - All four pages (index, workstreams, decisions, progress) now inject the live indicator into #observablehq-toc via injectTocTop("live-indicator", el) Left-aligned (no text-align: right), position:relative + padding-right for the ? button affordance - decisions.md: splits the former combined "decisions-sidebar" widget into two separate injectTocTop calls — KPI box first (ends lower), live indicator second (ends at top); both now have their own stable ids - withDocHelp(_liveEl, "/docs/live-data") wires the ? button on every page - src/docs/live-data.md: new documentation page explaining poll interval (15s), indicator colour semantics, offline recovery, and which endpoints each page hits - Removes the .live-bar CSS class from all pages; replaces with .live-indicator Co-Authored-By: Claude Sonnet 4.6 --- state-hub/dashboard/src/decisions.md | 16 ++++-- state-hub/dashboard/src/docs/live-data.md | 62 +++++++++++++++++++++++ state-hub/dashboard/src/index.md | 13 +++-- state-hub/dashboard/src/progress.md | 13 +++-- state-hub/dashboard/src/workstreams.md | 13 +++-- 5 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 state-hub/dashboard/src/docs/live-data.md diff --git a/state-hub/dashboard/src/decisions.md b/state-hub/dashboard/src/decisions.md index 7b98581..4bb54a4 100644 --- a/state-hub/dashboard/src/decisions.md +++ b/state-hub/dashboard/src/decisions.md @@ -151,16 +151,23 @@ const _kpiBox = html`
withDocHelp(_kpiBox, "/docs/decisions-kpi"); -// ── Build sidebar widget: live indicator + KPI box ────────────────────────── +// ── Build live indicator ──────────────────────────────────────────────────── const _liveEl = html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` : html`Offline — run: make api`}
`; +withDocHelp(_liveEl, "/docs/live-data"); -const _tocInjected = injectTocTop("decisions-sidebar", html`
${_liveEl}${_kpiBox}
`); -if (!_tocInjected) display(html`
${_liveEl}${_kpiBox}
`); +// ── Inject into TOC sidebar: KPI first (prepend → bottom), live last (→ top) ─ +const _toc = document.querySelector("#observablehq-toc"); +if (_toc) { + injectTocTop("decisions-kpi-box", _kpiBox); + injectTocTop("live-indicator", _liveEl); +} else { + display(html`
${_liveEl}${_kpiBox}
`); +} ``` ## Resolution History @@ -357,8 +364,9 @@ if (escalated.length > 0) { .live-indicator { font-size: 0.8rem; color: gray; - text-align: right; margin-bottom: 0.75rem; + position: relative; + padding-right: 1.6rem; } /* ── KPI infobox ──────────────────────────────────────────────────────────── */ diff --git a/state-hub/dashboard/src/docs/live-data.md b/state-hub/dashboard/src/docs/live-data.md new file mode 100644 index 0000000..53edf69 --- /dev/null +++ b/state-hub/dashboard/src/docs/live-data.md @@ -0,0 +1,62 @@ +--- +title: Live Data — Reference +--- + +# Live Data — How the Dashboard Refreshes + +All dashboard pages poll the State Hub API automatically. No manual refresh is ever needed. + +--- + +## Poll interval + +Every page fetches fresh data from `http://127.0.0.1:8000` every **15 seconds** using an async generator loop. The previous data stays visible while the next request is in flight, so the UI never goes blank. + +--- + +## The live indicator + +The **●** dot in the top-right corner of each page shows the current connection state: + +| Indicator | Meaning | +|---|---| +| **● Live · updated HH:MM:SS** | Last poll succeeded — data is current as of that time | +| **● Offline — run: `make api`** | API is unreachable — the dot turns red | + +The timestamp updates on every successful poll. If you see a time that is more than ~30 seconds in the past, the poll is stalled (browser tab backgrounded or network issue) — reloading the page resets the loop. + +--- + +## Offline recovery + +If the API goes offline while you are viewing a page: + +1. The indicator turns red immediately on the next failed poll (within 15 s) +2. The last successfully loaded data stays visible +3. Once the API restarts, the indicator turns green on the next poll — **no page reload needed** + +To restart the API: + +```bash +cd ~/the-custodian/state-hub +make api # starts uvicorn on 127.0.0.1:8000 +# or, if postgres is not running: +make start # db + migrate + api +``` + +--- + +## Which data each page polls + +| Page | Endpoints | +|---|---| +| Overview | `/state/summary` | +| Workstreams | `/workstreams/`, `/topics/`, `/state/summary` | +| Decisions | `/decisions/?limit=500`, `/topics/` | +| Progress | `/progress/?limit=500` | + +All endpoints are read-only GET requests. The dashboard never writes to the API. + +--- + +*Poll interval: 15 s. Data is refreshed in the background — the page never reloads itself.* diff --git a/state-hub/dashboard/src/index.md b/state-hub/dashboard/src/index.md index f53b2cc..10c9fa9 100644 --- a/state-hub/dashboard/src/index.md +++ b/state-hub/dashboard/src/index.md @@ -68,12 +68,17 @@ const regsState = (async function*() { # Custodian State Hub ```js -display(html`
+import {injectTocTop} from "./components/toc-sidebar.js"; +import {withDocHelp} from "./components/doc-overlay.js"; + +const _liveEl = html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` - : `Offline — run: cd ~/the-custodian/state-hub && make api`} -
`); + : html`Offline — run: cd ~/the-custodian/state-hub && make api`} +
`; +withDocHelp(_liveEl, "/docs/live-data"); +injectTocTop("live-indicator", _liveEl); ``` ```js @@ -395,7 +400,7 @@ display(Inputs.table((summary.recent_progress ?? []).map(e => ({ ``` diff --git a/state-hub/dashboard/src/workstreams.md b/state-hub/dashboard/src/workstreams.md index 5939c3b..7031b9a 100644 --- a/state-hub/dashboard/src/workstreams.md +++ b/state-hub/dashboard/src/workstreams.md @@ -47,12 +47,17 @@ const _ts = wsState.ts; # Workstreams ```js -display(html`
+import {injectTocTop} from "./components/toc-sidebar.js"; +import {withDocHelp} from "./components/doc-overlay.js"; + +const _liveEl = html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` - : `Offline — run: make api`} -
`); + : html`Offline — run: make api`} +
`; +withDocHelp(_liveEl, "/docs/live-data"); +injectTocTop("live-indicator", _liveEl); ``` ```js @@ -145,7 +150,7 @@ if (wsWithDeps.length === 0) { ```