From de936acd6dabc6add7cc2b5690c82fbc943041f2 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 26 Feb 2026 00:05:58 +0100 Subject: [PATCH] Dashboard workstreams: multi-select filters that survive data polls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace single-select Domain/Status dropdowns with checkbox multi-selects - Use Inputs.form() with a custom template to lay the three filters out side by side in a card-style filter bar - Filter options are now static constants (DOMAINS, STATUSES) — no dependency on the polled data, so selections are never reset on refresh - Empty selection = no filter applied (show all); any checked item = include - Updated filtered computation and wsWithDeps to use filters.domain / filters.status array semantics Co-Authored-By: Claude Sonnet 4.6 --- dashboard/src/workstreams.md | 43 +++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/dashboard/src/workstreams.md b/dashboard/src/workstreams.md index 2a663a5..7f17e45 100644 --- a/dashboard/src/workstreams.md +++ b/dashboard/src/workstreams.md @@ -56,19 +56,32 @@ display(html`
``` ```js -const domainOpts = ["(all)", ...new Set(data.map(w => w.domain))].sort(); -const statusOpts = ["(all)", "active", "blocked", "completed", "archived"]; +// Static options — no dependency on `data`, so selections survive polls +const DOMAINS = ["custodian", "railiance", "markitect", "coulomb_social", "personhood", "foerster_capabilities"]; +const STATUSES = ["active", "blocked", "completed", "archived"]; -const domainFilter = view(Inputs.select(domainOpts, {label: "Domain"})); -const statusFilter = view(Inputs.select(statusOpts, {label: "Status"})); -const ownerFilter = view(Inputs.text({label: "Owner contains"})); +const filters = view(Inputs.form( + { + domain: Inputs.checkbox(DOMAINS, {label: "Domain"}), + status: Inputs.checkbox(STATUSES, {label: "Status"}), + owner: Inputs.text({label: "Owner contains", placeholder: "filter by owner…"}), + }, + { + template: ({domain, status, owner}) => html`
+
${domain}
+
${status}
+
${owner}
+
`, + } +)); ``` ```js +// Empty array = no filter applied (show all) const filtered = data.filter(w => - (domainFilter === "(all)" || w.domain === domainFilter) && - (statusFilter === "(all)" || w.status === statusFilter) && - (!ownerFilter || (w.owner ?? "").toLowerCase().includes(ownerFilter.toLowerCase())) + (filters.domain.length === 0 || filters.domain.includes(w.domain)) && + (filters.status.length === 0 || filters.status.includes(w.status)) && + (!filters.owner || (w.owner ?? "").toLowerCase().includes(filters.owner.toLowerCase())) ); display(Inputs.table(filtered.map(w => ({ @@ -104,11 +117,12 @@ display(Plot.plot({ ```js // Build dep cards from the enriched open_workstreams in the summary -const wsWithDeps = openWs.filter(w => - (domainFilter === "(all)" || (data.find(d => d.id === w.id)?.domain ?? "unknown") === domainFilter) && - (statusFilter === "(all)" || w.status === statusFilter) && - (w.depends_on.length > 0 || w.blocks.length > 0) -); +const wsWithDeps = openWs.filter(w => { + const domain = data.find(d => d.id === w.id)?.domain ?? "unknown"; + return (filters.domain.length === 0 || filters.domain.includes(domain)) && + (filters.status.length === 0 || filters.status.includes(w.status)) && + (w.depends_on.length > 0 || w.blocks.length > 0); +}); if (wsWithDeps.length === 0) { display(html`

No dependency edges recorded for the current filter. Use create_dependency() via the MCP server to link workstreams.

`); @@ -132,6 +146,9 @@ if (wsWithDeps.length === 0) {