<!--
Responsibilities:
- Record explicit non-mappings between Figma and code.
- Explain why each non-mapping is intentional, temporary, or pending repair.
- Give audits a single place to distinguish documented divergences from bugs.
-->

<!--
Split 2026-05-14 — Tier 1-A Sprint 3 (commit TBD)
26 决议 entries 已迁到 divergences-decisions.json (+ schema)；本 md narrative 大幅保留。
活跃 SoT：
- 结构化决议 → src/design-system/translation/divergences-decisions.json
- 完整 rationale / 实证 / 历史 → 本 md
-->

# Divergences

## Figma SVG export pipeline limitation — Icon Variable resolved-to-hex

**问题**：Figma 的 SVG image export API 把 Color Variable 引用**解析为 literal hex**后再返回 SVG 字符串——**丢失了 Variable 绑定语义**。

**实证**：
- 设计师在 figma 端把 icon path fill 绑定 `Color Type/Icon/Default` Variable
- `Color Type/Icon/Default` Variable alias 到 `UX/Grey/grey-6 #9E9E9E`（dark mode）/ `UX/Grey/grey-8 #595959`（light mode）
- `pnpm sync:icons` 调 figma image API 拿到 SVG → 收到的字符串里 fill 是某个 literal hex（具体是哪个 hex，取决于 figma 当时 export pipeline 实现 / placeholder 选择）
- 经实证，TVU figma library 中**单色非品牌图标的 path fill literal = `#dbdbdb`**（实际不是 `Color Type/Icon/Default` 的解析值 `#9e9e9e`，而是 figma palette 的 `UX/Grey/grey-4 #DBDBDB`——designer 把 grey-4 用作"应该是 Icon/Default"的占位语义；理由不可考但实证如此）

**Code 端治本** ([`figma-sync/export-icons.mjs`](../../../figma-sync/export-icons.mjs) `transformSvgCurrentColor`)：
- 拉到 SVG 后扫所有 `<path fill="#xxx"/>`
- 任何 path fill === `#dbdbdb` 改成 `fill="currentColor"`（per-path 转换，不分 mono/multi）
- 其它 hex 值（语义色 / app-brand / 第三方 logo 色）保留不动——它们封装在 catalog SVG 内部，外部组件通过 `<Icon name="..." />` 引用，颜色不会泄漏

**消费方 fallback**：
- Icon 组件本身（`src/components/Icon/Icon.vue`）`.icon` 元素**不强制设 color** — 避免破坏现有消费者 cascade-based color override pattern (`InputNumber:disabled` / `PromptMessage status` 等通过父级 `color: var(--xxx)` 让 icon 继承)
- 消费组件容器自己设 `color: var(--icon-default)` 实现"icon 默认 = `--icon-default`"语义（实证：`PromptMessage.__close` / `InputNumber.__btn` / `Notification` 默认状态等）
- 这是消费者 responsibility，不是 Icon 组件 responsibility

**Audit 回归保护**（[`figma-sync/audit-icon-fill-currentcolor.mjs`](../../../figma-sync/audit-icon-fill-currentcolor.mjs)）：
- 只 ERROR `path fill === "#dbdbdb"`
- 当前 finding = 0；非 0 = 真 bug（transform 漏了 / 新 icon 引入未走 pipeline）

**Why 不视为 figma↔code 漂移**：
- figma 端：设计师用 Variable，意图正确
- code 端：transform 把 export pipeline 限制还原为 currentColor 语义
- 两端语义一致，只是 figma 的 export 把 Variable 解析掉了——不是 designer 写错，也不是 code 自创规则

**未来 figma 端重新绑定 Variable 后**（设计师 review 把 icon path fill 从 #dbdbdb 改成绑定 `Color Type/Icon/Default`），仍然走相同 transform 链路（Variable 解析为 #9e9e9e 后会被 `transformSvgCurrentColor` 转 currentColor，前提：扩展 NEUTRAL_HEX_SET 包括 `#9e9e9e` / `#595959`，或 transform 改为更智能 figma metadata 检测）。本次实现保守只覆盖现状 `#dbdbdb`。

