diff --git a/dashboard/src/index.md b/dashboard/src/index.md
index 182d614..6d42b39 100644
--- a/dashboard/src/index.md
+++ b/dashboard/src/index.md
@@ -151,43 +151,51 @@ display(html`
-
-
-
-`);
-```
-
-```js
-import * as Plot from "npm:@observablehq/plot";
-
// ── 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();
@@ -205,20 +213,54 @@ function _timeCutoff(mode) {
return null;
}
-const _chartWsFiltered = (
- _STATUS_MODES.has(_chartMode)
- ? wsAll.filter(w => normalizeWorkstreamStatus(w.status) === _chartMode)
- : _chartMode === "needs_review"
- ? wsAll.filter(needsReviewWorkstream)
- : _chartMode === "stalled"
- ? wsAll.filter(isStalledWorkstream)
- : (() => {
- const since = _timeCutoff(_chartMode);
- return wsAll.filter(w =>
- new Date(w.updated_at) >= since || new Date(w.created_at) >= since
- );
- })()
-);
+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 _modeSelect = html``;
+_modeSelect.value = _savedChartMode;
+_modeSelect.addEventListener("input", () => {
+ globalThis.__stateHubOverviewChartMode = _modeSelect.value;
+});
+
+// view() is the idiomatic Observable Framework reactive input:
+// it displays the element AND returns a reactive value that re-runs dependent blocks.
+const _chartMode = _modeValue(view(_modeSelect));
+const _chartWsFiltered = _workstreamsForMode(_chartMode, wsAll);
+```
+
+```js
+import * as Plot from "npm:@observablehq/plot";
// Sort by domain, then repository, then most recently updated workstream.
// The axis labels show each domain/repo group once.
@@ -625,6 +667,7 @@ display(Inputs.table((summary.recent_progress ?? []).map(e => ({