Skip to content

feat(chat): announce replies once and make the history keyboard-scrollable - #7295

Open
Haroenv wants to merge 8 commits into
masterfrom
feat/chat-accessibility
Open

Haroenv wants to merge 8 commits into
masterfrom
feat/chat-accessibility

Conversation

@Haroenv

@Haroenv Haroenv commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • ChatMessages is no longer an active live region (aria-live="off" on the role="log" root). A new visually hidden role="status" region (.ais-ChatMessages-status) announces once when a reply starts, once ("Reply received.") when it completes, and on error.
  • The scrollable history is now a focusable, named region (tabindex="0", role="region", label "Conversation"), with a :focus-visible outline in instantsearch.css.
  • New translations: conversationLabel, respondingStatusText, responseCompleteStatusText (receives the reply, so apps can announce its text), errorStatusText. New class name key: status.

Result

Screen readers read a reply once instead of chunk by chunk, and keyboard users can scroll text-only answers. Chat unit and common-widget tests pass; lint, format and type-check are clean. Not yet checked with VoiceOver or NVDA.

Both changes are inspired by accessibility fixes in a customer shopping assistant, which worked around these gaps on its own.

Not included, to follow separately: dialog semantics and focus management for the overlay layout, the heading outline, and product list semantics.

🤖 Generated with Claude Code

…lable

The message log announced every streamed chunk, and VoiceOver ignores its
aria-busy. Silence it and announce once when a reply starts, once with the
full reply when it completes, and on error through a status region.

Make the scrollable history a focusable, named region so keyboard users
can scroll text-only answers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@codacy-production

codacy-production Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 17 complexity

Metric Results
Complexity 17

View in Codacy

TIP This summary will be updated as you push new changes.

@Haroenv
Haroenv requested a balanced review from Copilot October 14, 2026 15:22
@pkg-pr-new

pkg-pr-new Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
More templates

algoliasearch-helper

npm i https://pkg.pr.new/algolia/instantsearch/algoliasearch-helper@7295

instantsearch-ui-components

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch-ui-components@7295

instantsearch.css

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch.css@7295

instantsearch.js

npm i https://pkg.pr.new/algolia/instantsearch/instantsearch.js@7295

react-instantsearch

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch@7295

react-instantsearch-core

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-core@7295

react-instantsearch-nextjs

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-nextjs@7295

react-instantsearch-router-nextjs

npm i https://pkg.pr.new/algolia/instantsearch/react-instantsearch-router-nextjs@7295

vue-instantsearch

npm i https://pkg.pr.new/algolia/instantsearch/vue-instantsearch@7295

commit: 7c03835

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Size Change: +2.38 kB (+0.23%)

Total Size: 1.04 MB

📦 View Changed
Filename Size Change
packages/instantsearch.css/components/chat-min.css 7.46 kB +58 B (+0.78%)
packages/instantsearch.css/components/chat.css 7.97 kB +61 B (+0.77%)
packages/instantsearch.css/themes/algolia-min.css 11.9 kB +60 B (+0.51%)
packages/instantsearch.css/themes/algolia.css 12.7 kB +62 B (+0.49%)
packages/instantsearch.css/themes/nova-min.css 12 kB +53 B (+0.44%)
packages/instantsearch.css/themes/nova.css 12.8 kB +52 B (+0.41%)
packages/instantsearch.css/themes/satellite-min.css 12.8 kB +61 B (+0.48%)
packages/instantsearch.css/themes/satellite.css 13.8 kB +61 B (+0.45%)
packages/instantsearch.js/dist/instantsearch.development.js 335 kB +1.06 kB (+0.32%)
packages/instantsearch.js/dist/instantsearch.production.min.js 152 kB +478 B (+0.31%)
packages/react-instantsearch/dist/umd/ReactInstantSearch.min.js 126 kB +381 B (+0.3%)
ℹ️ View Unchanged
Filename Size Change
packages/algoliasearch-helper/dist/algoliasearch.helper.js 44.5 kB 0 B
packages/algoliasearch-helper/dist/algoliasearch.helper.min.js 14.1 kB -1 B (-0.01%)
packages/instantsearch.css/components/ai-mode-button.css 1.76 kB 0 B
packages/instantsearch.css/components/autocomplete-min.css 4.33 kB 0 B
packages/instantsearch.css/components/autocomplete.css 4.68 kB 0 B
packages/instantsearch.css/components/button.css 1.98 kB 0 B
packages/instantsearch.css/components/filter-suggestions.css 1.89 kB 0 B
packages/instantsearch.css/themes/reset-min.css 1.3 kB 0 B
packages/instantsearch.css/themes/reset.css 1.38 kB 0 B
packages/react-instantsearch-core/dist/umd/ReactInstantSearchCore.min.js 73.8 kB -1 B (0%)
packages/vue-instantsearch/vue2/cjs/index.js 19.9 kB 0 B
packages/vue-instantsearch/vue2/umd/index.js 74.3 kB -4 B (-0.01%)
packages/vue-instantsearch/vue3/cjs/index.js 20.6 kB 0 B
packages/vue-instantsearch/vue3/umd/index.js 74.7 kB -6 B (-0.01%)

