# Token Layer Strategy — 双层抽象问题 + R1 推荐路径

> **Status**: Working hypothesis pending [`audit-component-attributes-vs-figma`](./_prompts/audit-component-attributes-vs-figma.prompt.md) (Codex A) baseline finding.
> **Last updated**: 2026-05-19
> **Owner**: plan owner (Claude) writes strategy + audit prompt; executor (Codex) implements; user拍板 R-path 选择
> **User preference (2026-05-19)**: 倾向 **R1** —— 等 A audit finding 分布回来后落地

---

## 1. 问题根因（不是命名问题）

2026-05-19 验证 session 抓到实证：

- **Figma 真源** 给 `select-chip` 绑：`UX/Grey/grey-3 #F0F0F0` —— **L1 palette / primitive 层**
- **Code 现状**（`src/canonical/SelectBoxBase.vue:302`）：`.select-chip { background: var(--bg-layer4); }` —— **L2 semantic / alias 层**
- light 主题下 `--bg-layer4 = #dbdbdb`（实际 grey-4 / 视觉 grey-2 等价），**≠ Figma 的 #f0f0f0**

**这不是命名相近的误选——是两层抽象独立做决策导致的不可避免漂移。**

把 `--bg-layer4` 改名 `--surface-chip-default` 也救不了：只要"Figma 在 L1 绑、code 在 L2 选、两层之间没有强制等价契约"，下一个开发或下一个组件仍会"凭语义直觉"挑错。

### Token 层结构现状（[src/tokens/variables.css](../../src/tokens/variables.css)）

| Layer | 例子 | 谁定的 | 用途 |
|---|---|---|---|
| **L1 palette** | `--color-grey-3`, `--brand`, `--red`, `--blue-bg` | Figma 真源 → `token-aliases.ts` 映射 | 颜色色阶字典；不带语义 |
| **L2 semantic alias** | `--bg-layer1..4`, `--text-body`, `--text-tips`, `--line-light`, `--input-filled-bg` | 项目内 code-side 定义（依赖 L1 值，theme 切换由 light/dark scope 重赋值）| 用途/层位语义；theme-aware |

**漂移机理**：Figma 决策永远在 L1；code 组件多数引用 L2（出于 theme 抽象习惯）；L1↔L2 没有 1:1 强契约 → 任意组件都可能落到"L2 的非 Figma 等价值"上。

## 2. 四条可选路径

