--- title: Overview --- ```js import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js"; import { WORKSTREAM_STATUSES, isClosedWorkstream, isStalledWorkstream, needsReviewWorkstream, normalizeWorkstreamStatus, } from "./components/workplan-status.js"; ``` ```js // Single polling loop — loads one bounded overview read model and keeps // last-known-good data visible if a refresh times out. const pageState = (async function*() { let failures = 0; let lastGood = null; while (true) { let nextState = lastGood ? {...lastGood, ok: false, stale: true, error: null} : {summary: {}, snapshots: [], snapshotCount: 0, totalPkgs: 0, milestones: [], wsAll: [], ok: false, stale: false, error: null, sources: {}, ts: new Date()}; try { const loadJson = async (name, path, options = {}) => { const response = await apiFetch(path, options); if (!response.ok) throw new Error(`${name} HTTP ${response.status}`); return response.json(); }; const overview = await loadJson("overview", "/state/overview", {timeout: 20_000, cache: "reload"}); const summaryData = { generated_at: overview.generated_at, totals: overview.totals ?? {}, topics: overview.topics ?? [], blocking_decisions: overview.blocking_decisions ?? [], waiting_tasks: overview.waiting_tasks ?? [], blocked_tasks: overview.blocked_tasks ?? overview.waiting_tasks ?? [], recent_progress: overview.recent_progress ?? [], next_steps: overview.next_steps ?? [], contribution_counts: overview.contribution_counts ?? {}, licence_risk_count: overview.licence_risk_count ?? 0, open_capability_requests: overview.open_capability_requests ?? 0, }; nextState = { summary: summaryData, snapshots: [], snapshotCount: overview.sbom_snapshot_count ?? 0, totalPkgs: overview.sbom_package_total ?? 0, milestones: overview.registration_milestones ?? [], wsAll: (overview.workplan_rows ?? []).map(w => ({ ...w, status: normalizeWorkstreamStatus(w.status), })), ok: true, stale: false, error: null, sources: overview.sources ?? {}, ts: new Date(), }; lastGood = nextState; } catch (e) { const message = `Dashboard refresh failed: ${e?.message ?? String(e)}`; if (lastGood) { nextState = { ...lastGood, ok: false, stale: true, error: `${message}; showing last successful data from ${lastGood.ts?.toLocaleTimeString?.() ?? "previous refresh"}`, summary: { ...(lastGood.summary ?? {}), error: `${message}; showing last successful data from ${lastGood.ts?.toLocaleTimeString?.() ?? "previous refresh"}`, }, }; } else { nextState = { summary: {error: message}, snapshots: [], snapshotCount: 0, totalPkgs: 0, milestones: [], wsAll: [], ok: false, stale: false, error: message, sources: {}, ts: new Date(), }; } } failures = nextState.ok ? 0 : failures + 1; yield nextState; await waitForVisible(pollDelay({ok: nextState.ok, base: POLL_HEAVY, failures})); } })(); ``` ```js const summary = pageState.summary ?? {}; const _ok = pageState.ok ?? false; const _stale = pageState.stale ?? false; const _ts = pageState.ts; const totals = summary.totals ?? {}; const ws = totals.workstreams ?? {}; const tasks = totals.tasks ?? {}; const decisions = totals.decisions ?? {}; const wsAll = pageState.wsAll ?? []; ``` ```js // Blocking decisions — fetched once on load, refreshed only after a resolve action. // Kept separate from the main poll so in-progress form inputs aren't wiped every 60 s. const blockingDecisions = Mutable([]); const refreshDecisions = async () => { const r = await apiFetch("/decisions/?decision_type=pending", {timeout: 12_000}).catch(() => null); const all = r?.ok ? await r.json() : []; blockingDecisions.value = all.filter(d => ["open", "escalated"].includes(d.status)); }; refreshDecisions(); ``` # Custodian State Hub ```js import {injectTocTop} from "./components/toc-sidebar.js"; import {withDocHelp} from "./components/doc-overlay.js"; const _liveEl = html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` : _stale ? `Stale · last successful update ${_ts?.toLocaleTimeString()}` : html`Offline — run: cd ~/state-hub && make api`}
`; withDocHelp(_liveEl, "/docs/live-data"); injectTocTop("live-indicator", _liveEl); const _h1 = document.querySelector("#observablehq-main h1"); if (_h1) { _h1.style.position = "relative"; withDocHelp(_h1, "/docs/overview"); } ``` ```js display(html`
⚠️ ${summary.error ?? ''}
`); ``` ## Workstreams by Repository ```js // ── Filter workstreams by selected mode ─────────────────────────────────────── // Lifecycle modes match stored canonical status values. // Health modes are derived labels; they are not stored lifecycle states. // Time modes filter by updated_at / created_at. const _STATUS_MODES = new Set(WORKSTREAM_STATUSES); const _HEALTH_MODES = new Set(["needs_review", "stalled"]); const _MODE_GROUPS = [ { label: "Lifecycle", options: [ ["ready", "ready"], ["active", "active"], ["blocked", "blocked"], ["proposed", "proposed"], ["backlog", "backlog"], ["finished", "finished"], ["archived", "archived"], ], }, { label: "Health", options: [ ["needs_review", "needs review"], ["stalled", "stalled"], ], }, { label: "Recently Changed", options: [ ["1h", "last 1 hour"], ["1d", "last 24 hours"], ["7d", "last 7 days"], ["30d", "last 30 days"], ["today", "today"], ["week", "this week"], ["month", "this month"], ], }, ]; const _MODE_VALUES = new Set(_MODE_GROUPS.flatMap(group => group.options.map(([value]) => value))); function _modeValue(mode) { const value = typeof mode === "string" ? mode : mode?.value; return _MODE_VALUES.has(value) ? value : "active"; } function _timeCutoff(mode) { const now = new Date(); if (mode === "1h") return new Date(now - 60 * 60 * 1000); if (mode === "1d") return new Date(now - 24 * 60 * 60 * 1000); if (mode === "7d") return new Date(now - 7 * 24 * 60 * 60 * 1000); if (mode === "30d") return new Date(now - 30 * 24 * 60 * 60 * 1000); if (mode === "today") return new Date(now.getFullYear(), now.getMonth(), now.getDate()); if (mode === "week") { const d = new Date(now.getFullYear(), now.getMonth(), now.getDate()); d.setDate(d.getDate() - ((d.getDay() + 6) % 7)); // back to Monday return d; } if (mode === "month") return new Date(now.getFullYear(), now.getMonth(), 1); return null; } function _validDate(value) { const date = new Date(value); return Number.isFinite(date.getTime()) ? date : null; } function _workstreamsForMode(mode, rows) { const modeValue = _modeValue(mode); const allRows = Array.isArray(rows) ? rows : []; if (_STATUS_MODES.has(modeValue)) { return allRows.filter(w => normalizeWorkstreamStatus(w.status) === modeValue); } if (modeValue === "needs_review") return allRows.filter(needsReviewWorkstream); if (modeValue === "stalled") return allRows.filter(isStalledWorkstream); const since = _timeCutoff(modeValue); if (!since) return allRows.filter(w => normalizeWorkstreamStatus(w.status) === "active"); return allRows.filter(w => { const updatedAt = _validDate(w.updated_at); const createdAt = _validDate(w.created_at); return (updatedAt && updatedAt >= since) || (createdAt && createdAt >= since); }); } const _savedChartMode = _MODE_VALUES.has(globalThis.__stateHubOverviewChartMode) ? globalThis.__stateHubOverviewChartMode : "active"; const _chartModeState = Mutable(_savedChartMode); function _setChartMode(value) { const mode = _modeValue(value); globalThis.__stateHubOverviewChartMode = mode; _chartModeState.value = mode; } ``` ```js const _modeSelect = html``; _modeSelect.value = _modeValue(_chartModeState); _modeSelect.addEventListener("input", () => { _setChartMode(_modeSelect.value); }); _modeSelect.addEventListener("change", () => { _setChartMode(_modeSelect.value); }); display(_modeSelect); ``` ```js import * as Plot from "npm:@observablehq/plot"; const _chartModeValue = _modeValue(_chartModeState); const _chartWsFiltered = _workstreamsForMode(_chartModeValue, wsAll); // Sort by domain, then repository, then most recently updated workstream. // The axis labels show each domain/repo group once. const chartWs = [..._chartWsFiltered].sort((a, b) => { const domainCompare = (a.domain ?? "").localeCompare(b.domain ?? ""); if (domainCompare !== 0) return domainCompare; const repoCompare = (a.repo_label ?? "").localeCompare(b.repo_label ?? ""); if (repoCompare !== 0) return repoCompare; return new Date(b.updated_at) - new Date(a.updated_at); }); // ── Status weight: bold for notable statuses in mixed-status modes ───────────── // Color is NOT used for status — avoids green-on-green when finished bars fill the row. const _isTimeBased = !_STATUS_MODES.has(_chartModeValue) && !_HEALTH_MODES.has(_chartModeValue); function _wsWeight(s) { return (isClosedWorkstream(s) || normalizeWorkstreamStatus(s) === "blocked") ? "bold" : "normal"; } // ── y-axis: domain/repo label for first workstream per repository only ──────── const _yLabels = {}; const _seen = new Set(); for (const w of chartWs) { const group = `${w.domain} / ${w.repo_label}`; _yLabels[w.id] = _seen.has(group) ? "" : group; _seen.add(group); } const statusOrder = ["done", "progress", "wait", "todo"]; const statusColors = ["#4caf50", "#8b5cf6", "#f59e0b", "#e0e0e0"]; const _taskRows = chartWs.flatMap(w => [ {id: w.id, title: w.title, status: "done", count: w.done ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href}, {id: w.id, title: w.title, status: "progress", count: w.progress ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href}, {id: w.id, title: w.title, status: "wait", count: w.wait ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href}, {id: w.id, title: w.title, status: "todo", count: w.todo ?? 0, domain: w.domain, repo: w.repo_label, workplan: w.workplan_filename, href: w.href}, ]).filter(d => d.count > 0); function _wsTitle(d) { return [ d.title, `Repo: ${d.repo ?? "unassigned"}`, `Domain: ${d.domain ?? "unknown"}`, `Workplan: ${d.workplan ?? "not file-backed"}`, `${d.status}: ${d.count}`, ].join("\n"); } // ── Render ──────────────────────────────────────────────────────────────────── if (chartWs.length === 0) { const _emptyMsg = { proposed: "No proposed workstreams.", ready: "No ready workstreams.", active: "No active workstreams.", blocked: "No blocked workstreams.", backlog: "No backlog workstreams.", finished: "No finished workstreams.", archived: "No archived workstreams.", needs_review: "No ready workstreams need review.", stalled: "No stalled workstreams — everything is moving.", "1h": "No workstreams changed in the last hour.", "1d": "No workstreams changed in the last 24 hours.", "7d": "No workstreams changed in the last 7 days.", "30d": "No workstreams changed in the last 30 days.", today: "No workstreams changed today.", week: "No workstreams changed this week.", month: "No workstreams changed this month.", }; display(html`