**决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: icon-variable-resolved-to-hex`

---

## 组件级映射（Component-level Translation）

### DateTime ↔ Select.feature=date|time

- 设计侧契约：Figma Select component 通过 feature 属性容纳 default / normal / multiselect / time-default / time-normal / date-default / date-normal 等 7+ 个变体
- 代码侧契约：按功能拆分为：
  · Select（覆盖 feature=default | normal | multiselect）
  · DateTime（覆盖 feature=time-default | time-normal | date-default | date-normal）
- 拆分理由：网站文档按用户功能组织（选择 vs 时间日期输入），Vue 生态约定也分别处理
- 设计源：Figma 不修改，保留单一 Select 组件
- 出处声明：DateTime.vue 顶部注释 + docs 页 intro 中明确 "Source: Figma Select component, feature=date | time variants"
- 后续审计：DateTime 应对照 Select.feature=date|time 子集审，不报"Figma 缺失"
- 当前仓库状态：docs 页 intro 已写明来源；`src/components/DateTime/DateTime.vue` 当前不存在，因此文件头注释待组件文件落位后补充
- 代码侧暂无独立组件文件
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: datetime-select-feature-topology`

### Input ↔ input box/filled + input box/line

- 设计契约：Figma 用 line / filled 两个 family 表达视觉风格
- 代码契约：单一 Input 组件，通过 prop（如 variant / appearance）切换两种风格
- 拆分理由：开发侧统一 import，避免 InputFilled / InputLine 两个组件
- 后续审计：Input audit 应对照 (input box/filled ∪ input box/line)，不视为 Figma 缺失
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: input-dual-family-single-component`

### Select ↔ select box/filled + select box/line

- 设计契约：同 Input 的 line / filled 双家族
- 代码契约：单一 Select 组件
- 后续审计：Select audit 对照 (select box/filled ∪ select box/line)，且 **不含 feature=date | time**（已拆分到 DateTime 的展示页）
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: select-dual-family-single-component`

### Breadcrumb ↔ Breadcrumb/Item（容器聚合）

- 设计契约：Figma 公开 Item，未公开容器
- 代码契约：Breadcrumb 是容器组件承担布局；BreadcrumbItem 对应 Figma Item
- 后续审计：Breadcrumb 容器组件允许“代码侧多容器、Figma 缺失”；BreadcrumbItem 对照 Figma 主体
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: breadcrumb-item-container-topology`

### Steps ↔ Step/Item（容器聚合）

- 设计契约：Figma 公开 Item，未公开容器
- 代码契约：Steps 容器 + StepItem 对应 Figma Item
- 后续审计：同 Breadcrumb 模式
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: steps-item-container-topology`

### Button ↔ 8 个 Button/{theme} {size} component sets

- 设计契约：Figma 用 8 个独立 set 拆分主题/尺寸（如 Button/dark L、Button/light M 等）
- 代码契约：单一 Button 组件，通过 props（theme / size / type）覆盖全部
- 拆分理由：开发侧避免 8 个不同 import
- 后续审计：Button audit 对照 8 个 set 的属性并集
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: button-eight-sets-single-component`

### Icon ↔ 大量 icon/* component sets（资产聚合）

- 设计契约：Figma 发布大量 icon/{category}/{name} component sets
- 代码契约：Icon.vue 是运行时 registry 入口，通过 name prop 加载具体图标
- 拆分理由：动态资产层，不对应单一组件
- 后续审计：Icon 不走标准 props/variants 流程；改走 icon-aliases.ts 完整性核对（每个 alias 都能 resolve 到存在的 Figma 图标）
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: icon-registry-asset-aggregation`

### Logo ↔ icon/logo/TVU + icon/logo/ts

- 设计契约：Figma 在 icon 资产中维护两个 logo set
- 代码契约：Logo 通过 type prop（tvu | ts）切换
- 后续审计：Logo audit 对照 (icon/logo/TVU ∪ icon/logo/ts)
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: logo-icon-assets-aggregation`

### Alert ↔ ❓（待设计师确认）

- 状态：仓库内未找到对应 Figma component set
- 待办：联系设计师确认 Alert 在 Figma 中的位置：
  · 选项 a：Alert = Notification 的某个 variant（重命名）
  · 选项 b：Alert = 独立 component set，但未发布
  · 选项 c：Alert = 代码侧自创组件（应删除或重命名）
- 在确认前 Alert 不进入任何 audit 范围
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: alert-figma-source-unconfirmed`

