Files
hermes-ui/docs/known-issues.md
T
toor b709ec6742
Build / build (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s
remove the desktop-pet system
The pet feature was three loosely coupled subsystems that no core flow
depended on: the Petdex catalog page, the pet install/preference store, and
the pet-state socket. All three are removed.

The only cross-boundary touch point was a try/catch-wrapped observer that
mapped chat-run events to pet animations. Removing it leaves the /chat-run
event contract unchanged: no event name, field name, field type, or emission
timing changed.

Removing petdexPublicRoutes also closes the unauthenticated outbound proxy
at /api/hermes/petdex/asset, which shrinks the SSRF surface.
2026-10-20 13:24:17 +08:00

234 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 已知问题与迁移遗留
记录 UI 库迁移(Phase 006-007,naive-ui → Arco Design Vue)过程中**发现但按规则未修**的问题,以及自动化测试**测不到、只能靠手工验证**的风险点。
用法:日常使用时遇到异常表现,先来本文档搜关键词。确认命中后按「证据」栏定位,确认是同一个问题再修。
新增条目请追加到对应分区末尾,不要重排已有条目。
---
## 一、已确认的业务逻辑缺陷
这些是读代码时确认存在的真实缺陷,与迁移无关(迁移前就存在),但迁移过程中被注意到。修它们属于逻辑改动,需单独开分支。
### 1. ~~ModelSelector 的 computed 里有副作用~~ — 已修(2026-10-19)
- **原现象**:切换模型时 provider 选择偶尔「自己跳」,或 `customProvider` 状态被莫名改写
- **位置**:`packages/client/src/components/layout/ModelSelector.vue`,`providerOptions` computed 的 getter
- **原证据**:computed 的 getter 内部给 `customProvider.value` 赋值。computed 应当是纯读。副作用是:任何其它 computed 或渲染逻辑读到 `providerOptions`,都会顺带改写 `customProvider`
- **修法**:把赋值移到 `watch` 或显式的 setter 里
- **实际改动**:getter 恢复成纯读(只返回 `activeModelGroups` 映射出的 options),
同步改由 `watch(() => appStore.selectedProvider, ...)` 承担。
`openModal()` 里原有的那次赋值保留 —— watch 是懒的,不在首帧触发。
- **回归测试**:`tests/client/model-selector-provider-sync.test.ts`
(断言 getter 无赋值、watch 挂在 `selectedProvider` 而非 `providerOptions` 上、
`openModal` 仍初始化)
### 2. ~~ProfileSelector 把 i18n key 当文案用~~ — 已核实为误报,无需修
- **原记录现象**:某些运行时状态提示里显示原始 key(如 `profiles.runtime.checking`)而不是中文/英文文案
- **位置**:`packages/client/src/components/layout/ProfileSelector.vue`,`gatewayStatusText` / `bridgeStatusText`
- **核实结论(2026-10-19)**:**该缺陷不存在**。三点证据:
1. `t` 来自 `useI18n()`(`:20`),`t('profiles.runtime.checking')` 返回的是
**已翻译的文案**,不是 key 本身。不存在「`t()` 的结果被当消息正文」的问题 ——
那正是 `t()` 的预期用法。
2. 组件内 33 处 `t()` 调用逐条比对 `zh.ts:1478-1510`,`avatar.*` 与 `runtime.*`
全部 key 齐全且中英双语都有对应条目,无缺失。
3. 全项目 grep `message.(warning|error|success|info|normal)\('[a-z]...')`
**零匹配** —— 没有裸传 key 字符串的调用点。
- **原记录为何误判**:原条目自己给出的「修法」与「证据」是同一句话
(都是 `message.warning(t('profiles.runtime.checking'))`),说明写条目时
把「`t()` 已经包了一层」当成了「少包了一层」。
- **若仍复现**:不要按原条目改代码。先确认复现路径,再查该路径上是否有
**另一处**未走 `t()` 的提示,或该 key 所属的 profile 名是否为空导致
`t(key, { name })` 插值异常。
### 3. ~~McpManagerView 的 `_autoRetryCount` 赋值可能静默失效~~ — 已核实为误报,无需修
- **原现象**:MCP 失败后点「重试」,自动重试计数似乎没有正确归零
- **原位置**:`packages/client/src/views/hermes/McpManagerView.vue`,`let _autoRetryCount`
- **原记录**:在 header 按钮的内联表达式 `@click="_autoRetryCount = 0; loadServers()"` 里被重置。
`<script setup>` 中的 `let` 绑定在模板内联语句里**不可写**(编译器只对 ref 做解包),这个重置很可能不生效。
- **核实结论(2026-10-19)**:**该缺陷不存在**。原记录的机制判断是错的:
1. `compileScript` 对 `setup-let` 绑定(`let` 而非 `ref`)会在 `__returned__` 里生成
**一对 getter/setter**:
`get _autoRetryCount() { return _autoRetryCount }, set _autoRetryCount(v) { _autoRetryCount = v }`。
2. 模板编译产物是 `$setup._autoRetryCount = 0`,走的是上面那个 setter,
**会写回闭包里的原变量**。
3. 运行时实测确认(真实 SFC + 真实 Vue 运行时):先把计数推到非 0,
再执行内联赋值,闭包内具名函数读到的就是复位后的值。
- **回归测试**:`tests/client/script-setup-let-assignment.test.ts`
(含夹具 `tests/client/fixtures/script-setup-let-assignment.vue`;
判据先把计数推到非 0 再复位读取,因此能区分「复位生效」与「复位没生效」)
- **若仍复现**:不要按原记录改代码。先确认复现路径,再查该路径上是否有
**另一处**真正的计数状态(比如 `scheduleReload` 的定时器没被取消导致旧的一次
`loadServers` 又把计数加回去)。
### 4. ~~McpManagerView 的 handleModeChange 有冗余重试~~ — 已修(2026-10-19)
- **原现象**:无功能影响,仅代码冗余
- **位置**:`packages/client/src/views/hermes/McpManagerView.vue`,`handleModeChange`
- **原记录**:两段重复的 try/catch 用同一个 `mode` 解析同一份文本,第一次失败后用相同模式重试一次才 return。第一个 catch 分支应直接 return
- **修正与实际改动**:原记录里「用同一个 `mode`」的说法不准确 —— 两段用的是
**不同**模式:第一段按 `oldMode`(切换前的格式)解析,第二段按 `mode`(切换后的格式)回退。
第二次尝试**不是**冗余,删掉会改变行为(用户在 JSON 编辑器里粘了一段 YAML 时,
切到 YAML 模式应当能救回来)。因此**不按原记录删除回退**。
实际改动是消除**代码重复**:抽出 `parseAsMode(text, mode)`,两处调用复用。
- **一个必须避开的坑**:解析结果用 `ParseOutcome` 判别式承载
(`{ ok: true; data }` / `{ ok: false }`),**不能**用 `null` 表示失败。
JSON 字面量 `null` 是**合法解析结果**,若用 `??` 短路,
`null` 会被误判成「解析失败」而触发回退,改变行为。
### 5. ~~WorkflowView 的批量删除确认 ref 已无读取方~~ — 已修(2026-10-19)
- **原现象**:无功能影响
- **位置**:`packages/client/src/views/hermes/WorkflowView.vue`,`showWorkflowBatchDeleteConfirm`
- **原记录**:naive 的 `NPopconfirm v-model:show` 需要它;Arco 的 `a-popconfirm` 无 `show` prop 后它失去读取方,只剩写。关闭改由 `@ok`/`@cancel` 驱动
- **实际改动**:确认无任何读取方后**整体删除**(声明 + 两处赋值)。
Arco 的 `a-popconfirm` 由 `@ok` / `@cancel` 驱动,删除不影响交互。
### 6. ~~ProfileCreateModal 有两个重复的 style 块~~ — 已修(2026-10-19)
- **原现象**:无功能影响
- **位置**:`packages/client/src/components/hermes/profiles/ProfileCreateModal.vue`
- **原记录**:两个 `<style scoped>` 内容相同(都是 `.modal-footer`)。迁移前存在
- **实际改动**:删除重复的第二个 `<style scoped>` 块,
保留第一个(它额外含 `.form-hint`)。`.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`
### a-modal 的 footer 默认 true
- **风险**:原本没有 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
- **文件**:`UserManagementSettings.vue`
- **备注**:naive 的 `ellipsis: { tooltip: true }` → Arco 的 `ellipsis: true` + 列顶层 `tooltip: true`
- **需确认**:长文本省略后悬浮能显示完整内容
### 视觉细节
- **圆角 tag**:naive 的 `NTag round` 无 Arco 对应,改用了 `border-radius: 999px`(`DevicesView.vue`)
- **按钮尺寸**:naive 的 `size="tiny"` → Arco `size="mini"`,可能与相邻元素不齐
- **滚动条**:naive 的 `:native-scrollbar="false"` 无对应,已删除,滚动条样式会变
### 弹窗的遮罩/ESC 行为
- **风险**:Arco 默认 `maskClosable` 和 `escToClose` 都是 `true`。用户填了一半的表单按 ESC 或点遮罩会**直接丢弃且不重置**
- **涉及**:`VoiceApiFormModal.vue`、`VoiceApiConfigurator.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 变更清单