${_emptyMsg[_chartModeValue] ?? "No workstreams."}

`); } else { display(Plot.plot({ y: { label: null, tickSize: 0, domain: chartWs.map(w => w.id), tickFormat: t => _yLabels[t] ?? "", }, x: {label: "Tasks", grid: true}, color: {domain: statusOrder, range: statusColors, legend: true}, marks: [ Plot.barX(_taskRows, { y: "id", x: "count", fill: "status", title: _wsTitle, href: "href", target: "_self", tip: true, }), // Title label — pushed to lower half of bar row (dy: +7) to separate from count Plot.text(chartWs.filter(w => w.total > 0), { y: "id", x: 0, dx: 6, dy: 7, text: d => d.title.length > 72 ? d.title.slice(0, 70) + "…" : d.title, textAnchor: "start", fontSize: 10, fill: "#1e293b", fontWeight: d => _isTimeBased ? _wsWeight(d.status) : "normal", title: d => [ d.title, `Repo: ${d.repo_label ?? "unassigned"}`, `Domain: ${d.domain ?? "unknown"}`, `Workplan: ${d.workplan_filename ?? "not file-backed"}`, ].join("\n"), href: "href", target: "_self", }), Plot.text(chartWs.filter(w => w.total === 0), { y: "id", x: 0, dx: 6, dy: 7, text: d => `${d.title.length > 48 ? d.title.slice(0, 46) + "…" : d.title} — no tasks yet`, textAnchor: "start", fontSize: 10, fill: "#94a3b8", title: d => [ d.title, `Repo: ${d.repo_label ?? "unassigned"}`, `Domain: ${d.domain ?? "unknown"}`, `Workplan: ${d.workplan_filename ?? "not file-backed"}`, ].join("\n"), href: "href", target: "_self", }), // Count label — pushed to upper half of bar row (dy: -7) to separate from title Plot.text(chartWs.filter(w => w.total > 0), { y: "id", x: "total", text: d => ` ${d.done}/${d.total}`, dx: 4, dy: -7, textAnchor: "start", fontSize: 11, fill: "gray", title: d => `${d.title}\nWorkplan: ${d.workplan_filename ?? "not file-backed"}`, href: "href", target: "_self", }), Plot.ruleX([0]), ], marginLeft: 220, marginRight: 70, height: Math.max(80, chartWs.length * 44 + 50), width: 700, })); } ``` ## Contribution & SBOM Health ```js const contribCounts = summary.contribution_counts ?? {}; const licenceRisk = summary.licence_risk_count ?? 0; const totalContribs = ["br","fr","ep","upr"].reduce((s, t) => s + (contribCounts[t] ?? 0), 0); const needsFollowUp = (contribCounts["submitted"] ?? 0) + (contribCounts["acknowledged"] ?? 0); const sbomSnaps = pageState.snapshots ?? []; const sbomSnapCount = pageState.snapshotCount ?? sbomSnaps.length; const totalPkgs = pageState.totalPkgs ?? 0; display(html`

