Skip to content

compose: Improve message view screen-reader focus order#6536

Merged
andremion merged 6 commits into
developfrom
andrerego/and-1280-message-view-screen-reader-order
Jul 3, 2026
Merged

compose: Improve message view screen-reader focus order#6536
andremion merged 6 commits into
developfrom
andrerego/and-1280-message-view-screen-reader-order

Conversation

@andremion

@andremion andremion commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Goal

Make the TalkBack focus order in the message view predictable and match the visual reading order, without changing the reversed message list layout that chat relies on. Before this change the reading cursor could enter the header on the subtitle, jump from the composer straight to the header and skip the messages, and, moving backwards from the composer, land inside a reaction or an attachment of a neighbouring message instead of on the message itself.

Resolves AND-1280

Implementation

  • Header: group the title and subtitle so the header reads back button, heading, subtitle, avatar.
  • Regions: mark the header, message list, and composer as ordered traversal regions (header, then list, then composer), because a Scaffold does not reliably sort its scrollable content between the top and bottom bars for screen readers.
  • Message list (the core fix): TalkBack rebuilds its own traversal from the Compose semantics tree order and silently drops any ordering hint that points from a node to its own descendant (a message cell hinted before its inner button). A reversed list composes newest-first but reads oldest-first, so those hints were constantly fought and the message cells got stranded, which is what made backward navigation skip messages and jump around. Each lazy item now carries a zIndex that mirrors its traversal index, so the semantics tree order equals the reading order and TalkBack has nothing to rearrange. The reactions bubble is pinned to its tree position for the same reason.
  • Docs: add stream-chat-android-compose/ACCESSIBILITY.md, a playbook covering the leaf-first principles, the semantics decision tree, and the traversal-order model with the diagnostics used to verify it.

The interactive parts of a message (open attachment, vote, View Results, reactions) stay individually reachable. They are not collapsed into a single stop, so poll voting and attachment actions remain usable.

No public API changes.

🎨 UI Changes

Before After
Bottom-up (swipe back from the composer)
Screen_recording_20260702_144920.webm
Screen_recording_20260702_131950.webm
Top-down (swipe forward from the header)
Screen_recording_20260702_144956.webm
Screen_recording_20260702_132040.webm

Testing

Manual, with TalkBack enabled on a device or emulator:

  1. Open a channel with a mix of messages (for example a group that has a poll, an image message, and a message with reactions).
  2. On entry, focus lands on the composer input (existing behaviour).
  3. Sweep through the screen and confirm the order matches the visual order: the header reads back button, heading, subtitle, avatar; the regions read header, then messages, then composer.
  4. Swipe left (backwards) from the composer: attachment picker, then the newest message, then older messages in order, and the header only after the oldest loaded message. Focus should not jump between messages or land inside a neighbour's reaction or attachment.
  5. On a poll message, focus "View Results" and swipe left: focus lands on the poll message, not on a neighbouring message.
  6. Confirm the interactive parts stay reachable and actionable: double-tap a reactions row, a poll option, "View Results", and an attachment.

One Compose platform limitation is out of scope and was accepted with QA: TalkBack speaks a region-name fragment when the reading cursor crosses between the header, list, and composer regions, which cannot be suppressed.

Summary by CodeRabbit

  • Accessibility
    • Improved screen-reader navigation across channel screens, message lists, headers, reactions, and the message composer.
    • Refined reading order so content is announced more consistently, including in reversed message lists.
    • Added accessibility guidance and verification notes for future updates.

andremion added 4 commits July 2, 2026 14:37
The title and subtitle were traversed by raw element position, so a screen
reader could enter the header on the subtitle instead of following the visual
order. Group the title and subtitle as one traversal unit so the header reads
back button, heading, subtitle, avatar.
A Material Scaffold does not reliably sort its scrollable content between the
top and bottom bars for accessibility traversal, so the reading cursor could
jump from the composer straight to the header, skipping the messages. Mark the
header, message list, and composer as traversal regions with explicit order
(header first, composer last) so traversal is header, messages, composer.
TalkBack rebuilds its traversal tree from the traversalBefore links that
Compose computes, relocating each link's source next to its target, and
it silently drops links from a node to its own descendant, such as a
message cell linked before its inner button. A reverseLayout list
composes newest-first but reads oldest-first, so nearly every link
demanded a relocation, and the dropped cell links left backward
navigation skipping the message cells and jumping across messages.

