# Figma 是真源（Figma as Source of Truth）

> **适用对象**：任何在本仓库工作的 AI 工具（Claude Code / Codex / Cursor / 其他）/ 开发 / 设计师。
> **优先级**：**高于** working-principles 任何条款；高于本文件之外的所有规则。
> **触发时机**：起手必读链路**第 0 步**——比 [`PROJECT_GOAL.md`](./PROJECT_GOAL.md) 还要先读。
> **历史背景**：2026-05-20 session 实证：纯静态 audit + 启发式 selector 匹配在一连串 finding 分类 / fix prompt 上产生 5 次错估错分，迫使本文件诞生。

---

## 唯一原则

**Figma 设计系统库是真源。Code 组件必须 1:1 还原 Figma 组件的所有视觉属性（Color / Padding / Size / Radius / Opacity / Gap / Border / etc.）。**

唯一允许的"差异"是**值形态映射**：Figma 端有些属性写的是 raw hex / px 字面量没走 Variable token；代码层用 token 变量去引用这些值，这是合法的映射层——**视觉等价**，命名形态不同。

例：
- Figma 节点 fill 写 `#F0F0F0`（raw hex）↔ code `var(--color-grey-3)`（token 引用）—— **合法映射**，视觉等价
- Figma 节点 fill 绑 `UX/Grey/grey-3` (Variable) ↔ code `var(--color-grey-3)` —— **完美一致**
- Figma 节点 padding 写 `12px`（raw num）↔ code `padding: var(--sp-sm)` 其中 `--sp-sm: 12px` —— **合法映射**

非例：
- Figma fill `#F0F0F0` ↔ code `var(--bg-layer4)`（其值是 `#dbdbdb`）—— **❌ 非法**，视觉不等价（这是 2026-05-19 select-chip 案例）

---

## 不允许的差异（违反 = code bug，不是 design choice）

| ❌ 反模式 | 历史案例 |
|---|---|
| Code 用 Figma 没绑的 token | `.select-chip background: var(--bg-layer4)` 但 Figma 绑 `--color-grey-3` |
| Code 用 CSS 技巧替代 Figma 具体设计 | `.tvu-input:disabled { opacity: 0.4 }` 但 Figma 用 token desaturation (`--text-disabled` / `--control-disabled-*`) |
| Padding / gap / size / radius / opacity 跟 Figma variant 数值不一致 | `.select-chip gap: 4px` 但 Figma 8px；`.dropdown-select width: 240` 但 Figma L variant 300 |
| Code 自创 status / variant axis 不在 Figma 库里 | 已删除的 `notification.status="success"` / `notification.type` / `badge.type` 形状轴 |
| Code 用 status-aspirational selector 但 Figma 实际绑别的 | `.select-wrap--error` 强行用 `--red`，但 Figma 在 error variant 实际绑 grey —— **fix code 跟 Figma，不是要求 Figma 改** |

---

## 合法的"差异"边界（**允许**，且必须显式登记到 [`src/design-system/translation/divergences-decisions.json`](../src/design-system/translation/divergences-decisions.json)）

| ✅ 合法 | 性质 | 例子 |
|---|---|---|
| 值形态映射 | 视觉等价，命名形态不同 | Figma raw `#F0F0F0` ↔ code `var(--color-grey-3)` |
| 拓扑合成 | code 提供合成层包多个 Figma 单元 | button 8-set → 一个 canonical + 8 axis prop（`button-eight-sets-single-component`）|
| Figma 设计态 vs 运行态 | Figma axis 是设计预览开关不是 runtime prop | `dark theme=on/off` / `enable` / `UX` 等已登记的 design-state axes |
| Figma SLOT ↔ Vue slot | API 形态技术不可达 1:1 | `figma-content-slot-vue-slot` |
| Figma export 工具限制 | Figma API 自身限制 | `icon-variable-resolved-to-hex` (SVG export 把 Variable 解析为 hex) |
| 用户显式 code-first 拍板 | 项目方向变化，code 走在 Figma 前面 | `chart-color-palette-2026-05-13` / `pill-component-family-2026-05-19` —— **必须**含 "user approved code-first YYYY-MM-DD" 注释 |

任何不属于上面 6 条的"差异"想登记 divergence ——**stop，重新分析，默认结论是 code bug 按 Figma fix**。

---

## 验证方法（**不允许启发式**）

任何 Figma↔code 验证必须 **deterministic**：

### 推荐机制（manifest-based render verification）