compressed-size-action

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Announcements can be delayed by busy-state nesting, repeat stale replies after cancellation, and remove meaningful reply characters.

3 open findings
What changed in this PR

Improves shared chat accessibility with reply announcements and keyboard-scrollable conversation history.

Changes:

  • Adds a dedicated status region and configurable announcement translations.
  • Makes conversation history focusable and labeled, with a visible focus outline.
  • Updates component tests and snapshots.
File Description
packages/​instantsearch.css/​src/​components/​chat/​_chat-messages.scss Adds hidden-status styling and history focus outline.
packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx Adds announcements and accessible history navigation.
packages/​instantsearch-ui-components/​src/​components/​chat/​__tests__/​ChatMessages.test.tsx Tests announcements, translations, and history semantics.
packages/​instantsearch-ui-components/​src/​components/​chat/​__tests__/​Chat.test.tsx Updates the integrated chat snapshot.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx Outdated
Comment thread packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx Outdated
- announce only a trailing assistant message, so a stopped request does not
  repeat the previous answer
- strip only unambiguous Markdown (and none in plain-text mode) so literal
  characters like C# and order_id survive
- move aria-busy to the history so the status region is not in a busy subtree

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Announcement behavior, literal-text preservation, and JavaScript translation support need correction.

1 open finding
3 resolved since last review
Previously missed (3)

In code that hasn't changed since last review

Medium severity Initial processing and error states are not announced

packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx:539

Initializing previous from the current props skips the first announcement when the component mounts with submitted, streaming, or error. A subsequent submitted → streaming transition also stays silent because previous.isProcessing is already true. Initialize the tracking state as idle so processing and error states are announced after mount, while existing history stays silent when mounting with ready.

Medium severity Expose missing vanilla widget translation options

packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx:908

The vanilla JavaScript widget does not expose these four translations: packages/instantsearch.js/src/widgets/chat/chat.tsx:782–788 omits them from messagesTranslations, and ChatTemplates.messages at lines 1059–1076 has no corresponding options. Localized JS chats therefore get English announcements and an English conversation label with no supported way to customize them. Add the public template options, forward them to messagesTranslations, and cover the mapping with translation tests.

Medium severity Remove chat history live-region semantics

packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx:1038

aria-live="off" does not guarantee silence while focus is inside the region. The newly focusable history is inside this log, so focusing it to scroll can still expose streamed updates to screen readers, followed by the full status announcement. Remove the log's live-region semantics and keep the status element as the only live region. Update the corresponding markup assertions and snapshots.

🧠 Review effort: Balanced

Comment thread packages/instantsearch-ui-components/src/components/chat/ChatMessages.tsx Outdated
Announce a plain 'Reply received.' by default and pass the reply to
responseCompleteStatusText, so consumers that know how a reply is rendered
can announce its text.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Announcement lifecycle issues and missing JavaScript widget translation options remain unresolved.

0 open findings

1 resolved since last review
Previously missed (3)

In code that hasn't changed since last review

Medium severity Announce initially active chat requests and errors

packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx:524

Initializing previous from the current props makes the first effect return when mounted with submitted, streaming, or error. An initially submitted request also stays silent when it switches to streaming, since previous.isProcessing is already true. Initialize the transition baseline to ready/non-processing so an already-active request or error gets an announcement, while restored ready history remains silent.

