Skip to content

Move the chat Mention Picker status out of its listbox #339

Description

@HMarzban

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

  • 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.

Out of scope

Activity

  1. added
    bugSomething isn't working
    ChatRelated to chat features
    on Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ChatRelated to chat featuresUIbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions