diff --git a/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx b/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx index 350876ed..d46129dd 100644 --- a/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/AgentsPage.tsx @@ -318,11 +318,13 @@ export function AgentsPage() { header: "hive", render: (a) => a.hive ?? "—", sortBy: (a) => a.hive ?? "", - // "—" (not "") so the missing-hive option in the filter dropdown - // reads the same as the cell itself, rather than showing a blank - // choice. - filterValue: (a) => a.hive ?? "—", - filterMode: "select", + // "—" (not "") so the missing-hive option in the filter list reads + // the same as the cell itself, rather than showing a blank choice. + // Multiselect, not single-value `"select"` — an operator narrowing + // to a handful of hives at once shouldn't need to filter one at a + // time. + filterValues: (a) => [a.hive ?? "—"], + filterMode: "multiselect", }, { key: "status", @@ -333,8 +335,9 @@ export function AgentsPage() { // label and blew the row out (mara: "looks messy"). That string // now lives in its own "message" column below. sortBy: (a) => FRESHNESS[a.freshness].label, - filterValue: (a) => FRESHNESS[a.freshness].label, - filterMode: "select", + // Multiselect, same reasoning as the hive column above. + filterValues: (a) => [FRESHNESS[a.freshness].label], + filterMode: "multiselect", render: (a) => { const { tone, label } = FRESHNESS[a.freshness]; return ( @@ -379,8 +382,9 @@ export function AgentsPage() { // real per-badge logic lives there, this column just wires its // callbacks to the page's own state/handlers. sortBy: (a) => a.wanted ?? "", - filterValue: (a) => a.wanted ?? "no declaration", - filterMode: "select", + // Multiselect, same reasoning as the hive column above. + filterValues: (a) => [a.wanted ?? "no declaration"], + filterMode: "multiselect", render: (a) => { const err = actionErrors.get(a.name); return ( diff --git a/frontend/packages/swarm-ui/src/pages/HivesPage.tsx b/frontend/packages/swarm-ui/src/pages/HivesPage.tsx index 4d0deda4..518aa0af 100644 --- a/frontend/packages/swarm-ui/src/pages/HivesPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/HivesPage.tsx @@ -91,8 +91,11 @@ const COLUMNS: TableColumn[] = [ ); }, sortBy: (h) => FRESHNESS[h.freshness].label, - filterValue: (h) => FRESHNESS[h.freshness].label, - filterMode: "select", + // Multiselect (not the single-value `"select"` mode) so the operator + // can filter for e.g. "stale or offline" in one pass rather than one + // freshness state at a time. + filterValues: (h) => [FRESHNESS[h.freshness].label], + filterMode: "multiselect", }, ]; diff --git a/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx b/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx index 1c95de8d..95f84da9 100644 --- a/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx +++ b/frontend/packages/swarm-ui/src/pages/IssueReportPage.tsx @@ -312,14 +312,12 @@ export function IssueReportPage() { ), ariaSort: ariaSortFor("assignees"), render: (r) => (r.assignees.length ? r.assignees.join(", ") : "—"), - // Text, not `"select"` — a row can carry more than one assignee, - // and `Table`'s select mode matches one whole string per row - // exactly, so a real per-assignee dropdown would need `Table` - // itself to grow multi-value filtering. Substring search over - // the joined string still finds "iris" inside "damocles, iris" - // correctly, which covers the actual gap (no way to filter by - // assignee at all today). - filterValue: (r) => r.assignees.join(", "), + // Multiselect, same machinery the "labels" column above already + // uses — `r.assignees` is already the per-row array this mode + // wants, so there's no reason to fall back to a joined-string + // substring search here. + filterValues: (r) => r.assignees, + filterMode: "multiselect", }, { key: "blocked", @@ -337,11 +335,11 @@ export function IssueReportPage() { r.blocked ? : "—", // A distinct gap from the existing "hide blocked" toggle above: // that one only *hides* blocked issues, there was no way to see - // *only* the blocked ones. Synthesized two-value string (not the - // raw boolean) — `Table`'s select mode needs a string to match, - // same as `AgentsPage`'s `wanted ?? "no declaration"` pattern. - filterValue: (r) => (r.blocked ? "blocked" : "not blocked"), - filterMode: "select", + // *only* the blocked ones. Synthesized two-value set (not the raw + // boolean) — multiselect, same as every other enum-shaped column, + // rather than the single-value `"select"` mode. + filterValues: (r) => [r.blocked ? "blocked" : "not blocked"], + filterMode: "multiselect", }, { key: "depended_on_by_count",