Give each lazy item a zIndex that mirrors its traversal index. Compose
orders sibling semantics nodes by zIndex, so this makes the semantics
tree order equal to the reading order, the links match the natural
order, and TalkBack's rebuild becomes a no-op. Pin the reading-order
slot of the reactions bubble to its tree position (last in the cell)
for the same reason.
Document the leaf-first / trust-defaults principles, the semantics
decision tree, and the TalkBack traversal-order model (semantics tree
order must match reading order) with the diagnostics used to verify it.
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled, or the PR is bot-authored.
  • An issue is linked (Linear ticket or GitHub issue), or the PR is bot-authored.

🎉 Great job! This PR is ready for review.

@andremion andremion added pr:bug Bug fix pr:improvement Improvement and removed pr:bug Bug fix labels Jul 2, 2026
@andremion

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 5.93 MB 5.93 MB 0.00 MB 🟢
stream-chat-android-ui-components 11.20 MB 11.20 MB 0.00 MB 🟢
stream-chat-android-compose 12.66 MB 12.66 MB 0.00 MB 🟢

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR adds explicit Jetpack Compose semantics-based traversal ordering to align TalkBack screen-reader focus order with visual layout across ChannelScreen, ChannelHeader, MessageContainer, and Messages, and adds ACCESSIBILITY.md documenting accessibility guidance and anti-patterns.

Changes

Compose accessibility traversal order

Layer / File(s) Summary
ChannelScreen traversal regions and helper
stream-chat-android-compose/.../ChannelScreen.kt
Adds a traversalRegion modifier and index constants (header/list/composer); wraps topBar, bottomBar, and MessageList content in Boxes to set explicit traversal order.
ChannelHeader title/subtitle traversal group
stream-chat-android-compose/.../header/ChannelHeader.kt
Marks the header title/subtitle Column as an ordered traversal group via isTraversalGroup.
Message reactions traversal group
stream-chat-android-compose/.../list/MessageContainer.kt
Wraps message reactions in a Box with isTraversalGroup and traversalIndex to control focus order within message bubbles.
Messages list item traversal ordering
stream-chat-android-compose/.../list/Messages.kt
Marks the LazyColumn as a traversal group and introduces itemTraversalOrder to explicitly order footer, loading, header, and message items, compensating for reverseLayout.
Accessibility guidance documentation
stream-chat-android-compose/ACCESSIBILITY.md
New document with an accessibility decision tree, TalkBack traversal explanation, diagnostics steps, anti-patterns table, and a verification checklist.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TalkBack
  participant ChannelScreen
  participant ChannelHeader
  participant Messages
  participant MessageContainer

  TalkBack->>ChannelScreen: traverse semantics tree
  ChannelScreen->>ChannelHeader: focus header region (traversalIndex -1)
  ChannelHeader-->>TalkBack: announce title/subtitle group
  ChannelScreen->>Messages: focus list region (traversalIndex 0)
  Messages->>MessageContainer: order message items (itemTraversalOrder)
  MessageContainer-->>TalkBack: announce message then reactions group
  ChannelScreen->>ChannelScreen: focus composer region (traversalIndex 1)
Loading

Possibly related PRs

Suggested reviewers: gpunto, VelikovPetar

Poem

A hop, a skip, through semantics trees,
TalkBack now flows with graceful ease.
Header, then list, then composer's call,
Traversal indices order it all.
🐰 With docs in hand to guide the way,
No more lost focus, come what may!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: improving Compose message-view screen-reader focus order.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description includes Goal, Implementation, UI Changes, and Testing, and it matches the template’s core structure.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch andrerego/and-1280-message-view-screen-reader-order

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@andremion andremion marked this pull request as ready for review July 2, 2026 13:53
@andremion andremion requested a review from a team as a code owner July 2, 2026 13:53
The message list now orders its semantics tree oldest-first so screen
readers traverse it in reading order, which makes the UiAutomator
enumeration order oldest-first as well. The e2e position lookups indexed
message cells through the enumeration order and assumed newest-first, so
they started acting on the wrong message (quote, delete, open thread).
Sort the matched cells by their on-screen position (bottom-most first)
so the lookups keep their newest-first meaning regardless of the
accessibility enumeration order.
@andremion andremion force-pushed the andrerego/and-1280-message-view-screen-reader-order branch from 5ee98c3 to 552b670 Compare July 3, 2026 07:38
@andremion andremion enabled auto-merge (squash) July 3, 2026 07:52
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

@andremion andremion merged commit 7eee615 into develop Jul 3, 2026
19 checks passed
@andremion andremion deleted the andrerego/and-1280-message-view-screen-reader-order branch July 3, 2026 13:45
@stream-public-bot stream-public-bot added the released Included in a release label Jul 13, 2026
@stream-public-bot

Copy link
Copy Markdown
Contributor

🚀 Available in v7.6.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:improvement Improvement released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants