fix(web-shell): make approval and question overlays keyboard accessible - #7074
Conversation
The tool-approval and ask-user-question overlays rendered their options as plain <div onClick> elements with no role, tabindex, or key handling, so keyboard and screen-reader users could not authorize a tool call or answer a question — the core interaction of the chat surface. Convert the options to real <button>s in a roving-tabindex group, with focus-scoped keyboard navigation (arrows/j/k, Home/End, Enter/Space, digit shortcuts, Escape), visible :focus-visible rings, and an alertdialog role with labelledby/describedby so assistive tech announces the prompt on arrival. The question text, previously display:none, is kept in the accessibility tree as the dialog description. The overlays now own their own focus, pulling it to the safe-default option when they become the topmost surface; the app just signals topmost-ness via a keyboardActive prop. Replacing the old global window key listener with panel-scoped handling also stops a keypress from confirming a different split-view pane's request.
|
Thanks for the PR! Template looks good ✓ Problem: Real accessibility gap — approval and question overlays rendered their options as plain Direction: Aligned. Making core interactive surfaces keyboard-accessible and screen-reader-perceivable is squarely within the web-shell's mission. Removing the global keyboard listener in favor of focus-scoped handling is the right architectural move — it eliminates the split-view cross-pane bug as a side effect. Size: Not applicable — all changes are in Approach: The scope is well-matched to the problem. The changes follow the WAI-ARIA Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题:真实存在的可访问性缺口——授权和提问浮层把选项渲染成没有语义 role、tabindex 和键盘处理的普通 方向:对齐。让核心交互表面支持键盘和屏幕阅读器是 web-shell 的职责所在。把全局键盘监听改为焦点作用域处理,还顺带消除了 split-view 跨面板问题。 规模:不适用——所有改动都在 方案:范围与问题匹配。采用了 WAI-ARIA 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
Code ReviewThe implementation is textbook WAI-ARIA. Both ToolApproval and AskUserQuestion follow the same clean pattern: Correctness: Focus management is well-designed — safe default on first mount, selection restore when a covering panel closes, and focus reset to safe default when a new request arrives. The No blockers found. The diff is tightly scoped to the accessibility goal with no scope creep. Reuse check: The Tests227 tests pass across the 4 affected files (up from 183 in the previous review — new App.test.tsx assertions cover the
Typecheck: Clean ( Test output中文说明代码审查实现遵循标准 WAI-ARIA 模式。ToolApproval 和 AskUserQuestion 采用相同的干净方案:带标签的 正确性: 焦点管理设计良好——首次挂载聚焦安全默认项、覆盖面板关闭后恢复选择、新请求到达时重置为安全默认项。 未发现阻塞问题。 diff 紧密围绕可访问性目标,无范围蔓延。 测试4 个受影响文件共 227 项测试全部通过(比上次审查的 183 项增加——App.test.tsx 新增了对 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 5/5 — clean across every stage; this is exactly the kind of focused, well-tested accessibility improvement the web-shell needs. The PR solves a real problem that blocks keyboard and screen-reader users from the two most important interactions in the chat surface — approving tool calls and answering agent questions. The implementation is textbook WAI-ARIA: 227 unit tests all pass locally, typecheck and lint are clean, CI is fully green (including This is ready to ship. ✅ 中文说明置信度:5/5 — 各阶段均通过;这是一个聚焦、测试充分的 web-shell 可访问性改进。 PR 解决了键盘和屏幕阅读器用户无法完成工具授权和回答提问的真实问题。实现遵循标准 WAI-ARIA 模式:带标签的 227 项本地单元测试全部通过,类型检查和 lint 无错误,CI 全绿(包括之前失败的 可以合并。 ✅ — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
Follow-up to the review comments on the keyboard-accessible approval overlays: - AskUserQuestion moveSelection now writes selectedIdxRef synchronously, so a held arrow key (repeating faster than React re-renders) advances correctly instead of sticking on one option. The reset effect syncs the ref too, so a fresh request focuses the right option rather than the previous request's stale selection. - ToolApproval restores the user's selected option when the overlay is re-activated (e.g. a covering panel closes) instead of snapping focus back to the safe default and silently changing what Enter would confirm. - The "Other" custom-input trigger now carries aria-keyshortcuts so its digit shortcut is discoverable to screen readers. - Adds tests for Home/End navigation, the rapid-repeat regression, the custom-input digit guard, and the AskUserQuestion keyboardActive wiring.
|
Thanks for the thorough review — all 6 suggestions addressed in
Verification: |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
Mirror the existing ToolApproval coverage: capture keyboardActive on the
AskUserQuestion mock and assert a split-view pane's question renders with
keyboardActive={false}, so a refactor that drops the prop — which would let a
pane's question auto-grab focus away from the pane the user is in — is caught.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: Agent 0: Issue fidelity & root-cause ownership — its prompt was built, but no agent was launched with it. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run. Not reviewed: verification — the review posts findings, but no verifier ran (Step 4 builds its prompt with agent-prompt --role verify; none was recorded, so the findings were not verified).
— qwen3.7-max via Qwen Code /review
Mirror the ToolApproval guard: when a covering panel flips keyboardActive false then true, focus must return to the option the user had selected rather than snapping back to the default (which would silently change what Enter submits). Verified the test fails if the focus effect snaps to the default.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run.
— qwen3.7-max via Qwen Code /review
handleKeyDown was attached to the whole panel, so digit / j-k / Home / End / Escape fired whenever focus was on any non-editable descendant — including the Submit/Previous/Next buttons and the collapse toggle. A keyboard user tabbed onto Submit could silently overwrite their selected answer with a digit, or cancel the question with Escape; when collapsed (only the toggle focusable) the same shortcuts fired against options that weren't even rendered. Guard the handler so it only reacts when focus is on an option (a roving-tabindex button or the "Other" trigger). Also adds the two tests the review asked for: the action-button hijack guard above, and a ToolApproval assertion that Enter is left to native button activation (so a reintroduced double-press guard would be caught).
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: verification — the review posts findings, but no verifier ran (Step 4 builds its prompt with agent-prompt --role verify; none was recorded, so the findings were not verified).
— qwen3.7-max via Qwen Code /review
- Single-select AskUserQuestion options now use radiogroup/radio + aria-checked (instead of toggle-button aria-pressed) so screen readers convey mutual exclusivity; multi-select keeps toggle buttons. - The expanded question dialog is named with both the tool name and the question (aria-labelledby references both), so the tool-name context isn't dropped when aria-labelledby overrides aria-label. - Drop the redundant role/label on ToolApproval's option container — the alertdialog already exposes the question via aria-describedby, so labelling the container with the same text made screen readers speak the question twice. Adds tests locking in the radio semantics and the expanded-dialog naming.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
— qwen3.7-max via Qwen Code /review
Local build & real-browser verification — LGTM, merge-ready ✅Independently verified at head Unit tests, typecheck, lint
(The PR body's "1689 tests" was measured against an older main; both endpoints above are freshly measured.) Real-browser E2E (Chromium + Playwright, mock-daemon harness)Temp spec driving the real app at
Merge-readiness cross-checks
ScreenshotsToolApproval — before/after (light; note the focus ring on the safe default): AskUserQuestion — before/after (light; after shows Dark theme: "Other" custom input with typed digits staying in the field: Notes (non-blocking)
Verification artifacts: screenshots hosted on 中文版本本地构建与真实浏览器验证 — LGTM,可合并 ✅在隔离 worktree 中以完整 单元测试、类型检查、Lint
(PR 描述中的 "1689 tests" 是基于更早的 main;上表两端均为本次实测。) 真实浏览器 E2E(Chromium + Playwright,mock-daemon harness)临时 spec 驱动真实应用页面
可合并性交叉核查
截图见上方英文部分(ToolApproval 前后对比、AskUserQuestion 前后对比、暗色主题、"Other" 自定义输入)。 备注(不阻塞)
验证产物:截图托管于 |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 2 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run.
— qwen3.7-max via Qwen Code /review
…roval Add a test that when a new request (different id) arrives while the approval is keyboard-active and the user has navigated off the default, focus moves to the new request's safe default rather than the stale option index (which could map to a more permissive option in the new request). Reverse-audited: it fails if both the reset's selectedRef sync and the focus effect's requestChanged branch are removed.
ytahdn
left a comment
There was a problem hiding this comment.
Reviewed diff-only — the PR’s existing discussion could not be fetched, so this is not an approval and not a no-blockers claim. 2 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Not reviewed: issue-fidelity — lightweight mode, no PR metadata in plan. Not reviewed: build-and-test — no local tree in lightweight mode. Not reviewed: cross-file-tracer — no local tree in lightweight mode. Not reviewed: chunk 5 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 12 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 19 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 10 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 3 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 14 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 2 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 15 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 4 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 13 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 20 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 9 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 18 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 16 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 17 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 8 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 7 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 1 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 11 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 6 — launched with a prompt that never named the diff file, so it could not have read it (build the prompt with qwen review agent-prompt). Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — its prompt was built, but no agent was launched with it. Not reviewed: chunk 2 — its prompt was built, but no agent was launched with it. Not reviewed: chunk 3 — its prompt was built, but no agent was launched with it. Not reviewed: chunk 4 — its prompt was built, but no agent was launched with it. Not reviewed: chunk 5 — its prompt was built, but no agent was launched with it. Not reviewed: Test coverage matrix (whole-diff) — its prompt was built, but no agent was launched with it. Not reviewed: Agent 1b: Removed-behavior audit — its prompt was built, but no agent was launched with it. Not reviewed: reverse audit — no auditor ran (Step 5 builds its prompt with agent-prompt --role reverse-audit; none was recorded, so the pass that looks for what Step 3 missed was skipped).
— qwen3.7-max via Qwen Code /review
…ogroup) Critical: in single-select AskUserQuestion, arrow keys moved focus but not the committed answer, so aria-checked (bound to `answers`) stayed on the original option and Submit sent the answer the user never chose — violating the radiogroup contract. moveSelection now updates the answer for single-select (the "Other" row still opens on Enter, not on arrow). Also: - aria-keyshortcuts is only advertised for options 1-9 (the handler ignores multi-digit keys, so a 10+ option would announce a shortcut that fails). - ToolApproval's focusOption blurs before refocusing an already-focused option, so a new request landing on the same index re-announces for screen readers. - Tests: arrow-keys-change-the-answer (Critical guard) and new-question-while-active focus reset. Note: this changes single-select interaction so arrows select immediately (standard radiogroup behavior) rather than highlight-only-then-Enter.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. 1 Suggestion-level finding(s) could not be anchored to the diff; see the terminal output. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built.
— qwen3.7-max via Qwen Code /review
…s focus Home/End and the "Other" branch of moveSelection didn't keep the committed answer in sync with focus, so aria-checked stayed on a stale option (a radiogroup-contract violation) — the same class of bug as the arrow-key fix. Extract a shared selectIndex() used by arrows, Home, and End: moving to a regular option commits it as the answer; moving to "Other" clears the regular answer (the custom answer isn't committed until the user types it). Tests: Home/End change the single-select answer; moving to "Other" clears the regular answer (both reverse-audited).
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
@qwen-code /resolve |
|
Qwen Code resolved the merge conflicts and pushed the branch update. Merge Conflict Resolution Summary — PR #7074Conflicted file
What conflictedBoth origin/main side
PR branch (HEAD) sideRenamed the same approval-overlay test to ResolutionKept both sides' contributions:
The symmetric ask-user-question test that follows was untouched by the conflict and remains intact. Commit
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: chunk 5 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 2 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 3 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 4 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 1 — launched with a prompt that is not the one the CLI built. Not reviewed: chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run.
— qwen3.7-max via Qwen Code /review
The "Other" row wrapper kept cursor:pointer (from styles.option) but lost its onClick when the inner span became a button/input, so clicking the row's padding showed a clickable cursor yet did nothing. Add onClick to the wrapper (the trigger button's click and native Enter/Space activation bubble up to it), and stop the custom input's click from bubbling so caret positioning isn't re-triggered. Test: clicking the row padding opens the custom input (reverse-audited).
ytahdn
left a comment
There was a problem hiding this comment.
LGTM ✅
增量 review (commit 3bc3ea0: fix(web-shell): make the whole "Other" row clickable)
✅ 修复精准:将 onClick 从 trigger button 移到 wrapper div,整行(含 padding)可点击
✅ stopPropagation() 防止 input 点击重复触发 chooseOption()
✅ 原生 button 的 Enter/Space 激活正确冒泡到 wrapper
✅ 新增测试覆盖 padding 点击场景
✅ 无障碍性保持良好(aria-expanded、aria-controls、aria-keyshortcuts)
代码质量高,改动最小化,逻辑正确。
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅






What this PR does
The tool-approval and ask-user-question overlays — the prompts that appear when the agent wants to run a tool or ask the user a question — rendered their options as plain clickable
divs with no semantic role, no tabindex, and no key handling. This PR makes both overlays fully operable by keyboard and perceivable by assistive technology. The options are now real buttons in a roving-tabindex group, the overlays announce themselves as analertdialogwith a labelled prompt, focus moves to the safe-default option when the overlay appears, and keyboard navigation (arrows /j/k,Home/End,Enter/Space, number shortcuts,Esc) works while focus is inside the overlay. The previously hidden prompt text is kept in the accessibility tree as the dialog description.Why it's needed
Authorizing a tool call and answering an agent question are the core interactions of the chat surface, and until now keyboard and screen-reader users could not perform them: the options weren't focusable, weren't announced, and had no keyboard path. The old design also drove approval shortcuts through a global
windowkey listener, which meant a keypress could in principle confirm a different split-view pane's request. Making the options real, focus-scoped buttons fixes the accessibility gap and removes that cross-pane hazard.Reviewer Test Plan
How to verify
Trigger a tool that needs approval (e.g. run a shell command in a non-yolo approval mode) and, without touching the mouse:
↑/↓(orj/k); the focused option shows a visible focus ring.Enter/Space, pick by number (1–9), and reject withEsc.Enterto reveal the field, type, thenTabto Submit), and that typing a digit inside the custom field does not select an option.Unit tests cover all of the above:
ToolApproval.test.tsx(alertdialog role, real buttons, safe-default focus, roving tabindex, digit/Escape/arrows,keyboardActive=falsedoesn't steal focus) andAskUserQuestion.test.tsx(same plus select-then-submit and digit-pick).npm run typecheck, ESLint, and the full web-shell unit suite (1689 tests) pass.Evidence (Before & After)
Verified via the unit tests above plus typecheck/lint rather than a live recording. Before: options were
<div onClick>with no role/tabindex — Tab skipped them and a screen reader announced nothing actionable. After: options are<button>s in analertdialog, focus lands on the default, and the full keyboard path works (asserted by the new tests).Tested on
Environment (optional)
Unit tests via
npx vitest runinpackages/web-shell.Risk & Scope
Enterconfirms a safe action. This drops the old "press Enter twice to confirm" guard, which existed only to protect a non-button focus model that no longer applies.data-web-shell-permission-option/data-option-idselectors used by e2e are preserved.Linked Issues
None.
中文说明
本 PR 做了什么
工具授权(tool-approval)与向用户提问(ask-user-question)这两个浮层——即 agent 想运行工具或向用户提问时出现的提示——此前把选项渲染成普通可点击
div,没有语义 role、没有 tabindex、也没有键盘处理。本 PR 让两个浮层都能被键盘完整操作、并能被辅助技术感知:选项改为 roving-tabindex 分组中的真实按钮,浮层以alertdialog自报家门并带带标签的提示文案,出现时焦点落到最安全的默认选项,键盘导航(方向键 /j/k、Home/End、Enter/Space、数字快捷键、Esc)在浮层获得焦点时可用;此前被隐藏的提示文案也保留在可访问性树中作为对话框描述。为什么需要
授权工具调用与回答 agent 提问是聊天界面的核心交互,而此前键盘与屏幕阅读器用户无法完成:选项不可聚焦、不被播报、也没有键盘路径。旧设计还通过全局
window按键监听来驱动授权快捷键,这意味着一次按键理论上可能确认了另一个 split-view 面板的请求。把选项改成真实的、焦点作用域内的按钮,既修复了可访问性缺口,也消除了这个跨面板隐患。评审验证计划
如何验证
触发一个需要授权的工具(例如在非 yolo 审批模式下运行 shell 命令),在不碰鼠标的情况下:
↑/↓(或j/k)在选项间移动,被聚焦的选项有可见焦点环。Enter/Space确认、数字键(1–9)直选、Esc拒绝。Enter展开输入框、输入后Tab到提交),以及在输入框里敲数字不会去选选项。单元测试覆盖以上全部:
ToolApproval.test.tsx(alertdialog 角色、真实按钮、安全默认聚焦、roving tabindex、数字/Escape/方向键、keyboardActive=false不抢焦点)与AskUserQuestion.test.tsx(上述外加"选中后提交"和数字直选)。npm run typecheck、ESLint、以及 web-shell 完整单测(1689 项)全部通过。证据(前后对比)
通过上述单元测试加 typecheck/lint 验证,而非录屏。改动前:选项是无 role/tabindex 的
<div onClick>——Tab 会跳过它们、屏幕阅读器读不出可操作项。改动后:选项是alertdialog中的<button>,焦点落在默认项,完整键盘路径可用(由新增测试断言)。测试环境
环境(可选)
在
packages/web-shell下通过npx vitest run跑单元测试。风险与范围
Enter确认的是安全动作。这去掉了旧的"按两次 Enter 才确认"的保护——那个保护只是为已不存在的非按钮焦点模型而设。data-web-shell-permission-option/data-option-id选择器均已保留。关联 Issue
无。