feat(board): multiselect combobox for milestone + label filters #587

Merged
charles merged 2 commits from feat/583 into main 2026-04-30 19:46:46 +00:00
Collaborator

Replace text-filter inputs with <MultiselectCombobox> (Base UI Combobox, multiple mode) for milestone and label facets. Values are derived from loaded cards; OR within a facet, AND across facets. URL switches to repeatable arrays.

Test plan

  • Open board — milestone and label show combobox triggers
  • Select multiple labels — board narrows to cards carrying any selected label
  • Select milestone + label — board shows only cards matching both facets
  • × clears a facet; chip turns bg-accent/10 while active
  • URL updates to ?label=a&label=b form; old ?label=foo links still work if verbatim match
  • just qa passes — all 541 web tests green

Closes #583

Replace text-filter inputs with `<MultiselectCombobox>` (Base UI Combobox, multiple mode) for milestone and label facets. Values are derived from loaded cards; OR within a facet, AND across facets. URL switches to repeatable arrays. ## Test plan - [ ] Open board — milestone and label show combobox triggers - [ ] Select multiple labels — board narrows to cards carrying any selected label - [ ] Select milestone + label — board shows only cards matching both facets - [ ] `×` clears a facet; chip turns `bg-accent/10` while active - [ ] URL updates to `?label=a&label=b` form; old `?label=foo` links still work if verbatim match - [ ] `just qa` passes — all 541 web tests green Closes #583
dev self-assigned this 2026-04-30 19:28:58 +00:00
feat(board): multiselect combobox with text filtering for milestone + label
All checks were successful
qa / dockerfile (pull_request) Successful in 9s
qa / qa (pull_request) Successful in 2m45s
471ccbd8db
Replace the milestone and label TextFilter inputs with a MultiselectCombobox
built on Base UI's Combobox (multiple mode). Each combobox:

- Derives available values from all loaded cards (stage:/type:user-story excluded
  from labels), sorted alphabetically with per-facet card counts.
- Supports OR selection within a facet, AND across facets.
- Shows a trigger chip with count badge (+N), single value, or placeholder; a
  small × clears the facet without opening the popup; chip background flips to
  bg-accent/10 when a selection is active.
- URL params switch to repeatable arrays (?label=a&label=b); single legacy
  string values are decoded as exact-match selections.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dev requested review from reviewer 2026-04-30 19:31:57 +00:00
reviewer requested changes 2026-04-30 19:39:46 +00:00
Dismissed
reviewer left a comment
  • a11y apps/web/src/components/multiselect-combobox.tsx ~line 91: the label <span> has aria-hidden="true" but its id is referenced by aria-labelledby on Combobox.Trigger. Per the accname spec, traversal of a referenced aria-hidden element yields empty string, and aria-label is not consulted as a fallback once aria-labelledby is present — the trigger ends up with no accessible name. Fix: remove aria-hidden="true" from the label span (it is a visible label, no reason to hide it from AT), or drop aria-labelledby and rely solely on aria-label.

  • behavior apps/web/src/components/multiselect-combobox.tsx: AC requires ⌘A selects all visible matches — not implemented. Base UI does not provide this natively; add an onKeyDown handler on Combobox.Input or Combobox.Popup: when metaKey && key === "a", merge visible unselected items into the current selection.

- **a11y** `apps/web/src/components/multiselect-combobox.tsx` ~line 91: the label `<span>` has `aria-hidden="true"` but its `id` is referenced by `aria-labelledby` on `Combobox.Trigger`. Per the accname spec, traversal of a referenced `aria-hidden` element yields empty string, and `aria-label` is not consulted as a fallback once `aria-labelledby` is present — the trigger ends up with no accessible name. Fix: remove `aria-hidden="true"` from the label span (it is a visible label, no reason to hide it from AT), or drop `aria-labelledby` and rely solely on `aria-label`. - **behavior** `apps/web/src/components/multiselect-combobox.tsx`: AC requires `⌘A selects all visible matches` — not implemented. Base UI does not provide this natively; add an `onKeyDown` handler on `Combobox.Input` or `Combobox.Popup`: when `metaKey && key === "a"`, merge visible unselected items into the current selection.
reviewer requested changes 2026-04-30 19:39:52 +00:00
Dismissed
reviewer left a comment
  • a11y apps/web/src/components/multiselect-combobox.tsx ~line 91: the label <span> has aria-hidden="true" but its id is referenced by aria-labelledby on Combobox.Trigger. Per the accname spec, traversal of a referenced aria-hidden element yields empty string, and aria-label is not consulted as a fallback once aria-labelledby is present - the trigger ends up with no accessible name. Fix: remove aria-hidden="true" from the label span, or drop aria-labelledby and rely solely on aria-label.

  • behavior apps/web/src/components/multiselect-combobox.tsx: AC requires cmd+A to select all visible matches - not implemented. Add an onKeyDown on Combobox.Input or Combobox.Popup: when metaKey && key === "a", merge visible unselected items into the current selection.

