replace the monochrome theme with the CYBER neon-cyan skin
Build / build (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

The project shipped a black-and-white theme that was only half converted:
the login page looked right, the message page was halfway, and the
settings page had not been started. This finishes the move to the CYBER
reference in prototype_reference/.

Colors now come from a single token source (styles/variables.scss), where
every semantic color is declared twice: once as OKLCH for the CSS, and
once as an RGB triple so translucent variants can be built as
rgba(var(--*-rgb), alpha). Cyan is the system itself, amber means the user
has to intervene, green means the machine is working, and red only ever
means something broke. The RGB triples are derived from the OKLCH values
so the two declarations cannot drift apart.

Pages adopt the .cy-* shell classes from the reference (view, panel,
kpis, tabs, grid, table, field) instead of ad-hoc markup, which is what
makes 19 of 26 views consistent. The remaining seven are deliberately
bespoke: chat streams, the kanban board, the workflow canvas, the
terminal, and the missing-agent screen.

The chart palette and the kanban column colors move into single-source
modules so they cannot drift either. A new guard test rejects raw hex
colors, named colors, and literal RGB triples anywhere in the client
source, with an explicit exemption list for the places that genuinely
cannot read a CSS variable. The existing chat semantics spec keeps the
chat chain honest.

No behavior changes: no logic, no i18n keys, no data structures, and the
/chat-run event contract is untouched.
This commit is contained in:
2026-10-20 11:17:46 +08:00
committed by trae-solo-agent
parent a1fff482bf
commit 5e1003ceb7
121 changed files with 13340 additions and 3364 deletions
+66 -21
View File
@@ -12,44 +12,89 @@
这些是读代码时确认存在的真实缺陷,与迁移无关(迁移前就存在),但迁移过程中被注意到。修它们属于逻辑改动,需单独开分支。
### 1. ModelSelector 的 computed 里有副作用
### 1. ~~ModelSelector 的 computed 里有副作用~~ — 已修(2026-10-19)
- **现象**:切换模型时 provider 选择偶尔「自己跳」,或 `customProvider` 状态被莫名改写
- **原现象**:切换模型时 provider 选择偶尔「自己跳」,或 `customProvider` 状态被莫名改写
- **位置**:`packages/client/src/components/layout/ModelSelector.vue`,`providerOptions` computed 的 getter
- **证据**:computed 的 getter 内部给 `customProvider.value` 赋值。computed 应当是纯读。副作用是:任何其它 computed 或渲染逻辑读到 `providerOptions`,都会顺带改写 `customProvider`
- **原证据**: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 当文案用
### 2. ~~ProfileSelector 把 i18n key 当文案用~~ — 已核实为误报,无需修
- **现象**:某些运行时状态提示里显示原始 key(如 `profiles.runtime.checking`)而不是中文/英文文案
- **原记录现象**:某些运行时状态提示里显示原始 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()` + 手动查表
- **核实结论(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` 赋值可能静默失效
### 3. ~~McpManagerView 的 `_autoRetryCount` 赋值可能静默失效~~ — 已核实为误报,无需修
- **现象**:MCP 失败后点「重试」,自动重试计数似乎没有正确归零
- **位置**:`packages/client/src/views/hermes/McpManagerView.vue`,`let _autoRetryCount`(模块级)
- **证据**:在 header 按钮的内联表达式 `@click="_autoRetryCount = 0; loadServers()"` 里被重置。`<script setup>` 中的 `let` 绑定在模板内联语句里**不可写**(编译器只对 ref 做解包),这个重置很可能不生效
- **修法**:改成 `ref`,或把重置逻辑移进一个具名函数
- **原现象**: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 有冗余重试
### 4. ~~McpManagerView 的 handleModeChange 有冗余重试~~ — 已修(2026-10-19)
- **现象**:无功能影响,仅代码冗余
- **原现象**:无功能影响,仅代码冗余
- **位置**:`packages/client/src/views/hermes/McpManagerView.vue`,`handleModeChange`
- **证据**:两段重复的 try/catch 用同一个 `mode` 解析同一份文本,第一次失败后用相同模式重试一次才 return。第一个 catch 分支应直接 return
- **原记录**:两段重复的 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 已无读取方
### 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` 驱动
- **原记录**:naive 的 `NPopconfirm v-model:show` 需要它;Arco 的 `a-popconfirm` 无 `show` prop 后它失去读取方,只剩写。关闭改由 `@ok`/`@cancel` 驱动
- **实际改动**:确认无任何读取方后**整体删除**(声明 + 两处赋值)。
Arco 的 `a-popconfirm` 由 `@ok` / `@cancel` 驱动,删除不影响交互。
### 6. ProfileCreateModal 有两个重复的 style 块
### 6. ~~ProfileCreateModal 有两个重复的 style 块~~ — 已修(2026-10-19)
- **现象**:无功能影响
- **原现象**:无功能影响
- **位置**:`packages/client/src/components/hermes/profiles/ProfileCreateModal.vue`
- **证据**:两个 `<style scoped>` 内容相同(都是 `.modal-footer`)。迁移前存在
- **原记录**:两个 `<style scoped>` 内容相同(都是 `.modal-footer`)。迁移前存在
- **实际改动**:删除重复的第二个 `<style scoped>` 块,
保留第一个(它额外含 `.form-hint`)。`.modal-footer` 规则不变。
---