Skip to content

fix(chat): screen readers hear each message's text, time and tool position - #2070

Closed
fire0220 wants to merge 1 commit into
openclaw:mainfrom
fire0220:fix/1944-chat-message-navigation
Closed

fire0220 wants to merge 1 commit into
openclaw:mainfrom
fire0220:fix/1944-chat-message-navigation

Conversation

@fire0220

@fire0220 fire0220 commented Nov 7, 2026 •

Copy link
Copy Markdown

Closes #1944

What Problem This Solves

Fixes: with a screen reader, the chat transcript is announced as one unnamed list, Up and Down do not move between messages, each message is announced as raw markdown with no send time, and repeated tool calls in one activity all announce the same text.

User Impact

User impact: screen reader users can Tab into the chat history, hear one message at a time with its sender, literal text and the time shown in the window, press Up or Down to walk the transcript, and hear each nested tool call's position and input summary instead of an identical label.

Why This Change Was Made

improve(chat): align native conversation and tool activity with web (#2093) rebuilt the transcript but left its ItemContainers unnamed, unfocusable and without a key handler, so the rows were unreachable from the keyboard and NVDA read the list as one group.

This change names each focusable container from the visible message text (markdown literals kept, no "code block" placeholder) plus the same timestamp the footer shows, makes message containers Tab stops, and moves focus with Up/Down through the parent ItemsRepeater. Nested tool calls keep their name, status and input, and add a localized position when a card is one of several.

Evidence

  • New UI proof MessageRows_AnnounceLiteralMarkdownAndTheVisibleTime mounts the production timeline renderer and asserts: the row container is a Tab stop, its name contains the literal fenced body 09:00 Focus time, snake_case and a<b (no code block), and it carries the visible time in the current UI language.
  • New UI proof RepeatedToolRows_AnnounceTheirPositionAndInput mounts two powershell cards with command: dir / command: pwd in one activity and asserts the localized position label plus each input summary.
  • Real shipped-app proof with NVDA on the PR head, including one-at-a-time Up/Down speech, literal text, tool-call positions and the message action button: see Real Behavior Proof below.
Raw (redacted) NVDA + UI Automation evidence
你: [user message body redacted], 下午10:28
花火: 电脑重启后,我先确认网关进程是不是新的,再逐项检查功能。, 下午10:29
花火: 网关进程已经是新的了(PID 11508,22:19:59 启动,系统开机 41 分钟)。接下来逐项验证功能。, 下午10:29

Speaking ['[user message body redacted], 下午10:28', '未选择', ',48 之 35 项', …]
Speaking ['花火: 电脑重启后,我先确认网关进程是不是新的,再逐项检查功能。, 下午10:29', '未选择', ',48 之 36 项', …]
Speaking ['花火: 网关进程已经是新的了(PID 11508,22:19:59 启动,系统开机 41 分钟)。接下来逐项验证功能。, 下午10:29', '未选择', ',48 之 38 项', …]
Speaking ['花火: 电脑重启后,我先确认网关进程是不是新的,再逐项检查功能。, 下午10:29', '未选择', ',48 之 36 项', …]

Speaking ['活动:2 个命令。2 个工具。已展开。', '按钮', '已展开', …]
Speaking ['工具调用 exec. 完成. 第 1 个,共 2 个. Check channels and plugins after reboot', '按钮', '已折叠', …]
Speaking ['工具调用 process. 完成. 第 2 个,共 2 个.', '按钮', '已折叠', …]

Speaking ['复制消息', '按钮', …]

Change Type

  • Bug fix

Scope

  • Tray or WinUI UX

Required proof pools

  • windows-winui-interactive: native Up/Down navigation and NVDA speech are covered above on a current-head isolated app; a maintainer-owned interactive run can confirm it on their desktop.

Validation

Host note: this machine has a zh-CN UI, so a number of pre-existing UI tests fail on it; the same failures reproduce on a clean upstream/main worktree (see below), and the two workarounds used for the .NET test host (local temp output and a workspace-local OPENCLAW_REPO_ROOT) do not touch product code.

  • ./build.ps1 — pass (prerequisites, docs validation, Shared/Cli/WinNodeCli/SetupEngine/WinUI builds).
  • dotnet test tests/OpenClaw.Tray.Tests -c Debug --no-build — 5843 passed, 0 failed.
  • dotnet test tests/OpenClaw.Shared.Tests -c Debug --no-build — 5411 passed, 28 skipped, 0 failed.
  • dotnet test tests/OpenClaw.Connection.Tests -c Debug --no-build — 1746 passed, 1 skipped, 0 failed.
  • dotnet test tests/OpenClaw.WinNode.Cli.Tests -c Debug --no-build — 141 passed, 0 failed.
  • dotnet test tests/OpenClaw.Tray.IntegrationTests -c Debug --no-build — 53 passed, 25 skipped, 0 failed.
  • dotnet test tests/OpenClawTray.FunctionalUI.Tests -c Debug -r win-x64 --no-build — 19 passed, 0 failed.
  • dotnet test tests/OpenClaw.SetupEngine.Tests -c Debug --no-build — 2595 passed, 2 failed, 4 skipped; both failures are host-specific (WSL prints localized output, and a fixture path with an apostrophe), and the same two fail on a clean upstream/main worktree.
  • UI lane Category!=Accessibility&Category!=NativeOnboardingProof&Category!=GatewayFixture: 605 passed / 276 failed / 9 skipped on this branch; clean upstream/main f18317a6 produces 604 / 276 / 9 with an identical failed-test name set, so the failures are pre-existing zh-CN/desktop environment issues, not regressions.

Real Behavior Proof

  • Environment tested: Windows 11 10.0.26320.0; MSIX OpenClawFoundation.OpenClaw.Dev 2026.11.1.13 (Debug) built from this head; NVDA 2026.3.1; local Gateway ws://127.0.0.1:18789.
  • PR head or commit tested: faa5aa54.
  • Exact steps or command run: focused a transcript row through UI Automation, then sent foreground native Down, Down, Up; expanded tool activity ChatToolActivity_e120 and focused its two nested cards; read %TEMP%\\nvda.log DEBUG speech deltas by byte offset.
  • Evidence after fix: row names/UIA names carry sender, literal markdown and 下午10:28-style times; each arrow press produced a new event_gainFocus plus speech and the list position moved 35 → 36 → 38 → 36; tool cards spoke 第 1 个,共 2 个 / 第 2 个,共 2 个 with the input preview; the focused row's action button spoke 复制消息, 按钮.
  • Observed result: one message per stop, literal text and times audible, Up/Down working in both directions, and repeated nested tool calls distinguishable.
  • Screenshot or artifact links verified? (Yes/No/N/A): N/A — evidence is copied UI Automation output and NVDA log lines.
  • Not verified or blocked: no screen capture video; the maintainer-owned interactive proof pool can re-run it on their desktop.

Security Impact

  • New permissions or capabilities? (No)
  • Secrets or tokens handling changed? (No)
  • New or changed network calls? (No)
  • Command or tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any answer is Yes, explain the risk and mitigation: n/a

Compatibility and Migration

  • Backward compatible? (Yes)
  • Config or environment changes? (No)
  • Migration needed? (No)
  • If yes, list the exact upgrade steps: n/a

Review Conversations

  • I replied to or resolved every bot review conversation addressed by this PR.
  • I left unresolved only conversations that still need maintainer judgment.

@clawsweeper

clawsweeper Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. P1 Urgent regression or broken agent/channel workflow affecting real users now. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. labels Nov 7, 2026
@clawsweeper

clawsweeper Bot commented Nov 7, 2026 •

Copy link
Copy Markdown

Codex review: needs changes before merge.

What this changes

This PR adds keyboard navigation and spoken message names, timestamps, and tool-call positions to native chat.

Example: NVDA reads two successful powershell calls with inputs dir and pwd.

  • Before: Both calls announce the same tool name and status.
  • After: Each announces its position and distinguishing input preview.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) Useful and supported by shipped-app runtime evidence, with three source-verifiable accessibility defects remaining.
Proof confidence 🐚 platinum hermit (4/6) Sufficient (logs): Current-head Windows MSIX UIA diagnostics and NVDA speech exercise the production timeline through native Down/Down/Up input, announce message text and local times, distinguish nested tools by position and preview, and reach Copy; this satisfies the previous runtime-proof request. Inspected downloaded screenshots belong to historical related work and do not establish this head's behavior.
Patch quality 🦐 gold shrimp (3/6) 3 actionable review findings remain.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Screen-reader users cannot review chat messages individually or distinguish repeated tool calls.
Reason: The linked report establishes an accessibility failure in a shipped primary workflow, and the patch repairs its existing presentation owners.

Merge readiness

⛔ Needs changes before merge - 4 items remain

This PR addresses a confirmed accessibility bug that remains on main, but three message-reading defects need correction before merge.

Priority: P1
Reviewed head: faa5aa54a73fbab8b774679d2664805aec8eff3b

Before merge

  • Preserve code literals while constructing assistant names (P2) - The previous literal-content finding is only partly resolved: after removing code delimiters, these replacements interpret the code body as Markdown. An assistant reply containing fenced Python def __init__(self): is announced as def init(self):; inline code containing [x](y) similarly loses (y). Preserve fenced and inline code contents verbatim before processing surrounding prose, or retain the literal source in the automation name, and extend the mounted regression with these inputs.
  • Include the user sender in each user-message name (P2) - UserRowAutomationName passes only BuildAccessibleUserText and the timestamp, and that helper contains no sender label. A user message Yes therefore announces Yes, 10:47, while assistant rows explicitly identify their speaker. This leaves the linked report's who-said-what requirement unmet and contradicts the PR's sender announcement claim. Prefix the name using the existing user identity or sender-label presentation input and assert it in the mounted message-row test.
  • Keep named message focus stops inside Worked disclosures (P2) - Removing BuildAssistant's automation name assumes every assistant message receives the new ItemContainer name. Dashboard narration inside an expanded Worked disclosure instead reaches BuildEntry directly through BuildRow's buildWorkEntry callback and never passes through that container factory. For the existing First inspection note / Second inspection note scenario, both messages consequently lack the new named message focus stop, and Up/Down only traverses outer transcript containers. Apply the message accessibility contract to this existing embedded-message path without duplicating names on top-level messages, and cover the expanded-work scenario.
  • Explain or restore the original intent of Message names inside expanded Worked groups - 243f9cf: Completed-work narration is rendered through BuildEntry with individual message ownership; moving names exclusively to outer containers overlooks this caller.

