Context
The chat rail's message rendering path — apps/loopover-miner-ui/src/components/chat/message-list.tsx
(the <ol> of MessageBubble rows), apps/loopover-miner-ui/src/components/chat/message-bubble.tsx,
and apps/loopover-miner-ui/src/components/streaming-text.tsx (the live-streaming answer) — has no
aria-live region anywhere in its normal, non-loading/non-error rendering path. A grep across all
four chat-rendering files for aria-live/role= finds matches only inside
@loopover/ui-kit's StateBoundary (used for the loading/error/empty branches) and in
typing-indicator.tsx (which, per a separate issue in this batch, is not currently wired up either).
The actual message content — both the final committed MessageBubble entries and the live
StreamingText answer as it accumulates — is rendered into plain, non-live DOM.
Because there is no live region, a screen-reader user who submits a chat question and then moves
focus elsewhere (as the composer's own accessible flow encourages, since ChatComposer does not trap
focus) receives no announcement when the assistant's answer starts streaming in, updates token by
token, or finishes. The response is only discoverable by manually re-navigating into the message
list. This is a genuine WCAG-relevant gap for a live, streaming chat surface, distinct from — but
adjacent to — the composing-indicator gap filed separately in this batch.
Requirements
- The region containing new assistant/system messages (or, at minimum, the newly-appended message
once a turn completes) must be an ARIA live region (aria-live="polite" is appropriate here — chat
answers are not urgent/interrupting content) so assistive tech announces new content without
requiring the user to have focus inside the message list.
- The live announcement must fire once per completed turn, not on every incremental streaming chunk —
announcing dozens of partial-token updates would be worse than the current silence (a classic
"too chatty" live-region regression). StreamingText's per-chunk re-renders must not each trigger
a fresh announcement.
- The existing
StateBoundary-driven loading/empty/error announcements (already role="status"/"alert")
must be unaffected by this change.
Deliverables
Test Coverage Requirements
apps/** is listed under ignore: in codecov.yml, so this app is not gated by Codecov's 99% patch
check — but the local npm run test:coverage invariant bar still applies, and the regression test
above is required. This is an accessibility/attribute change with no visual layout impact, so no
before/after screenshot is required.
Expected Outcome
A screen-reader user gets a single, well-timed announcement per completed chat turn — matching the
sighted experience of watching the answer arrive — without being spammed by per-token announcements.
Links & Resources
apps/loopover-miner-ui/src/components/chat/message-list.tsx,
apps/loopover-miner-ui/src/components/chat/message-bubble.tsx,
apps/loopover-miner-ui/src/components/streaming-text.tsx,
apps/loopover-miner-ui/src/components/chat/conversation.tsx.
Context
The chat rail's message rendering path —
apps/loopover-miner-ui/src/components/chat/message-list.tsx(the
<ol>ofMessageBubblerows),apps/loopover-miner-ui/src/components/chat/message-bubble.tsx,and
apps/loopover-miner-ui/src/components/streaming-text.tsx(the live-streaming answer) — has noaria-liveregion anywhere in its normal, non-loading/non-error rendering path. A grep across allfour chat-rendering files for
aria-live/role=finds matches only inside@loopover/ui-kit'sStateBoundary(used for the loading/error/empty branches) and intyping-indicator.tsx(which, per a separate issue in this batch, is not currently wired up either).The actual message content — both the final committed
MessageBubbleentries and the liveStreamingTextanswer as it accumulates — is rendered into plain, non-live DOM.Because there is no live region, a screen-reader user who submits a chat question and then moves
focus elsewhere (as the composer's own accessible flow encourages, since
ChatComposerdoes not trapfocus) receives no announcement when the assistant's answer starts streaming in, updates token by
token, or finishes. The response is only discoverable by manually re-navigating into the message
list. This is a genuine WCAG-relevant gap for a live, streaming chat surface, distinct from — but
adjacent to — the composing-indicator gap filed separately in this batch.
Requirements
once a turn completes) must be an ARIA live region (
aria-live="polite"is appropriate here — chatanswers are not urgent/interrupting content) so assistive tech announces new content without
requiring the user to have focus inside the message list.
announcing dozens of partial-token updates would be worse than the current silence (a classic
"too chatty" live-region regression).
StreamingText's per-chunk re-renders must not each triggera fresh announcement.
StateBoundary-driven loading/empty/error announcements (alreadyrole="status"/"alert")must be unaffected by this change.
Deliverables
aria-liveand that its accessible content updates once per completed turnTest Coverage Requirements
apps/**is listed underignore:incodecov.yml, so this app is not gated by Codecov's 99% patchcheck — but the local
npm run test:coverageinvariant bar still applies, and the regression testabove is required. This is an accessibility/attribute change with no visual layout impact, so no
before/after screenshot is required.
Expected Outcome
A screen-reader user gets a single, well-timed announcement per completed chat turn — matching the
sighted experience of watching the answer arrive — without being spammed by per-token announcements.
Links & Resources
apps/loopover-miner-ui/src/components/chat/message-list.tsx,apps/loopover-miner-ui/src/components/chat/message-bubble.tsx,apps/loopover-miner-ui/src/components/streaming-text.tsx,apps/loopover-miner-ui/src/components/chat/conversation.tsx.