Problem
The composer input sets tabIndex={1} on its wrapper and tabIndex={2} on EditorContent (apps/webapp/src/components/chatroom/components/MessageComposer/components/Input/Input.tsx:27, :31). A search of apps/webapp/src on 14ab7f9c1 finds no other positive tab index.
A positive tab index puts an element ahead of every normal element in the page tab order. When a composer is on screen, the first Tab press from the top of the page goes to it, ahead of the pad header. EditorContent passes the prop to its wrapper div. So the composer input gives two focus stops with no role and no name, before the editable text area.
This fails WCAG 2.2 success criterion 2.4.3, Focus Order. Both values arrived in 83cb3afa9 (refactor: emoji picker v0.5, 2025-07-24). The commit message gives no reason.
One Cypress spec depends on the second value. data-testid="composer-input" sits on the same element as tabIndex={2} (Input.tsx:30-31). apps/webapp/cypress/e2e/chatroom/send-and-retry.cy.ts types into that test id. Cypress refuses .type() on an element that is neither focusable nor editable.
Steps to reproduce
- On desktop, open a pad as a channel member, or signed out. Open a heading chat, and copy its Share link. The link carries
?chatroom=.
- Open that link in a new tab. Wait until the composer shows. Without clicking the page, press Tab once.
- See focus land on the composer input wrapper, not on the first control of the page.
- Press Tab again. See focus move to a second wrapper. The text area still has no caret.
Acceptance criteria
Agent Brief
Category: bug
Summary: Remove the two positive tab indexes, so the composer input is one focus stop in document order.
Current behavior:
The composer input wrapper and the EditorContent wrapper take focus first in the page, as two unnamed focus stops before the editable text area.
Desired behavior:
Inside the composer input, only the editable text area is focusable. It is reached in normal document order.
Key interfaces:
- The composer
Input component.
EditorContent from @tiptap/react — it forwards extra props to its wrapper div.
- The
composer-input test id — a Cypress spec types into it, so the spec must target a focusable or editable element.
Out of scope
Notes
Run the send-and-retry spec with NEXT_PUBLIC_E2E=true on the webapp dev server. Without it, the /c/test-channel page renders nothing, and each test times out. At 14ab7f9c1, the second and third tests also fail on a data-status check, which #263 fixes. All three tests type into the composer first, so the .type() criterion can still be checked.
The evidence is a code trace and a repo search on 14ab7f9c1. The steps above were not run in a browser.
Problem
The composer input sets
tabIndex={1}on its wrapper andtabIndex={2}onEditorContent(apps/webapp/src/components/chatroom/components/MessageComposer/components/Input/Input.tsx:27,:31). A search ofapps/webapp/srcon14ab7f9c1finds no other positive tab index.A positive tab index puts an element ahead of every normal element in the page tab order. When a composer is on screen, the first Tab press from the top of the page goes to it, ahead of the pad header.
EditorContentpasses the prop to its wrapperdiv. So the composer input gives two focus stops with no role and no name, before the editable text area.This fails WCAG 2.2 success criterion 2.4.3, Focus Order. Both values arrived in
83cb3afa9(refactor: emoji picker v0.5, 2025-07-24). The commit message gives no reason.One Cypress spec depends on the second value.
data-testid="composer-input"sits on the same element astabIndex={2}(Input.tsx:30-31).apps/webapp/cypress/e2e/chatroom/send-and-retry.cy.tstypes into that test id. Cypress refuses.type()on an element that is neither focusable nor editable.Steps to reproduce
?chatroom=.Acceptance criteria
apps/webapp/srchas a positivetabIndex.send-and-retryspec still types into the composer. Its.type()calls target the editable element inside thecomposer-inputtest id.Agent Brief
Category: bug
Summary: Remove the two positive tab indexes, so the composer input is one focus stop in document order.
Current behavior:
The composer input wrapper and the
EditorContentwrapper take focus first in the page, as two unnamed focus stops before the editable text area.Desired behavior:
Inside the composer input, only the editable text area is focusable. It is reached in normal document order.
Key interfaces:
Inputcomponent.EditorContentfrom@tiptap/react— it forwards extra props to its wrapperdiv.composer-inputtest id — a Cypress spec types into it, so the spec must target a focusable or editable element.Out of scope
Notes
Run the
send-and-retryspec withNEXT_PUBLIC_E2E=trueon the webapp dev server. Without it, the/c/test-channelpage renders nothing, and each test times out. At14ab7f9c1, the second and third tests also fail on adata-statuscheck, which #263 fixes. All three tests type into the composer first, so the.type()criterion can still be checked.The evidence is a code trace and a repo search on
14ab7f9c1. The steps above were not run in a browser.