Findings

  • [P2] Preserve code literals while constructing assistant names — src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:927-935
  • [P2] Include the user sender in each user-message name — src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:863-864
  • [P2] Keep named message focus stops inside Worked disclosures — src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:791-792

Provenance

  • Message names inside expanded Worked groups changes intended behavior without addressing why it exists (243f9cf: Completed-work narration is rendered through BuildEntry with individual message ownership; moving names exclusively to outer containers overlooks this caller.)

Tests

  • Missing end-to-end proof: The added UI tests are not shown failing on the base and omit code containing Markdown-like literals, user sender labels, and named focus stops inside expanded Worked narration.
Agent review details

How this fits together

The native chat timeline projects conversation entries into Reactor controls; message containers and tool expanders expose their text and focus behavior through Windows UI Automation.

flowchart LR
  A[Conversation entries] --> B[Timeline projection]
  B --> C[Message containers]
  B --> D[Tool activity renderer]
  C --> E[Windows UI Automation]
  D --> E
  E --> F[NVDA speech]
Loading

Technical review

Best possible solution:

Repair message accessibility in the existing timeline and tool owners while preserving code literals, sender identity, expanded-work narration, virtualization, and message actions.

Do we have a high-confidence way to reproduce the issue?

Current-main source confirms the reporter's unnamed, unfocusable message containers and indistinguishable repeated tool names; this review performed no live reproduction.

Is this the best way to solve the issue?

The existing owners are appropriate and no new subsystem is needed, but the message-name projection must preserve literal code and cover both top-level and expanded-work messages.

Full review comments:

  • [P2] Preserve code literals while constructing assistant names — src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:927-935
    The previous literal-content finding is only partly resolved: after removing code delimiters, these replacements interpret the code body as Markdown. An assistant reply containing fenced Python def __init__(self): is announced as def init(self):; inline code containing [x](y) similarly loses (y). Preserve fenced and inline code contents verbatim before processing surrounding prose, or retain the literal source in the automation name, and extend the mounted regression with these inputs.
    Confidence: 0.99
  • [P2] Include the user sender in each user-message name — src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:863-864
    UserRowAutomationName passes only BuildAccessibleUserText and the timestamp, and that helper contains no sender label. A user message Yes therefore announces Yes, 10:47, while assistant rows explicitly identify their speaker. This leaves the linked report's who-said-what requirement unmet and contradicts the PR's sender announcement claim. Prefix the name using the existing user identity or sender-label presentation input and assert it in the mounted message-row test.
    Confidence: 0.99
  • [P2] Keep named message focus stops inside Worked disclosures — src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:791-792
    Removing BuildAssistant's automation name assumes every assistant message receives the new ItemContainer name. Dashboard narration inside an expanded Worked disclosure instead reaches BuildEntry directly through BuildRow's buildWorkEntry callback and never passes through that container factory. For the existing First inspection note / Second inspection note scenario, both messages consequently lack the new named message focus stop, and Up/Down only traverses outer transcript containers. Apply the message accessibility contract to this existing embedded-message path without duplicating names on top-level messages, and cover the expanded-work scenario.
    Confidence: 0.96

Overall correctness: patch is incorrect
Overall confidence: 0.96

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 9d36f5c8bfa0.

