--- title: Overview --- ```js const API = "http://127.0.0.1:8000"; const POLL = 15_000; ``` ```js // Live polling — yields {data, ok, ts} every POLL ms const summaryState = (async function*() { while (true) { let data, ok = false; try { const r = await fetch(`${API}/state/summary`); ok = r.ok; data = ok ? await r.json() : {error: `HTTP ${r.status}`}; } catch (e) { data = {error: "API unreachable"}; } yield {data, ok, ts: new Date()}; await new Promise(res => setTimeout(res, POLL)); } })(); ``` ```js const summary = summaryState.data ?? {}; const _ok = summaryState.ok ?? false; const _ts = summaryState.ts; const totals = summary.totals ?? {}; const ws = totals.workstreams ?? {}; const tasks = totals.tasks ?? {}; const decisions = totals.decisions ?? {}; ``` ```js // Blocking decisions — fetched once on load, refreshed only after a resolve action. // Kept separate from the summary poll so in-progress form inputs aren't wiped every 15 s. const blockingDecisions = Mutable([]); const refreshDecisions = async () => { const r = await fetch(`${API}/decisions/?decision_type=pending`).catch(() => null); const all = r?.ok ? await r.json() : []; blockingDecisions.value = all.filter(d => ["open", "escalated"].includes(d.status)); }; refreshDecisions(); ``` ```js // Registered projects — milestone events tagged with registration const regsState = (async function*() { while (true) { let rows = []; try { const r = await fetch(`${API}/progress/?event_type=milestone&limit=500`); if (r.ok) { const all = await r.json(); rows = all.filter(e => e.summary?.startsWith("Project registered with State Hub:")); } } catch {} yield rows; await new Promise(res => setTimeout(res, POLL)); } })(); ``` # Custodian State Hub ```js display(html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` : `Offline — run: cd ~/the-custodian/state-hub && make api`}
`); ``` ```js if (summary.error) display(html`
⚠️ ${summary.error}
`); ``` ## Status ```js display(html`

Active Workstreams

${ws.active ?? 0}

${ws.blocked ?? 0} blocked

Blocking Decisions

${(decisions.open ?? 0) + (decisions.escalated ?? 0)}

${decisions.escalated ?? 0} escalated

Blocked Tasks

${tasks.blocked ?? 0}

of ${tasks.total ?? 0} total

Events Today

${(summary.recent_progress ?? []).filter(e => e.created_at?.startsWith(new Date().toISOString().slice(0,10))).length}

last 20 shown below
`); ``` ## 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 = regsState ?? []; 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})); } ``` ## Open Workstreams by Domain ```js import * as Plot from "npm:@observablehq/plot"; const topicById = Object.fromEntries((summary.topics ?? []).map(t => [t.id, t.domain])); const openWs = (summary.open_workstreams ?? []).map(w => ({ title: w.title, domain: topicById[w.topic_id] ?? "unknown", done: w.tasks_done ?? 0, in_progress: w.tasks_in_progress ?? 0, blocked: w.tasks_blocked ?? 0, todo: w.tasks_todo ?? 0, total: w.tasks_total ?? 0, })).sort((a, b) => a.domain.localeCompare(b.domain) || a.title.localeCompare(b.title)); const statusOrder = ["done", "in progress", "blocked", "todo"]; const statusColors = ["#4caf50", "steelblue", "#ff7043", "#e0e0e0"]; const taskRows = openWs.flatMap(w => [ {label: w.title, domain: w.domain, status: "done", count: w.done}, {label: w.title, domain: w.domain, status: "in progress", count: w.in_progress}, {label: w.title, domain: w.domain, status: "blocked", count: w.blocked}, {label: w.title, domain: w.domain, status: "todo", count: w.todo}, ]).filter(d => d.count > 0); // y-axis shows domain (only for the first workstream in each domain group) const yLabels = {}; const _seenDomains = new Set(); for (const w of openWs) { yLabels[w.title] = _seenDomains.has(w.domain) ? "" : w.domain; _seenDomains.add(w.domain); } if (openWs.length === 0) { display(html`

No open workstreams.

`); } else { display(Plot.plot({ y: {label: null, tickSize: 0, domain: openWs.map(w => w.title), tickFormat: t => yLabels[t] ?? ""}, x: {label: "Tasks", grid: true}, color: {domain: statusOrder, range: statusColors, legend: true}, marks: [ Plot.barX(taskRows, {y: "label", x: "count", fill: "status", tip: true}), // Workstream title inside the bar Plot.text(openWs.filter(w => w.total > 0), { y: "title", x: 0, dx: 6, text: d => d.title.length > 36 ? d.title.slice(0, 34) + "…" : d.title, textAnchor: "start", fontSize: 10, fill: "#333", }), Plot.text(openWs.filter(w => w.total === 0), { y: "title", x: 0, dx: 6, text: d => `${d.title.length > 24 ? d.title.slice(0, 22) + "…" : d.title} — no tasks yet`, textAnchor: "start", fontSize: 10, fill: "#aaa", }), // "done / total" label after the bar Plot.text(openWs.filter(w => w.total > 0), { y: "title", x: "total", text: d => ` ${d.done}/${d.total}`, dx: 4, textAnchor: "start", fontSize: 11, fill: "gray", }), Plot.ruleX([0]), ], marginLeft: 160, marginRight: 70, height: Math.max(80, openWs.length * 44 + 50), width: 700, })); } ``` ```js // Registered domains with no workstreams yet — show a getting-started hint const regs = regsState ?? []; const registeredDomains = new Set(regs.map(e => e.detail?.domain).filter(Boolean)); const emptyRegistered = (summary.topics ?? []).filter(t => registeredDomains.has(t.domain) && (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() || "Bernd"; 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})); ```