# Audit — Component attributes vs Figma source (Color token / Padding / Size / Radius / Opacity)

> **触发方式**：用户对 executor 说 `请按 docs/internal/_prompts/audit-component-attributes-vs-figma.prompt.md 执行`
>
> **角色**：executor (Codex)
> **Plan owner**：Claude Code（出 prompt + 审 diff）
> **Mode**：audit-only — **禁止改任何 src/ 文件**（只产出 audit script + report）
> **STOP**：完成后报告 baseline + 列 N 大类 finding + 等 plan owner 决定如何分波 fix

---

## 1. 起因

2026-05-19 验证 session 在 docs site 渲染中发现 multi-select chip 颜色实际取的 token 与 Figma 真源不一致：

- Figma source（参考截图 / `figma-data/normalized/components-tokenized/select_box_*.json` filled variant 内 `select-chip` 等价节点）：填充 = `UX/Grey/grey-3 #F0F0F0`
- Code 现状（[src/canonical/SelectBoxBase.vue:302](../../../src/canonical/SelectBoxBase.vue#L302)）：`.select-chip { background: var(--bg-layer4); }` —— light 主题 `--bg-layer4 = #dbdbdb` ≠ Figma 的 `#f0f0f0`

这一处 token 名字层错配（用错了 token，不是 token 值错）说明：**现有 audit pipeline 没覆盖"组件实际引用的 token 名 vs Figma 绑定的 token 名"**。需要补一份 attribute-level conformance audit。

不只查颜色——Padding / Size / Radius / Opacity / Gap 都要查，因为同样的"错引用"风险全属性都有。

## 2. 数据源

### Figma 真源（每个 component 每个 variant 全字段）
`figma-data/normalized/components-tokenized/*.json` — 4797 个 variant，每个含：

```jsonc
{
  "figmaName": "select box/filled",
  "variants": [{
    "name": "dark theme=on, status=default, ...",
    "h": 32, "w": 240,                                // size
    "pH": 12, "pV": 8, "pR": 12, "pB": 8,             // padding (horizontal/vertical/right/bottom)
    "gap": 8,                                          // auto-layout gap
    "r": 4,                                            // corner radius
    "opacity": 1,
    "fills": [{ "hex": "#353535", "token": { "cssVar": "--color-grey-10", "figmaName": "UX/Grey/grey-10 #353535" } }],
    "strokes": [...],
    "strokeWeight": 1,
    "effects": [...],
    "text": { "size": 14, "fontWeight": 400, "lineHeightPx": 21, "fills": [...] }
  }]
}
```

### Component 映射（Figma → code）
`figma-data/normalized/published-vs-code.audit.json` + `figma-data/normalized/canonical-components.json` —— 38 component / 642 icon 已映射。**只 audit 已映射的；unmapped / icon-only 跳过**。

### Code 端
- `src/canonical/*.vue` — Figma-aligned 桥层（**SoT for AI lookup**，硬规则 #6）
- `src/components/**/*.vue` — base 实现层（chip / sub-element 通常在这里）
- `src/tokens/variables.css` — token 值（dark @ `:root` / light @ `[data-theme="light"]`）

### Translation 真源
- `src/design-system/translation/divergences-decisions.json` — 已登记的"显式不映射"决议；audit 命中这些**不算 finding**（allowlist）
- `src/design-system/translation/token-aliases.ts` — token 名字 alias
- `src/design-system/translation/prop-aliases.json` — prop 名字 alias

## 3. 任务

### 3.1 写 audit 脚本

新建 `figma-sync/audit-component-attributes.mjs`，按下面流程跑：

1. 读取 38 个已映射 component（来自 `published-vs-code.audit.json` 的 `matched` 段）
2. 对每个 component：
   - 加载其 Figma extract（`components-tokenized/*.json`）
   - 加载其 canonical / base Vue 文件（路径来自 `canonical-components.json` 或 manifest）
   - 解析 Vue 文件的 `<style>` block，提取**每个 CSS selector → property → value/var(--token)** 的映射（用 PostCSS 或 regex）
   - 对每个 Figma variant：
     - 找到 code 端对应的 selector chain（按 BEM modifier 推断：variant axis `status=hover` ↔ `.btn--status-hover`；axis `dark theme=on` 默认 dark scope；etc.）
     - 对比下面 5 类属性
   - 用 `token-aliases.ts` 把 Figma `cssVar` 反向映射回 code 期望的 token 名（如有 alias 决议）
3. 输出报告

### 3.2 5 类属性的对比规则

| 类 | Figma 字段 | Code 取数方式 | 判定 "drift" |
|---|---|---|---|
| **Color** | `fills[*].token.cssVar`、`strokes[*].token.cssVar`、`text.fills[*].token.cssVar` | CSS prop `color` / `background` / `background-color` / `border-color` / `fill` 的 `var(--xxx)` | code 引用的 `--xxx` 必须等于 Figma `cssVar`（或经 token-aliases 等价） |
| **Padding** | `pH/pV/pR/pB` | CSS `padding`, `padding-*` | 数字一致（允许差 ≤ 1px 浮点） |
| **Size** | `w/h`（per variant） | CSS `width/height` / `min-*` / `max-*` / 由 padding+content 推算 | 数字一致；如果 code 用 `hug` (auto) 而 Figma 用 fixed，标 "size-mode-mismatch" |
| **Radius** | `r` | CSS `border-radius` 或 `var(--r-*)` 解析后 | 数字一致 |
| **Opacity** | `opacity` | CSS `opacity` | 数字一致（默认 1）|

附加：`gap` (auto-layout) → `gap` CSS / `--space-*` token。

### 3.3 输出格式

写 `figma-data/normalized/component-attributes.audit.json` + 人读 markdown `docs/internal/component-attributes-audit.md`：

```json
{
  "summary": {
    "checkedAt": "...",
    "componentsChecked": 38,
    "variantsChecked": 1234,
    "totalFindings": N,
    "byCategory": { "color-token-mismatch": A, "padding-drift": B, "size-mode-mismatch": C, "radius-drift": D, "opacity-drift": E },
    "bySeverity": { "blocker": X, "drift": Y, "info": Z }
  },
  "findings": [
    {
      "component": "select box/filled",
      "codePath": "src/canonical/SelectBoxBase.vue",
      "variant": "dark theme=off, status=default, ...",
      "selector": ".select-chip",
      "category": "color-token-mismatch",
      "property": "background",
      "figma": { "cssVar": "--color-grey-3", "hex": "#f0f0f0", "figmaName": "UX/Grey/grey-3 #F0F0F0" },
      "code":  { "cssVar": "--bg-layer4",   "resolvedHex": { "light": "#dbdbdb", "dark": "#353535" } },
      "severity": "blocker",
      "allowlisted": false,
      "allowlistRef": null
    }
  ]
}
```

Severity 规则：
- `blocker`：color-token-mismatch（用错 token），padding 差 > 2px，radius 差 > 1px，opacity 不一致
- `drift`：padding 差 1–2px，size-mode-mismatch（hug vs fixed），text size 差 ≤ 2px
- `info`：text-only（fontWeight / lineHeight）差异

### 3.4 Allowlist 机制

- 读 `divergences-decisions.json`，对于 `intentional-non-mapping` / `component-internal-override` 类别的 entry，audit 报 `allowlisted: true` + `allowlistRef`
- 不静默——markdown 报告里单独列 "Allowlisted findings" 段（透明度）

## 4. STOP 协议（mandatory）

跑完 audit script，**不要 fix 任何 finding**。报告：

1. Baseline 数字（总 finding / 按 category / 按 severity）
2. Top 20 finding（按 severity DESC、component asc）的完整记录
3. 你认为 audit 脚本本身的边界情况 / 误报风险（例如 selector → variant chain 推断不可靠的几个组件）
4. STOP，等 plan owner 复审

**禁止做的事**：
- ❌ 改 `src/canonical/*` 或 `src/components/*`（finding 出来后由 plan owner 拍板下一波）
- ❌ 改 `src/tokens/variables.css`（token 值改动等设计师走 Figma sync）
- ❌ 改 `src/design-system/translation/*`（translation SoT 改动需用户确认）
- ❌ 自己加 entry 到 `divergences-decisions.json` 来 allowlist 自己的 finding（典型 audit 反模式）

## 5. 命令 / 集成

新增 package.json script：
```json
"audit:component-attributes": "node figma-sync/audit-component-attributes.mjs"
```

**不要**把它加进 `prepublishOnly` 的 strict gate 链。第一次跑出 finding 是预期，不是 release blocker。

## 6. 输出物清单

- `figma-sync/audit-component-attributes.mjs` — audit 脚本
- `figma-data/normalized/component-attributes.audit.json` — 结构化报告
- `docs/internal/component-attributes-audit.md` — markdown 摘要 + top finding
- `package.json` 加 `audit:component-attributes` script
- 跑一次 baseline，在 STOP 报告里给数字

## 7. 复盘 / 后续

- finding 出来后，plan owner 会按 component × category 分波改组件 token 引用
- token 名层错配最危险——把这一波 fix 干净后，library scope 的 contrast finding 会大幅变化（很多是引用错 token 导致的，不是 token 值本身要改）
- 整波结束后写复盘到 `docs/internal/retrospection/<date>-component-attributes-audit-baseline.md`

---

**完成后 STOP，等 plan owner 决定下一步**。
