来源
2026-07-28 对 b1630e5^...HEAD 的 Standards review。相关重构:#236。
问题
SkillPreloadSection.tsx 将候选列表实现为手写绝对定位浮层,每个候选项使用:
<div role="button" onClick={...}>
但没有 tabIndex、键盘事件、Escape 关闭、焦点进入/返回或浮层碰撞管理。结果是鼠标用户可以添加/移除预载 Skill,键盘用户无法可靠聚焦和选择候选项。
这违反 DESIGN.md:
- Select / Combobox / Menu 使用 Radix 或现有可访问原语;
- 禁止裸
div onClick;
- Menu / Popover 正确管理 focus、Escape 和焦点返回。
该行为在 #236 前已存在,但拆分后的新组件继续固化了问题,因此单独修复,不混入纯重构票。
目标行为
- 触发按钮可用 Enter/Space 打开选择器。
- 打开后焦点进入搜索框或首个候选项。
- 候选项使用语义化 button、checkbox item 或现有可访问组件,不使用裸
div onClick。
- Enter/Space 切换选中状态;屏幕阅读器能感知名称和 checked/selected 状态。
- Escape 关闭最上层浮层并把焦点还给触发按钮。
- 点击外部关闭;关闭时沿用现有逻辑清空搜索词。
- 浮层进行窗口碰撞处理,不因靠近边缘而越界。
- 鼠标多选、搜索、已选列表删除和保存 payload 行为不变。
实现约束
- 优先复用
src/renderer/src/components/ui/ 中现有 Radix 封装;若现有 primitive 组合足够,不新增组件库。
- 保持
SkillPreloadSection props 与 AgentEditDialog 保存契约不变。
- 保持现有设计 token、密度和视觉层级;不引入新颜色、阴影体系或装饰性动画。
- 搜索输入和候选项必须有可访问名称;Tooltip 不作为唯一 label。
测试
使用 Testing Library 按用户行为验证:
- 键盘打开并聚焦搜索/候选区域;
- Enter/Space 选择和取消一个 Skill;
- 选中状态可通过 role + checked/selected 查询;
- Escape 关闭并恢复触发器焦点;
- 点击外部关闭并清空搜索;
- 搜索过滤与无结果状态;
- 鼠标点击和最终保存 payload 不回归。
不要依赖内部 DOM 层级或 CSS selector。
验收标准
非目标
- 不重新设计 AgentEditDialog 信息架构。
- 不修改 Skill preload 的领域规则或持久化格式。
- 不调整 MCP、Tool Scope 等其他字段分组。
来源
2026-07-28 对
b1630e5^...HEAD的 Standards review。相关重构:#236。问题
SkillPreloadSection.tsx将候选列表实现为手写绝对定位浮层,每个候选项使用:但没有
tabIndex、键盘事件、Escape 关闭、焦点进入/返回或浮层碰撞管理。结果是鼠标用户可以添加/移除预载 Skill,键盘用户无法可靠聚焦和选择候选项。这违反
DESIGN.md:div onClick;该行为在 #236 前已存在,但拆分后的新组件继续固化了问题,因此单独修复,不混入纯重构票。
目标行为
div onClick。实现约束
src/renderer/src/components/ui/中现有 Radix 封装;若现有 primitive 组合足够,不新增组件库。SkillPreloadSectionprops 与AgentEditDialog保存契约不变。测试
使用 Testing Library 按用户行为验证:
不要依赖内部 DOM 层级或 CSS selector。
验收标准
div role="button"。pnpm run typecheck、pnpm run build通过。非目标