1. 显式 manifest：`figma-data/render-verification-manifest.json`，每条 entry 是 (Figma nodeId, codeComponent, codeProps) 的 1:1 映射
2. Render harness：docs site 内 `/internal/render-harness/*` 路由按 manifest 渲染每个 variant 孤立实例
3. Playwright 跑 manifest：逐项 `getComputedStyle()` vs Figma extract 数值精确比较
4. 输出 per-variant PASS / FAIL（actual vs expected 数值）

### 禁止做法

| ❌ 禁止 | 理由 |
|---|---|
| 静态 selector-to-variant BEM 启发式匹配 | 实证假阳率 ≥40%（2026-05-20 session）|
| 凭 selector 名字字面 (`--error` / `--checked` / `--disabled`) 推 variant 含义 | selector 名字是 code 意图，不是 Figma 真源 |
| 把 audit finding "Figma 端不对" 默认结论为 designer漏标 | 默认是 code 错；designer 漂移需要 designer **明确 ack** |
| 把"实现策略差异"（如 CSS opacity vs token desaturation）登记为 divergence | 实现策略差 = code 偷懒；按 Figma 改 |

---

## AI 工具自检清单（起手必读第 0 步）

在写任何 audit / fix prompt 前，AI 必须确认：

- [ ] 我的 finding 是 deterministic 来源（manifest-based），还是启发式推断？
- [ ] 我是否在"猜" selector 跟哪个 variant 对应？如果是 → **STOP**，重写为 manifest 查表
- [ ] 我提议的 fix 方向是 code → 跟 Figma，还是 Figma → 跟 code？如果是后者 → 需要设计师 ack，否则默认 code fix
- [ ] 我登记的 divergence 是否落在上面 6 条合法边界内？如果不是 → 不要登记，按 Figma fix code
- [ ] 我对"X-axis cleavage"/"100% same binding"等批量结论是不是验证过**全数据**？还是只看了一部分子集就外推？

任一项打 No → **STOP，重新设计**。

---

## 设计师自检清单（review Figma 真源时）

设计师/用户在 Figma 端发现 binding 看起来不符合直觉（如 error 边框绑 grey 不是 red）时：

- [ ] **默认假设**：Figma 当前 binding 就是设计意图（code 应该 follow）
- [ ] 如果要"改 Figma"（认为 Figma 漂移了），明确 ack：在 git commit message / divergence-decisions entry 写 `designer-acknowledged-figma-fix-needed-YYYY-MM-DD`
- [ ] 改完 Figma 后跑 `pnpm sync:figma-library` 让 figma-data 同步，audit 自动消化
- [ ] 不做 ack 直接让 code 端逆向用 status token (如 `--red`/`--brand`) —— ❌ 违反原则

---

## 历史教训（避免再犯，session 2026-05-19/20 实证）

| # | 错误 | 根因 |
|---|---|---|
| 1 | 误判 170 case A rebind（实际 38）| axis cleavage 分析只看了 12/126 数据子集就外推 |
| 2 | 误判 126 dropdown-disabled 同 binding（实际 11）| 同上 |
| 3 | P3 §Fix 3 改错方向（把对的 200 改成 240）| 读 triage `"240 -> 200px"` 文本箭头方向歧义 |
| 4 | 误把 56 条 figma-drift 归到"设计师漏标"| audit selector-to-variant 启发式把 `enable=off` disabled variant 套到 `.select-wrap--error` selector |
| 5 | 推荐用 divergence 登记 opacity strategy | 没核实 disabled token 已存在；CSS opacity 是 code 偷懒不是必要妥协 |

**共同根因**：缺 deterministic manifest。机制不修，AI（任何工具任何模型）还会重复错。

---

## 文档关系

| 文档 | 用途 |
|---|---|
| **本文件**（`FIGMA_AS_SOURCE_OF_TRUTH.md`） | 第 0 步原则——优先级最高 |
| [`AGENTS.md`](../AGENTS.md) | AI 协作硬规则（含起手必读链路索引）|
| [`docs/PROJECT_GOAL.md`](./PROJECT_GOAL.md) | 项目目标 / 受众 / 路线图 |
| [`docs/working-principles.md`](./working-principles.md) | 9 条工作原则（次于本文件） |
| [`src/design-system/translation/divergences-decisions.json`](../src/design-system/translation/divergences-decisions.json) | 合法 divergence 显式登记表 |
| [`figma-data/render-verification-manifest.json`](../figma-data/render-verification-manifest.json) | ✅ **已建**（INFRA-F41）deterministic 验证 manifest（930 entries，每条 = Figma nodeId ↔ codeComponent ↔ codeProps + expectedNodes）；由 `pnpm test:render-verification` 跑、`pnpm audit:render-drift-gate` 闸 |
