You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Move the chat Mention Picker status out of its listbox #339
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-activedescendant and 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.md states 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, marked aria-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:
MentionSuggestions returns one root div with role="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:
MentionSuggestions returns 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-label and the scroll classes on the listbox.
The status node keeps sr-only, so the layout does not change.
Add aria-hidden to 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-swarm actor (see packages/document-swarm/README.md and AGENTS.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-theme on <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.
Parent
#328. Related: #299
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
role="status"region is a sibling of the listbox, not a child. It stays outside the composer's.ProseMirror.role="group"sections.aria-hidden, so the status speaks them once.aria-controls,aria-activedescendantand the listbox id work as before..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
.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).divs inside the listbox (MentionSuggestions.tsx:110-131).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.ref={listRef},id={MENTION_LISTBOX_ID},aria-labeland the scroll classes on the listbox.sr-only, so the layout does not change.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 ansr-onlypolite sibling of the listbox. The visible empty and error texts arearia-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 checkpackages/document-swarmactor (seepackages/document-swarm/README.mdandAGENTS.md§Test Policy).@zzzz. "No members found" shows. In the accessibility tree, the status node is outside the listbox. The visible text is hidden from the tree.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.@and a real member name. The row shows, and arrow keys still move the active row.data-themeon<html>).Out of scope
mentionOptionId(index)).