352 lines
18 KiB
Markdown
352 lines
18 KiB
Markdown
# CYBER 主题全局规划
|
||
|
||
CYBER 是本项目**唯一**的视觉语言。`docs/ui-baseline.md` 是它的**规范**(规定应该长什么样),
|
||
本文是它的**路线图**(规定推进到什么程度、什么算完成)。
|
||
|
||
- 规范:`docs/ui-baseline.md`
|
||
- 令牌源:`packages/client/src/styles/variables.scss`
|
||
- 运行时与骨架类:`packages/client/src/styles/global.scss`
|
||
- 变更片段:`docs/chat-chain-changes/<date>-<slug>.md`(改到聊天链时强制)
|
||
|
||
---
|
||
|
||
## 一、现状盘点(2026-10-19 实测)
|
||
|
||
### 1.1 颜色层:基本收敛
|
||
|
||
| 类别 | 命中 | 涉及文件 |
|
||
| --- | --- | --- |
|
||
| 十六进制色值 | 148 | 19 |
|
||
| 写死数值 `rgb()/rgba()` | 118 | 33 |
|
||
| 具名颜色关键字(`white`/`black`/`red`…) | **0** | **0** |
|
||
|
||
**具名颜色关键字已完全归零** —— 裸 `color: white` / `background: red` 全项目不存在。
|
||
剩下的 hex 与写死 rgba 集中在少数几处,性质可分四类:
|
||
|
||
| 类别 | 位置 | 处置 |
|
||
| --- | --- | --- |
|
||
| **A. 图表分类色板** | `styles/chart-colors.scss`、`stores/hermes/usage.ts`、`JourneyView.vue`、`SkillsUsageView.vue` | 允许,但应收拢到唯一来源(§7 已声明图表色板是唯一可硬编码处) |
|
||
| **B. 终端 ANSI 色板** | `utils/hermes/terminal-theme.ts`(63 处 hex) | xterm `ITheme` 只接受字面量,读不到 CSS 变量 → 豁免,但 CYBER 那一套要锁住 |
|
||
| **C. 看板列状态分类色** | `utils/hermes/kanban-status-colors.ts` | 有意为之(文件内已论证) |
|
||
| **D. 真正待治理** | 见 §1.2 | 全部改语义令牌 |
|
||
|
||
### 1.2 D 类残留清单(真正要改的)
|
||
|
||
| 文件 | 数量 | 问题 |
|
||
| --- | --- | --- |
|
||
| `App.vue` | 3 | `.node-warning-bar` 是**浅色主题遗留**:`#b45309` 字 + `#fef3c7` 底 + `#fde68a` 边。暗色 CYBER 下是一块刺眼亮黄条 |
|
||
| `components/hermes/chat/ChatInput.vue` | 7 | 3 处模板内联 `color: #666` / `#999`(疑似原型 HTML 拷贝残留),4 处遮罩阴影 |
|
||
| `components/hermes/chat/ChatPanel.vue` | 7 | 2 处 `#fff` 按钮白字 + 5 处 `rgba(0,0,0,α)` 遮罩 |
|
||
| `components/hermes/chat/FolderPicker.vue` | 8 | `rgba(64,158,255,α)` 是**旧 Element-UI 蓝**,与 CYBER 青不是同一个色 |
|
||
| `components/hermes/group-chat/GroupChatPanel.vue` | 8 | `rgba(200,50,50,0.08)` 旧红淡底 |
|
||
| `components/hermes/chat/MessageItem.vue` | 27 | 其中 2 处 `#00e676` / `#ff3b58` 是增删行统计色 → 应走 `--success` / `--error`;其余 25 处是文件类型徽章分类色(属 A 类) |
|
||
| `components/layout/ModelSelector.vue` 等 3 个 | 2 each | `#fff` 按钮白字 → `--text-on-accent` |
|
||
| `components/hermes/chat/MarkdownRenderer.vue` | 1 | `#000` 纯黑 |
|
||
| `styles/arco-theme.ts` | 4 | 意图说明文件里的参考值,与 `global.scss` 的 bridge 需保持同步 |
|
||
|
||
**遮罩/投影**:全项目约 60 处 `rgba(0,0,0,α)`。这批不全是「残留」—— 遮罩本来就是黑幕。
|
||
但**语义化**更好:新增 `--overlay-scrim` / `--shadow-*` 令牌,让「遮罩有多黑」变成可调参数,
|
||
而不是散落的字面量。
|
||
|
||
### 1.3 结构层:远未收敛(最大缺口)
|
||
|
||
`global.scss` 定义了 10 个 `.cy-*` 页面骨架类:
|
||
|
||
```
|
||
.cy-view .cy-panel .cy-panel-h .cy-panel-b .cy-kv
|
||
.cy-tabs .cy-tab .cy-stack .cy-sec-t .cy-led
|
||
```
|
||
|
||
**27 个 view 里只有 `SettingsView.vue` 一个在用**(`tests/client/style-system.test.ts` 也只锁这一页)。
|
||
`global.scss:672-675` 的注释写着「避免 25 个页面各写一遍同样的框」—— 设计意图与实际落地严重脱节。
|
||
|
||
### 1.4 页面落地程度
|
||
|
||
| 类别 | 页面 | 特征 |
|
||
| --- | --- | --- |
|
||
| **已落地** | `SettingsView.vue` | `cy-*` 骨架 + 令牌 |
|
||
| **已落地(无骨架)** | `LoginView.vue` | 令牌密度最高(24 处),装饰位面合规 |
|
||
| **部分落地** | `ChatView` / `GroupChatView` / `GlobalAgentView` / `HistoryView` / `JourneyView` / `WorkflowView` / `LogsView` / `PerformanceView` / `PluginsView` / `McpManagerView` / `MemoryView` / `TerminalView` / `SkillsView` / `SkillsUsageView` / `DevicesView` / `KanbanView` / `FilesView` / `HermesMissingView` | 用了基础令牌,但布局仍是 Arco 原生(`a-card` / `a-tabs`) |
|
||
| **未落地** | `JobsView` / `ModelsView` / `ProfilesView` / `UsageView` / `ChannelsView` / `VersionPreviewView` | style 块几乎没有色值,也没有语义色 |
|
||
|
||
**CYBER 目前是「孤岛式」的**:设置页 + 聊天链是两块完整的岛,其余 20+ 页面停留在迁移前原样。
|
||
|
||
### 1.5 令牌管道已就绪
|
||
|
||
23/27 个 view 已 `@use "@/styles/variables" as *;`。
|
||
推进**不需要引入任何新机制** —— 只需替换布局类名与残留色值。
|
||
|
||
### 1.6 三个非颜色依赖的阻塞项
|
||
|
||
| 项 | 问题 |
|
||
| --- | --- |
|
||
| `prototype_reference/` 已补回 | 2026-10-20 已恢复到 `prototype_reference/hermes-ui-cyber-api.html`,`variables.scss:2,43` 的注释指向有效。施工以该原型为唯一视觉依据 |
|
||
| `App.vue` 浅色警告条 | 见 §1.2,3 个 hex 集中一处,暗色下是亮块 |
|
||
| `terminal-theme.ts` 63 处 hex | xterm API 限制,无法收敛到 CSS 变量 |
|
||
|
||
---
|
||
|
||
## 二、目标
|
||
|
||
### 2.1 完成标准
|
||
|
||
CYBER 主题**完成**的定义是下面五条同时成立:
|
||
|
||
1. **D 类残留归零** —— §1.2 清单里的硬编码全部改为语义令牌。
|
||
2. **骨架类覆盖率 ≥ 80%** —— 27 个 view 中至少 21 个使用 `.cy-view` / `.cy-panel` 骨架。
|
||
3. **遮罩与投影令牌化** —— `rgba(0,0,0,α)` 收敛到 `--overlay-scrim` / `--shadow-*`。
|
||
4. **图表色板单一来源** —— A 类色板集中定义,页面不再自带色板。
|
||
5. **护栏自动化** —— 颜色与骨架类都有机械化检查,新增违规当场红。
|
||
|
||
### 2.2 明确不追求的
|
||
|
||
- **不做亮色主题**。`useTheme.ts` 的 `isDark` 恒为 `true` 是**刻意**的,不要「顺手加个亮色」。
|
||
- **不追求每个页面都有霓虹**。霓虹是点缀,不是主色。`cy-panel` 骨架 + 三色语义到位即可。
|
||
- **不强行收敛终端 ANSI 色板**。xterm 的限制是硬约束,用豁免 + 断言管理。
|
||
- **不把骨架类套成模板**。语义要按「每种颜色只承担一种含义」逐组件落,
|
||
机械套壳会把语义抹平(这正是聊天链当初没有套 `cy-panel` 的原因,见 `docs/chat-development-todo.md`)。
|
||
|
||
---
|
||
|
||
## 三、推进阶段
|
||
|
||
每阶段独立可交付、独立可验证、独立可回滚。
|
||
|
||
### P0 · 定点治理(低风险,先清账)
|
||
|
||
**目标**:D 类残留归零中风险最低、视觉收益最高的部分。
|
||
|
||
| 任务 | 位置 | 改法 |
|
||
| --- | --- | --- |
|
||
| 浅色警告条 | `App.vue:117-129` | `.node-warning-bar` 改 `--warning` 文字 / `--warning-soft` 底 / `--warning-line` 边(语义:本就需要你介入) |
|
||
| 旧 Element-UI 蓝 | `FolderPicker.vue` | `rgba(64,158,255,α)` → `rgba(var(--accent-primary-rgb), α)` |
|
||
| 旧红淡底 | `GroupChatPanel.vue` | `rgba(200,50,50,0.08)` → `--error-soft` |
|
||
| 按钮白字 | `ModelSelector.vue` / `CombinationModelsPanel.vue` / `WorkflowModelSelector.vue` | `#fff` → `--text-on-accent` |
|
||
| 增删行统计色 | `MessageItem.vue` | `#00e676` → `--success`,`#ff3b58` → `--error` |
|
||
| 纯黑 | `MarkdownRenderer.vue` | `#000` → `--text-primary` 或 `--bg-*`(按上下文) |
|
||
| 失效注释 | `variables.scss:2,43` | `prototype_reference/...` 改指 `docs/ui-baseline.md` |
|
||
|
||
**护栏**:`tests/client/style-tokens.test.ts` 新增 —— 扫描 `packages/client/src`,
|
||
除白名单(图表色板 / 终端主题 / 看板状态色)外不允许出现裸 hex。
|
||
`App.vue` 的浅色警告条一并锁死。
|
||
|
||
**风险**:低。全是样式替换,不动结构。
|
||
|
||
---
|
||
|
||
### P1 · 遮罩与投影令牌化
|
||
|
||
**目标**:§2.1 第 3 条。
|
||
|
||
**做法**:
|
||
|
||
1. 在 `variables.scss` 新增:
|
||
```scss
|
||
--overlay-scrim: rgba(0, 0, 0, 0.4); // 模态遮罩
|
||
--overlay-scrim-strong: rgba(0, 0, 0, 0.65); // 全屏遮罩
|
||
--shadow-sm / --shadow-md / --shadow-lg // 卡片/浮层投影
|
||
```
|
||
2. 全项目约 60 处 `rgba(0,0,0,α)` 按语义归类替换:
|
||
- 模态/抽屉遮罩 → `--overlay-scrim`
|
||
- 浮层投影 → `--shadow-*`
|
||
- 内阴影/凹陷 → `--shadow-inset-*`
|
||
3. 白色半透明 `rgba(255,255,255,α)`(高光/分隔)→ `rgba(var(--text-primary-rgb), α)`,与聊天链已有做法一致
|
||
|
||
**为什么单独成阶段**:这批改动横跨 30+ 文件但每处都是一行替换,混进结构改造里
|
||
会难以 review,出问题也难定位。单独一批,改完立刻 `git diff` 逐文件过一遍。
|
||
|
||
**护栏**:同 P0 的扫描测试,把 `rgba(0,0,0,` 纳入黑名单。
|
||
|
||
---
|
||
|
||
### P2 · 骨架类落地
|
||
|
||
**目标**:§2.1 第 2 条。
|
||
|
||
**做法**(**逐页推进,不做批量替换**):
|
||
|
||
1. 先在 `SettingsView.vue` 已有的 10 个 `.cy-*` 类基础上补齐缺口,
|
||
把 `global.scss` 的骨架类当成**唯一规格**而不是建议。
|
||
2. 按页面分批改,每批 3-5 个页面:
|
||
- **批次 A(数据展示型)**:`UsageView` / `JobsView` / `LogsView` / `ModelsView` / `ProfilesView`
|
||
- **批次 B(配置型)**:`DevicesView` / `ChannelsView` / `PluginsView` / `McpManagerView`
|
||
- **批次 C(内容型)**:`FilesView` / `MemoryView` / `SkillsView` / `SkillsUsageView`
|
||
- **批次 D(复杂型)**:`WorkflowView` / `KanbanView` / `JourneyView` / `PerformanceView` / `HistoryView` / `HermesMissingView` / `VersionPreviewView`
|
||
3. 每批的替换模式:**Arco 结构 → `.cy-panel` 外壳**,但**保留** Arco 组件本身
|
||
(`a-table` / `a-form` / `a-select` 都不动)。Arco 组件的外观已经被
|
||
`global.scss` 的 token bridge 接管,不需要重写。
|
||
|
||
**边界**:
|
||
- 不改任何逻辑、不改 i18n key、不改数据结构。
|
||
- 不动 `chat/` 与 `group-chat/` 下的文件(已由 `docs/chat-development-todo.md` 完成)。
|
||
- 每批一次改动,批与批之间不叠加。
|
||
|
||
**护栏**:
|
||
- `tests/client/style-system.test.ts` 扩展:断言 `.cy-*` 骨架类清单不变(防止有人删类),
|
||
并断言「不允许出现 `.dark` 选择器」(单主题)。
|
||
- 新增覆盖率检查:view 文件的 style 块中 `.cy-` 出现次数 ≥ 阈值的 view 数量不低于 80%。
|
||
|
||
**实际落地结果(2026-10-20)**:
|
||
|
||
| 批次 | 页面 | 结论 |
|
||
| --- | --- | --- |
|
||
| A | `UsageView` / `JobsView` / `LogsView` / `ModelsView` / `ProfilesView` | 大部分已合规;`JobsView` 补 `cy-sec-t` 分节标题 + i18n key `jobs.sectionList` |
|
||
| B | `DevicesView` / `ChannelsView` / `PluginsView` / `McpManagerView` | `ChannelsView` 内容区包进 `cy-panel`;`McpManagerView` 已合规(modal 内列表保留自定义边框,避免卡片套卡片) |
|
||
| C | `FilesView` / `MemoryView` / `SkillsView` / `SkillsUsageView` | 已合规 |
|
||
| D | `WorkflowView` / `KanbanView` / `JourneyView` / `PerformanceView` / `HistoryView` / `HermesMissingView` / `VersionPreviewView` / `TerminalView` / `GlobalAgentView` | **全部已是令牌驱动**。这些是专用布局(画布 / 看板 / 终端 / 聊天流),原型的 `panel` 结构不适用,不需要套骨架类 |
|
||
|
||
**批次 D 的判定依据**:「cy-* 计数为 0」是**误报**。判断标准应是
|
||
「该页面是否用了裸色值」,而不是「是否用了骨架类」。这批页面的
|
||
`border` / `background` / `box-shadow` 全部走 `$border-color` / `rgba(var(--*-rgb), α)`,
|
||
与全局令牌同源。
|
||
|
||
**骨架类覆盖率最终为 19/26(73%)**。未覆盖的 7 个页面是
|
||
`ChatView` / `GlobalAgentView` / `GroupChatView` / `HermesMissingView` /
|
||
`KanbanView` / `TerminalView` / `WorkflowView` —— 有意豁免,已在
|
||
`style-system.test.ts` 的 `intentional` 列表里显式列出。
|
||
|
||
---
|
||
|
||
### P3 · 图表色板单一来源
|
||
|
||
**目标**:§2.1 第 4 条。
|
||
|
||
**现状**:三处独立的分类色板。
|
||
|
||
| 位置 | 用途 | 问题 |
|
||
| --- | --- | --- |
|
||
| `styles/chart-colors.scss` | 3 个 token(input/output/cache) | 太少,撑不起多维图表 |
|
||
| `stores/hermes/usage.ts` | `MODEL_COLORS` 10 色 | 模型分布专用,与前者语义重叠 |
|
||
| `views/hermes/JourneyView.vue` | 12 个分类色 + canvas 渐变 | canvas 绘制,与 CSS 变量不通 |
|
||
| `views/hermes/SkillsUsageView.vue` | 6 色 `skillPalette` | 又一套 |
|
||
|
||
**做法**:
|
||
1. 扩充 `styles/chart-colors.scss` 为完整分类色板(12-16 色),按语义分组:
|
||
- **序列色**(同量纲的分类,如模型分布)—— 色相均匀分布
|
||
- **对比色**(需要互相可辨的并列,如看板列状态)
|
||
2. `usage.ts` / `SkillsUsageView.vue` / `JourneyView.vue` 改为引用统一色板。
|
||
3. **canvas 场景**(JourneyView)需要在 JS 侧读取令牌:
|
||
```ts
|
||
const styles = getComputedStyle(document.documentElement)
|
||
const color = styles.getPropertyValue('--chart-cat-3').trim()
|
||
```
|
||
抽一个 `readChartColor(index)` 工具函数,避免每个图表各写一遍。
|
||
4. 色板本身**允许硬编码**(这是 `ui-baseline.md` §7 明确豁免的),但**必须集中在一处**。
|
||
|
||
**护栏**:断言「图表色板定义只出现在 `chart-colors.scss`(+ 白名单)」。
|
||
|
||
---
|
||
|
||
### P4 · 自动化护栏收口
|
||
|
||
**目标**:§2.1 第 5 条。
|
||
|
||
新增/扩展测试:
|
||
|
||
| 测试 | 断言 |
|
||
| --- | --- |
|
||
| `tests/client/style-tokens.test.ts`(新) | 除白名单外无裸 hex / 无写死 `rgba(0,0,0,` / 无具名颜色关键字 / 无字面量 RGB 三元组 |
|
||
| `tests/client/style-system.test.ts`(扩) | `.cy-*` 骨架类清单完整;无 `.dark` 选择器;`arco-theme` 恒为 dark;骨架类覆盖率 ≥ 60% 且未覆盖页面须在显式豁免名单里 |
|
||
| `tests/e2e/chat-cyber-semantics.spec.ts`(已有) | 保持 —— 聊天链语义不回归 |
|
||
|
||
**颜色断言的分层策略**(沿用现有 spec 的结论):
|
||
|
||
> 色板同时以 OKLCH 值和 RGB 三元组两种形式声明同一颜色,两者**色相相差约 30°**,
|
||
> 因此**不能用「渲染后的 RGB vs 令牌值」做颜色断言**。
|
||
> 颜色语义必须在**源码层**断言,浏览器层只断言类名 / 关键帧 / 辉光的存在性。
|
||
|
||
**实际落地(2026-10-20)**:
|
||
|
||
`style-tokens.test.ts` 扫描 `packages/client/src` 下所有 `.vue` / `.scss` / `.css` / `.ts`,
|
||
四道断言:
|
||
|
||
1. 无裸 hex(`#rrggbb`)
|
||
2. 无具名颜色关键字(`color: red` 这类)
|
||
3. 无字面量 `rgba(0,0,0,`
|
||
4. 无字面量 RGB 三元组(`rgba(15, 23, 42, 0.05)` 这类旧主题残留)——
|
||
一律要换成 `rgba(var(--*-rgb), α)`
|
||
|
||
**扫描前会剥掉注释与 HTML 实体**:注释里写「之前是 `#b42318`」是历史说明,
|
||
`▾` 是 HTML 实体(▽)不是颜色。剥注释这一步本身有**自检用例**,
|
||
防止改坏了护栏却一直"通过"。
|
||
|
||
护栏上线时抓到并修掉的真实问题:
|
||
|
||
| 位置 | 问题 | 修法 |
|
||
| --- | --- | --- |
|
||
| `utils/hermes/kanban-status-colors.ts` | 分类色 `#c084fc` + `rgba(192,132,252,0.14)` 字面量 | 新增令牌 `--kanban-review` / `--kanban-review-rgb`;灰阶改走 `--text-muted-rgb` |
|
||
| `components/hermes/chat/MessageList.vue` | 浅色主题遗留阴影 `rgba(15,23,42,0.05)` | → `var(--shadow-sm)` |
|
||
|
||
---
|
||
|
||
## 四、豁免清单(明确不动)
|
||
|
||
这些地方有硬编码颜色是**有意的**,不要"清理":
|
||
|
||
| 位置 | 理由 |
|
||
| --- | --- |
|
||
| `styles/chart-colors.scss` | `ui-baseline.md` §7 已声明图表色板是唯一可硬编码处 |
|
||
| `styles/chart-palette.ts` | 同上(TS 侧色板常量) |
|
||
| `utils/hermes/terminal-theme.ts` | xterm `ITheme` 只接受字面量。CYBER 那一套要锁住并加断言;Tokyo Night / GitHub Dark 是用户自选外部主题,不参与品牌表达 |
|
||
| `vendor/arkscore/` | 装饰位面,零信息零交互。已由 `tests/client/arkscope-vendor.test.ts` 卡住 |
|
||
| `styles/arco-theme.ts` | 意图说明 + 测试锚点,改主色时要与 `global.scss` 的 bridge 同步 |
|
||
| `components/hermes/chat/MessageItem.vue` | 语言徽标(JS 黄 / Python 蓝 / HTML 橙…)是**品牌识别色**,换色就认不出语言了 |
|
||
|
||
**豁免不是免检**。每条豁免都要有一条对应的断言,防止「豁免范围悄悄扩大」。
|
||
|
||
> 注:`utils/hermes/kanban-status-colors.ts` 原本在豁免名单里("分类色字面量"),
|
||
> 2026-10-20 起**已移出** —— 看板分类色新增了 `--kanban-review` / `--kanban-review-rgb`
|
||
> 令牌,灰阶走 `--text-muted-rgb`,文件内已无任何字面量色值。
|
||
|
||
---
|
||
|
||
## 五、验证基线
|
||
|
||
每阶段结束时都跑:
|
||
|
||
```bash
|
||
npm run harness:check # 含 chat-chain 片段校验
|
||
npm run test # Vitest:client + shared + server
|
||
npm run test:e2e # Playwright
|
||
npm run build
|
||
```
|
||
|
||
改了 `packages/client/src/components/hermes/chat/` 或
|
||
`packages/server/src/services/hermes/group-chat/` 等聊天链路径的,
|
||
必须新增 `docs/chat-chain-changes/<date>-<slug>.md` 片段,
|
||
否则 `harness:check` 会失败。
|
||
|
||
**当前基线(2026-10-20,P2/P4 收口后)**:
|
||
|
||
| 检查 | 结果 |
|
||
| --- | --- |
|
||
| `harness:check` | 通过 |
|
||
| `vue-tsc -b` | 通过 |
|
||
| `test`(client + server + shared) | 2060 通过 / 8 跳过 / 1 超时(`tts-synthesize-controller` 并行负载下超时,单跑 23/23 通过,与本主题无关) |
|
||
| `test:e2e` | 46 通过 |
|
||
| `build` | 通过 |
|
||
|
||
---
|
||
|
||
## 六、推进顺序与依赖
|
||
|
||
```
|
||
P0 定点治理 ──→ P1 遮罩令牌化 ──→ P2 骨架类落地 ──→ P3 图表色板 ──→ P4 护栏收口
|
||
(独立) (独立) (分 4 批) (独立) (收口)
|
||
```
|
||
|
||
- P0 / P1 / P3 互不依赖,可并行。
|
||
- P2 的每批次之间有视觉连续性要求,**必须串行**。
|
||
- P4 放在最后 —— 护栏要在被保护对象稳定后才可靠;但 P0 的扫描测试可以提前写,
|
||
先卡住「不许变多」,再逐步加严。
|
||
|
||
---
|
||
|
||
## 七、相关文档
|
||
|
||
- `docs/ui-baseline.md` —— 视觉规范(唯一依据)
|
||
- `docs/chat-development-todo.md` —— 对话台已完成项与已知原型侧问题
|
||
- `docs/arco-mapping.md` —— Arco 组件映射
|
||
- `docs/known-issues.md` —— 已知问题清单
|
||
- `docs/chat-chain-changes/README.md` —— 变更片段规范
|