# Icon Naming Convention (BRIDGE-MOCKUP-008 prereq)

> 草案 2026-05-26 · plan owner: Claude Code · 2026-05-28 user 拍板 (Bundle 1/2/3 review 锁死核心原则)
> 数据依据：既有 SoT 35 entries 实证范式 + figma 库 28 folder 实证分布 + 615 todo entries leaf 形态 baseline scan
> 适用范围：所有 Phase B-v2 promote 进 `src/icons/manifest.ts` 的 entry 必须遵守；**后续新增 icon 一律遵此 spec，不再 case-by-case 拍板**。

---

## 0. 核心原则（2026-05-28 user 拍板，最高优先级）

任何命名决策前先过这 5 条；任一冲突 → 改 proposal，不破例。

| # | 原则 | 实证依据 |
|---|---|---|
| **R1 prefix 不变** | registry_name 的 `<category>` 段必须等于 Figma folder 对应的 cat (per §2 enum)。**不跨 cat 移动** entries 到"更语义化"的 cat（如不要把 `output/facebook` 改 `brand/facebook`）。Brand 属性靠 `svg_decision=brand-keep` 字段表达，不靠 cat 名表达 | Bundle 2 cross-cat 移动事后 revert / Bundle 3 input/output 独立 cat |
| **R2 同 leaf 不同 cat ≠ collision** | 不同 cat 下出现同 leaf 名（如 `input/facebook` + `output/facebook` + `user/facebook` + `indicator/facebook`）**不算 collision**，不需 disambiguation prefix。AI 靠 `<cat>/<leaf>` 完整 path + `description`/`synonyms_en/zh`/`when_to_use` metadata 区分 | Bundle 3: facebook 4 cat 同 leaf 共存 ✅ |
| **R3 cat==folder 时 leaf 不重复 folder** | 当 cat 名与 folder 名同（如 cat=`indicator` + folder=`indicator`），leaf 段**不再加 folder prefix**。`indicator/x` 不是 `indicator/indicator-x` | 2026-05-28 user 决策（撤 §3.5 旧"统一加 folder prefix"） |
| **R4 rename 只加连接符 (`-`)** | 重命名时 leaf 段**只加 `-` kebab 连接 + 视觉语义后缀**。不引入空格 / 下划线 / 驼峰 / 双 hyphen（负数除外，见 §3.2）| 全 batch consistent |
| **R5 数字 suffix → 视觉语义标记** | figma 中 `<base> 1/2`、`<base> 1/2/3/4/5` 等数字 leaf，按**图形特点**改语义标记：<br>· **binary outline/filled pair** (1 = outline, 2 = filled base) → `<base>-outline` / `<base>` (per §3.3)<br>· **multi-level levels** (1=最小, 5=最大，如 wifi 信号格、electric 电量) → **keep numeric** with semantic prefix (`wifi-bar-1..5`, `electric-1..3`)<br>· **shape-specific siblings** (Error 1/2 圆形 + Error 3/4 三角) → 加形状描述 (`error-outline-triangle`, `error-filled-triangle`)<br>· **layout count** (Preview 1/4/6/9/16/25 = N-up multiview) → keep numeric | Bundle 1 Help/Notification/update + Bundle 3 output/Meeting outline pair + #6 Preview numeric keep |

**反模式（命中 → 重写）**：
- ❌ 改 cat 因为"觉得更语义"（"facebook 是 brand 不是 indicator" → 拒，cat 跟 folder）
- ❌ 加 disambiguation prefix 因为"怕撞名"（"video-file 因为 file 撞 cat=file" → 拒，cross-cat 不撞）
- ❌ 把数字直接当 outline pair（"Preview 1 → preview-outline" → 拒，Preview 1 是 1-up 布局非 outline）
- ❌ Codex/AI 凭印象"语义美化"（如 wifi 1 → wifi-low），数字代表确切含义时 keep numeric

**参照 source of truth**：既有 SoT 35 entries（`status/error` / `status/error-outline` / `action/edit` / `time/clock` 等）的命名风格 = canonical reference。**新 entry 命名风格必须与这 35 个一致**。

---

## 1. registry_name 总形式

```
<category>/<leaf-kebab-case>
```

- 全 lowercase
- category 与 leaf 之间用 `/`
- leaf 多词用 kebab-case (`-` 连接)
- 不含空格 / 特殊字符 / 大写字母

---

## 2. Category enum (18 个)

> 7 个 existing 保持原样，11 个新增基于 figma folder 语义聚类
>
> **Examples 列规则**：仅列 SoT (`affordance-categories.json` `icons[]`) **实证 registry_name**；新 category 因尚无 promote entry，Examples 列写 `— (Codex Phase B-v2 propose per §3 rule)`。plan owner 不 invent 名字 — 由 Codex 按 §3 leaf rule 从 figma_name 机械推 proposal，user Phase C review 时拍板。

| Category | Folders | Examples (existing SoT 实证) | 数量估 |
|---|---|---|---|
| **action** | Edit, Menu, Search, Filter, mark, share, pay, Cut | `action/add`, `action/clear`, `action/search`, `action/copy`, `action/close`, `action/delete`, `action/edit`, `action/loading`, `action/minus`, `action/rename`, `action/save`, `action/selected` | 119 |
| **navigation** | Arrow | `navigation/arrow-down`, `navigation/arrow-dropdown`, `navigation/arrow-left`, `navigation/arrow-right`, `navigation/arrow-up`, `navigation/more`, `navigation/next`, `navigation/previous` | 16 |
| **status** | Message, load | `status/error`, `status/error-outline`, `status/info`, `status/info-outline`, `status/success`, `status/success-outline`, `status/warning`, `status/warning-outline` | 21 |
| **rating** | (existing only — no figma folder match) | `rating/star-empty`, `rating/star-filled` | 2 (existing only) |
| **brand** | logo | `brand/logo-ts`, `brand/logo-tvu` | 2 (existing only) |
| **time** | Time | `time/calendar`, `time/clock` | 13 |
| **theme** | Picture | `theme/picture-light` | 24 |
| **media** ✨ | Video, audio | — (Codex Phase B-v2 propose per §3 rule) | 74 |
| **file** ✨ | file, Preview | — (Codex Phase B-v2 propose per §3 rule) | 71 |
| **network** ✨ | network | — (Codex Phase B-v2 propose per §3 rule) | 17 |
| **input** ✨ | input | — (Bundle 3, folder == cat 特例) | 40 |
| **output** ✨ | output | — (Bundle 3, folder == cat 特例) | 79 |
| **indicator** ✨ | indicator | — (Codex Phase B-v2 propose per §3 rule) | 52 |
| **communication** ✨ | Meeting | — (Codex Phase B-v2 propose per §3 rule) | 39 |
| **user** ✨ | user, location | `user/location-1`, `user/location-filled`, `user/location-globe` (Bundle 1 promoted 5) | 34 |
| **setting** ✨ | Setting | — (Codex Phase B-v2 propose per §3 rule) | 14 |
| **feature** ✨ | Feature | `feature/demo`, `feature/insync`, `feature/restore`, `feature/ai-setting` ... (Bundle 1 promoted 11) | 11 |
| **others** ✨ | others | — (Codex Phase B-v2 propose per §3 rule) | 21 |

**Tie-break 规则**（当 folder 跨多 category 时）：

- `Picture` folder 含 `picture-light` (theme toggle 用) → `theme`；其余 figure/image 类 → `media`
- `Menu/More` 已 done `navigation/more`（不是 action/more）— 例外保留，因 v0.4.0 决策；新 entries 中 Menu folder 默认 `action`
- `output/input` 内的 network protocol → `network`；非 protocol 的输入输出（如纯 data flow icon）→ `network` 兜底

### 2.1 Folder == cat 特例（Bundle 1 2026-05-28 codify）

当 figma folder 名本身已是 affordance 概念（自描述、coherent semantic 边界），folder 名直接作 category，不再聚类到通用 affordance 名下。

**实证 (Bundle 1)**：
- `location/` → `user/*`（folder ≠ cat，因 location 太窄；user 是更广 affordance）— **不属本特例**
- `Time/` → `time/*` ✅ 特例
- `Feature/` → `feature/*` ✅ 特例（与 `Setting/` 拆分独立 cat）
- `Setting/` → `setting/*` ✅ 特例

**判别标准**：
1. folder 名本身是名词 / 短语，能 stand-alone 描述一组 affordance（如 Feature, Setting, Time）
2. folder 内 entries 语义高度同质，跨 affordance cat 边界的需要少
3. folder size ≥ 10（太小的 folder 不值得开 cat）

**反例**：
- `Edit/` → 不走 edit cat (折成 action + status)，因 Edit 是动词 + 内含跨 affordance entries (disabled / Edited 等 status)
- `Menu/` → 不走 menu cat (折成 navigation + action)，Menu 视觉聚类不等于 affordance 聚类

---

## 3. Leaf naming rules

### 3.1 多词 → kebab-case

| figma leaf | registry leaf |
|---|---|
| `align left` | `align-left` |
| `Copy link` | `copy-link` |
| `accreditation edit` | `accreditation-edit` |
| `to first page` | `to-first-page` |

规则：所有空格 → `-`，全 lower。

### 3.2 Trailing N-suffix → 保留 hyphen

| figma leaf | registry leaf | category |
|---|---|---|
| `Add 1` | `add-1` | action |
| `Clear 2` | `clear-2` | action |
| `wifi 3` | `wifi-3` | network |
| `Notification 2` | `notification-2` | status |
| `Rotation -90` | `rotation--90` | media（保留 figma 字面，负号 → double hyphen） |
| `Rotation 0` | `rotation-0` | media |
| `Rotation 90` | `rotation-90` | media |

规则：trailing `[\s-]N` → `-N`（保留全部 variant 各自独立 registry_name）。**负数 leaf 段保留 figma 字面**（user 2026-05-27 决策：保留 figma 命名，不意译 `neg`/`back`），机械 transform：空格→hyphen + 全小写 → 产生 `--N` double-hyphen 模式，audit kebab regex 须加例外（见 §7）。

### 3.3 Outline siblings (v0.4.0 范式)

figma 用 `<base>-1` 表示 outline variant：

| figma leaf | registry leaf |
|---|---|
| `Success-1` | `success-outline`（不是 `success-1`） |
| `Error-1` | `error-outline` |
| `Info-1` | `info-outline` |
| `warning-1` | `warning-outline` |
| `update-1` | `update-outline` |

**例外规则**：仅当 figma 已有 base entry（如 `Success` filled）且 `-1` 明显是 outline glyph 时套用。其他 numeric suffix（如 `wifi 1-5` signal level）按 3.2 保留 `-N`。

**判定方式**：**plan owner manual gate**，**不靠机械判定**。

- 反例（自查发现）：v0.4.0 已 promote 的 4 个 outline SVG（`Success-1` / `Error-1` / `Info-1` / `warning-1`）实际**含 hex fill**（`#2fb54e` / `#ea4233` 等），不是 "fill 仅 currentColor 或 only stroke"。任何机械 fill/stroke 判定标准都会误判这 4 个 v0.4.0 案例。
- 实际做法：Phase B-v2 prompt 中要求 Codex **列出疑似 outline pairs**（命名匹配 `<base>` 和 `<base>-1`）给 plan owner review，由 plan owner / user 视觉比较两个 SVG 拍板是否套 `-outline` suffix；Codex 不直接套。

### 3.4 Domain-specific 缩写保留

protocol/service 名缩写不展开：

| figma leaf | registry leaf |
|---|---|
| `RTP-FEC` | `rtp-fec` |
| `rtmp-push` | `rtmp-push` |
| `HDS` | `hds` |
| `1vN VoIP 1` | `1vn-voip-1`（数字开头允许） |
| `NDI` | `ndi` |

### 3.5 非英文 / 特殊字符 / 无语义 leaf

scan 已实证 615 todo **0 个 non-ASCII**。

特殊字符与无语义 leaf 处理（2026-05-27 user 决策更新）：
- **双 slash `//`** → 走 cleanup（figma 命名 bug，需设计师 figma 内 rename） — 实证 1 entry `icon/Meeting//lazy audio` designer 已 fix
- **单字符 `x` / 纯数字 leaf**（如 `location/1-3`、`Preview/1/4/6/9/16/25`、`indicator/x`、`input/x`）→ **机械 fallback 加 folder prefix**：registry = `<category>/<folder-lower>-<original-leaf>`（不进 manual gate）

**机械 fallback 范例**（2026-05-28 user 拍板 — R3 cat==folder 时不重复 folder）：

| figma_name | category (per §2) | folder | registry leaf | proposed_registry_name | R3 例外? |
|---|---|---|---|---|---|
| `icon/Preview/1` | file | Preview | `preview-1` | `file/preview-1` | — (cat≠folder) |
| `icon/Preview/16` | file | Preview | `preview-16` | `file/preview-16` | — |
| `icon/location/1` | user | location | `location-outline` (Bundle 1 review) | `user/location-outline` | — (cat≠folder) |
| `icon/indicator/x` | indicator | indicator | `x` | `indicator/x` | ✅ R3 (cat==folder, leaf 不加 folder prefix) |
| `icon/input/x` | input | input | `x` | `input/x` | ✅ R3 |
| `icon/others/box` | others | others | `box` | `others/box` | ✅ R3 |

**规则**（per R3 + R2）：
- **cat == folder** → leaf 段直接 = original leaf (lowercase kebab)，不加 folder prefix
- **cat ≠ folder** (如 Preview folder → cat=file) → leaf 段加 folder-lower prefix 提供 context: `<folder-lower>-<original-leaf>`
- 同 leaf 出现在多 cat 不算 collision（per R2），不需 disambiguation 改 cat 或加 prefix

---

## 4. Collision tie-break

### 4.1 Cross done-todo same base

| 情况 | 处理 |
|---|---|
| done `Add 1` (registry `action/add`) + todo `Add 1/2/3` | done 已占用 `action/add`；todo 改为 `action/add-1`（新 nodeId）、`action/add-2`、`action/add-3`。**注意** done 的 figma_name 是 `Add 1` 但 registry 是 `action/add`（去 suffix），todo 同 figma_name 但不同 nodeId 应 fall back 到 `action/add-1` 而不是抢占 `action/add`。<br><br>**Promote 顺序规则**：当多个 entry 共享同 figma leaf 但不同 nodeId 时，**figma nodeId 数值升序优先**（小 id 通常更早创建，更可能是 canonical）。已 done 的不动；未 done 中 nodeId 最小的占 `<category>/<base>`（去 suffix），其余按 §3.2 加 `-N`。 |
| case-only collision (`Left` vs `left`) | 走 cleanup（见 [`icon-cleanup-candidates.md`](icon-cleanup-candidates.md) Section C）— 不进 promote |

### 4.2 同 category 内 leaf 撞名（不同 folder）

罕见。**规则**：

- 先 promote 的 occupy short name `<category>/<leaf>`
- 后续同 leaf 不同 folder fall back 到 `<category>/<folder-hint>-<leaf>` form
- 判定优先级：figma folder name 与 category name 字面 match 越高的拿 short name

**具体 case 不在此 spec 内列举** — 由 Phase B-v2 Codex 实证发现 + plan owner 拍板，避免 plan owner 凭印象 invent 假设性 example 污染规范。

### 4.4 跨 category 同 leaf — 非 collision（per R2 2026-05-28）

**重要**：同 leaf 在不同 cat 下出现 **不算 collision，不需任何 disambiguation**。

实证（Bundle 3）：
- `input/facebook` + `output/facebook` + `user/facebook` + `indicator/facebook` — 4 cat 共存同 leaf ✅
- `input/twitch` + `output/twitch` + `user/twitch` + `indicator/twitch` — 4 cat 共存 ✅
- `input/youtube` + `output/youtube` + `user/youtube` + `indicator/youtube` — 4 cat 共存 ✅

**Why**：`<cat>/<leaf>` 完整 path 已是 globally unique identifier；同 leaf 在多 cat 反映 figma 真源结构（同品牌 logo 出现在 input/output/user signin/indicator status 多用途），是设计 intent 而非 bug。

**AI 检索**：靠 `description` / `synonyms_en` / `synonyms_zh` / `when_to_use` metadata 区分用途（user 2026-05-28 决策）；不靠 cat 名 / leaf prefix 区分。Phase C merge 时确保各 cat entry 的 metadata 描述准确反映该 cat 上下文。

### 4.3 Manual semantic disambiguation gate (2026-05-27 决策)

**触发条件**（任一 → 进 `_meta.skipped.manual_disambiguation_pending[]`，不 promote）：

**类 A — 撞名歧义**（folder + leaf case-insensitive equal ≥ 2 entries，含 done + todo 混合 case 也算）：

- 实证 cluster：
  - `Arrow/left` vs `Arrow/Left`、`Arrow/right` vs `Arrow/Right`（case-only intentional）
  - sync 前的 `input/NDI × 2`、`output/rtmp 1 × 2`、`output/rtmp 2 × 2`（sync 后 dup 已删/rename，cluster 消失）

**类 B** ~~无语义 leaf~~ **已移除**（2026-05-27 user 决策更新 — 改走 §3.5 机械 fallback `<category>/<folder-lower>-<original-leaf>`，不走 manual gate）

**规则**：

- **不机械套 `-2` / `-alt` suffix，也不删，也不强制 figma rename** — Codex 检测到时进 skip 数组
- 由 plan owner / user 在 Phase C review 时打开 figma 视觉确认 + Codex propose 描述参考，给具体语义 disambiguation 名（如 `navigation/left-canonical` vs `navigation/left-alt`、`user/location-pin-default` 等）
- `proposed_registry_name = null`、`registry_name_status = "manual-disambiguation-pending"`、`disambiguation_class = "A" | "B"`