## Code 端双源（AI 工具消费指引）

> 本节不登记 Figma↔Code 错位，登记**code 端两份实现并存**的情况。AI 工具（Claude / Codex / Cursor / Cline）需要明确以哪份为准，避免落到错文件。

### Badge 双源 — `canonical/Badge.vue` 是 SoT，`components/Badge/Badge.vue` 是 legacy

- **真源**：[`src/canonical/Badge.vue`](../../canonical/Badge.vue) —— `src/index.ts:8` 导出，npm 包消费这份
  - API：`color: 'Black' | 'Blue' | 'Green' | 'Orange' | 'Red'` / `tag: 'Filled' | 'Line'` / `type: 'Circle' | 'Rectangle'`
  - 用大写枚举值对齐 Figma axis name
- **legacy**：[`src/components/Badge/Badge.vue`](../../components/Badge/Badge.vue) —— 不进 npm 导出，仅为 `playground/App.vue` + `src/legacy/index.ts` + `playground/docs/pages/atomicAssets.ts` 保留
  - API：`type: 'brand' | 'red' | 'orange' | 'blue'` / `size: 'm' | 's'` / `dot: boolean` / `count: number` / `max: number`
  - 跟 Figma Badge axes 完全错位：legacy 没有 `tag` / `Circle/Rectangle` 概念，多出 `count/dot/max` 等运行时字段
- **为什么并存**：canonical 是完整 rewrite（不 wrap legacy 因为 Figma↔legacy API 错位太大无法 wrap），legacy 仍被 playground 等本地消费场使用，故未删
- **AI 工具消费规则**（重要）：
  - 拿 Figma URL → 生成 Vue 代码：**必须**用 canonical（grep `src/index.ts` 顺导出链）
  - Read tool 默认按路径 grep 时容易落到 legacy（因 `src/components/<X>/<X>.vue` 是更"标准"路径）→ 已在 legacy Badge.vue 文件头加显式 LEGACY 警告注释引导
- **后续审计**：Badge audit 对照 canonical/Badge.vue + Figma Badge component set；legacy Badge.vue 不进 audit 范围
- **后续清理路径**（M1 后再考虑，不在当前 v2 路线图）：
  - 选项 a：迁 playground/App.vue 到 canonical Badge（接受 dot/count/max 功能丢失或移到 canonical）
  - 选项 b：保留 legacy 作 playground 测试场，永久双源
- **来源**：T4-spike Badge vertical slice 跑 Claude 测试 1 时发现 — Claude 落 legacy，Codex 落 canonical，输出不一致；2026-04-30 登记本条
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: badge-canonical-legacy-dual-source`

### 其它 16 组件不算双源

`src/canonical/<X>.vue` 内部 `import Base<X> from '../components/<X>/<X>.vue'` 形成 wrapper 层（Figma-aligned 桥层 wrap legacy 实现层），是健康架构，**不动 legacy**。涉及组件：Breadcrumb / CheckBox / FormItem / InputNumber / Notification / Pagination / Progress / PromptMessage / Radio / Rating / Slider / Steps / Switch / Tab / Table / Tooltip / TopBar。

## 设计态属性批量不映射

### Figma `dark theme` 属性（Input/CheckBox/Radio/Switch/Select/Tooltip）

- Property type: `VARIANT` (`on/off`)
- Reason: 设计态主题预览开关；运行时主题由全局 ThemeProvider / CSS 变量处理
- Status: accepted documented divergence
- 例外：`Notification.theme` 和 `PromptMessage.theme` 是单实例运行时切换（toast 的 dark/light 不依赖全局），已映射为代码 prop
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: figma-dark-theme-design-state`

### Figma `enable` 属性（Input/CheckBox/Radio/Switch/Select）

