Skip to content

feat(app): announce the running turn status to screen readers - #57520

Draft
pranam-s wants to merge 1 commit into
anomalyco:devfrom
pranam-s:fix-turn-status-announcer
Draft

pranam-s wants to merge 1 commit into
anomalyco:devfrom
pranam-s:fix-turn-status-announcer

Conversation

@pranam-s

@pranam-s pranam-s commented Oct 20, 2026 •

Copy link
Copy Markdown

Issue for this PR

Refs #57512

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

A screen reader hears "Working" once and then nothing while the web UI is streaming a response. This adds one polite live region that names the phase of the running turn, so it hears "Running commands", "Searching the codebase", "Thinking", "Making edits", "Responding" and so on as the turn progresses.

The region sits beside the transcript's scroll container, outside the virtualized rows, and stays mounted for as long as the session is routed, so a screen reader announces it each time the text changes. The strings come from the ui.sessionTurn.status.* entries in packages/ui/src/i18n/en.ts, which are already translated in every locale; only "Responding" and "Waiting for your response" are new.

The generic "Working" chip keeps the same visibility it has today. Its role="status" is removed, along with the header indicator's, because neither could announce anything anyway: their text sits in aria-hidden spans, and the header one put its label on a role=generic span, which Chrome ignores. That leaves one live region for the turn status. The compaction notice in packages/session-ui keeps its own role="status" and is out of scope here.

This is a draft because it adds a user-visible behaviour and I would like a maintainer to sign off on the wording and on the set of phases before it lands.

How did you verify your code works?

  • bun run check and bun run lint:changed origin/dev pass.
  • packages/app unit tests: 760 pass, 0 fail.
  • The touched keepers in built mode: 84 pass (timeline stream, timeline geometry, session queue).
  • With only the production change reverted, the new case fails and the other 44 in that file still pass. It passes again with the change in place.
  • The phases were read out of the accessibility tree in Chromium: "Working", "Running commands", "Exploring", "Responding".

On the timeline benchmark that packages/app/AGENTS.md asks for, the change is within noise. The projection benchmark's readyMs moved by -0.10 %, +0.30 % and +0.06 % across three scenarios, with the sign flipping between two A/B rounds, against a run-to-run standard deviation of 0.25-0.65 %. missedFrameBudgetEquivalents was 0 in 60 of 60 runs and the maximum animation frame gap was 16.8 ms in every run, before and after. The projection is rebuilt when the row structure changes, not per text delta, so the new traversal does not run on the streaming hot path.

Screenshots / recordings

N/A: no visual change. The chip looks and behaves exactly as before.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant