Files
hermes-ui/docs/known-issues.md
T
toor cc644c59f6 Phase 008: close the test and CI loop around the migration
Adds the checks that make the constraints from Phase 001-007 mechanical
instead of tribal knowledge, and clears the legacy brand text that was still
reachable at runtime.

New red-line tests:
  - no-legacy-brand mirrors the harness scan under vitest, and asserts its
    allow-list has not drifted from harness-check.mjs
  - hermes-db-readonly classifies every new DatabaseSync call site by
    following the path it opens, and fails if a state.db handle drops
    readOnly. It also pins the one legitimately writable database.
  - i18n-parity compares the raw zh/en locale objects both ways. The
    existing coverage test merged en into zh at import time, so a key
    missing from zh could never fail there.

harness:check gains the four missing gates from the phase brief - retired
package directories, retired package scripts, a single UI library, and the
required root files. Each was verified to fail by mutating the repo.

The online-update channel had hardcoded upstream URLs. It now reads
HERMES_WEB_UI_VERSION_MANIFEST_URL and friends and degrades with a named
error, because a reimplementation has nowhere to pull releases from.

The remaining legacy strings are deliberate and each carries a reason in the
allow-list: the global-agent relay address and the OpenRouter referer are
load-bearing, the socket role identifier is a wire contract, and the pre-rename
DB filenames would orphan existing deployments if changed. AGENTS.md now says
so explicitly, so a future agent does not "clean them up".

Also removes a dead electron mock alias, an i18n exemption that had stopped
exempting anything, and a teardown race in plugins-view.test.ts where a-spin
animation frames outlived the jsdom environment.

Verified self-contained: with reference_code/ moved aside, harness:check,
build, test and test:e2e all pass, so the directory can now be deleted.
2026-10-19 00:23:19 +08:00

12 KiB
Raw Blame History

已知问题与迁移遗留

记录 UI 库迁移(Phase 006-007,naive-ui → Arco Design Vue)过程中发现但按规则未修的问题,以及自动化测试测不到、只能靠手工验证的风险点。

用法:日常使用时遇到异常表现,先来本文档搜关键词。确认命中后按「证据」栏定位,确认是同一个问题再修。

新增条目请追加到对应分区末尾,不要重排已有条目。


一、已确认的业务逻辑缺陷

这些是读代码时确认存在的真实缺陷,与迁移无关(迁移前就存在),但迁移过程中被注意到。修它们属于逻辑改动,需单独开分支。

1. ModelSelector 的 computed 里有副作用

  • 现象:切换模型时 provider 选择偶尔「自己跳」,或 customProvider 状态被莫名改写
  • 位置:packages/client/src/components/layout/ModelSelector.vue,providerOptions computed 的 getter
  • 证据:computed 的 getter 内部给 customProvider.value 赋值。computed 应当是纯读。副作用是:任何其它 computed 或渲染逻辑读到 providerOptions,都会顺带改写 customProvider
  • 修法:把赋值移到 watch 或显式的 setter 里

2. ProfileSelector 把 i18n key 当文案用

  • 现象:某些运行时状态提示里显示原始 key(如 profiles.runtime.checking)而不是中文/英文文案
  • 位置:packages/client/src/components/layout/ProfileSelector.vue,gatewayStatusText / bridgeStatusText
  • 证据:message.warning(t('profiles.runtime.checking')) —— t() 的结果(一个 key 字符串)被直接当消息正文,缺了外层再套一次 t()
  • 修法:改成 message.warning(t('profiles.runtime.checking'))(让 t() 返回字符串函数的结果),即检查是否需要 t(t(key)) 或改用 te() + 手动查表

3. McpManagerView 的 _autoRetryCount 赋值可能静默失效

  • 现象:MCP 失败后点「重试」,自动重试计数似乎没有正确归零
  • 位置:packages/client/src/views/hermes/McpManagerView.vue,let _autoRetryCount(模块级)
  • 证据:在 header 按钮的内联表达式 @click="_autoRetryCount = 0; loadServers()" 里被重置。<script setup> 中的 let 绑定在模板内联语句里不可写(编译器只对 ref 做解包),这个重置很可能不生效
  • 修法:改成 ref,或把重置逻辑移进一个具名函数

4. McpManagerView 的 handleModeChange 有冗余重试

  • 现象:无功能影响,仅代码冗余
  • 位置:packages/client/src/views/hermes/McpManagerView.vue,handleModeChange
  • 证据:两段重复的 try/catch 用同一个 mode 解析同一份文本,第一次失败后用相同模式重试一次才 return。第一个 catch 分支应直接 return

