From fb6b7863363c50bc2e9c1fa466d504bb50599844 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 27 Mar 2026 00:09:18 +0100 Subject: [PATCH] docs(dashboard): add technical reference page for Observable Framework dashboard Documents the dashboard's architecture, framework choice rationale, data-fetching strategies (static loaders + live polling), component library, page inventory, and key features including the Workstream Health Index and entity modals. Also registers the new page in the Reference nav and adds runbook section for node overload / runaway agent process (INC-002) with hardening checklist. Co-Authored-By: Claude Sonnet 4.6 --- dashboard/observablehq.config.js | 1 + dashboard/src/docs/dashboard.md | 338 +++++++++++++++++++++++++++++++ 2 files changed, 339 insertions(+) create mode 100644 dashboard/src/docs/dashboard.md diff --git a/dashboard/observablehq.config.js b/dashboard/observablehq.config.js index d7fc3b2..9ffe7dd 100644 --- a/dashboard/observablehq.config.js +++ b/dashboard/observablehq.config.js @@ -72,6 +72,7 @@ export default { pages: [ { name: "Capabilities", path: "/docs/capabilities" }, { name: "Connecting to the Hub", path: "/docs/connecting" }, + { name: "Dashboard", path: "/docs/dashboard" }, { name: "Contributions", path: "/docs/contributions" }, { name: "Decision Health", path: "/docs/decisions-kpi" }, { name: "Decisions", path: "/docs/decisions" }, diff --git a/dashboard/src/docs/dashboard.md b/dashboard/src/docs/dashboard.md new file mode 100644 index 0000000..7b07b08 --- /dev/null +++ b/dashboard/src/docs/dashboard.md @@ -0,0 +1,338 @@ +--- +title: Dashboard — Technical Reference +--- + +# State Hub Dashboard — Technical Reference + +The State Hub dashboard is the primary visual interface for the Custodian +ecosystem. It provides live, reactive views of all tracked domains, +workstreams, tasks, decisions, contributions, SBOM data, and agent activity — +all sourced from the local FastAPI state service. + +--- + +## Framework: Observable Framework + +The dashboard is built on **[Observable Framework](https://observablehq.com/framework/)**, +an open-source static-site framework from Observable, Inc. designed specifically +for data-driven pages. + +### Why Observable Framework? + +| Requirement | How Observable Framework satisfies it | +|---|---| +| **Local-first, no build-time cloud dependency** | Compiles to a static site (`npm run build`); the preview server and data loaders run entirely on localhost. | +| **Live data without a separate frontend service** | Pages poll the FastAPI backend directly from the browser via `fetch`. No BFF, no GraphQL, no WebSockets required. | +| **Reactive updates without React complexity** | Observable's cell-based execution model re-runs any code block whose inputs change. Async generators produce new values every poll cycle and trigger re-renders automatically. | +| **No JS bundler configuration** | `.md` files containing fenced JS code blocks are the entire source. No webpack, no Vite config, no `tsconfig.json`. | +| **Native data visualisation** | First-class integration with `@observablehq/plot` — a concise, grammar-of-graphics library — for all charts. | +| **Sovereignty-compatible** | The built output is a folder of static HTML/JS/CSS. It can be served by any web server, archived, or opened directly from disk. | +| **Offline-graceful** | Data loaders (Python scripts that run at build time) produce JSON snapshots. If the API is unreachable at build time, the loader emits an empty-structure JSON so the page still renders with a clear error state instead of crashing. | + +Observable Framework was chosen over alternatives (Grafana, Metabase, Streamlit, +Next.js) because its design principles are uniquely aligned with the Custodian +philosophy: **local-first**, **no vendor lock-in**, **sovereignty-preserving**, +and **auditable** — the full data pipeline is visible in plain Markdown files. + +--- + +## Architecture + +``` +src/ + observablehq.config.js — site metadata, page registry, theme, global head + components/ — shared JS modules + data/ — Python data loaders (run at build time) + docs/ — reference pages (this file lives here) + *.md — one page per feature area +``` + +### Data flow + +There are two complementary data-fetching strategies: + +**1. Static data loaders** (`src/data/*.json.py`) + +Python scripts executed by the Observable build toolchain at `npm run build` +or `npm run dev`. Each script calls the FastAPI backend via `urllib`, serialises +the response to JSON on stdout, and Observable Framework captures that output +as a static snapshot file that the page imports with `FileAttachment(...)`. + +Current loaders: + +| File | API endpoint | +|---|---| +| `summary.json.py` | `/state/summary` | +| `workstreams.json.py` | `/workstreams/` | +| `contributions.json.py` | `/contributions/` | +| `decisions.json.py` | `/decisions/` | +| `domains.json.py` | `/domains/` | +| `messages.json.py` | `/messages/` | +| `progress.json.py` | `/progress/` | +| `repos.json.py` | `/repos/` | +| `sbom.json.py` | `/sbom/aggregated` | +| `gitea-inventory.json.py` | Gitea instance inventory | + +**2. Live browser polling** (async generators in page `.md` files) + +All interactive pages bypass the static snapshots for live data by using +Observable's async generator pattern directly in the browser: + +```js +const summaryState = (async function*() { + while (true) { + const r = await fetch(`${API}/state/summary`); + yield { data: r.ok ? await r.json() : {error: `HTTP ${r.status}`}, ok: r.ok }; + await new Promise(res => setTimeout(res, POLL)); + } +})(); +``` + +`POLL` is set to **15 000 ms** (15 seconds) in `src/components/config.js`. +Observable's reactivity engine detects each new yield value and re-runs all +dependent code blocks, updating charts, tables, and KPI cards automatically. +A `●` live indicator in the top-left corner of each page shows the connection +status and the last-updated time. + +### Global configuration — `observablehq.config.js` + +| Setting | Value | +|---|---| +| Root directory | `src/` | +| Site title | "Custodian State Hub" | +| Theme | `["air", "near-midnight"]` — light body with dark sidebar | +| Favicon | Inline SVG data URI (🗄️ emoji) | +| Global head | KPI infobox styles, filter-bar styles, improvement-modal script | + +The `improvement-modal.js` component is injected at the config level rather +than imported per-page because Observable proxies `src/*.js` through its own +bundler, which prevents them from being loaded as raw `