14 KiB
title
| 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, 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:
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 — workstream 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 |
Workstreams section
| Page | Route | Purpose |
|---|---|---|
| Workstreams | /workstreams |
All workstreams with Workstream Health Index |
| 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 two constants used by every live-polling page:
API = "http://127.0.0.1:8000"— the FastAPI base URLPOLL = 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: workstream, 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 Workstreams 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 Workstream 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 Workstreams 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.
Workstream Health Index (WHI)
The Workstreams page computes a Workstream 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 workstream; high = tightly coupled |
| Blocked Ratio | BR | 25% | Share of workstreams in a blocked state |
| Single-Point Risk | SPR | 15% | Share of workstreams that others depend on but are not yet complete |
| Parallel Execution Potential | PEP | 20% | Share of workstreams 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 workstream chart
The Overview page renders a horizontal stacked bar chart using @observablehq/plot
showing task counts (done / in progress / blocked / todo) per workstream.
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 workstream 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 Workstreams page both surface inter-workstream
dependency data. Each workstream card shows the workstreams it depends on
(↳ depends on) and the workstreams it blocks (⊳ blocks), derived from
the WorkstreamDependency table.
Entity modals
Any table row on any list page (workstreams, 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 — Observable's concise, composable grammar-of-graphics library. It is imported on demand per page:
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 |
Workstream 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
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 — overall architecture and design principles
- Live Data — polling mechanism and offline behaviour in detail
- Connecting to the Hub — MCP server registration
- Overview — Overview page feature walkthrough
- Workstreams — Workstreams page and WHI in depth