5. WorkflowView 的批量删除确认 ref 已无读取方

  • 现象:无功能影响
  • 位置:packages/client/src/views/hermes/WorkflowView.vue,showWorkflowBatchDeleteConfirm
  • 证据:naive 的 NPopconfirm v-model:show 需要它;Arco 的 a-popconfirm 无 show prop 后它失去读取方,只剩写。关闭改由 @ok/@cancel 驱动

6. 两个弹窗共用一个 saving ref

  • 现象:新增设备弹窗与改名弹窗同时存在时,按钮的 loading/disabled 会互相干扰
  • 位置:packages/client/src/components/layout/SettingsCircuitBadge.vue
  • 证据:两个 modal 共用 saving。迁移前就是这样

7. ProfileCreateModal 有两个重复的 style 块

  • 现象:无功能影响
  • 位置:packages/client/src/components/hermes/profiles/ProfileCreateModal.vue
  • 证据:两个 <style scoped> 内容相同(都是 .modal-footer)。迁移前存在

二、Arco 与 naive 的行为差异(可能表现为「功能坏了」)

迁移时按等价语义做了处理,但以下差异在某些边界条件下可能暴露。如遇异常,先查这里。

a-input-password 默认是遮蔽的

  • 风险:任何给 a-input-password 传 :default-visibility="false" 的地方,密码/API Key 会明文显示
  • 正确做法:不要传这个 prop。源码 es/input/input-password.js:77 是 type: mergedVisible ? "password" : "text",defaultVisibility 默认 true 即遮蔽
  • 检查命令:grep -rn "default-visibility\|defaultVisibility" packages/client/src(期望 0 命中)

a-upload 默认会发真实 XHR

  • 风险:没加 :auto-upload="false" 的上传组件,选一次文件就向当前页 URL 发一个无意义 multipart 请求
  • 正确做法:凡是「只选文件、由 store 走 API」的上传,a-upload 必须显式加 :auto-upload="false"
  • 涉及文件:ProfileImportModal.vue、SkillImportModal.vue、FileUploadModal.vue

Arco Trigger 不能嵌套

  • 风险:a-tooltip / a-dropdown / a-popconfirm / a-popover 互相嵌套时,菜单或提示点不开
  • 原因:它们内部都用 mergeFirstChild 接管首个元素节点的事件,多层会互相踩
  • 正确做法:同一元素既要 tooltip 又要 dropdown 时,tooltip 改用原生 :title
  • 已处理:GroupChatInput.vue 的设置按钮(NDropdown > NTooltip > NButton → a-dropdown > a-button + :title)、ChatInput.vue L1130-1162

tooltip/dropdown 默认插槽必须只含一个元素

  • 风险:尾随的纯文本({{ t(...) }})会被当可见内容渲染进布局流,变成 0 宽的 flex item,把工具栏挤成竖排或让文本被截断
  • 正确做法:文案放 content prop,默认插槽只留触发元素
  • 已踩坑位置:ChatInput.vue 三处、GroupChatInput.vue、TerminalPanel.vue、KanbanTaskCard.vue、KanbanView.vue 排序按钮
  • 自查:grep -rn "a-tooltip" -A 8 packages/client/src | grep -B2 "{{ t" 然后肉眼确认

NDropdown 的 droplist 没有对应

  • 风险:给 a-dropdown 传 droplist 会静默失效(Arco 不对未声明的 prop 报错,未声明的 prop 落为普通 attribute)
  • 正确做法:菜单写进 #content 插槽用 <a-doption>
  • 历史:Phase 006 的三个下拉全打不开,根因就是这个

a-input / a-textarea 的 modelValue 不接受 null

  • 风险:null 初值会导致该组件显示异常或 TS 报错
  • 正确做法::model-value="x ?? undefined",配合 @change="x = $event ?? null" 桥接保住 null 语义
  • 涉及:AuxiliaryModelsPanel.vue、DisplaySettings.vue、VoiceApiFormModal.vue、KanbanCreateForm.vue、JobFormModal.vue、GroupChatPanel.vue

a-spin 的 size 是 Number(px)不是字符串档位

  • 风险:传 size="small" 会被拼成 fontSize: "smallpx"
  • 正确做法:传 :size="14" 这样的数字
  • 涉及:PlatformSettings.vue、ProfileCard.vue、JobRunHistory.vue、VoiceApiFormModal.vue

a-input-password 没有 width prop

  • 风险::width="220" 被静默丢弃(底层 Trigger 设了 inheritAttrs: false)
  • 正确做法:改用 :content-style

a-scrollbar 用 max-height 会高度塌陷

  • 正确做法:必须显式给 height
  • 涉及:McpManagerView.vue
  • 风险:原本没有 footer 插槽的弹窗,会多出取消/确定按钮
  • 正确做法:无自定义 footer 插槽时加 :footer="false";有插槽时绝对不能加(加了插槽内容不渲染)