判定与 §3.3 outline manual gate 同范式 — figma 真源歧义时由 user 视觉拍板，不让 Codex invent。

**Codex 在 manual_disambiguation_pending[] 每条 cluster/entry 必填**：

- 各 entry 的 SVG path count + viewBox + stroke 数 + fill region 数（基于 SVG content）
- **`disambiguation_description_candidate`** — Codex 基于 SVG 内容差异 propose 一句话区分性描述（类 A 比较 cluster 内多个 entry 差异；类 B 用单 entry 描述其视觉特征）
- 推荐 `registry_name` 候选 ≥ 2 个（user 拍板时挑或自填）

---

## 5. SVG color 处理（与 [`icon-svg-color-decisions.json`](icon-svg-color-decisions.json) 配对）

进 manifest 前每个 SVG 按 `decision` 字段处理：

| decision | 处理 |
|---|---|
| `semantic-convert` | hex → `currentColor`（CSS 控制颜色，consumer 用 `<TvuIcon style="color: var(--success-fg)">`） |
| `brand-keep` | 保留 hex（service brand / 3rd-party logo / payment provider 视觉一致性） |
| `strip` | 不 promote，走 cleanup 删 figma source |

实证分布（n=137）：
- 84 `brand-keep` (51 likely-service + 33 service brand)
- 33 `semantic-convert` (29 token + 4 outline)
- 18 `brand-keep` (3rd-party 自动归类 — payment/SaaS logo)
- 2 `brand-keep` (TVU logo)
- 0 `strip` (Research page 不在 dist；strip 是预留状态)

---

## 6. Manifest entry 生成范式（v0.4.0 范式扩展）

每 promote entry 触发 5 文件改动：

```ts
// 1. src/icons/raw.ts — 加 SVG raw export
export const ICON_NETWORK_WIFI_1 = `<svg ...>...</svg>`

// 2. src/icons/generated/network.ts — 加 entry
export const network = {
  'wifi-1': { svg: ICON_NETWORK_WIFI_1, tags: ['wifi','signal','1','level-1'] },
  // ...
}

// 3. src/icons/index.ts — re-export
export { network } from './generated/network'

// 4. src/icons/manifest.ts — 加 registry entry
{ name: 'network/wifi-1', category: 'network', svg: ICON_NETWORK_WIFI_1, /*...*/ }

// 5. docs/internal/affordance-categories.json — registry_name 字段从 null 升 'network/wifi-1'
```

新 category（8 个 ✨）需先建 `src/icons/generated/<category>.ts` 文件 + `src/icons/index.ts` 加 re-export。建议 Phase B-v2 startup script 先批量生成 8 个空 category file 避免每批重复 boilerplate。

---

## 7. Audit guards (Phase B-v2 必过)

| Guard | 规则 |
|---|---|
| `audit:icon-fill-currentcolor` | `semantic-convert` SVG 100% currentColor；`brand-keep` SVG 跳过 |
| `audit:component-no-inline-svg` | 已有，沿用 |
| `audit:design-system` | manifest entry 全有 registry_name + category ∈ 14 enum |
| 新增：`audit:icon-category-enum` | manifest 内 registry_name 前缀 ∈ 15 category enum 白名单 |
| 新增：`audit:icon-leaf-kebab` | leaf 部分 主 regex `^[a-z0-9]+(-[a-z0-9]+)*$`；**negative-number 例外**：允许 `--\d+` 出现在 leaf 末尾（用于 `rotation--90` 这类保留 figma 负号字面的 case）；扩展 regex `^[a-z0-9]+(-[a-z0-9]+|--\d+)*$` |

---

## 8. 决策记录 (2026-05-27 finalized)

| # | 决策点 | 用户拍板 |
|---|---|---|
| 1 | 15 category enum (7 existing + 8 new + others) | ✅ 接受 |
| 2 | rotation 负号策略 | ✅ 保留 figma 字面 `rotation--90`（不意译 `neg`/`back`/`minus`）+ audit kebab regex 加 `--\d+` 例外 |
| 3 | outline 范式判定 (manual gate vs 机械化) | ✅ 接受 plan owner manual gate（Codex 列疑似 pair，user 拍板） |

规范 finalized，进入 Phase B-v2 prompt 写作。
