2026-03-27 00:09:18 +01:00
---
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,
2026-07-08 16:08:32 +02:00
workplans, tasks, decisions, contributions, SBOM data, and agent activity —
2026-03-27 00:09:18 +01:00
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` |
2026-07-08 16:08:32 +02:00
| `workplans.json.py` | `/workplans/` |
2026-03-27 00:09:18 +01:00
| `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 `<script>` tags in
`<head>` . The config reads the file at build time, strips ES module export
keywords, and injects the result as a plain inline `<script>` .
---
## Page Inventory
The dashboard has 30+ pages organised in four navigation groups:
### Top-level pages
| Page | Route | Purpose |
|---|---|---|
2026-07-08 16:08:32 +02:00
| Overview | `/` | Cross-domain summary — workplan chart, status KPIs, blocking decisions, recent activity |
2026-03-27 00:09:18 +01:00
| Capabilities | `/capability-requests` | Capability request routing and fulfilment status |
| Contributions | `/contributions` | Upstream contribution Kanban (bug reports, feature requests, upstream PRs) |
| Domains | `/domains` | Per-domain health overview and management |
| Goals | `/goals` | Domain goals and repo-scoped goals |
| Inbox | `/inbox` | Agent message inbox and inter-repo communication |
| Progress | `/progress` | Session progress event log |
| Services (TPSC) | `/tpsc` | Third-party services catalog with GDPR maturity status |
| Todo | `/todo` | Consolidated todo list across all repos |
| Tools & Apps | `/tools` | Registered tools and applications |
### Repositories section
| Page | Route | Purpose |
|---|---|---|
| Repositories | `/repos` | All registered repos with DoI compliance tier |
| Debt | `/techdept` | Technical debt registry |
| Repo Sync | `/repo-sync` | Consistency checker results and sync status |
| SBOM | `/sbom` | Software bill of materials — packages, licences, copyleft risk |
2026-07-08 16:08:32 +02:00
### Workplans section
2026-03-27 00:09:18 +01:00
| Page | Route | Purpose |
|---|---|---|
2026-06-04 08:25:31 +02:00
| Workplans | `/workstreams` | All workplans with Workplan Health Index; route name remains compatibility-backed |
2026-03-27 00:09:18 +01:00
| Decisions | `/decisions` | Decision log with resolve-in-place form |
| Dependencies | `/dependencies` | Dependency graph explorer |
| Extensions | `/extensions` | Extension point registry |
| Interventions | `/interventions` | Tasks flagged for human intervention |
| Tasks | `/tasks` | Task list with filters and status tracking |
| UI Feedback | `/ui-feedback` | UI improvement feedback and issue tracking |
### Reference section
22 reference pages covering every feature, data model, and integration in detail.
---
## Component Library
All shared components live in `src/components/` and are imported as ES modules:
### `config.js`
2026-06-04 08:25:31 +02:00
Exports shared runtime configuration used by every live-polling page:
- `API` — the FastAPI base URL. It defaults to `http://127.0.0.1:8000`
outside the browser, derives from the dashboard host in browser sessions, and
can be overridden with `?api_base=...` , `globalThis.STATE_HUB_API_BASE` , or
`localStorage.stateHubApiBase` .
2026-03-27 00:09:18 +01:00
- `POLL = 15_000` — polling interval in milliseconds
### `entity-modal.js`
A lightweight detail overlay for entities. Any table row or card can call
`openEntityModal(entity, type)` to open a full-detail panel without navigating
2026-07-08 16:08:32 +02:00
away from the page. Supports four entity types: `workplan` , `task` , `ep`
2026-03-27 00:09:18 +01:00
(extension point), and `td` (technical debt).
Also exports `buildEntityTable()` — a function that constructs a consistent,
clickable HTML table for any list of entities, with proportional column widths,
overflow ellipsis, and native tooltip-on-hover for truncated values.
### `toc-sidebar.js`
Provides `injectTocTop(id, element)` — injects a DOM element into the
Observable Framework table-of-contents sidebar above the page's first section
2026-07-08 16:08:32 +02:00
heading. Used on the Overview and Workplans pages to embed live KPI infoboxes
2026-03-27 00:09:18 +01:00
directly in the sidebar.
### `doc-overlay.js`
Provides `withDocHelp(element, docPath)` — attaches a small `?` icon to any
element that opens the linked reference page in a lightweight overlay panel
without leaving the current page.
### `help-tip.js`
A custom HTML element (`<help-tip>` ) that renders an inline abbreviated label
with an expandable tooltip containing a longer description and a link to the
2026-07-08 16:08:32 +02:00
relevant reference page. Used in the Workplan Health Index card to annotate
2026-03-27 00:09:18 +01:00
each metric abbreviation.
### `multiselect.js`
A multi-value dropdown filter input compatible with Observable's `Inputs.form()`
2026-07-08 16:08:32 +02:00
reactive pattern. Used on the Workplans and Tasks pages for domain and status
2026-03-27 00:09:18 +01:00
filtering.
### `improvement-modal.js`
A floating feedback button that opens a modal form for submitting UI improvement
suggestions. Injected globally via `observablehq.config.js` so it is available
on every page.
### `action-confirm.js`
A confirmation-dialog helper for destructive or irreversible actions triggered
from the dashboard.
---
## Key Features
### Live polling with connection status
Every interactive page runs one or more async generator loops that poll the
FastAPI backend every 15 seconds. A `●` indicator in the top-left corner
shows green when the API is reachable and red with a restart command when it
is not. This allows the dashboard to be used as a persistent, always-on monitor
without requiring a page refresh.
2026-07-08 16:08:32 +02:00
### Workplan Health Index (WHI)
2026-03-27 00:09:18 +01:00
2026-07-08 16:08:32 +02:00
The Workplans page computes a **Workplan Health Index** — a single
2026-03-27 00:09:18 +01:00
composite score (0– 100%) derived from five graph metrics:
| Metric | Abbrev. | Weight | Interpretation |
|---|---|---|---|
2026-07-08 16:08:32 +02:00
| Dependency Density | DD | 30% | Average deps per open workplan; high = tightly coupled |
| Blocked Ratio | BR | 25% | Share of workplans in a blocked state |
| Single-Point Risk | SPR | 15% | Share of workplans that others depend on but are not yet complete |
| Parallel Execution Potential | PEP | 20% | Share of workplans that could start/continue immediately |
2026-03-27 00:09:18 +01:00
| Cross-Domain Dependency Ratio | CDDR | 10% | Share of edges crossing domain boundaries |
A **Cycle Presence Indicator** (CPI) detected via DFS halves the total score
when a dependency cycle is found, since cyclic dependencies cause deadlock.
The index is computed per-domain as well as globally and displayed in the
TOC sidebar as a persistent KPI card.
2026-07-08 16:08:32 +02:00
### Multi-mode workplan chart
2026-03-27 00:09:18 +01:00
The Overview page renders a horizontal stacked bar chart using `@observablehq/plot`
2026-07-08 16:08:32 +02:00
showing task counts (done / progress / wait / todo) per workplan.
2026-03-27 00:09:18 +01:00
A `<select>` dropdown switches between:
2026-05-18 01:31:36 +02:00
- **Lifecycle modes**: proposed, ready, active, blocked, backlog, finished, archived
- **Health modes**: needs review, stalled
2026-03-27 00:09:18 +01:00
- **Time modes**: last 1h, 24h, 7d, 30d, today, this week, this month
2026-07-08 16:08:32 +02:00
Domains are sorted by most recent workplan activity (most active domain at
2026-03-27 00:09:18 +01:00
the top). Title labels and done/total counters are overlaid directly on the bars.
### Resolve-in-place for blocking decisions
Blocking decisions on the Overview page render with an expandable form
(`<details>` element). The human can enter a rationale and click "Record & close"
to call `POST /decisions/{id}/resolve` without leaving the page. The decision
list refreshes after a successful resolve; other decisions remain unchanged and
retain any in-progress text the user was typing.
### SBOM and licence-risk tracking
The Overview page shows three SBOM/contribution health KPI cards. The SBOM
page renders a horizontal bar chart of package counts by licence, with
highlighted cards for any detected copyleft licences (GPL, AGPL, LGPL, etc.)
in direct production dependencies.
### Dependency graph
2026-07-08 16:08:32 +02:00
The Dependencies page and the Workplans page both surface inter-workplan
dependency data. Each workplan card shows the workplans it depends on
(`↳ depends on` ) and the workplans it blocks (`⊳ blocks` ), derived from
2026-03-27 00:09:18 +01:00
the `WorkstreamDependency` table.
### Entity modals
2026-07-08 16:08:32 +02:00
Any table row on any list page (workplans, tasks, extension points, tech debt)
2026-03-27 00:09:18 +01:00
can be clicked to open a detail modal with full field data, dependency lists,
task progress, and timestamps — without a page navigation or a separate detail
route.
### Graceful offline state
All async generator polls wrap API calls in `try/catch` . When the API is
unreachable, pages display an error banner and a `make api` restart command
rather than crashing or showing stale cached data without warning. Static
data loaders emit an empty-structure JSON fallback so build-time failures do
not block the dashboard from loading.
---
## Visualisation library: `@observablehq/plot`
All charts use ** [@observablehq/plot ](https://observablehq.com/plot/ )** —
Observable's concise, composable grammar-of-graphics library. It is imported
on demand per page:
```js
import * as Plot from "npm:@observablehq/plot ";
```
Observable Framework resolves `npm:` specifiers at build time (no `npm install`
needed in the source directory). Typical mark types used across the dashboard:
| Mark | Used for |
|---|---|
| `Plot.barX` | Horizontal stacked task-count bars, SBOM licence distribution |
2026-07-08 16:08:32 +02:00
| `Plot.text` | Workplan title labels and done/total counters overlaid on bars |
2026-03-27 00:09:18 +01:00
| `Plot.ruleX([0])` | Zero-axis rule on all bar charts |
Charts are rendered as inline SVG and inherit Observable Framework's theme
CSS variables, so they adapt correctly to both light (`air` ) and dark
(`near-midnight` ) themes.
---
## Running the dashboard
```bash
2026-05-17 20:01:21 +02:00
cd ~/state-hub/dashboard
2026-03-27 00:09:18 +01:00
npm run dev # Preview server on :3000 with hot reload
npm run build # Static build into dist/
```
The API must be running (`make api` in `state-hub/` ) for the data loaders and
live polling to work. If the API is not running, the dashboard loads with empty
data and shows the offline error state on each page.
---
## Related
- [State Hub — Reference ](/docs/state-hub ) — overall architecture and design principles
- [Live Data ](/docs/live-data ) — polling mechanism and offline behaviour in detail
- [Connecting to the Hub ](/docs/connecting ) — MCP server registration
- [Overview ](/docs/overview ) — Overview page feature walkthrough
2026-07-08 16:08:32 +02:00
- [Workplans ](/docs/workstreams ) — Workplans page and WHI in depth