`);
```
## Workplans by Repository
```js
import {workplanMatchesSearch} from "./components/workplan-search.js";
// ── Filter workplans 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(w => isStalledWorkstream(w));
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;
}
// Free-text filter over title / repo / domain / workplan filename (persists in-tab).
const _savedChartSearch = typeof globalThis.__stateHubOverviewChartSearch === "string"
? globalThis.__stateHubOverviewChartSearch
: "";
const _chartSearchState = Mutable(_savedChartSearch);
function _setChartSearch(value) {
const q = String(value ?? "");
globalThis.__stateHubOverviewChartSearch = q;
_chartSearchState.value = q;
}
```
```js
const _modeSelect = html``;
_modeSelect.value = _modeValue(_chartModeState);
_modeSelect.addEventListener("input", () => {
_setChartMode(_modeSelect.value);
});
_modeSelect.addEventListener("change", () => {
_setChartMode(_modeSelect.value);
});
const _searchInput = html``;
_searchInput.value = String(_chartSearchState ?? "");
_searchInput.addEventListener("input", () => {
_setChartSearch(_searchInput.value);
});
display(html`
${_modeSelect}
${_searchInput}
`);
```
```js
import * as Plot from "npm:@observablehq/plot";
const _chartModeValue = _modeValue(_chartModeState);
const _chartSearchValue = String(_chartSearchState ?? "");
const _chartWsFiltered = _workstreamsForMode(_chartModeValue, wsAll)
.filter(w => workplanMatchesSearch(w, _chartSearchValue));
// Sort by domain, then repository, then most recently updated workplan.
// 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 workplan 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 _searchQ = _chartSearchValue.trim();
if (_searchQ) {
display(html`
No workplans match “${_searchQ}” for the selected mode.
`);
} else {
const _emptyMsg = {
proposed: "No proposed workplans.",
ready: "No ready workplans.",
active: "No active workplans.",
blocked: "No blocked workplans.",
backlog: "No backlog workplans.",
finished: "No finished workplans.",
archived: "No archived workplans.",
needs_review: "No ready workplans need review.",
stalled: "No stalled workplans — everything is moving.",
"1h": "No workplans changed in the last hour.",
"1d": "No workplans changed in the last 24 hours.",
"7d": "No workplans changed in the last 7 days.",
"30d": "No workplans changed in the last 30 days.",
today: "No workplans changed today.",
week: "No workplans changed this week.",
month: "No workplans changed this month.",
};
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 workplans 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 workplans yet:
${emptyRegistered.map(t => html`
${t.domain_slug} — open repo in Claude Code and say "Hi!" to kick off first session, or create a workplan file under workplans/ and run statehub fix-consistency
`)}
`);
}
```
## 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`