fix(a11y): accessible names, dialog titles, keyboard controls, and toast dismissal across viewers, chat, and modals - #14300
fix(a11y): accessible names, dialog titles, keyboard controls, and toast dismissal across viewers, chat, and modals#14300viktoravelino wants to merge 11 commits into
Conversation
- announce shortcut modifier keys (sr-only text next to aria-hidden icons) - role=log chat containers + one-shot completion announcement in both playground and IO modal chat views - real DialogTitle on list-selection dialog and model-provider modal - accessible names for 12 icon-only controls (image viewer, PDF viewer, flow save, sidebar clear-filter, node output copy) - password toggle, multiselect badge remove, and slider value edit are real buttons now (testids preserved) - focusable named dismiss button on error/success/notice toasts - tabIndex 1 -> 0 on template get-started cards 24 new i18n keys in all 7 locales; 6 new a11y test files.
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThe PR adds localized accessible names, semantic buttons, dialog titles, alert dismissal controls, keyboard labels, viewer controls, and chat live-region announcements across the frontend. It also adds accessibility tests and translation keys in eight locale files. ChangesAccessibility and localization
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Messages
participant ChatHistory
participant ScreenReader
Messages->>ChatHistory: detect build completion
ChatHistory-->>Messages: provide final assistant response
Messages->>ScreenReader: announce completed response
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 3 warnings)
✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/frontend/src/components/common/ImageViewer/index.tsx (1)
102-155: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd regression coverage for the new accessible names.
No changed test covers either newly named icon control in the supplied PR files. Add assertions that query these buttons by their localized accessible names.
src/frontend/src/components/common/ImageViewer/index.tsx#L102-L155: cover all five viewer toolbar button names.src/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsx#L211-L211: cover the save-flow button name.As per coding guidelines, “For new frontend implementations or bug fixes, ensure corresponding test files are included” and verify changed behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/frontend/src/components/common/ImageViewer/index.tsx` around lines 102 - 155, Add regression tests for the accessible names introduced in ImageViewer, querying all five toolbar buttons by their localized names: zoom in, zoom out, reset view, full screen, and download. In FlowMenu, add coverage for the save-flow button’s localized accessible name; update the relevant test files for both components.Source: Coding guidelines
🧹 Nitpick comments (1)
src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/__tests__/slider-value-edit.a11y.test.tsx (1)
54-64: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExercise the real keyboard activation path.
fireEvent.keyDownfollowed by a manualfireEvent.clickverifies only the click handler; the test passes even if Enter does nothing. Use the project’s user-level keyboard or browser helper to press Enter and assert edit mode without manually clicking.As per coding guidelines, frontend tests should verify meaningful behavior rather than only smoke-testing it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/__tests__/slider-value-edit.a11y.test.tsx` around lines 54 - 64, Update the "should_enter_edit_mode_from_the_keyboard" test to use the project’s user-level keyboard or browser interaction helper to press Enter on the focused default_slider_display_value trigger, removing the manual fireEvent.click call. Keep the assertion that slider_input appears so the test verifies the real Enter activation path.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/frontend/src/alerts/error/index.tsx`:
- Around line 17-22: Cancel the pending auto-dismiss timer when alerts are
manually dismissed so removal is idempotent. Update handleDismiss in
src/frontend/src/alerts/error/index.tsx#L17-L22, route the new notice button
through cancellation-safe dismissal in
src/frontend/src/alerts/notice/index.tsx#L74-L84, and cancel the timer after
immediate removal in src/frontend/src/alerts/success/index.tsx#L14-L17. Extend
src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L60-L65, `#L74-L77`,
and `#L86-L90` to advance beyond 5.5 seconds and assert each error, success, and
notice toast is removed exactly once.
- Around line 15-16: Move toast dismissal visibility from local useState into
the alert Zustand store in src/frontend/src/alerts/error/index.tsx lines 15-16
and src/frontend/src/alerts/success/index.tsx lines 12-13; update ErrorAlert and
SuccessAlert to read and update the store’s visibility state while preserving
their existing dismissal behavior.
In
`@src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/index.tsx`:
- Around line 246-259: Propagate the disabled state to both interactive buttons:
add the existing disabled value to the slider value-edit button in
sliderComponent/index.tsx (lines 246-259) and the password visibility button in
textAreaComponent/index.tsx (lines 239-259), ensuring keyboard activation is
also blocked when disabled.
In
`@src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/messages.tsx`:
- Around line 300-302: Force a DOM mutation for each completion announcement by
clearing or re-keying the status region around completedAnnouncement in
messages.tsx (lines 300-302) and chat-view.tsx (lines 257-259), so identical
consecutive messages are announced independently.
- Around line 199-211: Update the completion-announcement logic in messages.tsx
and the corresponding chat-view.tsx logic at lines 120-137 to track the current
assistant message identity rather than reading only the latest chatHistory
entry. Keep the completion pending when isBuilding falls before the new
assistant message is committed, then announce only that correlated message once
available and avoid announcing a prior-turn response.
In
`@src/frontend/src/modals/templatesModal/components/TemplateGetStartedCardComponent/index.tsx`:
- Around line 66-68: Add button semantics to the clickable template card in
TemplateGetStartedCardComponent by applying role="button" or replacing the div
with a native button, while preserving keyboard and click activation. Add a
regression test covering tab focus and activation with both Enter and Space.
---
Outside diff comments:
In `@src/frontend/src/components/common/ImageViewer/index.tsx`:
- Around line 102-155: Add regression tests for the accessible names introduced
in ImageViewer, querying all five toolbar buttons by their localized names: zoom
in, zoom out, reset view, full screen, and download. In FlowMenu, add coverage
for the save-flow button’s localized accessible name; update the relevant test
files for both components.
---
Nitpick comments:
In
`@src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/__tests__/slider-value-edit.a11y.test.tsx`:
- Around line 54-64: Update the "should_enter_edit_mode_from_the_keyboard" test
to use the project’s user-level keyboard or browser interaction helper to press
Enter on the focused default_slider_display_value trigger, removing the manual
fireEvent.click call. Keep the assertion that slider_input appears so the test
verifies the real Enter activation path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 82674382-5162-4fca-9c47-0e4f44242ba3
📒 Files selected for processing (32)
src/frontend/src/CustomNodes/GenericNode/components/ListSelectionComponent/__tests__/listSelection.a11y.test.tsxsrc/frontend/src/CustomNodes/GenericNode/components/ListSelectionComponent/index.tsxsrc/frontend/src/CustomNodes/GenericNode/components/outputModal/index.tsxsrc/frontend/src/CustomNodes/GenericNode/index.tsxsrc/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsxsrc/frontend/src/alerts/error/index.tsxsrc/frontend/src/alerts/notice/index.tsxsrc/frontend/src/alerts/success/index.tsxsrc/frontend/src/components/common/ImageViewer/index.tsxsrc/frontend/src/components/common/renderIconComponent/components/renderKey/__tests__/renderKey.a11y.test.tsxsrc/frontend/src/components/common/renderIconComponent/components/renderKey/index.tsxsrc/frontend/src/components/core/appHeaderComponent/components/FlowMenu/index.tsxsrc/frontend/src/components/core/parameterRenderComponent/components/inputComponent/components/popover/__tests__/optionBadge.a11y.test.tsxsrc/frontend/src/components/core/parameterRenderComponent/components/inputComponent/components/popover/index.tsxsrc/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/__tests__/slider-value-edit.a11y.test.tsxsrc/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/index.tsxsrc/frontend/src/components/core/parameterRenderComponent/components/textAreaComponent/index.tsxsrc/frontend/src/components/core/pdfViewer/index.tsxsrc/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/messages.tsxsrc/frontend/src/locales/de.jsonsrc/frontend/src/locales/en.jsonsrc/frontend/src/locales/es.jsonsrc/frontend/src/locales/fr.jsonsrc/frontend/src/locales/ja.jsonsrc/frontend/src/locales/pt.jsonsrc/frontend/src/locales/zh-Hans.jsonsrc/frontend/src/modals/IOModal/components/chatView/components/chat-view.tsxsrc/frontend/src/modals/modelProviderModal/__tests__/modelProviderModal.a11y.test.tsxsrc/frontend/src/modals/modelProviderModal/index.tsxsrc/frontend/src/modals/templatesModal/components/TemplateGetStartedCardComponent/index.tsxsrc/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarFilterComponent.test.tsxsrc/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFilterComponent.tsx
| const { t } = useTranslation(); | ||
| const [show, setShow] = useState(true); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate relevant frontend files =="
git ls-files 'src/frontend/**' | sed -n '1,200p'
echo
echo "== target files =="
for f in src/frontend/src/alerts/error/index.tsx src/frontend/src/alerts/success/index.tsx; do
if [ -f "$f" ]; then
echo "--- $f ($(wc -l < "$f") lines) ---"
cat -n "$f"
else
echo "missing $f"
fi
done
echo
echo "== Zustand usages in frontend/src alerts and nearby store files =="
rg -n "create Zustand|createZustand|create\(|use.*Zustand|store|setShow|show|visibility|toast" src/frontend/src -S || true
echo
echo "== package deps =="
if [ -f "package.json" ]; then jq -r '.dependencies.zustand // .devDependencies.zustand // empty' package.json; fiRepository: langflow-ai/langflow
Length of output: 50377
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target files =="
for f in src/frontend/src/alerts/error/index.tsx src/frontend/src/alerts/success/index.tsx; do
if [ -f "$f" ]; then
echo "--- $f ($(wc -l < "$f") lines) ---"
cat -n "$f"
else
echo "missing $f"
fi
done
echo
echo "== alert store files =="
fd -a 'alert.*\.(tsx|ts)$|store' src/frontend/src/stores 2>/dev/null | sed 's#^\./##' | rg 'alert|stores' | head -50 || true
echo
echo "== alertStore implementation =="
if [ -f "src/frontend/src/stores/alertStore.ts" ]; then
echo "--- src/frontend/src/stores/alertStore.ts ($(wc -l < src/frontend/src/stores/alertStore.ts) lines) ---"
cat -n src/frontend/src/stores/alertStore.ts
fi
echo
echo "== focused Zustand/alert usages without globbing file count =="
rg -n "create\(.*alert|alertStore|showAlert|hideAlert|show.*toast|toast.*show|setToast|toast" src/frontend/src/stores src/frontend/src/alerts src/frontend/src/components/src -S | head -120 || trueRepository: langflow-ai/langflow
Length of output: 15909
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== alert display area =="
cat -n src/frontend/src/alerts/displayArea/index.tsx
echo
echo "== alert store type =="
fd 'AlertStoreType|alert' src/frontend/src/types -x sh -c 'echo "--- $1 ---"; cat -n "$1"' sh {}
echo
echo "== imports/usages around alert components =="
rg -n "ErrorAlert|SuccessAlert|displayArea|useAlertStore|removeAlert|tempNotificationList|show=" src/frontend/src/alerts src/frontend/src/components -S | head -200 || true
echo
echo "== simple deterministic check: local show state exists in changed alert components =="
python3 - <<'PY'
from pathlib import Path
for f in ("src/frontend/src/alerts/error/index.tsx", "src/frontend/src/alerts/success/index.tsx"):
text = Path(f).read_text()
print(f, "useState(show):", ("const [show, setShow] = useState(true)" in text), "imports(useEffect, useState):", ("import { useEffect, useState }" in text))
PYRepository: langflow-ai/langflow
Length of output: 2975
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== alert store type files =="
git ls-files 'src/frontend/src/types/**alert*' | while read -r f; do
echo "--- $f --"
cat -n "$f"
done
echo
echo "== alert component files =="
git ls-files 'src/frontend/src/alerts/**' | while read -r f; do
echo "--- $f -- ($(wc -l < "$f") lines) ---"
sed -n '1,120p' "$f" | cat -n
done
echo
echo "== notice component for comparison =="
cat -n src/frontend/src/alerts/notice/index.tsxRepository: langflow-ai/langflow
Length of output: 40094
Move toast visibility into the alert Zustand store.
Both ErrorAlert and SuccessAlert still manage their own toast dismissal visibility with useState(true). Track dismissal state in the alert store so these components do not bypass the required frontend Zustand state-management pattern.
📍 Affects 2 files
src/frontend/src/alerts/error/index.tsx#L15-L16(this comment)src/frontend/src/alerts/success/index.tsx#L12-L13
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/frontend/src/alerts/error/index.tsx` around lines 15 - 16, Move toast
dismissal visibility from local useState into the alert Zustand store in
src/frontend/src/alerts/error/index.tsx lines 15-16 and
src/frontend/src/alerts/success/index.tsx lines 12-13; update ErrorAlert and
SuccessAlert to read and update the store’s visibility state while preserving
their existing dismissal behavior.
Source: Coding guidelines
| const handleDismiss = () => { | ||
| setShow(false); | ||
| setTimeout(() => { | ||
| removeAlert(id); | ||
| }, 500); | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Cancel pending auto-dismiss timers after manual dismissal.
Manual dismissal removes the toast, but its 5-second timer still fires and invokes removeAlert again around T+5500ms. Make dismissal idempotent and clear pending timers; advance these tests past 5.5 seconds before asserting exactly one removal.
src/frontend/src/alerts/error/index.tsx#L17-L22: cancel the auto-dismiss work when manually dismissing.src/frontend/src/alerts/notice/index.tsx#L74-L84: route the new button through cancellation-safe dismissal.src/frontend/src/alerts/success/index.tsx#L14-L17: cancel the auto-dismiss work after immediate removal.src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L60-L65: verify error-toast removal remains exactly once after T+5500ms.src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L74-L77: add the same assertion for success.src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L86-L90: add the same assertion for notice.
As per coding guidelines, frontend tests should verify meaningful behavior for new functionality rather than only smoke-testing it.
📍 Affects 4 files
src/frontend/src/alerts/error/index.tsx#L17-L22(this comment)src/frontend/src/alerts/notice/index.tsx#L74-L84src/frontend/src/alerts/success/index.tsx#L14-L17src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L60-L65src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L74-L77src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L86-L90
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/frontend/src/alerts/error/index.tsx` around lines 17 - 22, Cancel the
pending auto-dismiss timer when alerts are manually dismissed so removal is
idempotent. Update handleDismiss in
src/frontend/src/alerts/error/index.tsx#L17-L22, route the new notice button
through cancellation-safe dismissal in
src/frontend/src/alerts/notice/index.tsx#L74-L84, and cancel the timer after
immediate removal in src/frontend/src/alerts/success/index.tsx#L14-L17. Extend
src/frontend/src/alerts/__tests__/toast-dismiss.a11y.test.tsx#L60-L65, `#L74-L77`,
and `#L86-L90` to advance beyond 5.5 seconds and assert each error, success, and
notice toast is removed exactly once.
Source: Coding guidelines
| <button | ||
| type="button" | ||
| onClick={() => { | ||
| setIsEditing(true); | ||
| setInputValue(valueAsNumber.toFixed(2)); | ||
| }} | ||
| aria-label={t("paramRender.editSliderValue", { | ||
| value: valueAsNumber.toFixed(2), | ||
| })} | ||
| data-testid={`default_slider_display_value${editNode ? "_advanced" : ""}`} | ||
| className="relative bottom-[1px] font-mono text-sm hover:cursor-text" | ||
| > | ||
| {valueAsNumber.toFixed(2)} | ||
| </span> | ||
| </button> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Propagate disabled state to both newly interactive controls.
Both buttons remain independently interactive when their surrounding component is disabled:
src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/index.tsx#L246-L259: adddisabled={disabled}to the slider value-edit button.src/frontend/src/components/core/parameterRenderComponent/components/textAreaComponent/index.tsx#L239-L259: adddisabled={disabled}to the password visibility button; pointer-events styling does not block keyboard activation.
📍 Affects 2 files
src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/index.tsx#L246-L259(this comment)src/frontend/src/components/core/parameterRenderComponent/components/textAreaComponent/index.tsx#L239-L259
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/frontend/src/components/core/parameterRenderComponent/components/sliderComponent/index.tsx`
around lines 246 - 259, Propagate the disabled state to both interactive
buttons: add the existing disabled value to the slider value-edit button in
sliderComponent/index.tsx (lines 246-259) and the password visibility button in
textAreaComponent/index.tsx (lines 239-259), ensuring keyboard activation is
also blocked when disabled.
| const [completedAnnouncement, setCompletedAnnouncement] = useState(""); | ||
| const wasBuildingRef = useRef(isBuilding); | ||
| useEffect(() => { | ||
| if (wasBuildingRef.current && !isBuilding) { | ||
| const lastMessage = chatHistory[chatHistory.length - 1]; | ||
| if (lastMessage && !lastMessage.isSend) { | ||
| setCompletedAnnouncement( | ||
| typeof lastMessage.message === "string" ? lastMessage.message : "", | ||
| ); | ||
| } | ||
| } | ||
| wasBuildingRef.current = isBuilding; | ||
| }, [isBuilding, chatHistory]); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repo files of interest =="
git ls-files | rg '(^|/)chat-messages/messages\.tsx$|(^|/)chat-view\.tsx$' || true
echo
echo "== first file outline and relevant snippets =="
for f in $(git ls-files | rg '(^|/)chat-messages/messages\.tsx$|(^|/)chat-view\.tsx$'); do
echo "--- $f ---"
wc -l "$f"
sed -n '160,235p' "$f"
echo
done
echo "== searches for chatHistory/isBuilding/message lifecycle =="
rg -n "chatHistory|isBuilding|setCompletedAnnouncement|completedAnnouncement|completion|assistant|isSend" src/frontend/src/components/core/playgroundComponent/chat-view src/frontend/src/modals/IOModal/components/chatView/components 2>/dev/null | head -200Repository: langflow-ai/langflow
Length of output: 25986
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== IOModal chat-view relevant lifecycle =="
sed -n '40,270p' src/frontend/src/modals/IOModal/components/chatView/components/chat-view.tsx
echo
echo "== useChatHistory relevant lifecycle =="
sed -n '1,270p' src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/hooks/use-chat-history.ts
echo
echo "== stream/duration hooks relevant =="
for f in $(git ls-files | rg 'src/frontend/src.*use-streaming-message\.ts$|src/frontend/src/.*/chat-messages/hooks/use-message-duration\.ts$|src/frontend/src/.*/chat-messages/hooks/use-message-duration\.ts$'); do
echo "--- $f ---"
wc -l "$f"
sed -n '1,120p' "$f"
done
echo
echo "== flow store isBuilding definitions =="
rg -n "setIsBuilding|isBuilding|stopBuilding|setStartTime|setComplete" src/frontend/src -g '*.ts' -g '*.tsx' | head -250Repository: langflow-ai/langflow
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== sendMessage/build flow call sites =="
rg -n "function sendMessage|const sendMessage|async .*sendMessage|buildFlow|setIsBuilding\\(true\\)|stopBuilding\\(|setIsBuilding\\(false\\)" src/frontend/src/modals/IOModal src/frontend/src/components/core/playgroundComponent -g '*.ts' -g '*.tsx' | head -220
echo
echo "== focused IOModal sendMessage section =="
sed -n '170,235p' src/frontend/src/modals/IOModal/playground-modal.tsx
echo
echo "== focused flow store build helpers =="
sed -n '340,390p' src/frontend/src/stores/flowStore.ts
rg -n "function buildFlow|const buildFlow|setIsBuilding\\(true\\)|stopBuilding\\(" src/frontend/src/stores/flowStore.ts src/frontend/src/hooks/use-webhook-events.ts src/frontend/src/components/core/chatComponents/HumanInputCard.tsx -A8 -B8 | head -220Repository: langflow-ai/langflow
Length of output: 10111
Correlate completion announcements with newly committed assistant messages. The falling-edge isBuilding check still reads chatHistory?.[chatHistory.length - 1]; if that message belongs to a previous turn or completes via backend cache/update after the build flag clears, the status region will announce the wrong response or miss the new one. Track the assistant message identity and keep the pending completion attached until the current message has been announced.
📍 Affects 2 files
src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/messages.tsx#L199-L211(this comment)src/frontend/src/modals/IOModal/components/chatView/components/chat-view.tsx#L120-L137
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/messages.tsx`
around lines 199 - 211, Update the completion-announcement logic in messages.tsx
and the corresponding chat-view.tsx logic at lines 120-137 to track the current
assistant message identity rather than reading only the latest chatHistory
entry. Keep the completion pending when isBuilding falls before the new
assistant message is committed, then announce only that correlated message once
available and avoid announcing a prior-turn response.
| <div role="status" aria-live="polite" className="sr-only"> | ||
| {completedAnnouncement} | ||
| </div> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Force a DOM mutation for every completion announcement. Setting the same message string twice can be optimized away, preventing the second response from being announced.
src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/messages.tsx#L300-L302: clear or re-key the status region per completion.src/frontend/src/modals/IOModal/components/chatView/components/chat-view.tsx#L257-L259: apply the same repeated-text handling.
📍 Affects 2 files
src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/messages.tsx#L300-L302(this comment)src/frontend/src/modals/IOModal/components/chatView/components/chat-view.tsx#L257-L259
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/messages.tsx`
around lines 300 - 302, Force a DOM mutation for each completion announcement by
clearing or re-keying the status region around completedAnnouncement in
messages.tsx (lines 300-302) and chat-view.tsx (lines 257-259), so identical
consecutive messages are announced independently.
| tabIndex={0} | ||
| onKeyDown={handleKeyDown} | ||
| onClick={handleClick} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Expose the template card as an actionable control.
This focusable, click-driven div should use role="button" or a native button so assistive technologies announce its purpose. Add a regression test covering tab focus and Enter/Space activation.
As per coding guidelines, frontend bug fixes must include tests that verify the changed behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/frontend/src/modals/templatesModal/components/TemplateGetStartedCardComponent/index.tsx`
around lines 66 - 68, Add button semantics to the clickable template card in
TemplateGetStartedCardComponent by applying role="button" or replacing the div
with a native button, while preserving keyboard and click activation. Add a
regression test covering tab focus and activation with both Enter and Space.
Source: Coding guidelines
The chat status live region rendered the full assistant message into an sr-only node, duplicating every response in the DOM. Playwright text locators then matched twice (fileUploadComponent strict-mode violation) or matched content that should have been gone (bulk-delete-sessions). Announce a short "Response complete" cue instead, keyed so repeats still re-announce. Also clears the biome errors the PR's changed-file set surfaced (unused imports, untyped test mocks) and refreshes the detect-secrets baseline for the new i18n password labels.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## release-1.12.0 #14300 +/- ##
==================================================
+ Coverage 61.63% 62.51% +0.88%
==================================================
Files 2416 2366 -50
Lines 242162 239341 -2821
Branches 36186 33716 -2470
==================================================
+ Hits 149249 149623 +374
+ Misses 90995 87898 -3097
+ Partials 1918 1820 -98
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…d focus order Fills coverage gaps from the a11y quick-wins PR: chat log region and response-complete announcement, ImageViewer/PdfViewer toolbar names, password show/hide toggle, and template card tabindex.
… response cue - keep password toggle mounted while input is focused (visually hidden) so forward Tab reaches it - add role=button and aria-label to template get-started card - extract duplicated response-complete cue into shared hook + status component, used by both chat views
QA round on PR #14300: sr-only text and keyed live-region children work in Chromium but not WebKit. - shortcuts: WebKit drops visually-clipped text from grid cell values, so name the shortcut wrapper explicitly (role=img + aria-label) and hide the icon visuals; covers settings grid and canvas tooltips - chat: role=log list gets aria-live=off - React remounts earlier messages on send, which a live region re-announces as additions (full-history re-read in VoiceOver); completion cue now mutates one persistent text node instead of re-keying a child Safari drops; stabilize IOModal message keys to chat.id - welcome: ignore Escape already consumed (defaultPrevented) by a Radix dialog so closing the templates modal no longer also closes the welcome; focus returns to the Browse more trigger Refs LE-2041
QA VoiceOver pass: 'Response complete' alone forces the user to hunt for the answer in a muted transcript. The status region is the reply's only path to the screen reader, so it must carry the reply text — matching the acceptance criterion (announce the completed reply once) and standard practice for streamed AI chat. - useResponseCompleteCue also captures the settled reply's text - status region announces the markdown-stripped text; falls back to 'Response complete' for non-text replies - syntax stripped so VO does not read 'asterisk asterisk bold' Refs LE-2041
# Conflicts: # .secrets.baseline
Jira: LE-2041 · Parent epic: LE-1505
Summary
Seven independent a11y quick wins on top of release-1.12.0. Label/semantics-level only — no visual or behavioral redesign. 24 new i18n keys added to all 7 locales.
RenderKeywerearia-hidden, so "Cmd+S" announced as "S" on every /settings/shortcuts row and canvas shortcut tooltip. Added sr-only text alternatives.role="log"containers and an sr-only status region that announces a reply once it finishes streaming — no token-by-token spam.DialogTitle(no accessible name). Real titles added; list-selection search input labeled.data-testids preserved. Review round: the password toggle now stays mounted (visually hidden) while the field is being edited, so forward Tab from the field still reaches it.tabIndex={1}→0(only positive tabindex in the codebase). Review round: cards also gainedrole="button"and an accessible name (the template name).QA — how to test
Verification
Summary by CodeRabbit
Accessibility Improvements
Localization
Tests