# Code Implementation Conventions（Path B — Code）

> 任何 AI 工具**在消费 TVU UX Design System 写代码**时必须遵循的硬约束。
> 项目级真源；不要在对话或工具私有 memory 里复刻规则。
>
> **作用域**：写**前端代码**（HTML / Vue / React / 其它）消费 TVU library 的任何场景——本仓库的 `playground/docs/*` / `src/components/*` / 下游产品 demo（如 `MicroApps Console`）/ 任何 PoC。
> **不**作用于：Figma mockup（→ 见 [`mockup-conventions.md`](./mockup-conventions.md)）；非 UI 代码（generators / build scripts）。

---

## 🤖 AI 读取指引（按需加载，避免全量吞）

本文件 ~1100 行，**不要起手就全量 Read**。按下表只读起手必读段，具体 R-rule jump to 时再读：

| 起手必读（任务前先吃这些） | 行段 |
|---|---|
| 本文件顶部 intro + §作用域 + §必读链路 + §与 mockup-conventions 的边界 + §真源关系 | §0–§3 (1–48) |
| §Convention Priority Hierarchy（冲突解决总则） | §4 (49–78) |
| §Task Entry Modes + §Pre-Phase 0 + §Lazy Reference Discipline | §5–§7 (79–162) |
| §R0 Phase 0 Element-to-Component Mapping（**所有任务前置**） | §8 (163–200) |

| 触发后再读（按场景 jump，不预读） | 触发条件 |
|---|---|
| §R1 / §R15 library-first | 要画 / 写新 UI 元素时 |
| §R2 token discipline | 要写颜色 / 间距值时 |
| §R3 mirror canonical | 要做复合组件时 |
| §R7 4 大场景判断 + §规则适用速查表 | 任务意图未明 / 多场景候选时 |
| §R8 视觉还原自检 + §R9 静态→交互 + §R10 vector 导出 | R7 场景 1 / 3 / 4 命中后 |
| §R12 annotation 过滤 + §R16 字符串边界 | 任务涉及 designer 标注 / i18n key 时 |
| §R13 affordance 搜索 | 要找 chevron / sort / close 等"基本语义元素"前 |
| §R14 layout primitive | 要决定 flex / grid / absolute 时 |
| §R5 session handoff + §R6 增量复用 + §R4 process | 跨 session 接力 / 改既有产品时 |

**红线**：跳过"起手必读段"直接进 R-rule = 协议违反（漏 R0 Phase 0 是历史最高频回归源）。

---

## 必读链路（起手按顺序读）

| 文件 | 内容 | 必读时机 |
|---|---|---|
| [`design-process.md`](./design-process.md) | 通用 process 规则（M22 / Pre-Phase 0 / Phase 0 / M11 / M14 / M15 / M16 / M21 / M6）| **所有任务** |
| [`domain-tvu.md`](./domain-tvu.md) | TVU 业务规则（M3 / M4 / M5 / M7 / M8 / M9）| **所有任务** |
| 本文件 | Path B 专属：TVU code source 真源关系 / R0 / R1 / R2 / R3 / R4 / R5 / R6 | **所有 Path B 任务** |

---

## 与 mockup-conventions 的边界

| 维度 | mockup-conventions (M1-M20) | code-conventions (R1-R4，本文件) |
|---|---|---|
| 操作对象 | Figma 文件 | 前端代码（HTML/Vue/React/...） |
| 库实证目标 | Figma published library components / icons | `tvu-design-system/dist/icons/svg/` + `src/canonical/` + `src/tokens/variables.css` |
| 输出 | mockup frame | UI 代码 |
| 共享原则 | "Library 优先 + 实证搜索 discipline" — 两边都是同一原则的不同实例 | 同左 |

**M2**（Figma mockup 不许凭直觉自画组件）和 **R1**（code 不许凭直觉自画 icon）是同源原则在不同 scope 的镜像规则。**它们不能互相替代**——做 mockup 用 M2，写 code 用 R1-R4。

---

## 真源关系（必读 — 影响所有下游判断）

| 层级 | 是什么 | 完整度 |
|---|---|---|
| Figma `TVU UX Design System` library（key `lk-057f6ba0...`）| **视觉真源** | 100%（即时反映新设计） |
| `dist/icons/svg/` + `src/canonical/` + `src/tokens/variables.css` | 该真源的**代码集成** | ~60% 持续 sync |

**关键判断**：代码端 gap **不构成自画许可证**。遇 gap 的正确路径：

1. 当 code 路径（priority 1）miss → fallback MCP `get_design_context` 取 Figma 真源（priority 2）→ 内联使用
2. 同步登记 [`backlog.md`](./backlog.md) 的 BRIDGE-CODE-ICONS / BRIDGE-CODE-CANONICAL 段作 sync 候选
3. **Figma library 也 miss 时**才走 R1 / R3 的自画例外条款

> "code lib 没 sync 故自画" 是 60% 现实下最高频的反模式。`dist/icons/svg/` 和 `src/canonical/` 不是 truth source，是 truth 的 sync target。

---

## Convention Priority Hierarchy（冲突解决优先级）

TVU 设计规范当前约 60% sync 进度，仍在持续整理。AI 遇规则冲突或 TVU 规范未覆盖的判断时，按此优先级处理：

