state-hub/dashboard/src/workstreams.md
tegwick 0ea2788943 Add state-hub v0.1 — local-first state service for the Custodian
Implements the first live layer of the Custodian cognitive infrastructure:
PostgreSQL schema, FastAPI REST API, FastMCP stdio server, and Observable
Framework telemetry dashboard.

- state-hub/: full stack (docker-compose, FastAPI, Alembic, MCP server, dashboard)
- 5 DB tables: topics, workstreams, tasks, decisions, progress_events
- 11 MCP tools + 5 resources registered in .mcp.json
- Observable dashboard: Overview, Workstreams, Decisions, Progress pages
- CLAUDE.md: session protocol (get_state_summary / add_progress_event ritual)
- ~/.claude/CLAUDE.md: global cross-project reference to the hub
- scripts/pull_image.py: WSL2 TLS-resilient Docker image downloader

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 17:47:49 +01:00

1.5 KiB

title
Workstreams

Workstreams

const workstreams = await FileAttachment("data/workstreams.json").json();
const data = Array.isArray(workstreams) ? workstreams : [];
const domainFilter = view(Inputs.select(
  ["(all)", ...new Set(data.map(w => w.domain ?? "unknown"))],
  { label: "Domain" }
));
const statusFilter = view(Inputs.select(
  ["(all)", "active", "blocked", "completed", "archived"],
  { label: "Status" }
));
const ownerFilter = view(Inputs.text({ label: "Owner contains" }));
const filtered = data.filter(w =>
  (domainFilter === "(all)" || w.domain === domainFilter) &&
  (statusFilter === "(all)" || w.status === statusFilter) &&
  (!ownerFilter || (w.owner ?? "").toLowerCase().includes(ownerFilter.toLowerCase()))
);

const STATUS_COLOR = {
  active: "green",
  blocked: "orange",
  completed: "blue",
  archived: "gray",
};

display(Inputs.table(filtered.map(w => ({
  Title: w.title,
  Domain: w.domain,
  Status: w.status,
  Owner: w.owner ?? "—",
  "Due": w.due_date ?? "—",
  "Updated": new Date(w.updated_at).toLocaleDateString(),
})), {
  rows: 20,
}));
import * as Plot from "npm:@observablehq/plot";

display(Plot.plot({
  title: "Workstream Status Distribution",
  marks: [
    Plot.barX(
      Object.entries(
        filtered.reduce((acc, w) => { acc[w.status] = (acc[w.status] ?? 0) + 1; return acc; }, {})
      ).map(([status, count]) => ({ status, count })),
      { y: "status", x: "count", fill: "status", tip: true }
    ),
    Plot.ruleX([0]),
  ],
  marginLeft: 80,
  width: 500,
}));