- Property type: `VARIANT`（`on/off` 或 `yes/no`）
- Reason: 设计态预览开关；运行时禁用由代码 `disabled` prop 处理
- Status: accepted documented divergence
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: figma-enable-design-state`

### Figma `status` 属性的设计态值

- 出现：`Input` / `CheckBox` / `Radio` / `Select`
- 不映射的值：`default` / `normal` / `hover` / `click` / `error` / `Filled` 等纯视觉状态预览
- 已映射为运行时的值（详见 `docs/internal/runtime-additions.md`）：`Switch.status=live`、`Select.status=multi select`
- Reason: 多数 status 值由 CSS pseudo-classes + 运行时 `error` / `disabled` prop 处理
- Status: accepted documented divergence (with exceptions)
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: figma-status-design-state-values`

### Figma `UX` 属性的设计态值

- 出现：`Input` / `Select`
- 不映射的值：`click` / `default` / `hover` / `error` 等纯视觉状态预览
- 已映射为运行时的值（详见 `docs/internal/runtime-additions.md`）：`Select.UX=editable`
- Status: accepted documented divergence (with exceptions)
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: figma-ux-design-state-values`

### Figma `Show Icon` / `Show Option` 属性（CheckBox/Radio）

- Property type: `BOOLEAN`
- Reason: 设计态装饰开关；运行时由 Vue slot 处理图标和文案的显隐
- Status: accepted documented divergence
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: figma-show-icon-option-design-state`

### Figma `Content` SLOT（Input/FormItem/Notification/PromptMessage）

- Property type: `SLOT`
- Reason: Figma SLOT ≠ Vue slot API；Vue 默认 slot 已承担同等职责
- Status: accepted documented divergence
- 注：`FormItem.Label` 和 `Tooltip.Content` 是双形态决策，单独登记于 `## API 双形态映射`
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: figma-content-slot-vue-slot`

### Figma `Icon` SLOT（CheckBox/Radio）

- Property type: `SLOT`
- Reason: 同 Content SLOT 决策
- Status: accepted documented divergence
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: figma-icon-slot-vue-slot`

### Figma Progress.theme 属性（伪主题，特殊登记）

- Property type: `VARIANT` (`dark/light`)
- 现状：figma 真源 dark/light variant 视觉完全相同（fills 同色 `#cccccc`）；code 之前在 `.progress--light` selector 自创视觉差，违反双层导入精神
- 修复（T1b 2026-04-29）：删除 `src/components/Progress/Progress.vue` 中 `.progress--light` selector + 删除 `:class` 中 `progress--${theme}` 部分；视觉切换由站点级 ThemeProvider + CSS 变量（`--bg-layer3` 等）自动驱动
- `Progress.theme` prop 保留：runtime + canonical 两层 API 不动（避免破坏调用方），prop 变成 vestigial（无视觉差异作用）；待 Phase 6.4 评估是否 deprecate
- Status: accepted documented divergence（按 figma 真源对齐，code 不自创视觉差）
- 区别于 line 81 的 Class B 5 组件：Class B 是"figma 设计态预览开关，runtime 不暴露 prop"；Progress 是"figma 真伪主题，runtime + canonical 都暴露 prop 但视觉无差异"
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: progress-pseudo-theme-accepted-divergence`

## API 双形态映射

### FormItem.label ↔ Figma Label SLOT

- Figma form: `SLOT`
- Code form: 双形态（`string` prop 或 named `#label` slot）
- 实现规则：
  - 简单文本 → 用 prop（如 `label="Form Label"`）
  - 复杂内容（含图标、富文本）→ 用 `#label` 命名 slot
  - 同时存在时命名 slot 优先
  - **default slot 保持原语义**（content/control 区域），不冲突
- Status: ✅ implemented in Phase 6.6a (2026-05-14); Tooltip part Phase 6.6b pending
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: formitem-label-dual-form`

### Tooltip.content ↔ Figma Content SLOT

- Figma form: `SLOT`
- Code form: 双形态（`string` prop 或 named `#content` slot）
- 实现规则：
  - 简单文本 → 用 prop（如 `content="Description"`）
  - 复杂内容（含图标、富文本）→ 用 `#content` 命名 slot
  - 同时存在时命名 slot 优先
  - **default slot 保持原语义**（trigger 元素），不冲突