| 级别 | 来源 | 何时引用 |
|---|---|---|
| **P0 最高** | TVU 项目级硬规则 ([`AGENTS.md`](../../AGENTS.md) 硬规则 #1-#N) | 任意 TVU 相关任务 |
| **P1** | TVU consumption conventions (本文件 R0-R5 / [`mockup-conventions.md`](./mockup-conventions.md) M0-M20) | 消费 TVU 库时 |
| **P2** | `role-ux` skill UX guidance | Pre-Phase 0 / UX 判断 |
| **P3 兜底** | 通用 UX heuristics (Nielsen / WCAG / 平台惯例) + AI 训练知识 | TVU 未覆盖的判断 |

### 冲突解决规则

1. **高优先级胜出**：P0 > P1 > P2 > P3
2. **显式 override 需用户拍板**：AI 不能自作主张用 P3 推翻 P1
3. **TVU 未规定 → P3 兜底**：例如响应式断点 / 表单 validation 时机 / toast 显示时长 等 TVU 没明说的，AI 用通用 UX 规范，**不停下追问**

### 示例

- **M3 红=Live (P1) vs 通用 UX 红=危险 (P3)** → M3 胜出（broadcasting 行业惯例）
- **R1 库实证 (P1) vs "这个图标我快速画一个就行" (无来源)** → R1 胜出，必须查库
- **role-ux 说 "按钮焦点态加 outline" (P2) vs P3 无强约束** → P2 胜出
- **TVU 没规定 toast 显示时长** → P3 兜底（成功 2-3s / 错误 5s+ / 永久错误 dismissable）

### Why

TVU 规范持续整理中，**hierarchy 让 AI 在规范不完整时仍能输出合规结果**——TVU 没覆盖的不停下追问按通用 UX 兜底；TVU 已覆盖的不被通用 UX 反推。

---

## Task Entry Modes

AI 接到任务后**先 classify** 再决定走哪条路径：

| ID | 用户表达 | 流程 |
|---|---|---|
| **US-1** Greenfield 产品 | "基于 TVU 设计一个 X 产品，功能大概 ABC..." | Pre-Phase 0 → R0 → build |
| **US-2** Greenfield 单页 | "基于 TVU 给 X 产品画 dashboard 页" | Pre-Phase 0 (单页 micro 版，可合并 Gate 1+2) → R0 → build |
| **US-3** Existing product 增 / 改 / 删 | "MicroApps Console 加/改/删任意页或功能" | Read handoff → Pre-Phase 0 (仅变化部分) → R0 → build (仅变化部分) |
| **US-4a** Figma → code visual-fidelity mirror | "客户/第三方 Figma 按原样还原" / "验证 TVU 库够不够" / "按这张截图实现" | R7 场景 1 → 跳过 Pre-Phase 0 → R0 with visual-fidelity priority → build（Figma 视觉为准） |
| **US-4b** Figma → code TVU-normalize translation | "用 TVU 实现 Figma X frame" / "TVU library Figma 1:1 还原" / "用 TVU 规范化这个设计" | R7 场景 3 → 跳过 Pre-Phase 0 → 从 Figma metadata 反推 element → R0 → build |
| **US-5** 小调整 (1-2 element) | "登录按钮换个位置" | 跳过 Pre-Phase 0 + R0 (R0 trivial 跳过条款) → 直接做 |
| **US-6** Review / Audit | "帮我审 X 代码是否符合 TVU 规范" / "找出 X 违反的规则" | 跳过 Pre-Phase 0 + R0 → 对照 R0-R5 + Convention Priority 逐条审 → 产 audit report |

Classification 不确定时 → AI **主动问用户**哪种入口，不要默默走错。**特别：US-4 必须 ask 是 4a 还是 4b**（R7 disambiguation 是高频翻车点，详见 R7）。

---

## Pre-Phase 0：Product Definition（前置硬规则）

**触发条件**：用户给的是高层产品 / 页面 brief（如 "基于 TVU 设计一个 X 产品/页面，功能大概 ABC"），element 未列出。

**进入时同步 load `role-ux` skill 作为 UX guidance layer** —— Pre-Phase 0 内的功能假设 / UX flow 提案带上 role-ux 的 UX 判断、直播行业惯例、a11y、中英双语文案 discipline。

**不触发**：用户已给完整 element 清单 / 已有 handoff 定义了产品 scope（US-3 沿用现有定义） / Figma frame → code mirror（US-4）。

### 5 步流程（两个用户 gate）

#### Step 1 — AI 提功能假设清单
- **条数**：由 AI 按产品复杂度自决（简单 3-5 / 中等 6-10 / 复杂 10+），不设硬上限
- **分层**：核心 (MVP) + 增值 两层
- **市场参考**：用户没指明则**不主动引**；用户说"参考市场"才按同类产品对照（vMix / TriCaster / OBS / LiveU / 行业内同类）

#### ⏸ Gate 1：用户校功能假设
- 用户可：删 / 改 / 增 / 重排核心 vs 增值
- 用户确认 MVP scope 后才进 Step 3

#### Step 3 — AI 提 UX flow 草稿
- **默认格式**：文字 bullet list
  - 用户角色：...
  - 主任务：...
  - 信息架构 / page list：每页一句话核心任务
  - 关键 page-to-page 跳转
- **复杂产品升级**（≥ 5 页 或 多角色）：ASCII flow diagram 或 Mermaid（用户能渲染时）

#### ⏸ Gate 2：用户校 UX flow
- 用户可：调整 flow / 增删 page / 调整跳转
- 用户确认骨架后才进 Step 5

#### Step 5 — AI 按 page list 逐页拆 element → Pre-Phase 0 完成
- 此时手里：MVP 功能清单 + UX flow + page list + per-page element list
- 进入 Phase 0 mapping（R0）

### Why

高层 brief 跳过 Pre-Phase 0 直接走 R0 会得到"基于 TVU 组件凑出来但产品逻辑空洞"的结果——element 都对，但 flow 没思考 / 角色不清晰 / page 间割裂。Pre-Phase 0 强制 2 个用户 gate 把产品决策权显式还给用户，AI 只做"基于 brief 做合理提案"的辅助，**不做"代用户决定产品做什么"的越权**。

### 与 role-ux skill 的分工

- `role-ux` skill：独立 UX 咨询角色（用户问"这个交互怎么设计"）
- Pre-Phase 0：workflow 内嵌产品定义阶段，目标输出 element list 喂给 R0

两者**不互相 chain**——用户主动唤 role-ux 时只用 role-ux；用户走 `tvu-design-code` 时 Pre-Phase 0 内嵌做。

---

## Lazy Reference Discipline（按需加载 / 不预加载全量）

TVU library 体积大（647 icons / 49 components / N tokens / canonical 桥层）。**不预加载全量**——按 "先索引后细节" 三阶段：

| 阶段 | 加载什么 | 命令示例 |
|---|---|---|
| **起手** | conventions 真源（必）+ catalog headers + namespace 目录 | `Read AGENTS.md + code-conventions.md`<br>`head -60 figma-component-catalog.md`（只看 ### 标题列表）<br>`ls dist/icons/svg/`（28 个分类目录，已是轻索引） |
| **Phase 0** | 每 element 按需 grep 具体细节 | `ls dist/icons/svg/<namespace>/` + grep<br>`grep -A 5 "^### <Comp>" figma-component-catalog.md`<br>`grep "<keyword>" src/tokens/variables.css` |
| **Build** | 具体落地点才 cat 文件内容 | `cat dist/icons/svg/<ns>/<icon>__<id>.svg`<br>`Read src/canonical/<Comp>.vue` |

### Why

eager 加载 ~50KB+ 库目录到 context 浪费 reasoning space。lazy 加载让 context 只保留当下任务相关的几十行。

**额外收益**：lazy 模式下 AI 每次 grep query 都会显式输出 → 你能看到查询词是否系统性覆盖（catch "Phase 0 查得不彻底" 的 anti-pattern，正好和 ledger 异常启发式 `🟡→✅=0` 形成双重监控）。

---

## R0 — Phase 0：Element-to-Component Mapping（前置硬规则）

任务涉及 **≥ 3 个 element** 时**必须**在写代码前先产出 mapping table。trivial 1–2 element 任务可省。

### 输入

用户的需求清单 / 元素列表（用户给或 AI 从需求拆解）。

### Lookup 序列（按序）

1. **grep [`figma-component-catalog.md`](./figma-component-catalog.md)** — 拿 Figma component_set key + variants + Primary use / Extension / Don't use；每个 element 至少 2-3 个同义词 / casing 变体试
2. **grep [`figma-data/figma-to-code-mapping.json`](../../figma-data/figma-to-code-mapping.json)** — Figma name → code name + status
3. **grep `src/canonical/`** — canonical 桥层是否存在
4. **grep `dist/icons/svg/`** — icon SVG 是否已 sync
5. **catalog miss 时** → MCP `search_design_system` + `get_design_context`（priority 2 fallback）

### 输出格式（mandatory 4 列 table）

| Element | Figma component_set (key + variants) | Code path | 状态 |
|---|---|---|---|
| top bar | `Top bar` (`918b928e...`, Tag=AfterLogin/BeforeLogin) | `src/canonical/TopBar.vue` | ✅ sync |
| status badge | `Badge` (`4db5246d...`, Type/Color/Tag) | canonical 缺 → Figma fallback | 🟡 sync gap → BRIDGE-CODE-CANONICAL |
| live icon | `indicator/live-1` | `dist/icons/svg/indicator/live-1__1133_22954.svg` | ✅ sync |
| (假想真无) | catalog + MCP search 全 miss | n/a | ⚠️ 真 gap → 用户拍板自画 + BRIDGE-CODE-ICONS |

### 处理流程

1. AI 产出整张 table
2. 把 table 发给用户校验（用户纠正误读 / 补充遗漏 / 一次性决策 gap 处理）
3. 用户确认后才开始写代码
4. build 过程中若发现新 element 没在 table 里 → **回到 Phase 0** 补一行，**禁止**现场判断"library 缺件"

### Why

反应式（边写边查）模式下，gap 分散在过程里 → 用户无法预审 → 容易现场误判走自画（R1 / M2 反模式同根）。前置 mapping 让所有 gap 一次暴露，用户一次决策（接受 Figma fallback / 暂停补 sync / 拍板自画），同时把 `figma-component-catalog.md` 系统性 exercise（🟡 skeleton 顺手回填升级 ✅ verified）。

---

## R1 — 图标必须从 TVU library 取（不准内联自画 SVG）

> **⚠️ Merged into [R15](#r15--library-first-for-all-product-ui-elementscode-side-mirror-of-m32r1--r3-扩展)** as the icon sub-clause (R15 §icons). R15 is the umbrella for all product UI elements (icons + buttons + inputs + modals + ...); R1 was the icon-specific subset and is now absorbed.
>
> The 12-row anti-pattern table (2026-05-11 MicroApps Console retrospective) + R1 例外条款（自画唯一允许场景）+ Affordance synonym expansion are preserved in R15 §icons + carried by R13 (Affordance-Category Search) which generalizes synonym expansion.
>
> This anchor is preserved for legacy reference.

---

## R2 — 颜色必须用 TVU token，不写 hex literal

- 取数路径：`tvu-design-system/src/tokens/variables.css` 全部 `--*` 变量
- 写代码时：CSS 用 `var(--brand)` / `var(--bg-layer1)` 等；不写 `#2fb54e` / `#141414`
- Token 缺失场景：先确认是 Figma 真源缺还是 sync 缺；如果是真源缺 → 用户拍板新建 + 同步 figma-data。**绝不直接 hardcode hex**

同 [`AGENTS.md`](../../AGENTS.md) 硬规则 #4 ("颜色硬编码 = bug")。

---

## R3 — 复合组件优先 mirror TVU canonical

- 取数路径：`tvu-design-system/src/canonical/*.vue`（Vue + canonical 桥层）
- 能直接消费（npm install） → 直接用
- 不能消费（如单 HTML demo，无构建链）→ **手写 1:1 mirror canonical 的 props / events / slots / 视觉**；不要按自己理解重新设计 API
- 决策来源必须可追溯到 canonical spec（API / 视觉）
- **canonical 还没桥但 Figma library 有** → 用 Figma MCP `get_design_context` 拿 spec → mirror Figma 输出的 React+Tailwind reference（按目标项目栈适配）→ 同步登记 [`backlog.md`](./backlog.md) "BRIDGE-CODE-CANONICAL" 段（暂不存在则新建）作 sync 候选；**不**视为 "library 缺件" → **不**走自画路径

---

## R4 — 不"先实现，再问要不要换库"

库实证是 implementation 的**前置**，不是 nice-to-have：

- 起步第一动作：`ls /Users/nancy/Documents/AICoding/VS_Code/tvu-design-system/dist/icons/svg/` 拿到全库 SVG catalog
- 每写一个 icon → 先 grep 这个目录
- 每写一个颜色 → 先 grep `src/tokens/variables.css`
- 每写一个复合组件 → 先 grep `src/canonical/`

**Why:** "先按印象实现，让用户审" 这种 anti-pattern 把 review burden 推给用户，违反 [`docs/meta-rules.md`](../meta-rules.md) 反模式清单。

---

## Acceptance criteria（build 自审）

任何 implementation PR 完工前，作者必须答得出：

- 自画 SVG icon 数量？每个有库实证证据吗（搜索词 + 结果）？
- 用了多少 hex literal？token 化了吗？
- 复合组件 mirror 自哪个 canonical？API 差异有登记吗（如 prop name 不同应进 `src/design-system/translation/`）？

任一条答不出 → 不可宣布"完成"，必须返工。

---

## R5 — Session Handoff Convention（跨 session 任务接力）

任何在 TVU sibling consumer product（如 `MicroApps Console`）上的代码 / 设计工作 wrap up 时，必须在产品根目录 `docs/` 下留一份 handoff 文档，命名格式 **`YYYY-MM-DD-handoff.md`**。新 session 通过这份文档冷启动接力，不需要 user 重新解释上下文。

### Handoff 文档必备 5 段

```markdown
# <Product Name> — Session Handoff (YYYY-MM-DD)

## 1. Context
（1-3 句话）这个产品是什么，consumer of TVU。

## 2. 必读 onboarding 顺序
1. `../tvu-design-system/AGENTS.md`
2. `../tvu-design-system/docs/internal/code-conventions.md`（R1-R5）
3. `../tvu-design-system/docs/internal/mockup-conventions.md`（如做 Figma 任务）
4. `./docs/<PRODUCT>_PRODUCT_CONTEXT.md` 全文 + 重点段（如 §10.5 当前状态）
5. 本 handoff 文档

## 3. Current State
- 当前产物路径（文件 / 目录）
- 已落实功能清单
- 已确认 working 的依赖 / 链路

### Iteration log（mockup / code 版本迭代追踪）
| 版本 | 产物 path / frame node-id | 主要决策 | 复盘指针 |
|---|---|---|---|
| v1 | ... | ... | ... |

## 4. 已知 gap（按严重度排序）
| Gap | 严重度 🔴🟡🟢 | 解决路径 |

## 5. 下一步优先级
按顺序列 3 个最高优先级任务，每个标"是否需要 user 决策"。

## 6. Quick-start prompt（可直接 copy 到新 session）
```（嵌套代码块）
继续 <Product Name> 工作。
工作目录：<absolute path to product>
按 docs/<date>-handoff.md §2 onboarding 顺序读完，然后从 §5 选一个任务起步。
```（嵌套结束）

## 7. Phase 0 ledger entry（Wrap-up checklist）
- [ ] 本任务执行了 Phase 0 mapping（element ≥ 3）→ 已 append 一行到 `tvu-design-system/docs/internal/_metrics/phase0-ledger.md`
- [ ] Trivial 任务跳过了 Phase 0（element < 3）→ 标 `N/A — trivial`，不 append
- [ ] US-4 (Figma → code mirror) 走了 Phase 0 → 同样 append
- [ ] **Release 类任务（含 changeset:version + tag push）wrap-up 必含 Packages 页实物 verify link 或 screenshot 作为 evidence**（2026-05-11 落实 — v0.1.0/v0.1.1 silent fail 盲点修复；tag pushed + CI green ≠ publish 成功，必看 registry 实物。详见 [`STATUS.md`](../STATUS.md) §Release wrap-up 必改项）
```

### Handoff 文档保存位置（红线）

- ✅ 写到 **consumer 产品根目录 `docs/`**（如 `MicroApps Console/docs/2026-05-11-handoff.md`）；**若 `docs/` 不存在，先 `mkdir -p ./docs/`**
- ❌ 不写到 TVU 仓库（污染源）
- ❌ 不写到 user home / 临时目录（找不到）

### 何时生成 handoff

| 触发 | 是否必须写 handoff |
|---|---|
| 当前 session 即将结束 / 用户说"先收尾" | ✅ 必须 |
| 完成阶段性里程碑 | ✅ 必须 |
| 短任务（< 30 min） + 没遗留 open item | ❌ 可省 |
| 仅 review / 仅讨论无代码改动 | ❌ 可省 |

### Convention 持续性

新增 gap → 更新 handoff §4；下一阶段优先级变 → 更新 §5。**handoff 不是一次性快照，是 cross-session living document**——下一个 session 拿起来工作之前自己负责更新这份文档（再交棒下一个 session）。

---

## R6 — 已建立产品的增量优先复用 local

### 触发场景

US-3（existing product 增 / 改 / 删 feature），product app 已有 working 的本地 layout component / composable / 页面范式。

**不**触发：US-1 / US-2 greenfield、US-4 Figma→code mirror、US-5、US-6。

### 与 R0-R3 关系（不冲突，时序差）

| 时序 | scope | 优先级 |
|---|---|---|
| 视觉基线**建立期** | US-1 / US-2 greenfield | R0-R3: TVU library / canonical 优先 |
| 基线**已建立后**增量 | US-3 existing | **R6**: existing local > TVU library 重建 > 自写 |

R6 不改 R0-R3，只在 US-3 scope 显式覆盖。

### 决策树

1. 产品 app 已有等价 layout component / composable / 页面范式 → **优先复用**
   - props 可适配 → 复用 + props override
   - props 不可适配 → mirror 该范式的视觉 / 结构，按当前需求重建 internals
2. 产品 app 没有但 TVU library / canonical 有 → 按 **R3** 走 library 优先
3. 都没有 → 按 **R0 / R1 例外** 走自写候选

### 例外（user 显式 override）

- existing local 有已知 bug 需替换
- 跨产品级 refactor（需全 app 同步更新）
- user 明确说"用 TVU canonical 新版替换旧 local"

**澄清 — pre-design-system 不视为 bug**：existing local 违反 R0-R3（自画 icon / hex literal / 不 mirror canonical）**不**构成 "local 有已知 bug" 的例外。pre-design-system 时代 product code 的**合规债不在增量任务 scope 内修正**；需用户显式发起独立 refactor 任务。同 M21 同源澄清，避免增量任务在 pre-design-system codebase 上 silent compliance fix（违 R6 mirror discipline + 制造未审批的破坏性变更）。

### Why

同 M21：消除"同产品同时间窗口跨页面视觉割裂"。

### 反例

- 增 settings 页时不看 profile 页已有的 `<SettingsField>` composable，重新从 canonical 拼 → ❌
- 增 chart 页时绕过 dashboard 已用的 `<ChartCard>` layout 包装器，独立用 raw chart lib → ❌

### 与既有规则关系

- R1（icon 库实证）/ R2（token 库实证）优先级 > R6（leaf primitive 维度）
- R3（canonical mirror）在 R6 决策树第 2 步生效

---

## R7 — 任务意图 disambiguate：4 大场景判断（起手必判）

### 触发背景

TVU library 60% sync 现实下，消费侧任务的**视觉真源未必是 TVU library**：

- Figma 效果图可能是 pre-design-system 时代 / 第三方 / 客户提供，不符合 TVU 规范
- 用户拿 Figma 来"验证 TVU 库覆盖度"而**不**想做 TVU 化重构
- Existing product 本地已有非 TVU 组件，新增量不应越界做 silent compliance fix

AI 默认走 "Figma → TVU mapping → 用 TVU 替换" 会在用户只想 1:1 还原时**过度规范化**——把原本应保留的视觉强行变成 TVU 版本。

### 4 大场景 + 判断维度

任务起手时（在 R0 Phase 0 mapping 之前），AI 必须**显式 disambiguate 用户意图**，归到 4 大场景之一：

| 场景 | 名字 | 视觉真源 | 产品状态 | 任务性质 | TVU 库角色 | 路径要点 |
|---|---|---|---|---|---|---|
| **场景 1** | 非 TVU 视觉还原 | Figma / 截图 / 草图 本身 | 任意 | 新建（按视觉还原）| 软建议，命中即用，**不**强推替换 | R0 mapping 每行标 "visual-fidelity priority"；缺件按 Figma 原样实现（hex / 自画 SVG / 非 canonical 组件允许）；标 sync 候选；**不**走 R1/R2 例外授权流程 |
| **场景 2** | 已有产品增量 | 已有产品视觉惯例（含已 sync 的 TVU + local + 已用 token） | 已有产品 | 增量 / 迭代 | element 级决策：local 有用 local，local 没用 TVU | R6 决策树：local > TVU > 自写；**不**借机修 pre-design-system 合规债 |
| **场景 3** | 全新产品 TVU 构建 | TVU library | greenfield | 新建 | 强约束（priority 1） | R0 + R1/R2/R3 全套；gap 走 fallback 链（code → MCP Figma → 自画例外） |
| **场景 4** | TVU 规范校正 | TVU library | 任意（多数已有产品）| 重构 / 校正 | 强约束（priority 1） | 独立 refactor 任务，需用户**显式发起**，不混在增量任务里 |

### 场景 2 视觉真源细化（element-level + 成熟度光谱）

场景 2 不是"禁用 TVU"——而是**逐 element 判断**，TVU 库经常会用、且应该用：

| element 状况 | 决策 |
|---|---|
| ✅ local 有 + ✅ TVU 也有 | **优先 local**（避免跨页面割裂） |
| ❌ local 没 + ✅ TVU 有 | **用 TVU** ← 完全合法且鼓励 |
| ❌ local 没 + ❌ TVU 没 | **自写候选**（走 R0 / R1 例外，需用户授权 + 登记 backlog） |
| ⚠️ local 有但旧/不规范 + ✅ TVU 有新版 | **仍用 local**（除非用户显式说"用 TVU 替换"）— pre-design-system 不视为 bug |

产品 TVU 化成熟度光谱（不改变规则，仅影响新增 element 多数走哪）：

| 产品 TVU 化程度 | 新增 element 多数走 |
|---|---|
| 高（80% TVU + 20% local） | 多数走 TVU，少数复用 local |
| 中（50/50） | 严格 R6 决策树逐 element 判断 |
| 低（20% TVU + 80% local） | 多数复用 local，少数走 TVU |

规则统一——**不**因 TVU 化程度而变。

### 常见子场景（20 个）+ 触发关键词

#### 场景 1（非 TVU 视觉还原）

| 子场景 | 典型表达 | 触发关键词 |
|---|---|---|
| **1.1** 客户 / 第三方 Figma 设计 | "客户给了这个 Figma，按原样还原" | 客户 Figma / 第三方设计 + 还原 |
| **1.2** Pre-design-system 时代旧 Figma | "这是 TVU 规范前的旧设计图，照实现" | 旧设计 / legacy / pre-design-system + 实现 |
| **1.3** 截图 / 草图 / 手稿（非 Figma 文件） | "按这张截图 1:1 实现" | 截图 / 草图 / 手稿 + 1:1 / 实现 |
| **1.4** 验证 TVU 库覆盖度（diagnostic） | "看 TVU 库够不够撑这个 Figma" / "找出 Figma 里哪些 TVU 库没有" | 验证库 / 覆盖度 / 撑得起 / 找 gap |
| **1.5** 临时 PoC / demo / 草稿（不进生产） | "做个 demo 试试效果" | PoC / demo / 临时 / 试试 |

#### 场景 2（已有产品增量）

| 子场景 | 典型表达 | 触发关键词 |
|---|---|---|
| **2.1** 已有产品加全新页面 | "MicroApps Console 加 dashboard 页" | 已有产品名 + 加 + 页 |
| **2.2** 已有页面加新 feature / element | "在 setting 页加一个 X 选项" | 已有页面名 + 加 + 元素 |
| **2.3** 修改已有元素行为（不动视觉） | "登录失败提示改一下文案" | 已有元素 + 改 / 调整 + 行为/文案词 |
| **2.4** 补一个 local 没有但 TVU 有的组件 | "需要个 progress bar，TVU 有现成的" | local 没 + TVU 有 + 直接用 |
| **2.5** 基于已有 layout / composable 拓展 | "用现有的 SettingsField 加一行" | 复用 + 已有 composable 名 |

#### 场景 3（全新产品 TVU 构建）

| 子场景 | 典型表达 | 触发关键词 |
|---|---|---|
| **3.1** Greenfield 产品（从 brief 起步） | "基于 TVU 设计一个 X 产品，功能 ABC" | 基于 TVU + 设计 / 建 + 新产品 |
| **3.2** Greenfield 单页 / dashboard | "用 TVU 给 X 产品画 dashboard 页" | greenfield + 单页 + 用 TVU |
| **3.3** TVU library 画的 Figma → 翻译代码 | "把这个 TVU Figma frame 翻译成代码" / "拿这个 TVU URL 1:1 还原" | TVU Figma / TVU URL + 翻译 / 还原 |
| **3.4** 客户 Figma 但要求用 TVU 重新实现 | "客户给了 Figma，**用 TVU** 实现" | 客户 Figma + **用 TVU** + 实现（视觉真源被显式覆写为 TVU） |
| **3.5** 从 PRD / 文字 brief 起步（无 Figma 输入） | "按 PRD 用 TVU 实现 X feature" | PRD / spec + 用 TVU + 实现 |

#### 场景 4（TVU 规范校正）

| 子场景 | 典型表达 | 触发关键词 |
|---|---|---|
| **4.1** 整体迁移到 TVU 规范 | "把这个产品 / 页面整体迁移到 TVU 规范" | 迁移 / 整体规范化 |
| **4.2** 替换 hex literal 为 TVU token | "把 hex 全换成 token" | 替换 + hex / hardcode + token |
| **4.3** 替换自画 SVG 为 TVU library icon | "把自画图标全换成 TVU 库的" | 替换 + 自画 / inline SVG + TVU icon |
| **4.4** 替换 local 组件为 TVU canonical | "把这个 Button 换成 TVU 新版" | 替换 + local 组件名 + TVU 新版 |
| **4.5** 审计代码符合度（仅产报告，不改） | "审 X 代码是否符合 TVU 规范" / "找出违反规则的地方" | 审 / audit / 找违规 + TVU |

### Hybrid 边界 case（AI 必须 ask 用户裁定，不强行归类）

下列 hybrid 表达**不直接归类**，AI 必须先 ask 用户裁定视觉真源：

| Hybrid 案例 | AI 应 ask 的问题 |
|---|---|
| 在已有产品里按客户 Figma 还原新页面 | "新页面视觉按客户 Figma 1:1 还是按已有产品惯例校正？" → 答 1:1 = 场景 2 scope + 场景 1 视觉；答校正 = 场景 2 |
| 已有产品里某具体页面整页迁移 TVU | "这是顺手提到还是显式发起独立 refactor？" → 显式发起 = 场景 4；顺手 = 不动（场景 2） |
| Greenfield 但已有部分 PoC 代码可参考 | "PoC 是 throwaway 还是要保留？" → throwaway = 场景 3；要保留 = 场景 2 |
| 拿一个 Figma 同时验证库覆盖度 + 还原 | 双目的（1.4 + 1.1）可合并执行：1:1 还原同时产 gap 清单（无需 ask） |

### 起手 disambiguation 协议

1. 用户表达包含子场景表的"触发关键词"任一组合 → AI 可直接归类
2. 关键词不明确 / 关键词冲突（如同时含"已有产品" + "用 TVU"）→ AI 必须 ask
3. 命中 Hybrid 边界 case → AI 必须 ask 视觉真源
4. **默认偏向不 TVU 化**——歧义时优先归场景 1 或场景 2（保留原貌），不默认场景 3 / 4（这是最容易"过度规范化"的方向）

### 与 Task Entry Modes (US-1..6) 的关系

R7 是**正交维度**——US 维度判断"任务类型"，R7 维度判断"视觉真源 / 意图"。两者配合：

| US 类型 | 常见对应场景 |
|---|---|
| US-1 / US-2 Greenfield | 默认场景 3 |
| US-3 Existing increment | 默认场景 2 |
| **US-4a** Figma visual-fidelity mirror | 场景 1（显式） |
| **US-4b** Figma TVU-normalize translation | 场景 3（显式） |
| US-5 trivial 调整 | 按 product 现状（场景 2 子集） |
| US-6 Audit | 场景 4.5（审计子情况） |

### Acceptance criteria（build 自审）

- 任务开始前，AI 显式声明 **"intent classified as 场景 X.Y（子场景）"** 一行？
- 场景 1 任务中保留的非 TVU 视觉（hex / 自画 SVG / 非 canonical 组件）有标 `<!-- VISUAL-FIDELITY: from source <ref>, candidate for tvu-design-system intake -->`？
- 场景 2 任务中是否**抑制**了对 pre-design-system 合规债的"顺手修复"冲动？
- 场景 1 / 场景 2 暴露的 gap 是否登记到 [`backlog.md`](./backlog.md) 对应 BRIDGE / sync 候选段（暴露不修复，但记录）？
- 命中 Hybrid 边界 case 时，是否先 ask 了用户而非自作主张？

### Why

60% sync 现实下，AI 默认 "Figma → TVU mapping → 替换" 会污染用户的真实意图——尤其当用户只是想验证库覆盖度或 1:1 临摹既有视觉时。强制起手 disambiguation 把"视觉真源"决策权显式还给用户，AI 不替用户做"该不该 TVU 化"的越权判断。

同根原则：R6 在 product code 层覆盖了同问题（已建立产品的增量不要 silent refactor）；R7 把同原则提升到 **task intent 维度**，覆盖更广（含 Figma 输入维度 + 用户验证意图 + element-level 决策）。

### 反模式记录

- 用户说"帮我把这个 Figma 翻译成代码"（未指明场景 1 / 3），AI 默认走场景 3 → 把客户自由设计的颜色全替换成 TVU token → 视觉走样 → 用户返工说"我只是要还原而已"。
- 用户说"在 MicroApps Console 加 X 页"（场景 2），AI 顺手把已有页面的 hex literal 全替换成 token + 加 changeset → 越权 + 制造未审批破坏性变更（同 R6 既有反例）。
- 用户说"看 TVU 库够不够撑住这个 Figma"（场景 1.4），AI 走场景 3 把所有缺件强行用 TVU 近似品替换 → 失去了"验证库覆盖度"的诊断价值，用户拿不到 gap 列表。
- 用户说"在已有产品加一个新页面"（场景 2.1），AI 误读为"已有产品 = 禁用 TVU"，强行只用 local 组件甚至自写 → 错过 TVU 库可用项（场景 2 是 element-level 决策，不是禁用 TVU）。
- 用户历史用语"1:1 还原"（项目语境多指场景 3.3 — TVU library Figma 还原），AI 误归场景 1 → 不走 R1/R2 强约束 → 输出与 TVU 库脱钩。**纠偏**：单独的"1:1 还原"不构成场景 1 trigger；必须同时出现"客户 / 第三方 / 截图 / 草图 / 旧设计"等非 TVU 来源关键词。

### 与既有规则关系

- R1（icon）/ R2（token）/ R3（canonical）在 **场景 3 / 4** 路径下强约束；在 **场景 1** 路径下退为软建议（命中即用）
- R6 是 **场景 2** 的 product code 层实现细节；R7 是上游 intent 判断
- 与 mockup-conventions 的 M2 / M21 同根（同源原则在 mockup scope 的镜像）

---

## 规则适用速查表（按 R7 场景查 R7-R10 + meta + 工具）

R7 classify 之后，用此表快速判断该 fire 哪些规则。读者不用 grep 各规则段拼矩阵。

### 适用矩阵

| 规则 / 工具 | 场景 1<br>非 TVU 视觉还原 | 场景 2<br>已有产品增量 | 场景 3<br>全新 TVU 构建 | 场景 4<br>TVU 规范校正 |
|---|---|---|---|---|
| **R7** 意图 disambiguate（起手必判） | ✅ 必判 | ✅ 必判 | ✅ 必判 | ✅ 必判 |
| **R8** 视觉 self-check（L3 visual-diff.py） | ✅ 强制 | ⚠️ 小增量（R6 子集 ≤2 elem）可豁免 | ✅ 强制 | ✅ 强制 |
| **R8** 矢量源 → 矢量输出 | ✅ | ✅ | ✅ | ✅ |
| **R8** 禁止 approximate 重画 vector | ✅ | ✅ | ✅ | ✅ |
| **R9** Hover / focus / disabled 补全 | ✅ 强制 | ⚠️ 按已有产品惯例 | ✅ 强制 | ❌ 不主动补新交互 |
| **R10** Vector 导出策略决策 | ✅ 强制 | ⚠️ 仅涉及新 vector 时 | ✅ 强制 | ✅ 强制（组件替换时） |
| **R12** Designer annotation 过滤 | ✅ 强制 | ✅ 强制 | ✅ 强制 | ✅ 强制 |
| **Meta-K** enforcement 层级声明 | ✅ 写规则时 | ✅ | ✅ | ✅ |
| **Meta-L** AI 不确定必 ask | ✅ **全场景 + 不限 Figma 任务** | ✅ | ✅ | ✅ |
| **A** pre-commit hook（[`.husky/pre-commit`](../../.husky/pre-commit)） | ✅ 任何视觉 commit | ✅ | ✅ | ✅ |
| **B'** [`figma-asset-fetch.py`](../../scripts/figma-asset-fetch.py) | ✅ 任何取 asset 操作 | ✅ | ✅ | ✅ |
| [`visual-diff.py`](../../scripts/visual-diff.py) | ✅ R8 配套 | ⚠️ 按 R8 例外 | ✅ R8 配套 | ✅ R8 配套 |

### 全场景永远适用（无豁免）

| 规则 / 工具 | 为什么 |
|---|---|
| **R7 起手 classify** | 跳起手 = 跳基础协议 |
| **R8 矢量源 → 矢量输出** + **禁止 approximate** | 物理原则（矢量降级是损失），不是 scope 问题 |
| **Meta-L** AI 不确定必 ask | 元规则，超越 Figma scope，任何任务都适用 |
| **A** pre-commit hook | 物理拦截，hook 不分场景 |
| **B'** / visual-diff.py | 工具，谁用都受益 |
| **R12** annotation 过滤 | 设计师批注永远不该渲染为 UI |

### 起手 5 步 checklist（任何 Figma → Code 任务）

1. **R7 classify**：判场景 1/2/3/4 + 子场景 X.Y → 显式声明 `"intent classified as 场景 X.Y"`
2. **查上表**：知道这次 fire 哪些规则
3. **Meta-L 兜底**：任何不确定 → ask user，**不**自作主张
4. **执行**：按选定规则跑（如 R8 自检 close-loop / R9 hover 推断 / R10 vector strategy 声明）
5. **commit 前**：触发 pre-commit hook（视觉文件 staged 时需 `VISUAL_COMMIT_APPROVED=1`）

### 易混淆边界（场景 2 vs 场景 4）

| 行为 | 场景归属 | 处理 |
|---|---|---|
| 加**新**元素，local 没有 → 用 TVU vector | 场景 2.4（合法增量） | R10 适用，按 case A/B 取 TVU canonical |
| 替换**已有** local 组件为 TVU canonical | 场景 4.4（compliance refactor） | 需用户显式发起独立 refactor 任务 |

边界：增量是 OK 的，**借机替换已有元素**就越界进场景 4，必须用户显式授权。

---

## R8 — 视觉还原任务自检 protocol（场景 1 / 3 / 4 强制；enforcement L3）

### 触发场景

所有 HTML / Vue / React 视觉还原 / 翻译 / 校正任务（R7 场景 1 / 3 / 4），交付给用户前**必须**完成自检 close-loop。

**反模式**：
1. **"open 浏览器 + 直接给用户对照清单审"** — 把视觉验证负担推给用户
2. **"AI Read PNG 主观对比"** — 主观判断已被实证不可靠（2026-05-13 测出 AI 主观估"≥95% match"实际客观工具测 35% diff，详见反模式记录第 2 条）

AI 视觉感知能力薄弱是已知约束，但**不构成免责借口**——工具链 workaround 完全可行；主观对比不可靠则要求 R8 v2 必须强制 L3 客观工具。

### 自检 7 步（v2 — L3 enforcement）

1. **Chrome headless 截屏自己的产出 PNG**（`--screenshot=/tmp/X.png` + `--window-size` 配 Figma frame 维度或目标 viewport）
2. **Read 自己的 PNG**（Read 工具支持 PNG/JPG，AI 能"看到"渲染——但**主观对比不可靠**，必须配合 step 5-6 客观工具）
3. **取对照真源 PNG**（mcp `get_screenshot` Figma frame + curl 下载）
4. **Read 真源 PNG**，建立 macro 印象
5. **★ 调用 `scripts/visual-diff.py`** 拿客观 diff% + 高亮 diff PNG（**L3 enforcement 核心**）
6. **Read diff PNG**（红色高亮区域 = 客观差异位置），逐个红色区域 explain 或修
7. diff% > threshold → **自己修一轮** → 回 step 1；diff% ≤ threshold → **交付**

### macOS 工具链示例

```bash
# 1) 截自己 HTML
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
  --headless=new --disable-gpu --hide-scrollbars \
  --window-size=1280,800 --screenshot=/tmp/my.png \
  "file:///path/to/index.html"

# 2) 取 Figma 真源（mcp 返回 URL，curl 下载）
# mcp__claude_ai_Figma__get_screenshot fileKey=X nodeId=Y maxDimension=2000
curl -o /tmp/truth.png "<returned URL>"

# 3) 局部裁切（如 logo）
sips -c <H> <W> --cropOffset <Y> <X> input.png --out output.png

# 4) ★ 客观 visual diff（R8 v2 L3 核心）
python3 scripts/visual-diff.py /tmp/my.png /tmp/truth.png /tmp/diff.png 5.0
#   → 输出：Diff pixels / 百分比 / bbox / 高亮 PNG
#   → exit 0 = pass (diff% ≤ threshold)
#   → exit 1 = fail (diff% > threshold) — AI 必须修

# 5) AI 必须 Read diff.png 看红色高亮区域，逐个 explain 或修
```

### 例外（必须显式声明豁免理由）

- **场景 2 增量小调整**（R6 子集，元素改动 ≤ 2 个，无视觉重塑）→ 可豁免
- **场景 1.5 临时 PoC / demo**（用户明确说"快速试试"）→ 可豁免（声明后）
- **浏览器/工具链不可用环境** → 必须**显式声明**"无视觉自检能力，请用户当眼睛"，让用户知能力边界
- **Python / PIL 不可用环境** → 退到 v1 主观对比，**但必须显式声明**"客观 diff 工具不可用，使用 v1 主观 protocol"

### Acceptance criteria（build 自审）

- **必须 run scripts/figma-extract-css.py 作为起手 step**: AI 不许自己估算 layout values (padding/margin/font-size/gap 等)，全部 from `extracted-css.css`。任务交付时 AI 必须显式声明 "已 run figma-extract-css.py，extracted-css.css 用作 layout baseline"。
- 任务交付前 AI 显式声明 **"已完成自检 N 轮，最终 visual-diff.py diff% = X.XX%，截图见 [path]"** 一行？
- `visual-diff.py` 客观 diff% ≤ threshold（default 5%）？若 > threshold 是否显式声明"已接受偏差"作豁免（如 "anti-alias noise 12% 可接受，bbox 仅文本区域"）？
- 自检发现的问题 + 修复轨迹是否记录（commit message / handoff / PR 描述）？
- 命中例外条款时是否显式声明豁免理由？
- **矢量源 → 矢量输出**：Figma 中的矢量元素（icon / logo / illustration / 几何 shape）是否用 **inline SVG** 或 `.svg` 文件输出，**不**经 raster (PNG/JPG/sips crop) 中转？例外：固定大背景图（照片 / artwork / 3D render 等本来就是 raster 源）。

  反模式实证（2026-05-13 close-loop 第二轮）：v3 logo 用 sips crop PNG（raster），用户指出"Figma 是矢量，code 也得是矢量"——sips crop 是反向降级（vector → raster），失去缩放 / DPR / editability。**纠偏**：复杂 logo 用 Figma plugin API（`use_figma` exportAsync SVG）或 Figma desktop 手动 export；简单几何手写 inline SVG。

- **禁止 approximate 重画 vector 元素**：AI 不允许手写 inline SVG geometry（`<circle>` / `<rect>` / `<text>` / `<path>` 等）近似重画 logo / icon / illustration / 复杂 shape。Vector source 必须从原始 asset 取真实 paths（[`scripts/figma-asset-fetch.py`](../../scripts/figma-asset-fetch.py) + composite，或 `use_figma` plugin `exportAsync`，或 Figma desktop 手动 export）。例外：纯几何形（chevron arrow / 简单 line / 纯 stroke shape）无 source 时可手写。

  反模式实证（2026-05-13 close-loop 第三轮）：v6 AI 手写 `<circle cx=19 cy=19 r=16>` + `<text>TVU</text>` approximate 重画 logo → 视觉错位 + 字距错 → 用户连续 2 轮 push back（"还是不对" / "拆得很散了"）。**纠偏**：先 `figma-asset-fetch.py` verify Figma asset 实际格式（SVG / PNG）+ 拿真 vector paths，不重画。

- **Vector 元素导出策略先判断**：取 Figma vector 之前先用 R10 决策树判断"单一 SVG / 多个 SVG / ask user"，**不**从下游 `get_design_context` React code 反推（详见 R10）。

### Why

"AI 没视觉感知 → 默认依赖用户当眼睛"是结构性反模式。**Workaround 存在**（Chrome headless + Read PNG + mcp get_screenshot + sips + visual-diff.py），AI **必须主动 workaround**。

更深的发现：**AI 主观 Read PNG 对比也不可靠**——2026-05-13 R7 测试中 AI 主观估"≥95% match"实际客观工具测出 35% diff，偏差 30 percentage points。所以 R8 v2 强制 L3 — 必须调用 visual-diff.py 拿客观 diff，主观判断降为辅助。

2026-05-13 R7 测试 Config-T HTML 还原（场景 1.2）首次实证：自检 pipeline 全链路成功（broken v1 → v3 close-loop 自主修复 logo + 居中两个问题，用户零参与），同时暴露 v1 主观对比不可靠驱动 v2 升 L3。详见 [retrospection/2026-05-13-r7-test-self-check-validation.md](./retrospection/2026-05-13-r7-test-self-check-validation.md)。

### 反模式记录

- **2026-05-13 broken v1 case**（场景 1.2 Config-T HTML 还原）：AI Write HTML 200 行 → `open` 浏览器 → 直接给用户对照清单 → 用户截图发现 logo 4 层 img 拼合错乱 + 卡片居中失败。**纠偏**：`open` 后应**立刻** Chrome headless 截屏 → Read PNG → 发现问题 → 自己修一轮 → 再给用户审。
- **2026-05-13 主观 vs 客观偏差实证**：AI 主观估 v3 vs Figma truth "match ≥ 95%"，但 `visual-diff.py` 客观测出 **35.34% diff**——主观与客观偏差 **~30 percentage points**。**纠偏**：R8 v2 升 L3，必须调用 visual-diff.py 拿客观 diff%，AI 主观判断**不**作为 release 依据。

### 与既有规则关系

- 适用 R7 场景 1 / 3 / 4；场景 2 子集（小增量）可豁免（per R6 element-level 决策本身的 scope 限制）
- 与 INFRA-F20 Playwright 视觉 baseline 不同源：F20 是 tvu-design-system 内的 component-level 视觉回归测试；R8 是 consumer 侧实施级别自检 protocol，工具更轻（Chrome headless + sips + visual-diff.py）
- 实施 lessons（Figma reference 不可 1:1 翻译 / 居中用 grid + 单方向 align）见 [retrospection/2026-05-13-r7-test-self-check-validation.md](./retrospection/2026-05-13-r7-test-self-check-validation.md) Lesson 1-2 段
- enforcement 层级声明遵循 [meta-rules.md 触发器 K](../meta-rules.md)

---

## R9 — Static-to-Interactive Augmentation Protocol（场景 1 / 3 强制；enforcement L1，v2 待升 L3）

### 触发场景

R7 场景 1（visual-fidelity）/ 场景 3（greenfield TVU 构建）的 code implementation。

- 场景 2（已有产品增量）按已有产品交互惯例
- 场景 4（compliance refactor）保持原代码状态不主动补新交互

### 强制补的交互态（设计图没画也要补）

| 交互态 | 推断规则 |
|---|---|
| **hover** | 从 active state 反推 — "active 的弱版本"（bg 略浅 / 加 subtle bg / weight 不 bold） |
| **focus** | active-like bg + outline ring（键盘 a11y） |
| **disabled** | opacity ≈0.5 + cursor: not-allowed + 去交互态 |
| **active/pressed** | 比 hover 更深的 bg（仅交互瞬间） |

### AI 应主动声明推断规则

任务交付前 AI 必须列推断的交互态规则表（一行/状态）：

> "本任务 hover 从 active state 反推：Menu hover 用 bg `rgba(255,255,255,0.05)` —
> 推断依据：active 用 bg `#333` + bold，hover 应是 active 弱版本（no bold, lighter bg）"

### 例外（必须显式声明豁免理由）

- 用户明确说 "static only" / "no interactions"
- R7 场景 1.5 临时 PoC / demo
- 元素本身无交互（如纯展示文字 / logo / 静态 illustration）

### Acceptance criteria（build 自审）

- 任务交付前 AI 列了推断的交互态规则表？
- hover / focus / disabled 是否都补上（除豁免元素外）？
- 推断规则跟 active state 的"调整方向"逻辑一致？

### Enforcement 层级

**L1（v1 现状）**：AI 主动从 active state 反推 + 主动列推断表（per meta-rules 触发器 K，"AI 自我约束类规则"可接受 L1）。

**v2 待升 L3**：写 linter `scripts/check-interactive-states.py`，扫 CSS / Vue / React 文件检查每个 `.menu-item / .button / .radio-input` 等交互类元素是否有对应 `:hover` 规则。无则 fail。

v1 暂不升 L3 的理由：CSS linter 涉及 CSS parser + selector 类别识别，工程成本中等；先 L1 跑一段时间收集真实场景再决定。

### Why

Code 与 mockup 的核心差别就是**交互**。AI 实现 code 时只做 static fidelity = 浪费 medium 能力。从 active state 反推 hover 是 conventional pattern，AI 训练数据充足，完全能做。

### 反模式记录

- **2026-05-13 Config-T HTML v3**：实现完静态 nav 后没补 hover，用户指出"Code 应该比 mockup 多补交互层"。**纠偏**：AI 主动从 active state 反推 hover/focus，交付时声明推断规则让用户审。

### 与既有规则关系

- R8 检查 static 对齐（L3 工具客观）；R9 检查 dynamic 增强（L1 主观；v2 升 L3 通过 CSS linter）— 两规则正交，都跑
- 与 mockup-conventions 不交集（mockup 本身不实现交互）
- enforcement 层级声明遵循 [meta-rules.md 触发器 K](../meta-rules.md)

---

## R10 — Vector 元素导出策略决策（场景 1 / 3 / 4 强制；enforcement L1，v2 待升 L3）

### 触发场景

任何"从 Figma 取 vector 元素（icon / logo / illustration / 复杂 shape）"任务，在 fetch + composite 之前**必须**先判断：**单一 SVG / 多个独立 SVG / ask user**。不判断就 fetch → 可能取错粒度（如 Logo 拆成 4 个 layer 各自取，错过"作为单一单元"的 signal）。

### 决策表（4 种 case）

| Figma 层级结构 | 判断信号 | 决策 |
|---|---|---|
| **A. Component / Instance** | `node.type = COMPONENT` 或 `INSTANCE`，有 name（可能含 variants） | ✅ **单一 SVG 导出**（设计师已显式标记单元） |
| **B. Named Group / Frame**（semantic 命名） | `node.name` 含语义前缀（`Icon/X`, `Logo/Y`, `Illustration/Z`，用 `/` 作 namespace separator）+ children 紧凑 | ✅ **单一 SVG 导出**（命名 = 设计师认为是 unit） |
| **C. Unnamed Group / Frame** + 紧凑 children | name 像 `Group`, `Frame 16` 等默认名 | ⚠️ **看 children 信号**：全 vector path / bbox 紧凑重叠 → 倾向单一；children 各有独立 a11y / interaction → 倾向分开；其他 → **ask user** |
| **D. 散乱 layers**（无 common parent / 跨多 frame） | 视觉上看起来一组但结构不相干 | 🛑 **必须 ask user** + 默认分开 — AI 不能替设计师决定"这是不是一个 icon" |

### 关键 signal — Figma `/` 命名 convention

Figma 设计师行业惯例：
- `Icon/Search`, `Icon/Close`, `Logo/TVU` → `/` 作 component namespace separator → **强 signal: 单一单元**
- `Button/Primary`, `Button/Secondary` → 同上
- `Group`, `Frame 16`, `Rectangle 42` → 默认名 → 弱 signal, 需 children 信号判断

任何 `data-name` 含 `/` separator 或 PascalCase 语义名 → 默认走 case A/B。

### AI 实操判断流程

0. **必须先 run `scripts/figma-extract-css.py`** 拿数据驱动 CSS baseline + assets-manifest.json + annotation-filtered.md (no estimation allowed; 全部 from Figma metadata)
1. mcp `get_metadata` `<fileKey>` `<nodeId>` 拿 root node type + name + children tree
2. （或）mcp `get_design_context` 看 `data-name` attribute
3. 应用上面决策表
4. **Ambiguous case 必须 ask user**（per [meta-rules.md 触发器 L](../meta-rules.md)），列举疑问点：
   - "这个 Group 是 1 个 icon 还是 N 个独立元素？"
   - "命名信号不明确，你设计意图是…？"
5. 命中 case D → 默认分开 + ask 确认

### AI 应主动声明策略

任务交付前 AI 必须显式声明：

> "vector strategy: single SVG (reason: Figma node 是 Component named 'Logo/TVU' — 落 case B)"

### Acceptance criteria（build 自审）

- 任务开始前 AI 显式声明 **"vector strategy: X (reason: ...)"**？
- Ambiguous case (C/D) 是否 ask user 而非自作主张？
- 命中 case D 时是否列举疑问点等用户拍板？
- 任务开始前 AI 是否 run figma-extract-css.py + 用其 outputs 作为 baseline?

### 例外

- 用户明确说"取这 N 个 layer 各自导出"
- 任务 scope 只是 partial layer（调试 / 局部修复）

### Enforcement 层级

**L1（v1 现状）**：AI 主动应用决策树 + 显式声明 strategy。

**v2 待升 L3**：写 `scripts/figma-vector-strategy.py <fileKey> <nodeId>`：
- 调 mcp `get_metadata` 拿 node tree
- 应用决策表 → 输出 `{strategy, reason, recommended_command}`
- ambiguous → 列疑问点 + 推荐 ask user prompt

### Why

2026-05-13 R7 测试 logo case：Figma node `data-name="Logo/TVU"` 是 component-style 命名（`/` separator），本应一眼看出是单一 SVG。但 AI 当时被 `get_design_context` 返回的 4-layer img 拼合带跑（**"从下游反推决策"反模式**），没退一步看 upstream name signal → v6 approximate 重画 → v7 composite（仍是被动应对，而非主动正确判断）。

**Root cause**：AI 倾向"从下游（React code reference）反推" instead of "从 upstream（Figma node name/type）主动判断"。R10 强制 upstream 优先判断。

### 反模式记录

- **2026-05-13 v6/v7 logo case**：AI 看 `get_design_context` 4 layer img URLs → 直接 approximate 手写 SVG → 视觉错位 → v7 composite（仍是被动应对）。**纠偏**：先 `get_metadata` 看 root node name = "Logo/TVU" → case B "单一 SVG" 决策 → 然后 `figma-asset-fetch.py` 取 + composite（或 `use_figma` `exportAsync` single SVG）。
- **Root cause**：AI 从下游（get_design_context React code）反推决策，没先看 upstream signal（root node name / type）。规则化"先看 root name signal" 是 R10 核心。

### 与既有规则关系

- 与 R8 acceptance criteria "禁止 approximate 重画" 互补：R8 阻止重画；R10 帮 AI 做正确的"应该怎么取"决策
- 与 mockup-conventions M2（不许凭直觉自画组件）同根原则：upstream signal 优先
- enforcement 层级声明遵循 [meta-rules.md 触发器 K](../meta-rules.md)
- 不确定时 ask user 遵循 [meta-rules.md 触发器 L](../meta-rules.md)

---

## R12 — Designer Annotation 过滤（场景 1 / 3 / 4 强制；enforcement L3）

### 触发场景

任何 Figma → Code 任务。Figma 内常有**设计师批注** (annotation / spec markers / 高亮虚线框)，**不是 UI 元素**。AI 必须**自动过滤**，不许渲染为产品 UI。

### Annotation 识别 signals (4 维)

| Signal | 含义 |
|---|---|
| **stroke-dasharray** (虚线 stroke) | annotation 典型 (UI 元素罕用虚线 border) |
| **命名约定** | `_annotation/`, `mark/`, `annotation/`, `note/`, `spec/`, `callout`, `批注`, `标注` |
| **鲜艳对比色** (橙/红/紫, 不在 design system palette) | annotation 标记色 |
| **浮于布局外** | 不参与父容器 layout, 视觉上 "覆盖" 其他元素 |

满足 **任一信号** → 默认 annotation, 由 figma-extract-css.py 自动过滤到 annotation-filtered.md, AI 必须 review。

### AI 协议

1. Run `figma-extract-css.py` (per R8/R10 强制) → 生成 `annotation-filtered.md`
2. AI **必须** Read annotation-filtered.md
3. 若所有 filtered 节点都是真 annotation → continue (skeleton.html 已过滤)
4. 若 false positive (UI 被误判 annotation) → 用 `--no-annotation-filter` re-run OR 显式声明 "node X 是 UI, override filter"
5. **禁止** 直接渲染 annotation 节点为产品 UI

### 反模式实证 (2026-05-13)

Config-T return-video 案例: AI 把橙色虚线 "mark area" 框渲染成 UI div, 用户指出"橙虚框是 UX 交付的标注,不该出现在产品 code"。**纠偏**: R12 立此规则 + figma-extract-css.py 自动过滤。

### Enforcement 层级

**L3**: scripts/figma-extract-css.py 自动 detect + output annotation-filtered.md。AI 必须 read + confirm。
**L1 兜底**: AI 应用 4 维 signal 主观判断 (script miss 时)。

### 与既有规则关系

- 与 R8 acceptance criteria "必须 run figma-extract-css.py" 联动
- 与 R10 第 0 步联动 (必走 figma-extract-css.py)
- Meta-L: ambiguous case (信号不明) → ask user

---

## R14 — Layout Primitive Selection（code-side mirror of M31）

写 product UI 代码（JSX / Vue template / HTML）时，**必须按场景选最合适的 CSS layout 机制**，绝对定位（`position: absolute / fixed`）仅在有充足理由时使用。"先 flex/grid，找不到合适的再退到 absolute" 是默认顺序。

#### 决策树

| 场景 | 推荐 | 理由 |
|---|---|---|
| 1D 排列（按钮组 / nav / list / footer action row） | **flex** (`display: flex`) | 内容长度变化自动重排；`gap` / `justify-content` / `align-items` 处理间距和对齐 |
| 2D 网格（dashboard / form grid / card 网格） | **grid** (`display: grid`) | row / col 显式，比 nested flex 清晰 |
| 子元素的"显示 / 隐藏"是状态 | **conditional render**（`v-if` / `&&` / ternary） | 不要 `position: absolute; visibility: hidden` 留尸 DOM |
| 子元素位置随 sibling 动（流式 wrap / baseline align） | **flex-wrap + align-items** | 不手算 `top` / `left` |
| 一次性 overlay 浮层（dialog / popover / tooltip / dropdown menu） | **`position: fixed / absolute`** 允许，但必须 JSDoc / 注释注明理由 | overlay 类没 layout 语义 |
| 动画 transform 起点终点 | **`position: absolute`** 允许（动画 anchor 限定） | transform origin 需要稳定 reference |

#### 反例

| ❌ 错误做法 | 后果 |
|---|---|
| dialog 内 title / body / button row 各自 `position: absolute; top/left` | 改 title 字数 / 加按钮 / 改 padding → 全部要重算坐标 |
| sidebar field row 用 `position: absolute` + 手写 `top: 48px / 90px / 132px` | 加一行字段就要平移下面所有 `top`；改字段顺序要重排所有坐标 |
| 用 inline style 写 `style={{position: 'absolute', top: ..., left: ...}}` 不写注释 | 后续维护者无法判断是浮层还是图省事 |

#### Acceptance

- 任何 dialog / sidebar / footer / list row / toolbar → **必须 flex 或 grid**
- 任何 `position: absolute / fixed` 必须有 JSDoc / inline comment 注明 affordance（`/* overlay anchored to backdrop */` / `/* portal mount point */`）
- ESLint rule 候选（未来加）：禁止 inline style 含 `position: absolute` 而无紧邻注释

#### 实证

- 镜像 M31 实证（Touch-Screen v8 mockup M1 dialog v1 用 absolute）。代码端尚无单独实证；理论同源——dialog box / button row 的代码实现若也用 `position: absolute` 会犯同样的可维护性问题。早立规则避免重蹈。

---

## R15 — Library-First for All Product UI Elements（code-side mirror of M32；R1 + R3 扩展）

R1 规定图标必须从 library 取；R3 规定复合组件必须 mirror canonical。**R15 统一**：写任何 product UI 元素（Button / Input / Modal / Dropdown / List row / Badge / Chip / Card / Toast / Tooltip）之前，**必须**先按以下优先级查找资产，不准从零自写 JSX / Vue template。

#### 优先级

| 优先级 | 来源 | 触发动作 |
|---|---|---|
| **1** | `tvu-design-system/src/canonical/*.vue`（Vue canonical 桥层） | `ls src/canonical/` + grep by element role |
| **2** | Published `TVU UX Design System` library（MCP `get_design_context` / `search_design_system` with library key `lk-057f6ba0...`） | MCP invoke + 同义词矩阵（同 R13 词表）扩展 |
| **3（最后手段）** | 自画 + 标 `<!-- LIBRARY GAP: <reason>; candidate for tvu-design-system intake -->` + 登记 `backlog.md` BRIDGE-CODE-CANONICAL 段 | 仅 1 / 2 均 verified miss 时 |

#### 反例

| ❌ 错误做法 | 后果 |
|---|---|
| 写 `<button class="bg-green-500 px-4 py-2 rounded-full">Confirm</button>` 不查 canonical Button | canonical 的 size / state / hover token 没继承；UI 看上去像但状态不全 |
| 写 `<div class="modal-overlay"><div class="modal-content">...` 不查 canonical Modal | canonical 已有 focus trap / ESC handling / aria attrs；自写丢失 a11y |
| 用 `<select>` 原生元素当 Dropdown，跳过 canonical | 视觉 / 交互和设计系统断层 |
| 一次 `ls src/canonical/` 没看到就降级自写 | 同义词没穷尽 / library MCP 没尝试 |

#### Acceptance

- handoff doc / PR description 必含一行 "UI primitives used: \[\<canonical / library component list\>\]"
- 任何 product UI 元素（按钮 / 输入 / 弹窗 / 下拉）若 git diff 显示**新写的 JSX / Vue template 且未 import canonical 或 library** → 触发 flag
- 自画必备 escalation 痕迹：commit message 写 "`<component>` lookup miss across canonical + library; user authorized custom → flagged at backlog.md BRIDGE-CODE-CANONICAL"

#### 与 R1 / R3 关系

- **R1** = 图标专项（仅 icon）
- **R3** = 复合组件 mirror canonical（含 Button / Modal 等）
- **R15** = 统一所有 product UI 元素（含 R1 / R3 + atomic 元素：Badge / Chip / Tooltip / Toast / Divider 等）
- 三条联用，R15 是顶层 umbrella，R1 / R3 是 R15 在 icon / 复合组件 sub-domain 的细化

#### 实证

- 镜像 M32 实证（Touch-Screen v8 mockup 自画 gradient rectangle 当按钮）。代码端 2026-05-11 R1 反模式表 12 个自画图标是同源——只是当时只立了 icon 维度规则。R15 把规则范围从 icon 扩展到所有 atomic 和 compound UI 元素。

---

## R16 — Annotation vs Product UI String Boundary（code-side mirror of M33）

代码侧的"注释 / 文档"和"产品 UI 字符串"是两层，**语言策略不同，必须严格分离**。

#### 边界判定

| 元素归属 | 语言策略 |
|---|---|
| 代码注释（`//` / `/* */` / JSDoc）/ commit message / PR description / handoff doc | **双语 / 多语 / 任意**——服务于团队理解 |
| 文档（README / *.md / convention 文件）| **双语**（中英对照，便于跨语言团队）|
| 产品 UI 字符串（button label / dialog title / tooltip / placeholder / error message）| **走 i18n key / const map，单源**——禁止 inline 中英混排 / 双语字符串拼接 |

#### Why

- 双语字符串在产品 UI 里直接出现 = i18n 框架（vue-i18n / react-intl）无法处理；翻译团队 audit 不到；切换语言时一处文字两边都要改
- 即使**当前项目还没接 i18n**，也应用 `const labels = { confirm: 'Confirm', cancel: 'Cancel' }` 这种 const map 隔离，为未来 i18n 留接口
- 代码注释里写"// 这是 confirm 按钮 / This is the confirm button"完全 OK，因为注释不进 runtime / 不进翻译流

#### 反例

| ❌ 错误做法 | 后果 |
|---|---|
| `<button>Confirm 确认</button>` JSX 直写双语 | 用户看到双语并排（视觉拥挤）；i18n 接入时要重写所有 JSX |
| `alert('Channel 2 missing Receiver / 通道 2 缺少 Receiver')` | runtime 字符串硬编码双语 |
| 用 `\n` 在按钮文字里塞中文：`'Confirm\n确认'` | 按钮高度被撑；CSS / 排版失控 |
| Vue template 写 `{{ '确认' }}` 中文 hardcode | 同上 |

#### 正例

```ts
// ✅ const map（哪怕没 i18n 框架）
const T = {
  confirm: 'Confirm',
  cancel: 'Cancel',
  channelMissingReceiver: 'Channel {n} missing Receiver',
};

<button>{T.confirm}</button>

// ✅ 注释可双语（不进 runtime）
/**
 * Renders the partial-config confirm dialog.
 * 部分未配置确认弹窗 — partial 状态下点 Live all 触发。
 */
function PartialConfirmDialog() { ... }
```

#### Acceptance

- grep 产品 UI 文件（`.tsx` / `.vue` / `.html` template 部分）的字符串字面量，**不应同时包含 ASCII 字母和中文字符**
- probe: `grep -rEn '"\s*[a-zA-Z]+\s*\p{Han}+\s*"' src/components/` (or 反向) → 0 命中
- 代码注释不在此规则范围内（注释允许任何语言混排）

#### 与既有规则关系

- 与 [`AGENTS.md`](../../AGENTS.md) 硬规则联动："产品文案走 i18n / token，不 hardcode"
- 与 mockup-conventions.md M33 同源——M33 限定 mockup 内产品 UI 不混双语；R16 限定代码内产品 UI 不混双语

#### 实证

- 镜像 M33 实证（Touch-Screen v8 mockup M1 dialog v1 全双语）。代码端同源：若按 mockup v1 实现，会写 `<h3>2 channels have no Receiver  2 路通道未选择 Receiver</h3>` 这样的 hardcode 双语 JSX。R16 + R15（library 化）+ R14（layout）共同把 mockup 端 v1 三个错误模式在代码端拦下。

---

## R13 — Affordance-Category Search Discipline（code-side mirror of M35）

代码侧写 product UI 任何"基础语义元素"（方向指示 / 排序 / 关闭 / 状态 / link-share / loading 等）**之前**，必须按 affordance category 分类 + 在 `dist/icons/svg/` + published library 双层扫描视觉词汇 + 跨上下文复用判断，**思考过程外化为 3 行 mandatory trace**。

#### 触发场景

- 任何场景 1 / 3 / 4 任务里写 icon import（含 SVG inline / `<symbol>` 引用 / icon font / 自画 path）
- 任何 component 内部需要小三角 / 箭头 / chevron / sort indicator / loading spinner 等 primitive 元素

#### Why

R1 / R3 / R4 规定了 **WHERE 找**（`dist/icons/svg/` priority 1 → MCP `get_design_context` priority 2 → 自画 priority 3）。但**实操中 AI 默认搜索是 name regex**，搜功能词（`dropdown`, `link`, `chevron`）→ miss 按形状命名的资产（`Arrow/Down`, `Arrow/Sorting`, `share/open-link`）→ fall back inline SVG / Unicode 字符 / emoji。

mockup-conventions.md M35 提出的解药同样适用于代码：**强制 AI 把分类思考外化为 3 行 trace**，trace 缺失即不算合规交付。

#### Step 1 · 显式分类（必须 1 句话）

> "我要 render 的是【方向 / 操作 / 状态】类指示，affordance category = **`<category>`**，意图是 '\<purpose\>'"

| 反例 | 正例 |
|---|---|
| `<svg>...dropdown chevron...` | "我要个**向下方向指示**，affordance = `directional-vertical`，意图是 'dropdown 触发器图标'" |
| `<span>🔗</span>` | "我要个**链接 / 共享指示**，affordance = `link-share`，意图是 'mark field as shared'" |
| `<span>×</span>` | "我要个**取消 / 关闭**指示，affordance = `dismissive`，意图是 'close modal'" |

#### Step 2 · 视觉词汇扫描（双层）

**Priority 1（local sync）** — `ls dist/icons/svg/` 后按 affordance 同义词矩阵 grep：

| Affordance | 必扫关键词 | typical 命中 |
|---|---|---|
| `directional-vertical` | `up`, `down`, `chevron`, `arrow`, `triangle`, `caret`, `expand`, `collapse`, `sort` | `Arrow/Down`, `Arrow/Up`, `Arrow/Sorting`, `Arrow/Double-up`, `Arrow/Double-down` |
| `directional-horizontal` | `left`, `right`, `back`, `forward`, `previous`, `next`, `chevron`, `breadcrumb` | `Arrow/Left`, `Arrow/Right`, `Arrow/Previous`, `Arrow/Next` |
| `sort` | `sort`, `sorting`, `order`, `ascending`, `descending` | `Arrow/Sorting` |
| `dismissive` | `close`, `cancel`, `delete`, `remove`, `x`, `clear` | `Edit/Close`, `Edit/Delete` |
| `additive` | `add`, `plus`, `create`, `new` | `Edit/Add-1`, `Edit/Add-2` |
| `status-positive` | `success`, `done`, `check`, `selected`, `confirm`, `tick` | `Edit/Selected`, `Message/Success-1` |
| `status-warning` | `warning`, `alert`, `caution` | `Message/warning-1` |
| `status-negative` | `error`, `fail`, `stop`, `block` | `Message/Error-1`, `Video/Record-stop` |
| `link-share` | `link`, `chain`, `connect`, `share`, `external`, `open` | `share/open-link`, `Edit/Copy-link`（按实际库 sync） |
| `loading` | `loading`, `spinner`, `progress`, `wait` | （按实际库 sync）|

**Priority 2（library MCP fallback）** — local 全 miss 后必须 invoke `mcp__claude_ai_Figma__search_design_system(query: '<keyword>', includeLibraryKeys: ["lk-057f6ba0f771bfa7f63a6a197502999462c2974f995488b381708ca5faadb7f9f6675e04aa442b3a5ffb31732150a7f5aa8ca3102073ab210edc684022fc6c21"])`，扫每个同义词。

#### Step 3 · 跨上下文复用判定

候选集得到后**默认复用**，理由清单同 M35 Step 3：

- ✅ 不 reuse 理由：几何形状不匹配 / 渲染破图 / 颜色 token 无法 override
- ❌ 不构成理由："命名上看是 sort 用的不能借给 dropdown" / "其他文件用过这个图标"

对 svg 文件路径而言，**复用 = import 同一个 SVG path**。不需要担心 "Arrow/Sorting" 路径名让 dev 困惑——可以加 inline comment `<!-- using Arrow/Sorting (TVU library) as directional-vertical indicator -->` 标记跨上下文复用意图。

#### Step 4 · 自画 / Unicode / emoji 黑名单（自查触发词）

| 黑名单 | STOP 后回到 |
|---|---|
| 想写 `<span>▼</span>` / `▲` / `▶` / `←` / `↑` 等 Unicode | Step 1 |
| 想写 `<svg viewBox="..."><path d="M0 0..."/></svg>` 自画路径 | Step 1 |
| 想用 `🔗 ✅ ❌ ⚠️` emoji 当 product UI 图标 | Step 1（注释 / log 内允许，product UI 禁用）|
| 想写 `<i class="i-add">+</i>` 字符占位 icon | Step 1 |

只有 Step 1+2+3 verified miss **且** 用户授权 → 才能进入自画路径（同 R1 既有条款 + `<!-- LIBRARY GAP -->` 标记 + `backlog.md` BRIDGE-CODE-ICONS 登记）。

#### Acceptance

每次 affordance 类 icon import / render 之前，**commit message / PR description / handoff doc** 必含 3 行 trace：

```
1. Affordance: <category> (intent: <purpose>)
2. Vocabulary scan: [Arrow/Down (dist), Arrow/Up (dist), share/open-link (dist)]
3. Chosen: Arrow/Down — same geometry, dropdown indicator usage  (or: None match → custom + BRIDGE-CODE-ICONS log)
```

probe 命令：

```sh
git log --grep "Affordance:" -p
# 任何 code commit 引入 icon 应包含 3 行 trace
```

#### 与 R1 / R4 关系

- **R1** 规定图标必须从 library 取，禁止自画 SVG（WHERE + WHAT）
- **R4** 规定库实证是前置不是 nice-to-have（WHEN）
- **R13** 规定**搜之前必须先按 affordance 分类**（HOW 思考）+ trace 外化（VERIFICATION）
- 三条联用：R13 先分类 + 扫词 → R4 强制扫前置 → R1 决定从哪个 priority 层取

#### 实证

- **2026-05-18** Touch-Screen v8 同源案例：mockup 端 link 图标 / dropdown chevron 两次 miss 都因 search query 用功能词不用 affordance 同义词。代码端若 sync 同 mockup，写 `<svg>` 也会犯同样的错。R13 + R1 + R4 三层防线在代码端镜像 mockup 端 M35 + M30 + M32 的设防。
- **2026-05-11** MicroApps Console HTML demo 自画 12 个图标（R1 反模式表）—— 都是没按 affordance 分类 + 没扫 `dist/icons/svg/` 完整目录的直接后果。R13 形式化"分类前置"避免重蹈覆辙。

---

## 关联文档

- [`mockup-conventions.md`](./mockup-conventions.md) — 同源原则在 Figma scope 的版本
- [`AGENTS.md`](../../AGENTS.md) 硬规则 #4 / #6 — Token 和 canonical 的项目级约束
- [`docs/meta-rules.md`](../meta-rules.md) — 反模式清单（含"先实现再审"）
- [`backlog.md`](./backlog.md) — 自画图标 candidate-for-intake 登记位置

---

## AI 工具集成

本规则被 `~/.claude/skills/tvu-design-code/SKILL.md`（Claude Code 全局 skill）引用作为真源。

**规则改动权在本文件**——新增 R 号 / 修改既有规则都改这里，**不**修改 skill。skill 只做 trigger + 指路。仅当本文件新增规则改变了起手协议（如新增起手步骤）时才回头同步 skill。