Contributions

${totalContribs}

${needsFollowUp > 0 ? html`${needsFollowUp} awaiting upstream response` : "all up to date"}

Licence Risk

${licenceRisk}

${licenceRisk === 0 ? html`✓ no copyleft in direct deps` : html`copyleft in direct prod deps`}

SBOM

${totalPkgs.toLocaleString()}

${sbomSnapCount} snapshot${sbomSnapCount !== 1 ? "s" : ""} tracked · ${licenceRisk > 0 ? html`${licenceRisk} copyleft risks` : html`✓ no copyleft`}
`); ``` ## Status ```js const waitingTasks = summary.waiting_tasks ?? summary.blocked_tasks ?? []; const wsById = Object.fromEntries((summary.open_workstreams ?? []).map(w => [w.id, w])); const todayCount = (summary.recent_progress ?? []).filter(e => e.created_at?.startsWith(new Date().toISOString().slice(0, 10))).length; const decCount = (decisions.open ?? 0) + (decisions.escalated ?? 0); const statusEl = html`

Active Workstreams

${ws.active ?? 0}

${ws.blocked ?? 0} blocked

Blocking Decisions

${decCount}

${decisions.escalated ?? 0} escalated

Events Today

${todayCount}

last 20 shown below
`; statusEl.querySelector('[data-toggle="waiting-panel"]').addEventListener('click', () => { const panel = statusEl.querySelector('#waiting-panel'); const isOpen = panel.style.display !== 'none'; panel.style.display = isOpen ? 'none' : 'block'; statusEl.querySelector('[data-toggle="waiting-panel"] small').textContent = isOpen ? `of ${tasks.total ?? 0} total · click to expand` : `of ${tasks.total ?? 0} total · click to collapse`; }); display(statusEl); ``` ## What's next? ```js // next_steps comes from the summary poll (derived, never persisted) const nextSteps = summary.next_steps ?? []; const typeLabel = { resolved_decision: "Decision resolved", dependency_cleared: "Dependency cleared", unblocked_task: "Task unblocked", }; const typeBadgeClass = { resolved_decision: "ns-badge-decision", dependency_cleared: "ns-badge-dep", unblocked_task: "ns-badge-task", }; if (nextSteps.length === 0) { display(html`