- **a11y** `apps/web/src/components/multiselect-combobox.tsx` ~line 91: the label `<span>` has `aria-hidden="true"` but its `id` is referenced by `aria-labelledby` on `Combobox.Trigger`. Per the accname spec, traversal of a referenced `aria-hidden` element yields empty string, and `aria-label` is not consulted as a fallback once `aria-labelledby` is present - the trigger ends up with no accessible name. Fix: remove `aria-hidden="true"` from the label span, or drop `aria-labelledby` and rely solely on `aria-label`. - **behavior** `apps/web/src/components/multiselect-combobox.tsx`: AC requires cmd+A to select all visible matches - not implemented. Add an `onKeyDown` on `Combobox.Input` or `Combobox.Popup`: when `metaKey && key === "a"`, merge visible unselected items into the current selection.
fix(board): a11y label aria-hidden + ⌘A select-all visible
All checks were successful
qa / dockerfile (pull_request) Successful in 7s
qa / qa (pull_request) Successful in 1m48s
a693b803be
Remove aria-hidden="true" from the label span whose id is referenced by
aria-labelledby on Combobox.Trigger — per the accname spec a referenced
aria-hidden node yields empty string, leaving the trigger nameless.

Implement ⌘A: onKeyDown on Combobox.Input merges all items whose value
matches the current search query (case-insensitive substring) into the
existing selection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Collaborator

Both findings fixed in the latest commit.

  • a11y: removed aria-hidden="true" from the label <span> — it is a visible label with no reason to hide from AT, and the accname spec drops referenced aria-hidden nodes from the computed name.
  • behavior: added onKeyDown on Combobox.Input; when metaKey && key === "a", filters items by the current input value (case-insensitive substring, mirroring Base UI built-in filtering) and merges all visible matches into the selection via Array.from(new Set([...value, ...visible])). e.preventDefault() suppresses browser select-all on the input text.
Both findings fixed in the latest commit. - **a11y**: removed `aria-hidden="true"` from the label `<span>` — it is a visible label with no reason to hide from AT, and the accname spec drops referenced aria-hidden nodes from the computed name. - **behavior**: added `onKeyDown` on `Combobox.Input`; when `metaKey && key === "a"`, filters `items` by the current input value (case-insensitive substring, mirroring Base UI built-in filtering) and merges all visible matches into the selection via `Array.from(new Set([...value, ...visible]))`. `e.preventDefault()` suppresses browser select-all on the input text.
Author
Collaborator

Both findings fixed in the latest commit.

  • a11y: removed aria-hidden="true" from the label span -- it is a visible label, and the accname spec drops aria-hidden nodes from the computed name of a referencing aria-labelledby.
  • behavior: added onKeyDown on Combobox.Input; when metaKey && key === "a", filters items by the current search query (case-insensitive substring) and merges visible matches into the selection via Array.from(new Set([...value, ...visible])). e.preventDefault() suppresses browser select-all on the input text.
Both findings fixed in the latest commit. - **a11y**: removed `aria-hidden="true"` from the label span -- it is a visible label, and the accname spec drops aria-hidden nodes from the computed name of a referencing aria-labelledby. - **behavior**: added `onKeyDown` on `Combobox.Input`; when `metaKey && key === "a"`, filters items by the current search query (case-insensitive substring) and merges visible matches into the selection via `Array.from(new Set([...value, ...visible]))`. `e.preventDefault()` suppresses browser select-all on the input text.
dev requested review from reviewer 2026-04-30 19:44:12 +00:00
reviewer approved these changes 2026-04-30 19:46:06 +00:00
reviewer left a comment

Both prior findings addressed. CI green.

  • aria-hidden removed from the label span — accessible name now resolves correctly via aria-labelledby.
  • handleInputKeyDown on Combobox.Input implements ⌘A correctly: filters visible items by current query, merges into selection, prevents default.
Both prior findings addressed. CI green. - `aria-hidden` removed from the label span — accessible name now resolves correctly via `aria-labelledby`. - `handleInputKeyDown` on `Combobox.Input` implements ⌘A correctly: filters visible items by current query, merges into selection, prevents default.
charles deleted branch feat/583 2026-04-30 19:46:50 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!587
No description provided.