--- 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 `