No actionable suggestions right now — all open workstreams are making progress or waiting on decisions.

`); } else { display(html`
${nextSteps.map(s => html`
${typeLabel[s.type] ?? s.type} ${s.domain ?? "—"}
${s.workstream_title ?? "—"}
${s.task_title ? html`→ ${s.task_title}` : ""}
${s.message}
`)}
`); } ``` ## Registered Projects ```js const regs = pageState.milestones ?? []; if (regs.length === 0) { display(html`

No projects registered yet. Run custodian register-project inside a repo.

`); } else { display(Inputs.table(regs.map(e => ({ Project: e.detail?.project_path?.split("/").at(-1) ?? "—", Domain: e.detail?.domain ?? "—", Path: e.detail?.project_path ?? "—", Registered: new Date(e.created_at).toLocaleString(), })), {maxWidth: 900})); } ``` ```js // Registered domains with no workstreams yet — show a getting-started hint const regs = pageState.milestones ?? []; const registeredDomains = new Set(regs.map(e => e.detail?.domain).filter(Boolean)); const emptyRegistered = (summary.topics ?? []).filter(t => registeredDomains.has(t.domain_slug) && (t.workstreams ?? []).length === 0 ); if (emptyRegistered.length > 0) { display(html`
💡 Getting started

These registered projects have no workstreams yet:

