18 KiB
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 主题完成的定义是下面五条同时成立:
- D 类残留归零 —— §1.2 清单里的硬编码全部改为语义令牌。
- 骨架类覆盖率 ≥ 80% —— 27 个 view 中至少 21 个使用
.cy-view/.cy-panel骨架。 - 遮罩与投影令牌化 ——
rgba(0,0,0,α)收敛到--overlay-scrim/--shadow-*。 - 图表色板单一来源 —— A 类色板集中定义,页面不再自带色板。
- 护栏自动化 —— 颜色与骨架类都有机械化检查,新增违规当场红。
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 条。
做法:
- 在
variables.scss新增:--overlay-scrim: rgba(0, 0, 0, 0.4); // 模态遮罩 --overlay-scrim-strong: rgba(0, 0, 0, 0.65); // 全屏遮罩 --shadow-sm / --shadow-md / --shadow-lg // 卡片/浮层投影 - 全项目约 60 处
rgba(0,0,0,α)按语义归类替换:- 模态/抽屉遮罩 →
--overlay-scrim - 浮层投影 →
--shadow-* - 内阴影/凹陷 →
--shadow-inset-*
- 模态/抽屉遮罩 →
- 白色半透明
rgba(255,255,255,α)(高光/分隔)→rgba(var(--text-primary-rgb), α),与聊天链已有做法一致
为什么单独成阶段:这批改动横跨 30+ 文件但每处都是一行替换,混进结构改造里
会难以 review,出问题也难定位。单独一批,改完立刻 git diff 逐文件过一遍。
护栏:同 P0 的扫描测试,把 rgba(0,0,0, 纳入黑名单。
P2 · 骨架类落地
目标:§2.1 第 2 条。
做法(逐页推进,不做批量替换):
- 先在
SettingsView.vue已有的 10 个.cy-*类基础上补齐缺口, 把global.scss的骨架类当成唯一规格而不是建议。 - 按页面分批改,每批 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
- 批次 A(数据展示型):
- 每批的替换模式: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 |
又一套 |
做法:
- 扩充
styles/chart-colors.scss为完整分类色板(12-16 色),按语义分组:- 序列色(同量纲的分类,如模型分布)—— 色相均匀分布
- 对比色(需要互相可辨的并列,如看板列状态)
usage.ts/SkillsUsageView.vue/JourneyView.vue改为引用统一色板。- canvas 场景(JourneyView)需要在 JS 侧读取令牌:
抽一个
const styles = getComputedStyle(document.documentElement) const color = styles.getPropertyValue('--chart-cat-3').trim()readChartColor(index)工具函数,避免每个图表各写一遍。 - 色板本身允许硬编码(这是
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,
四道断言:
- 无裸 hex(
#rrggbb) - 无具名颜色关键字(
color: red这类) - 无字面量
rgba(0,0,0, - 无字面量 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,文件内已无任何字面量色值。
五、验证基线
每阶段结束时都跑:
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—— 变更片段规范