Provenance checked

  • Reactor message realization and focus keeps the original intent (9acafa6: The Reactor migration established stable-key native containers, non-selection, and existing message actions; non-selection remains intact.)
  • Tool-call announcements keeps the original intent (aac07ab: Compact disclosures retained tool identity and inspectable details; localized position and preview extend that contract.)
  • Visible timestamp formatting changes intended behavior with a stated reason (9acafa6: Footers used local h:mm tt formatting; the PR shares formatting with accessible names and explains the Chinese-language ordering.)

Testing

Proof path: shipped entry point.

Security

None.

Evidence

Acceptance criteria:

  • ./build.ps1
  • dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore
  • dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore
  • dotnet test ./tests/OpenClaw.Tray.UITests/OpenClaw.Tray.UITests.csproj -c Debug -r win-x64 --filter "FullyQualifiedNameMessageRows_AnnounceLiteralMarkdownAndTheVisibleTime|FullyQualifiedNameRepeatedToolRows_AnnounceTheirPositionAndInput|FullyQualifiedName~ExpandedWork_MessageFooters"
  • dotnet test ./tests/OpenClaw.Tray.UITests/OpenClaw.Tray.UITests.csproj -c Debug -r win-x64 --filter Category=Accessibility

What I checked:

Likely related people:

  • Barbara Kudiess: Raw commit 243f9cf adds src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:435 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 243f9cff53d8; files: src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs)
  • Caleb Eden: Raw commit aac07ab adds src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs:257 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: aac07abb1fbd; files: src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs)

Preflight source

Source unknown. Source comparison at the observed PR snapshot only. Completion is self-reported; body, policy, tests, execution, and report bytes are not verified.

Root-cause cluster

Relationship: fixed_by_candidate
Canonical: #1944
Summary: This PR is the proposed repair for the linked report's message-focus and repeated-tool announcement defects.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Labels

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P1: The confirmed installed-build defect prevents NVDA users from reviewing a primary chat workflow message by message.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🐚 platinum hermit and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.
  • proof: sufficient: Contributor real behavior proof is sufficient.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

History

Review history (1 earlier review cycle)
  • reviewed 2026-11-07T20:51:28.521Z sha cf0a97d :: needs real behavior proof before merge. :: [P2] Preserve literal message content in assistant automation names

Reviewed November 10, 2026, 11:40 AM ET / 16:40 UTC (Revision 2).

…position

Transcript rows were unnamed, unfocusable and announced raw markdown, so NVDA read the whole history as one group and Up/Down did nothing. Name each ItemContainer from the visible message text and time, keep markdown literals in the name, make containers tab stops with Up/Down navigation, and add a localized position and input summary to repeated nested tool calls.
@fire0220
fire0220 force-pushed the fix/1944-chat-message-navigation branch from 3aa6017 to faa5aa5 Compare November 10, 2026 16:32
@fire0220 fire0220 changed the title fix: let screen reader users review chat messages one at a time fix(chat): screen readers hear each message's text, time and tool position Nov 10, 2026
@fire0220

Copy link
Copy Markdown
Author

Rebuilt this fix on top of improve(chat): align native conversation and tool activity with web (#2093), which replaced the timeline this PR originally patched.

Current head faa5aa54 addresses the review's actionable P2 (literal markdown in the accessible name, per-row focus) and adds the missing proof on the shipped app: NVDA speech for one-at-a-time Up/Down, literal text and visible time, nested tool-call position plus input, and the message action button. The updated description has the commands, counts and redacted log lines.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. clawsweeper:intake:bug Preliminary submission classification; full source review pending. labels Nov 10, 2026
@roboclaw-bot

Copy link
Copy Markdown
Contributor

PR cleanup review

Reviewed head: faa5aa54a73fbab8b774679d2664805aec8eff3b. Main comparison: e59374c064f43d68f5c72d070d4c89ef2d20ec3b.

Source cleanup review at faa5aa5 against main e59374c found three remaining gaps: (1) ReactorChatTimeline.cs:925-935 strips code delimiters before Markdown rewriting, so literal init and x content is altered in the accessible name; (2) lines 857-864 omit the user speaker label; (3) the Worked commentary path at 458-462 bypasses the new outer name after the inner assistant name was removed. The existing NVDA evidence is valuable, but these paths need focused coverage and a rerun. Current-head checks also have no CI Gate/build-test receipt yet.

Action in this cleanup pass: leaving this PR open. No branch changes or runtime-proof waiver. The findings above come from source, discussion and recorded-CI review; Windows validation was not rerun in this pass.

@fire0220 fire0220 closed this Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P1 Urgent regression or broken agent/channel workflow affecting real users now. proof: sufficient Contributor real behavior proof is sufficient. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Screen reader cannot read chat messages one at a time in the Companion window

2 participants