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
This commit is contained in:
xiaohan.yuan committed 2026-10-18 18:10:23 +08:00
1 parent 18aaca5843
commit e73c59b10e
1 file changed
+247
@@ -0,0 +1,247 @@
# 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. 迁移设置页作为试点,跑浅色/深色/窄窗截图和回归。
这一批完成后,后续页面可以按同一模式复制,不需要再讨论架构。