Medium severity Clear stale completion announcements when chat history is emptied

packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx:529

Clearing a completed conversation empties messages without changing the ready status, so this early return preserves the old announcement. When responseCompleteStatusText returns reply text, the cleared answer remains in the DOM and accessible to screen readers. Clear the announcement when ready history becomes empty, before checking whether the status changed.

Medium severity Expose all chat translation options in the vanilla widget

packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx:893

The vanilla JavaScript Chat widget cannot customize these four translations. Its explicit messagesTranslations mapping in packages/instantsearch.js/src/widgets/chat/chat.tsx:782–788 and ChatTemplates.messages type at lines 1059–1076 omit them. Localized JavaScript chats therefore use English labels and announcements, and cannot use the completion callback to announce reply text. Expose and forward all four options through the widget's templates API, and cover them in the common translation tests.

🧠 Review effort: Balanced

Haroenv and others added 2 commits October 15, 2026 10:23
Start the announcement baseline as idle so a request already in flight
when the messages mount is announced, and clear the announced reply when
the history is emptied.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Forward conversationLabelText, respondingStatusText,
responseCompleteStatusText and errorStatusText to the messages
translations.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Haroenv

Haroenv commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the three findings from Copilot's latest review summary in 0633d83 and db884a5:

  • Initially active requests / errors: the announcement baseline now starts idle, so a request already in flight on mount announces "Responding…". An error that predates the mount stays silent on purpose, so reopening the chat doesn't replay it. (submitted → streaming staying silent is intended: "Responding…" is said once per reply.)
  • Stale announcement after clearing: the announcement is cleared when the history becomes empty.
  • JS widget translations: conversationLabelText, respondingStatusText, responseCompleteStatusText and errorStatusText are now exposed under templates.messages and forwarded. The common translations test covers conversationLabelText in JS and React.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

An optional translation can crash reply completion, and screen-reader behavior still needs manual validation.

1 open finding

🧠 Review effort: Balanced

Merge translations through a helper that ignores keys explicitly set to
undefined, so a default is never replaced by a missing label or callback
(responseCompleteStatusText: undefined used to throw when a reply
finished).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@Haroenv

Haroenv commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Fixed in the latest push, at the source rather than at the call site: chat components now merge translations through a mergeTranslations helper that ignores keys explicitly set to undefined, so defaults are resolved once and never overwritten. This covers every chat component, not only ChatMessages, and has a regression test for responseCompleteStatusText: undefined. The same spread pattern exists in the non-chat components; I left those for a separate change.

🤖 Generated with Claude Code

…fined"

This reverts commit 6db8804. Explicitly
undefined translation overrides replace the default in every React and
ui-components widget; fixing that belongs in its own change across all
widgets rather than only in chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The no-stream resume announcement needs correction, and live-region behavior still requires screen-reader validation.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Resume announces stale historical reply when no stream is restored

packages/​instantsearch-ui-components/​src/​components/​chat/​ChatMessages.tsx:553

A resume attempt with no active stream can announce an unchanged historical reply as newly received. resumeStream() sets submitted, then returns to ready without changing messages when reconnection returns null (for example, on HTTP 404; see ai-lite/transport.ts:225–236 and abstract-chat.ts:996–1004). If the mounted history ends in an assistant message, this role-only check passes and calls responseCompleteStatusText on that old answer. Track whether the attempt produced or updated a reply before announcing completion, and add a regression test for a no-stream resume with existing history. Preserve completion announcements when mounting during an active stream.

🧠 Review effort: Balanced

A resume with no stream to reconnect to goes submitted → ready without
touching the history, and the restored assistant message was announced
as a new reply. Remember the last message while the request is
submitted, and only announce a trailing assistant message that changed
since.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The core announcement behavior still needs manual validation with VoiceOver or NVDA beyond DOM assertions.

0 open findings

🧠 Review effort: Balanced

@Haroenv
Haroenv marked this pull request as ready for review October 15, 2026 12:40
@Haroenv
Haroenv requested review from a team, afrencalg, anjola-adeuyi and mikaa123 and removed request for a team October 15, 2026 12:40

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.

3 participants