| 路径 | 怎么做 | 改动量 | 取舍 |
|---|---|---|---|
| **R1**（推荐）| `token-aliases.ts` 加 L1↔L2 等价类登记；audit 把 alias-equivalent 当 pass，剩下的就是真不等价 | **小**（补 alias 数据 + audit 逻辑）| 保留双层语义，**用合同强制等价**——开发可以继续写 `--bg-layer3` 风格 code，只要它跟 Figma 绑的 L1 是登记过的等价就 pass |
| R2 | code 端降到 L1：把组件里所有 `--bg-layer*` 等 semantic 替换成对应 `--color-grey-*` | 中（38 组件 × N 处） | 失去语义抽象；theme 切换全靠 light/dark scope 重定义 primitive，可读性下降 |
| R3 | Figma 端升到 L2：设计师把 Figma variables 重排成 semantic（bg-layer3 在 Figma 也存在） | 大（设计师重排）| 干净；但 Figma Pro plan 限制 + 设计师工作量 |
| R4 | codegen 锁死：组件 `<style>` 自动从 figma-data 生成 | 大（codegen 管线 + Code Connect publish） | Pro plan 下 Code Connect 不可用，目前不可行（[AGENTS.md §项目约束](../../AGENTS.md#项目约束)） |

## 3. R1 实施路径（pending A audit data）

### 3.1 数据登记位置（已选定）

[`src/design-system/translation/token-aliases.ts`](../../src/design-system/translation/token-aliases.ts) 已有 Figma → code L1 单向映射。在同文件追加 **L1↔L2 等价类** 字段：

```ts
// 新增：每个 L2 semantic token 列出它在 dark / light 两 scope 下等价的 L1 primitive
export const SEMANTIC_TO_PRIMITIVE_EQUIVALENCE: Record<string, { dark: string; light: string }> = {
  '--bg-layer1':       { dark: '--color-grey-13', light: '--color-white' },
  '--bg-layer2':       { dark: '--color-grey-12', light: '--color-grey-2' },
  '--bg-layer3':       { dark: '--color-grey-11', light: '--color-grey-3' },
  '--bg-layer4':       { dark: '--color-grey-10', light: '--color-grey-4' },
  '--text-body':       { dark: '--color-grey-3',  light: '--color-grey-13' },
  // ...（A audit 跑完按 finding 实证补全）
}
```

**注意**：alias 的 source of truth 仍是 `variables.css`——这份 map 只是 audit 用的"等价见证表"，由 audit 工具校验它跟 `variables.css` 保持一致（不一致 = SoT 自己漂了）。

### 3.2 Audit 升级（依赖 A 的产出）

Codex A 写出来的 `audit:component-attributes` 在 color-token-mismatch 判定里加一步：

1. Figma 绑 `--color-grey-3`，code 用 `--color-grey-3` → ✅ exact match
2. Figma 绑 `--color-grey-3`，code 用 `--bg-layer3`（per equivalence map: light=grey-3, dark=grey-11）→ ✅ light pass；但 dark 是 `grey-11` ≠ Figma 的 `grey-3` → 🟡 **theme-asymmetric**（需要拍板：Figma 是否在 dark variant 给了不同绑定？还是漏了？）
3. Figma 绑 `--color-grey-3`，code 用 `--bg-layer4`（per equivalence map: light=grey-4）→ ❌ **non-equivalent layer mismatch**（SelectChip 这种情况）

### 3.3 不要做的事

- ❌ **不要去掉 L2 token**——semantic alias 是 theme 抽象的关键，去掉就退回 R2
- ❌ **不要让 audit 自动加 entry 进 equivalence map**——alias 是设计决策，需要 plan owner 复审 + 用户拍板（典型 audit 反模式，参 [`docs/meta-rules.md`](../meta-rules.md) audit 自我饲喂）
- ❌ **不要新建 token**——R1 期间所有 token 都已存在；缺的只是 L1↔L2 等价 metadata

## 4. 与其他路径的关系

- **R1 不阻断 R3**：等价 map 登记的是当前事实；如果未来设计师按 R3 把 Figma 升 semantic，等价 map 退化为 1:1（每个 L2 在两 theme 都映射到自己），但结构不变
- **R1 是 R2 的 super-set**：如果某 alias 决议是"全部 L2 用法都应是 L1 X"，等价 map + audit 暴露出来后批量改 code 即可，不需要换框架
- **R1 不动 R4**：codegen 是更激进路径，需要 Figma plan 升级；当前作废

## 5. 决策时点 + 依赖

| 节点 | 触发 | 决策 |
|---|---|---|
| **本文档落地** | 2026-05-19 | 选 R1 为主路径（user preference） |
| **A audit baseline finding 出炉** | Codex A STOP 报告 | plan owner 按 finding 把 L1↔L2 等价 vs 真不等价分桶 |
| **equivalence map 第一波填充** | A finding 数据驱动 | plan owner 写 prompt → Codex 改 `token-aliases.ts` 加 map + 升 audit 逻辑 |
| **non-equivalent finding 拍板** | equivalence map 接入后剩下的真不等价 | 用户 + 设计师决定：改 code 引用 还是 改 Figma 绑定 |
| **写入 working-principles** | 全套跑通后 | plan owner 把 L1↔L2 等价类规则正式归到 [working-principles.md](../working-principles.md) |

## 6. 与 v0.7.0 / v1.0.0 的关系

- v0.7.0 contrast cleanup 目前等设计师 review Figma token 值——R1 这一波**不冲突**（R1 只动 mapping/audit，不动 token 值）
- 真不等价的 finding（如 SelectChip）属于 v0.7.0 范围（code 端 token 引用错），需要 BREAKING-flag 检查（如果 chip API surface 没变只是 internal style，则非 BREAKING）
- v1.0.0 API lock 必须含 token 引用契约——R1 完工后的 audit 应进 `prepublishOnly` strict gate

## 7. 落地复盘位置

R1 走完后写复盘到 `docs/internal/retrospection/<date>-token-layer-r1-equivalence.md`，含：
- equivalence map 第一波完整登记的 entries
- A finding 在等价 map 接入前/后的数字对比
- non-equivalent finding 列表 + 各自最终决议
- audit 接入 prepublishOnly 后回归 detector 验证

---

**当前 status**：等 Codex A 跑完 baseline finding，再 append §3.1 实际登记 + §5 第二行决策。
