---
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,
workplans, 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` |
| `workplans.json.py` | `/workplans/` |
| `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 |
|---|---|---|
| Overview | `/` | Cross-domain summary — workplan chart, status KPIs, blocking decisions, recent activity |
| 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 |

### Workplans section

| Page | Route | Purpose |
|---|---|---|
| Workplans | `/workstreams` | All workplans with Workplan Health Index; route name remains compatibility-backed |
| 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`
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`.
- `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
away from the page. Supports four entity types: `workplan`, `task`, `ep`
(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
heading. Used on the Overview and Workplans pages to embed live KPI infoboxes
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
relevant reference page. Used in the Workplan Health Index card to annotate
each metric abbreviation.

### `multiselect.js`
A multi-value dropdown filter input compatible with Observable's `Inputs.form()`
reactive pattern. Used on the Workplans and Tasks pages for domain and status
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.

### Workplan Health Index (WHI)

The Workplans page computes a **Workplan Health Index** — a single
composite score (0–100%) derived from five graph metrics:

| Metric | Abbrev. | Weight | Interpretation |
|---|---|---|---|
| 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 |
| 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.

### Multi-mode workplan chart

The Overview page renders a horizontal stacked bar chart using `@observablehq/plot`
showing task counts (done / progress / wait / todo) per workplan.
A `<select>` dropdown switches between:

- **Lifecycle modes**: proposed, ready, active, blocked, backlog, finished, archived
- **Health modes**: needs review, stalled
- **Time modes**: last 1h, 24h, 7d, 30d, today, this week, this month

Domains are sorted by most recent workplan activity (most active domain at
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

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
the `WorkstreamDependency` table.

### Entity modals

Any table row on any list page (workplans, tasks, extension points, tech debt)
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 |
| `Plot.text` | Workplan title labels and done/total counters overlaid on bars |
| `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
cd ~/state-hub/dashboard

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
- [Workplans](/docs/workstreams) — Workplans page and WHI in depth
