fix(a11y): announce that a reply is generating, and that one failed - #3014
fix(a11y): announce that a reply is generating, and that one failed#3014opensource-joe wants to merge 2 commits into
Conversation
The chat has no live regions at all, so a screen reader user gets no signal for either state. Generating. The only cue that a reply is on its way is `BlinkingCursor`, an empty `<span>` animated by CSS. It carries no text, so there is nothing to announce and nothing to read. Adds a visually hidden `role="status"` region in `MessagesContainer`, driven by the same `loading` value that drives the cursor, so the announcement covers exactly the period the cursor covers and no more. It announces the state rather than the reply. Wrapping streamed content in a live region makes assistive technology re-read the partial message on every chunk, which is worse than silence. Failed. An errored reply is marked only by a red avatar and red text, which is colour alone. Adds `role="alert"` to the message wrapper when `message.isError` is set. These are complete messages rather than streamed ones, so an alert does not re-read. The new string is added to all 23 locale files. The non-English values are the English text, untranslated, rather than machine translation, so they can be routed to translators. `fallbackLng` means behaviour is identical either way. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
10 issues found across 25 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="backend/chainlit/translations/bn.json">
<violation number="1" location="backend/chainlit/translations/bn.json:102">
P2: The added key uses the English placeholder "Generating response" in the Bengali locale file bn.json. Since this string is what a screen reader announces in the live region, Bengali users will hear English text, contradicting the a11y goal of the PR. Translate the value to Bengali (e.g., "প্রতিক্রিয়া তৈরি হচ্ছে") so the announcement is localized like the surrounding keys in this file.</violation>
</file>
<file name="backend/chainlit/translations/te.json">
<violation number="1" location="backend/chainlit/translations/te.json:102">
P2: The new key uses an English placeholder ("Generating response") in the Telugu locale (and the other 22 non-English locale files). Because this value drives the role="status" live region, Telugu screen-reader users — the very audience this a11y change targets — will hear the announcement in English. The PR description suggests narrowing to en-US.json, but keeping untranslated placeholders in every locale means the shipped announcement is wrong in all of them. Translate this value (and the equivalents in the other locale files), or if the key should only exist in en-US.json, drop it from the non-English files.</violation>
</file>
<file name="backend/chainlit/translations/de-DE.json">
<violation number="1" location="backend/chainlit/translations/de-DE.json:102">
P2: The new `generating` key in the German locale ships the English string "Generating response" instead of a German translation, while every sibling key (`using`, `used`) is translated. Since this live-region text is announced aloud to screen-reader users, German users hear English during generation — the exact a11y behavior this PR adds will be in the wrong language. Use the German translation (e.g., "Antwort wird generiert") in this file.</violation>
</file>
<file name="backend/chainlit/translations/zh-CN.json">
<violation number="1" location="backend/chainlit/translations/zh-CN.json:102">
P2: This key's value is the English placeholder "Generating response" in the Simplified-Chinese locale file, so Chinese screen-reader users will hear the new a11y announcement in English. Every sibling key under chat.messages.status is translated (使用中, 已使用). Provide a Chinese translation, e.g. "正在生成回复", here (and in the other non-English locale files). The live-region announcement added elsewhere in this PR only achieves its a11y goal when it is localized.</violation>
</file>
<file name="backend/chainlit/translations/it.json">
<violation number="1" location="backend/chainlit/translations/it.json:102">
P2: The new generating value is an English placeholder in the Italian locale, so Italian UI users get the English string "Generating response" in the screen-reader live region instead of a localized announcement. This key is displayed to users (not just a key-maintenance artifact), so it should be translated, e.g. "Generazione della risposta".</violation>
</file>
<file name="backend/chainlit/translations/es.json">
<violation number="1" location="backend/chainlit/translations/es.json:102">
P2: The Spanish locale file ships the untranslated placeholder "Generating response" for the new `chat.messages.status.generating` key. Since this string drives the `role="status"` live region, Spanish-locale screen reader users will hear the generation announcement in English while the surrounding keys ("Usando", "Usado") are properly localized. Replace the placeholder with Spanish, e.g. "Generando respuesta".</violation>
</file>
<file name="backend/chainlit/translations/hi.json">
<violation number="1" location="backend/chainlit/translations/hi.json:102">
P2: The `generating` value in the Hindi locale file is left as the English placeholder "Generating response", so Hindi users with screen readers will hear the new role="status" announcement in English. Translate it (e.g., "प्रतिक्रिया उत्पन्न हो रही है") instead of committing the English placeholder to the hi.json file.</violation>
</file>
<file name="backend/chainlit/translations/fr-FR.json">
<violation number="1" location="backend/chainlit/translations/fr-FR.json:102">
P2: The `generating` value in fr-FR.json is the English placeholder "Generating response", but this key drives the new live region in MessagesContainer (`t('chat.messages.status.generating')`). French screen reader users will be announced "Generating response" in English, which undermines the very accessibility feature this PR adds. The PR description flags the placeholder as a way to keep keys in sync, but for this announcement the locale value is the user-facing content. Translate it (e.g. "Génération de la réponse en cours") rather than shipping English to every non-English locale; missing keys can fall back instead.</violation>
</file>
<file name="backend/chainlit/translations/ja.json">
<violation number="1" location="backend/chainlit/translations/ja.json:101">
P2: The ja.json "generating" key uses the English placeholder "Generating response" instead of a Japanese translation. This value is announced to Japanese-speaking screen reader users through the new role="status" live region, so they will hear English text in a Japanese UI. Translate it for the ja locale (e.g., "応答を生成中") rather than shipping the placeholder; only en-US.json should keep the English string.</violation>
</file>
<file name="backend/chainlit/translations/da-DK.json">
<violation number="1" location="backend/chainlit/translations/da-DK.json:101">
P2: The new `messages.status.generating` key ships the English placeholder "Generating response" in the Danish locale file. This string is read aloud to screen-reader users via the new role="status" live region, so Danish users will hear the announcement in English rather than Danish, unlike every neighboring status string in this file (e.g. "using": "Bruger", "used": "Brugte"). Provide a proper Danish translation (e.g. "Genererer svar") before shipping this locale.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The added key uses the English placeholder "Generating response" in the Bengali locale file bn.json. Since this string is what a screen reader announces in the live region, Bengali users will hear English text, contradicting the a11y goal of the PR. Translate the value to Bengali (e.g., "প্রতিক্রিয়া তৈরি হচ্ছে") so the announcement is localized like the surrounding keys in this file.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/bn.json, line 102:
<comment>The added key uses the English placeholder "Generating response" in the Bengali locale file bn.json. Since this string is what a screen reader announces in the live region, Bengali users will hear English text, contradicting the a11y goal of the PR. Translate the value to Bengali (e.g., "প্রতিক্রিয়া তৈরি হচ্ছে") so the announcement is localized like the surrounding keys in this file.</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "ব্যবহার করছে",
"used": "ব্যবহৃত"
</file context>
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The new key uses an English placeholder ("Generating response") in the Telugu locale (and the other 22 non-English locale files). Because this value drives the role="status" live region, Telugu screen-reader users — the very audience this a11y change targets — will hear the announcement in English. The PR description suggests narrowing to en-US.json, but keeping untranslated placeholders in every locale means the shipped announcement is wrong in all of them. Translate this value (and the equivalents in the other locale files), or if the key should only exist in en-US.json, drop it from the non-English files.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/te.json, line 102:
<comment>The new key uses an English placeholder ("Generating response") in the Telugu locale (and the other 22 non-English locale files). Because this value drives the role="status" live region, Telugu screen-reader users — the very audience this a11y change targets — will hear the announcement in English. The PR description suggests narrowing to en-US.json, but keeping untranslated placeholders in every locale means the shipped announcement is wrong in all of them. Translate this value (and the equivalents in the other locale files), or if the key should only exist in en-US.json, drop it from the non-English files.</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "ఉపయోగిస్తోంది",
"used": "ఉపయోగించబడింది"
</file context>
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The new generating key in the German locale ships the English string "Generating response" instead of a German translation, while every sibling key (using, used) is translated. Since this live-region text is announced aloud to screen-reader users, German users hear English during generation — the exact a11y behavior this PR adds will be in the wrong language. Use the German translation (e.g., "Antwort wird generiert") in this file.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/de-DE.json, line 102:
<comment>The new `generating` key in the German locale ships the English string "Generating response" instead of a German translation, while every sibling key (`using`, `used`) is translated. Since this live-region text is announced aloud to screen-reader users, German users hear English during generation — the exact a11y behavior this PR adds will be in the wrong language. Use the German translation (e.g., "Antwort wird generiert") in this file.</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "Verwendet",
"used": "Verwendete"
</file context>
| "generating": "Generating response", | |
| "generating": "Antwort wird generiert", |
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: This key's value is the English placeholder "Generating response" in the Simplified-Chinese locale file, so Chinese screen-reader users will hear the new a11y announcement in English. Every sibling key under chat.messages.status is translated (使用中, 已使用). Provide a Chinese translation, e.g. "正在生成回复", here (and in the other non-English locale files). The live-region announcement added elsewhere in this PR only achieves its a11y goal when it is localized.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/zh-CN.json, line 102:
<comment>This key's value is the English placeholder "Generating response" in the Simplified-Chinese locale file, so Chinese screen-reader users will hear the new a11y announcement in English. Every sibling key under chat.messages.status is translated (使用中, 已使用). Provide a Chinese translation, e.g. "正在生成回复", here (and in the other non-English locale files). The live-region announcement added elsewhere in this PR only achieves its a11y goal when it is localized.</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "使用中",
"used": "已使用"
</file context>
| "generating": "Generating response", | |
| "generating": "正在生成回复", |
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The new generating value is an English placeholder in the Italian locale, so Italian UI users get the English string "Generating response" in the screen-reader live region instead of a localized announcement. This key is displayed to users (not just a key-maintenance artifact), so it should be translated, e.g. "Generazione della risposta".
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/it.json, line 102:
<comment>The new generating value is an English placeholder in the Italian locale, so Italian UI users get the English string "Generating response" in the screen-reader live region instead of a localized announcement. This key is displayed to users (not just a key-maintenance artifact), so it should be translated, e.g. "Generazione della risposta".</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "In uso",
"used": "Utilizzato"
</file context>
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The Spanish locale file ships the untranslated placeholder "Generating response" for the new chat.messages.status.generating key. Since this string drives the role="status" live region, Spanish-locale screen reader users will hear the generation announcement in English while the surrounding keys ("Usando", "Usado") are properly localized. Replace the placeholder with Spanish, e.g. "Generando respuesta".
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/es.json, line 102:
<comment>The Spanish locale file ships the untranslated placeholder "Generating response" for the new `chat.messages.status.generating` key. Since this string drives the `role="status"` live region, Spanish-locale screen reader users will hear the generation announcement in English while the surrounding keys ("Usando", "Usado") are properly localized. Replace the placeholder with Spanish, e.g. "Generando respuesta".</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "Usando",
"used": "Usado"
</file context>
| "generating": "Generating response", | |
| "generating": "Generando respuesta", |
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The generating value in the Hindi locale file is left as the English placeholder "Generating response", so Hindi users with screen readers will hear the new role="status" announcement in English. Translate it (e.g., "प्रतिक्रिया उत्पन्न हो रही है") instead of committing the English placeholder to the hi.json file.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/hi.json, line 102:
<comment>The `generating` value in the Hindi locale file is left as the English placeholder "Generating response", so Hindi users with screen readers will hear the new role="status" announcement in English. Translate it (e.g., "प्रतिक्रिया उत्पन्न हो रही है") instead of committing the English placeholder to the hi.json file.</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "उपयोग कर रहे हैं",
"used": "उपयोग किया"
</file context>
| "generating": "Generating response", | |
| "generating": "प्रतिक्रिया उत्पन्न हो रही है", |
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The generating value in fr-FR.json is the English placeholder "Generating response", but this key drives the new live region in MessagesContainer (t('chat.messages.status.generating')). French screen reader users will be announced "Generating response" in English, which undermines the very accessibility feature this PR adds. The PR description flags the placeholder as a way to keep keys in sync, but for this announcement the locale value is the user-facing content. Translate it (e.g. "Génération de la réponse en cours") rather than shipping English to every non-English locale; missing keys can fall back instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/fr-FR.json, line 102:
<comment>The `generating` value in fr-FR.json is the English placeholder "Generating response", but this key drives the new live region in MessagesContainer (`t('chat.messages.status.generating')`). French screen reader users will be announced "Generating response" in English, which undermines the very accessibility feature this PR adds. The PR description flags the placeholder as a way to keep keys in sync, but for this announcement the locale value is the user-facing content. Translate it (e.g. "Génération de la réponse en cours") rather than shipping English to every non-English locale; missing keys can fall back instead.</comment>
<file context>
@@ -99,6 +99,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "Utilise",
"used": "Utilisé"
</file context>
| "generating": "Generating response", | |
| "generating": "Génération de la réponse en cours", |
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The ja.json "generating" key uses the English placeholder "Generating response" instead of a Japanese translation. This value is announced to Japanese-speaking screen reader users through the new role="status" live region, so they will hear English text in a Japanese UI. Translate it for the ja locale (e.g., "応答を生成中") rather than shipping the placeholder; only en-US.json should keep the English string.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/ja.json, line 101:
<comment>The ja.json "generating" key uses the English placeholder "Generating response" instead of a Japanese translation. This value is announced to Japanese-speaking screen reader users through the new role="status" live region, so they will hear English text in a Japanese UI. Translate it for the ja locale (e.g., "応答を生成中") rather than shipping the placeholder; only en-US.json should keep the English string.</comment>
<file context>
@@ -98,6 +98,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "使用中",
"used": "使用済み"
</file context>
| "generating": "Generating response", | |
| "generating": "応答を生成中", |
| }, | ||
| "messages": { | ||
| "status": { | ||
| "generating": "Generating response", |
There was a problem hiding this comment.
P2: The new messages.status.generating key ships the English placeholder "Generating response" in the Danish locale file. This string is read aloud to screen-reader users via the new role="status" live region, so Danish users will hear the announcement in English rather than Danish, unlike every neighboring status string in this file (e.g. "using": "Bruger", "used": "Brugte"). Provide a proper Danish translation (e.g. "Genererer svar") before shipping this locale.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/translations/da-DK.json, line 101:
<comment>The new `messages.status.generating` key ships the English placeholder "Generating response" in the Danish locale file. This string is read aloud to screen-reader users via the new role="status" live region, so Danish users will hear the announcement in English rather than Danish, unlike every neighboring status string in this file (e.g. "using": "Bruger", "used": "Brugte"). Provide a proper Danish translation (e.g. "Genererer svar") before shipping this locale.</comment>
<file context>
@@ -98,6 +98,7 @@
},
"messages": {
"status": {
+ "generating": "Generating response",
"using": "Bruger",
"used": "Brugte"
</file context>
|
Worth taking seriously, and it made me test something I had asserted without testing. I was wrong in the PR description, so let me correct that first. I wrote that t: (path, options) => {
if (!i18n.exists(path, options)) {
return '...';
}
return t(path, options);
}Two other things point the same way. I checked it rather than reasoning from the source. Running with So removing the placeholder does not give Bengali users Bengali. It gives them That is why the key is in all 23 files. The values are untranslated English rather than machine translation, so they can go to translators. If you would rather not carry placeholders at all, the option that does not regress anything is to translate them, and I am happy to do that as a follow-up rather than hold this one up. |
The chat UI has no live regions anywhere, so a screen reader user gets no signal that a reply is coming or that one failed. I checked before assuming: zero
aria-live,role="status",role="log",role="alert"oraria-atomicacross all 208 files infrontend/src.This adds the two that matter most. It is a small change and I have kept it deliberately narrow.
Generating
The only cue that a reply is on its way is
BlinkingCursor, which is an empty<span>animated by CSS. It has no text, so there is nothing for assistive technology to announce.I added a visually hidden
role="status"region inMessagesContainer, driven by the sameloadingvalue that drives the cursor. The announcement therefore covers exactly the period the cursor covers, no more and no less.It announces the state, not the reply. Wrapping streamed content in a live region makes assistive technology re-read the partial message on every chunk, which is worse than saying nothing.
Failed
An errored reply is currently distinguished only by a red avatar and red text, which is colour alone.
role="alert"goes on the message wrapper whenmessage.isErroris set. These are complete messages rather than streamed ones, so an alert does not re-read.Verified against a running app
Not just a build. I ran the server from source with a deliberately slow-streaming app and read the DOM live.
While a reply was generating:
{ "status": "Generating response", "steps": 2, "alerts": 0 }and once it completed the region emptied again, so it announces once per reply rather than repeating:
{ "status": "", "steps": 2, "alerts": 0 }Sending a
cl.ErrorMessage:{ "alertCount": 1, "alertText": ["Something went wrong generating that reply."], "aiMessages": [{ "role": "alert", "text": "Something went wrong generating that reply." }] }Also green:
pnpm type-check,pnpm lintandpnpm format-checkon the changed files,prettier --checkon all 23 locale files, andpnpm run --recursive testat 32 passed.Two things worth your call
Translations. The new key is in all 23 locale files, since they are otherwise kept in sync at 136 keys each. The non-English values are the English string left untranslated rather than machine translated, so they can go to translators.
fallbackLngmeans behaviour is identical either way, so I am happy to reduce this toen-US.jsononly if you would rather not carry placeholder values.No unit test. I tried.
MessagereachesuseLayoutMaxWidthand thenuseConfig, which needs a Recoil provider the currentfrontend/testssetup does not have, and mocking the tree ended up larger than the change itself. I would rather say that than add scaffolding you have deliberately avoided. Happy to add one if you can point me at the pattern you would want.Not included on purpose
Loaderis a bare spinningLoaderIconwith no accessible name, used inApp.tsx,ReadOnlyThread.tsx,Messages/index.tsxandActionButton.tsx. That is a real gap and a separate change, so I left it out rather than widen this one. Glad to follow up if you want it.Summary by cubic
Announces reply generation and failures for screen readers to meet a11y expectations. Previously there were no live regions; now a hidden
role="status"announces "Generating response" while loading, and errored replies are marked withrole="alert".MessagesContainer: adds a screen-reader-onlyrole="status"region bound toloading; does not wrap streamed content to avoid re-reads.Message: setsrole="alert"whenmessage.isError; visual behavior unchanged.chat.messages.status.generatingto all locale files with English placeholders;fallbackLngkeeps runtime behavior identical.Written for commit 5a104c1. Summary will update on new commits.