- Status: ✅ implemented in Phase 6.6b (2026-05-14); 配套 Phase 6.6a FormItem.label 已 ship (commit 529bb366)
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: tooltip-content-dual-form`

### Badge API 拆分（type 同名异义）

- **历史问题**：legacy `src/components/Badge/Badge.vue` `type=brand/red/orange/blue`（颜色语义）vs Figma `Type=Circle/Rectangle`（形状语义）—— 同名异义。
- **决策（已实施）**：canonical Badge 拆分为三轴对齐 Figma：
  - `color`：颜色语义（`Neutral | Blue | Green | Orange | Red`），对齐 Figma Color
  - `tag`：填充样式（`Filled | Line`），对齐 Figma Tag
  - `type`：形状语义（`Circle | Rectangle`），**对齐 Figma Type axis 命名**（项目翻译层最小化原则——避免在翻译层登记额外 `type ↔ Type` 命名 alias）
- **同名异义隔离**：[`badge-canonical-legacy-dual-source`](./divergences-decisions.json) decision 锁定 canonical 是 npm SoT；legacy 仅留 playground/App.vue + `src/legacy/index.ts` + atomicAssets demo，**不导出 npm**——consumer 看不到 legacy.type=颜色，AI 从 Figma URL → canonical.type 永远是形状语义，0 歧义。
- Status: ✅ implemented (canonical Badge.vue 自 checkpoint `9e3ca1ae` 起即为新 API；Phase 6.7 ack 2026-05-14 — SoT drift artifact 在 pickup 2026-05-14 被发现并修正)
- **早期 decision 措辞 "split into color + shape"**：`shape` 是形状语义描述，**不是 prop name 规定**；canonical 用 `type` 保持与 Figma "Type" axis 命名一致
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: badge-api-split`

### Button 双 API 完成迁移决策

- 现状：`variant` / `size` / `disabled` / `loading` 旧 API 与 `canonicalColor` / `canonicalStyle` / `canonicalRadius` / `canonicalFixedWidth` / `canonicalStatus` / `canonicalIcon` / `canonicalSize` 双轨并存
- 决策：完成迁移到 canonical contract
- 步骤：
  1. 补全 canonical API：新增 `canonicalTheme: 'dark' | 'light'`（覆盖 Figma 8 个 set 的 theme 维度）
  2. 文档主推 canonical API
  3. 旧 API（`variant` / `size` / `disabled` / `loading`）标 `@deprecated`，保留一个版本
  4. 下版本删除旧 API
- Status: ✅ resolved in Phase 6.8 (2026-05-18) — canonicalTheme axis added; legacy API @deprecated since v0.5.0, removed in v0.6.0
- TODO: 实现待 Phase 6.8
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: button-canonical-api-migration`

## PromptMessage.interact

- Figma property: `interact`
- Code mapping: not mapped
- Reason: design-time preview switch only; runtime interaction is handled by CSS `:hover` / `:active` and event handlers.
- Status: accepted documented divergence
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: promptmessage-interact-design-state`

## Notification.success

- Figma property/value: no `success` status exists in the published Notification component set
- Code mapping: historical runtime-only status
- Reason: code invented a status outside the Figma family
- Status: ✅ resolved (2026-04-28, Phase 6.3)
- Resolution: code 中已无 `success` runtime status；本轮删除残留 dead CSS `.notif--success`
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: notification-success-deletion`

## Notification.type

- Figma property/value: no legacy `type` axis exists in the published Notification component set
- Code mapping: historical legacy compatibility axis
- Reason: code invented a second semantic axis outside the Figma family
- Status: ✅ resolved (2026-04-28, Phase 6.3)
- Resolution: 历史 legacy `type` 轴已不存在于 runtime 或 canonical Notification 实现；grep 验证无残留
- **决议详情**：[`divergences-decisions.json`](./divergences-decisions.json) `id: notification-type-deletion`