`); } ``` ## Blocking Decisions ```js // Uses blockingDecisions (Mutable) — only re-renders when refreshDecisions() is called, // not on every summary poll, so in-progress form input is preserved between polls. const blocking = blockingDecisions ?? []; if (blocking.length === 0) { display(html`

✓ No blocking decisions.

`); } else { for (const d of blocking) { const card = html`
${d.title} ${d.escalation_note ? html`⚠ escalated` : ""} ${d.deadline ? html`Due ${new Date(d.deadline).toLocaleDateString()}` : ""}
${d.description ? html`

${d.description}

` : ""} ${d.rationale ? html`

Context: ${d.rationale}

` : ""} ${d.escalation_note ? html`

${d.escalation_note}

` : ""}
Resolve this decision →
`; // Copy to clipboard const copyBtn = card.querySelector(".r-copy"); copyBtn.addEventListener("click", () => { const parts = [ `# ${d.title}`, "", d.description ?? "", d.rationale ? `\n**Context:** ${d.rationale}` : "", d.escalation_note ? `\n**⚠ Escalated:** ${d.escalation_note}` : "", `\n**Status:** ${d.status} | **Created:** ${new Date(d.created_at).toLocaleDateString()}`, d.deadline ? `**Due:** ${new Date(d.deadline).toLocaleDateString()}` : "", ].filter(Boolean).join("\n"); navigator.clipboard.writeText(parts).then(() => { copyBtn.textContent = "✓ Copied"; setTimeout(() => { copyBtn.textContent = "Copy"; }, 1500); }).catch(() => { copyBtn.textContent = "⚠ Failed"; setTimeout(() => { copyBtn.textContent = "Copy"; }, 2000); }); }); // Resolve const btn = card.querySelector(".r-submit"); const msg = card.querySelector(".r-msg"); btn.addEventListener("click", async () => { const rationale = card.querySelector(".r-text").value.trim(); const decidedBy = card.querySelector(".r-by").value.trim() || "human"; if (!rationale) { msg.textContent = "⚠ Please enter a rationale."; return; } btn.disabled = true; btn.textContent = "Saving…"; try { const r = await fetch(`${API}/decisions/${d.id}/resolve`, { method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({rationale, decided_by: decidedBy}), }); if (r.ok) { await refreshDecisions(); // re-fetches list — resolved decision won't appear } else { const err = await r.json().catch(() => ({})); msg.textContent = `Error ${r.status}: ${err.detail ?? "unknown"}`; btn.disabled = false; btn.textContent = "Record & close"; } } catch (e) { msg.textContent = `Network error: ${e.message}`; btn.disabled = false; btn.textContent = "Record & close"; } }); display(card); } } ``` ## Decisions Due Within 7 Days ```js const in7 = new Date(Date.now() + 7*24*60*60*1000); const due = (summary.blocking_decisions ?? []).filter(d => d.deadline && new Date(d.deadline) <= in7); if (due.length === 0) { display(html`

No decisions due in next 7 days.

`); } else { display(Inputs.table(due.map(d => ({ Title: d.title, Deadline: new Date(d.deadline).toLocaleString(), Status: d.status, })))); } ``` ## Recent Activity ```js display(Inputs.table((summary.recent_progress ?? []).map(e => ({ Time: new Date(e.created_at).toLocaleString(), Type: e.event_type, Author: e.author ?? "—", Summary: e.summary, })), {maxWidth: 900})); ```