Arco 按钮在 loading 时吞掉后续 click

  • 风险:快速连点同一按钮,第二次点击不触发
  • 影响:SkillsUsageView.vue 的刷新按钮(测试已改写场景绕过,但 UI 上真实存在)
  • 备注:不是 bug,是 Arco 的行为

三、自动化测不到、需手工确认的风险点

单元测试和 e2e 都覆盖不到这些。首次正式使用时请重点走一遍。

右键菜单

  • 文件:FileContextMenu.vue、WorkflowView.vue、HistoryView.vue、GroupChatPanel.vue
  • 风险:naive 的 NDropdown trigger="manual" + :x/:y 迁到了 a-trigger trigger="contextMenu" + :popup-style
  • 需确认:① 右键能弹出 ② 菜单打开时滚动页面,菜单是否跟随滚动(Arco 在 autoFitPosition: false 时不重算位置,菜单会留在原地)
  • 需确认:③ 刚右键弹出就立刻点别处,菜单是否被误关(naive 靠 contextMenuOpenedAt 时间戳防抖,Arco 靠内置 clickOutsideToClose,时序可能微妙不同)

文件树懒加载

  • 文件:FileTree.vue(naive NTree → Arco a-tree)
  • 需确认:① 点目录能加载子目录 ② 只加载目录不加载文件(filter(e => e.isDir))③ 快速切换目录时不会出现子节点串台(深层懒加载的竞态保护弱于根节点,rootLoadSeq 只在切 profile/session 时自增)

Kanban 折叠与布局

  • 文件:KanbanView.vue、KanbanColumn.vue
  • 备注:迁移时发现 kanban 实际没有拖拽逻辑(排序靠卡片上的 sort 按钮),任务书里的「拖拽落点」风险不存在
  • 需确认:① 折叠/展开的列状态正确 ② Arco a-collapse 默认全部展开(已显式设 :default-expand-all="false")

表格列的省略与 tooltip

  • 文件:SettingsCircuitBadge.vue、UserManagementSettings.vue
  • 备注:naive 的 ellipsis: { tooltip: true } → Arco 的 ellipsis: true + 列顶层 tooltip: true
  • 需确认:长文本省略后悬浮能显示完整内容

视觉细节

  • 圆角 tag:naive 的 NTag round 无 Arco 对应,改用了 border-radius: 999px(PetdexView.vue、DevicesView.vue)
  • 按钮尺寸:naive 的 size="tiny" → Arco size="mini",可能与相邻元素不齐
  • 滚动条:naive 的 :native-scrollbar="false" 无对应,已删除,滚动条样式会变

弹窗的遮罩/ESC 行为

  • 风险:Arco 默认 maskClosable 和 escToClose 都是 true。用户填了一半的表单按 ESC 或点遮罩会直接丢弃且不重置
  • 涉及:VoiceApiFormModal.vue、VoiceApiConfigurator.vue、SettingsCircuitBadge.vue 的设备面板
  • 需确认:是否应该设 :mask-closable="false" / :esc-to-close="false"

模型选择弹窗(ProviderFormModal)

  • 备注:naive 的 NButton type="primary" secondary(浅色主按钮)在 Arco 无对应组合,去掉了 secondary 保留 type="primary",视觉变成实心主色按钮
  • 需确认:视觉是否可接受

四、其它观察

  • TerminalView.vue 的终端主题下拉选项 label 是硬编码英文("Default"、"Monokai"),未走 i18n。迁移前既有问题
  • AuxiliaryModelsPanel.vue 有 5 处手写 <label> + <span> 而非 a-form-item,与其它 Arco 表单的对齐/必填星号规则不一致
  • ProviderFormModal.vue 的 handleSave 有 6 个近乎重复的 OAuth 分支,重复度高、容易漏改,建议后续合并成 provider→trigger 映射表
  • FileUploadModal.vue 关闭时只清空了本地 fileList,未清 a-upload 内部状态。目前靠 modal 默认 unmount-on-close 兜底,若有人关掉该 prop 会出现「列表已清空但 UI 仍显示」
  • McpManagerView.vue 的 handleModeChange 与 FileContextMenu.vue 的 show() 快速连点时,Arco popup 的 transition 理论上可能动画排队

相关文档

  • docs/arco-migration-rules.md —— 迁移规约与完整组件映射表
  • docs/arco-mapping.md —— naive → Arco 映射与结构差异
  • docs/chat-chain-changes/2026-10-18-phase007-arco-full-migration.md —— Phase 007 变更清单