swarm-ui: persist table filters, add reset button, multi-select, negate

Per mara's issue: tables should remember their filters (with a reset
button), the labels filter should be multi-select, and any filter should
support negation (search vs exclude).

All in the shared Table component (ui/table/Table.tsx), used by
AgentsPage/HivesPage/IssueReportPage:

- filters now persist via the same useLocalSetting hook IssueReportPage
  already used for its own state, keyed by a new required storageKey
  prop (required, not optional, so no caller can forget it and every
  table gets persistence for free)
- a small 'reset filters' button clears every column's filter at once,
  shown only when at least one is active
- new filterMode: "multiselect" (+ a filterValues extractor, alongside
  the existing single-value filterValue) renders a checkbox list and
  matches on any overlap - IssueReportPage's own bespoke label-checkbox
  sidebar is folded into this instead of staying a second, separate
  filter mechanism
- a negate toggle ('exclude') sits under every filter mode's control,
  applying uniformly to text/select/multiselect

Verified: tsc --noEmit and the esbuild bundle both clean.
This commit is contained in:
iris 2026-09-09 19:03:03 +02:00 committed by mara
commit 915c6c6f92
7 changed files with 386 additions and 197 deletions

View file

@ -1,11 +1,13 @@
/* <IssueReportPage> the repo-picker + hide-blocked toggle sit in one
row (mirrors CreateAgentForm's row/wrap pattern: flex-wrap so narrow
viewports stack instead of overflowing), the label chips get their
own row below since the set is open-ended and shouldn't fight the
controls row for width. Sort buttons live inside the table's own
`<th>` cells (Table.tsx's `header` now accepts real markup for
exactly this) so they inherit the table's header styling for free
only the pointer cursor + no-underline reset is this page's to own. */
viewports stack instead of overflowing). The label filter used to
have its own chip row here; folded into the "labels" column's own
`Table` filter (`ui-table-filter-multiselect` in Table.css) instead,
so there's nothing page-specific left to style for it. Sort buttons
live inside the table's own `<th>` cells (Table.tsx's `header` now
accepts real markup for exactly this) so they inherit the table's
header styling for free only the pointer cursor + no-underline
reset is this page's to own. */
.issue-report-controls {
display: flex;
flex-wrap: wrap;
@ -19,19 +21,6 @@
gap: 0.4em;
cursor: pointer;
}
.issue-report-labels {
display: flex;
flex-wrap: wrap;
gap: 0.6em;
margin-bottom: 1em;
}
.issue-report-label-chip {
display: flex;
align-items: center;
gap: 0.35em;
cursor: pointer;
color: var(--muted);
}
.issue-report-sort-btn {
background: none;
border: none;