Repository navigation
Move the chat Mention Picker status out of its listbox #339
Description
Parent
What to build
The chat Mention Picker puts its status region inside role="listbox". A listbox should hold only options and groups. The visible "No members found" and "Couldn't load members" texts also sit in the listbox, so browse mode can read the empty text twice.
After the fix, the status is a sibling of the listbox, and each empty or error text is spoken once. This follows the picker status rule from #333.
Acceptance criteria
- The
role="status"region is a sibling of the listbox, not a child. It stays outside the composer's.ProseMirror. - In the accessibility tree, the listbox holds only options and
role="group"sections. - The visible "No members found" and "Couldn't load members" texts carry
aria-hidden, so the status speaks them once. - The status texts do not change: "Couldn't load members", "Loading members", "No members found", and the option count.
-
aria-controls,aria-activedescendantand the listbox id work as before. - The popup looks the same in light and dark, on desktop and phone.
- The "Mention picker popup" entry in
.cursor/docs/design-system.mdstates where the status sits. It also names the two differences from the Slash menu recipe (see Blocked by).
Blocked by
- Announce an empty Slash menu result, and give each option a stable id #333 — it adds the picker status recipe to the design system. This issue follows its placement rule: one polite region, outside the listbox and outside
.ProseMirror. Two differences are on purpose. The visible empty and error texts stay under the Members header, markedaria-hidden, so the status is a separate node. The status keeps the option count.
Agent brief
Type: AFK — an agent can finish this alone.
Category: bug
Current behavior:
MentionSuggestionsreturns one rootdivwithrole="listbox". Its first child is<div role="status" aria-live="polite" className="sr-only">(apps/webapp/src/components/chatroom/components/MessageComposer/helpers/MentionSuggestions.tsx:78-87).- The Members group renders "Couldn't load members" and "No members found" as plain
divs inside the listbox (MentionSuggestions.tsx:110-131). - The popup is appended to
document.body, outside the composer editor (helpers/suggestion.ts:73-85).
Desired behavior:
MentionSuggestionsreturns a fragment: the listbox, then the status node as its sibling. Both stay inside the popup element.- Keep
ref={listRef},id={MENTION_LISTBOX_ID},aria-labeland the scroll classes on the listbox. - The status node keeps
sr-only, so the layout does not change. - Add
aria-hiddento the two visible texts.
Where to start: MentionSuggestions.tsx (MentionSuggestions, statusText). Then the "Mention picker popup" entry in .cursor/docs/design-system.md §Floating overlays. Update its "loading / error / empty" entry. The status is an sr-only polite sibling of the listbox. The visible empty and error texts are aria-hidden. The status keeps the count.
Line numbers are hints as of 2026-09-28; the agent searches by symbol.
Rules that apply:
apps/webapp/src/components/chatroom/CLAUDE.md§Mention Picker: mirror, do not import across features. Import no Slash menu code. Keep the 4-layer split.apps/webapp/src/components/TipTap/CLAUDE.md§Editor Performance: no live region inside.ProseMirror.AGENTS.md§Test Policy: add no new test.
Verify:
bun run check- Start the local stack and the webapp dev server. Sign in, then open a pad and a heading chat as a member. Visitors skip the member search, so they never see "No members found". To sign in, use an email link, or a
packages/document-swarmactor (seepackages/document-swarm/README.mdandAGENTS.md§Test Policy). - In the composer, type
@zzzz. "No members found" shows. In the accessibility tree, the status node is outside the listbox. The visible text is hidden from the tree. - In DevTools, block requests that match
fetch_mentioned_users. Type@a. "Couldn't load members" shows. The status holds that text, and the visible text is hidden from the tree. Remove the block. - Type
@and a real member name. The row shows, and arrow keys still move the active row. - Check light and dark (set
data-themeon<html>). - Phone: a narrow window does not produce the mobile shell. Use a real phone, or a mobile user agent on a real pad route.
Out of scope
- Dropping the option count from the status. That follows the maintainer screen-reader walk in Announce an empty Slash menu result, and give each option a stable id #333.
- Index-based mention option ids (
mentionOptionId(index)). - The member search, its debounce, or the Notify row.
Activity