Files
ycode/docs/设计系统提取与统一方案-2026-10-18.md
xiaohan.yuan e73c59b10e docs: add design system extraction plan
- record current token fragmentation, component inventory, target layering, and migration order
- note the stale verify-ui bridge as a P0 prerequisite
2026-10-18 18:10:23 +08:00

248 lines
11 KiB
Markdown
Raw Permalink 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.
# YCode 设计系统提取与组件统一方案(2026-10-18)
## 结论
可以,而且当前代码已经具备做这件事的基础,但**不能直接从现有 CSS 里“提炼一套 token”后全局替换**。
原因是 YCode 现在不是一套设计系统,而是四套来源同时生效:
1. `--yc-*`:YCode 自有的根级语义 token,主要在 `src/renderer/src/styles/tokens.css`。
2. `--wb-*`:从 WorkBuddy 恢复的 token,但**没有全局根定义**,而是按组件作用域分别复制。
3. `--cb-*` / `--cr-*` / `--dc-*` / `--ec-*` / `--wb-kb-*`:会话、能力中心、项目页等恢复模块各自的主题闭包。
4. 少量本地 `.btn` / `.input` / `.dialog` 等旧类名,和恢复 UI 的 `wb-button`、`wb-input`、`wb-modal` 并存。
因此正确的路径不是“抽一套 token 全量替换”,而是:
> 冻结恢复层,收敛本地层;把 `--wb-*` 明确降级为“原版兼容层”,把 `--yc-*` 提升为“YCode 自有界面的唯一语义层”;在语义层之上再抽通用组件。
## 现状证据
### 规模
统计范围:`src/renderer/src` 下的 `.css` / `.tsx` / `.ts`。
| 指标 | 数值 |
| --- | ---: |
| CSS 文件 | 109 |
| TSX 文件 | 151 |
| CSS 总行数 | 19,899 |
| CSS 声明 | 63,042 |
| 裸 `px` | 16,103 |
| 十六进制颜色 | 13,008 |
| `!important` | 925 |
| `--yc-*` 定义 / 使用 | 246 / 1,372 |
| `--wb-*` 定义 / 使用 | 2,129 / 22,756 |
| 导出的大写组件名 | 350(其中大量是图标组件) |
构建产物 `out/renderer/assets/*.css` 中:
- 规则数 14,082,`!important` 934。
- 声明 `--wb-*` 变量 21,935 次,`--yc-*` 仅 314 次。
- 大多数 `--wb-*` 来自按作用域复制的原版闭包,而不是一个全局主题。
### 样式分层
| 层 | 文件 | 现状 |
| --- | --- | --- |
| 核心 token | `src/renderer/src/styles/tokens.css` | `--yc-*` 有完整的颜色、间距、字号、圆角、阴影、布局定义;根级生效 |
| 排版 token | `src/renderer/src/workbuddy/typography/tokens.css` | REA 校验过的 `--wb-*` 排版 token,但不含颜色/圆角/控件 token |
| 恢复 source | `src/renderer/src/**/source-*.css`、`*-source.css` | 50 个文件、约 2.14 MB,原版规则与局部 token 闭包 |
| 恢复 recovered | `src/renderer/src/**/*-recovered.css` | 17 个文件、约 0.68 MB,生成的页面级投影 |
| 本地适配 | `**/*-adapter.css`、`**/*-adapters.css` | 7 个文件、约 32 KB |
| YCode 自有页面 | `styles/app.css`、`styles/sidebar.css`、`components/*.css`、`library/*.css`、`automations/*.css`、`projects/*.css` | 27 个本地文件、约 0.47 MB |
### 同名 token 在不同子树解析出不同来源
`--wb-palette-brand-8` 至少在这些作用域被分别重新声明:
- `src/renderer/src/capabilities/capability-center-source.css:2`(`.yc-capability-center`)
- `src/renderer/src/components/model-settings.css:146`(`.models-settings-panel`)
- `src/renderer/src/components/source-home-recovered.css:2`(`.source-home`)
- `src/renderer/src/components/source-workspace-picker-recovered.css:2`(`.source-workspace-picker`)
- `src/renderer/src/workbuddy/conversation/source-styles.css`(`.chat-pane.cr-theme` 及其 dark/light 分支)
这意味着:
- 全局根上并没有 `--wb-palette-*` / `--wb-color-*` 的单一权威来源。
- 页面在自己的 subtree 内看起来是对的,但一旦组件被移出原作用域,就会掉色、掉圆角或掉主题。
- 新组件如果引用 `--wb-*`,只能碰运气;引用 `--yc-*` 才有全局保证。
### 根级 `--yc-*` 已在渲染态生效
对当前构建产物 `out/renderer/index.html` 做了只读渲染抽查:
| 变量 | 计算值 |
| --- | --- |
| `--yc-text-primary` | `rgba(0, 0, 0, 0.9)` |
| `--yc-bg-primary` | `#ffffff` |
| `--yc-radius-lg` | `8px` |
当前 `scripts/verify-ui.mjs` 的假桥已落后于生产渲染树,缺少 `window.ycode.library`,因此首页/设置只能加载到 token,不能完整挂载页面。这是 P0 需要修的工具问题,不是产品 CSS 问题。
### 组件重复度
TSX 中的原生控件数量:
| 元素 | 数量 |
| --- | ---: |
| `<button>` | 362 |
| `<input>` | 48 |
| `<textarea>` | 8 |
| `<select>` | 2 |
| `<svg>` | 164 |
类名使用:
| 模式 | 出现次数 |
| --- | ---: |
| `wb-button` | 53 |
| `wb-button--medium` | 37 |
| `wb-button__content` | 24 |
| `wb-button--secondary` | 21 |
| `wb-icon` | 17 |
| `wb-button--primary` | 12 |
| 本地 `.btn` | 10 |
本地还存在独立的:
- `.btn` / `.btn--primary` / `.btn--ghost` / `.btn-icon`:`src/renderer/src/styles/app.css`
- `.automation-button` / `.automation-button--secondary` / `.automation-input`:`src/renderer/src/automations/automation-page.css`
- `library-*`、`project-*`、`memory-modal`、`help-feedback-panel` 等各自的按钮/输入/弹窗样式
也就是说:**控件没有被统一;它们只是分别长得像原版。**
## 目标架构
采用三层 token + 一层兼容桥,禁止跨层直连。
### L0 基础层
`--yc-palette-*`、`--yc-space-*`、`--yc-radius-*`、`--yc-font-size-*`、`--yc-size-*`、`--yc-motion-*`
- 只在 `styles/tokens.css` 声明。
- 任何页面、组件、adapter 都不得重新声明 L0。
- 深色主题只允许在 `html[data-theme='dark']` 下重映射。
### L1 语义层
`--yc-text-*`、`--yc-bg-*`、`--yc-border-*`、`--yc-status-*`、`--yc-elevation-*`
- 组件只消费 L1,不直接消费 L0 的颜色值。
- 语义层负责浅色/深色切换。
### L2 组件层
`--yc-button-*`、`--yc-input-*`、`--yc-dialog-*`、`--yc-menu-*`、`--yc-tabs-*`、`--yc-card-*`
- 只在 `ui/` 组件样式中声明。
- 页面只能通过组件 props 或变体影响外观,不能重写内部尺寸。
### 兼容层
`--wb-*`、`--cb-*`、`--cr-*`
- 只服务 REA 恢复的 source/recovered 模块。
- 在根或明确的 host scope 上做一次桥接:`--wb-bg-primary: var(--yc-bg-primary)` 等。
- 不再允许新页面按块复制整套 `--wb-palette-*`。
- 恢复文件本身不改:它们由 `scripts/rea/recover-*.mjs` 生成,手改会被下一次生成覆盖。
## 目标组件清单
按优先级抽取。优先复用已有实现,不重写行为。
| 优先级 | 组件 | 复用来源 |
| --- | --- | --- |
| P1 | `Button` / `IconButton` | `wb-button` 变体、本地 `.btn` |
| P1 | `Input` / `Textarea` | `wb-input`、`automation-input`、设置页输入 |
| P1 | `Select` | 已有 `SourceSelect` / `RecoveredSelect` |
| P1 | `Dialog` / `ConfirmDialog` | `wb-modal`、`task-confirm-dialog` |
| P1 | `Popover` / `Menu` | `wb-popover`、`wb-dropdown`、`SourceSelectPopover` |
| P1 | `Tabs` / `Segmented` | `wb-segmented`、设置导航、能力中心导航 |
| P2 | `Checkbox` / `Switch` | `wb-checkbox`、`wb-switch` |
| P2 | `Card` | 专家卡、项目卡、设置分组卡 |
| P2 | `ListItem` / `NavItem` | 侧栏、选择器、列表页 |
| P2 | `Badge` / `Tag` | Beta、状态、文件类型 |
| P2 | `EmptyState` | 侧栏空态、资料库空态、项目空态 |
| P3 | `Avatar` / `MemberStatus` | 已有 `MemberAvatar` / `MemberStatus` |
| P3 | `Spinner` / `Skeleton` | 设置加载、资料库骨架、能力加载 |
| P3 | `Tooltip` | `wb-tooltip`、`RecoveredSelectTooltip` |
| P3 | `Toast` / `Notice` | 未接入提示、错误提示、更新提示 |
图标不进入第一轮统一:164 个 `<svg>` 分散在十多个 `*Icons.tsx` 和恢复模块中,先把按钮/输入/弹窗收敛,收益更大、风险更低。
## 迁移顺序
采用绞杀者模式(strangler fig),禁止一次性替换全部 CSS。
### P0 冻结与可观测
- 新增 `design-system.md`,记录实际生效的 token、组件清单、例外与恢复模块边界。
- 修正 `scripts/verify-ui.mjs` 的假桥,使首页、设置、至少一个非聊天页面可以完整挂载。
- 建立 6 个基准面的浅色/深色截图:首页、会话、设置、能力中心、资料库/自动化、项目。
- 新增静态检查:新增文件禁止裸 hex、裸 px、裸 `!important`;存量文件设白名单和递减额度。
验收:能对同一页面在迁移前后做计算样式归档和截图对比。
### P1 收敛主题定义
- 在根上补齐 `--wb-* -> --yc-*` 桥接,覆盖背景、文字、边框、状态、圆角、控件尺寸。
- 按页面逐个删除块级 `--wb-palette-*` / `--wb-color-*` 复制,只保留原版恢复模块真正依赖的闭包。
- 固定 CSS 加载顺序:typography -> yc tokens -> bridge -> app -> 组件/页面适配 -> 恢复 source。恢复 source 不再依赖 `@import` 的隐式顺序。
- 每个页面迁移后必须验证浅色、深色和窄窗。
验收:根级 `--wb-*` 与 `--yc-*` 解析到同一语义值;页面内不再出现第二份调色板。
### P2 抽 P1 组件
- 先做 `Button`、`Input`、`Select`、`Dialog`、`Popover`、`Tabs`。
- 组件接受 `className` 并合并到根元素,不把 margin 写进组件。
- 变体只覆盖已有变体:`primary / secondary / grey / ghost / link / danger`,尺寸只覆盖 `small / medium / large`。
验收:同一组件在至少 3 个不同页面替换后,截图零回归或差异有明确解释。
### P3 迁移 YCode 自有页面
顺序建议:
1. 设置页(最多重复的输入/弹窗/分组)
2. 侧栏与标题栏
3. 自动化、资料库、项目
4. 能力中心宿主壳
5. 首页与输入区外壳
恢复的会话渲染、工具卡、Mermaid、代码块、Diff 暂不迁移,只通过外层适配器接入新 token。
### P4 清理
- 删除本地 `.btn` / `.input` / `.dialog` 等旧类名。
- 合并重复选择器,清掉无主题差异的 `!important`。
- 更新 `design-system.md`,重新跑提取。
## 不做什么
- 不手改 `*-recovered.css` 和 REA 生成脚本的产物。
- 不重写会话、工具卡、Diff、Mermaid 等已经过 REA 对齐的恢复模块。
- 不在这一轮改动 WorkBuddy 原版采集通道。
- 不为了“统一”把原版恢复层的具体尺寸改成 `--yc-*`;先桥接语义色和主题,几何差异以 REA 证据为准。
## 风险
| 风险 | 说明 | 应对 |
| --- | --- | --- |
| 破坏 1:1 | 恢复模块依赖当前作用域和层叠顺序 | 先桥接、后替换;恢复文件只读 |
| 主题回归 | 浅色/深色由不同 scope 各写一遍 | 先做根桥接,再做页面迁移 |
| `!important` 冲突 | 925 处,主要集中在恢复层 | 不全局清理,只清本地层 |
| 组件行为回归 | `Select`、弹窗、菜单有交互状态 | 复用现有实现,只抽外观壳 |
| 工具失真 | `verify-ui.mjs` 假桥已落后 | P0 先修,否则无法做渲染验收 |
## 建议的第一批交付
如果现在开始,建议第一批只做四件事:
1. `design-system.md`:现状 + 目标 token 表 + 组件清单 + 例外。
2. 根级 `--wb-*` 桥接:先让语义色和主题归一。
3. `Button` / `Input` / `IconButton` 三个基础组件。
4. 迁移设置页作为试点,跑浅色/深色/窄窗截图和回归。
这一批完成后,后续页面可以按同一模式复制,不需要再讨论架构。