# 设计系统工作原则

> **🤖 AI 读取指引（L-ref，按需加载 — STATUS §起手必读链路）**：每 session 最小集 = 扫一遍原则 0-8 的标题；实现决策（命名 / token / API 形态 / 图标 alias / 跨 theme 处置）命中哪条 → 全读那条 + 末尾「决策 Log」相关项。原则 0（边界翻译层）与 FIGMA_AS_SOURCE_OF_TRUTH 同级常驻，违反即 bug。

## 原则 0：边界翻译层（最高原则）
- Figma 侧命名服务设计师可读性（保持 Figma 原名）
- Code 侧命名服务开发可读性（遵循 Vue 生态约定）
- 任何两侧不一致必须在 src/design-system/translation/ 下显式登记
- 无登记的不一致 = bug
- 设计态属性（仅设计预览）不映射代码，在 divergences.md 说明
- 组件粒度的拆分/合并（一对多、多对一）登记在 divergences.md 的“组件级映射”节
- 常见组件级映射模式：仅命名差异（登记于 prop-aliases.md）/ 双家族风格聚合 / 容器+Item 拆分 / 多 set 聚合 / 资产 registry 聚合（后四种登记于 divergences.md “组件级映射”节）

## 原则 1：API 形态判定
- 给设计师看的（hover 预览、装饰开关） = 设计态，不映射
- 给开发用的（运行时行为/外观） = 运行时，映射
- 不确定时：单值或 Yes/No 大概率设计态；多值且每值有业务语义大概率运行时

## 原则 2：命名优先级
- Vue 生态通行命名（closable / disabled / size）优先于 Figma 命名
- 差异必须在 prop-aliases.md 登记

## 原则 3：图标 alias 层
- src/icons/ 用 Figma 原名
- 组件用 alias
- 映射在 icon-aliases.ts 维护
- alias 指向错的 Figma 图标（glyph family 错）= bug

## 原则 4：尺寸与颜色 token
- 颜色硬编码 = bug，必须用 token
- Figma 是内容驱动尺寸时代码必须 fit-content / max-content
- "代码偏好简单实现"导致的不一致 = bug

### 4.1 填充上的内容色 = `Text/Primary Button`（两主题一致，不随主题反相）

> **规则（设计 + Code 通用，所有未来 TVU 设计系统设计都须遵守）**：任何**文字或图标坐落在 brand / primary / status 等彩色填充之上**时（按钮文字、Step completed 勾、filled badge/tag 文字、彩底 chip 文字等），用 **`Color Type/Text/Primary Button`**（code `--text-primary-btn`）——它在 **dark 与 light 两主题下值一致**（近白 `#F8F8F8`），永远是彩底白字。

> **反例（必须避免）**：用 `Color Type/Text/Heading & Button`（`--text-heading`：dark `#ffffff` / light `#000000`）给填充上的内容上色。`--text-heading` **随主题反相**——它是为「页面/中性底上的标题与描边」设计的（深底白字、浅底黑字才可读），但用在**彩色填充**上会导致 light 主题变成「绿底黑字 / 黑勾」。

| 场景 | 正确 token | 理由 |
|---|---|---|
| 按钮文字（filling green/red/orange）| `--text-primary-btn` | 彩底，两主题白字。Button.vue 已遵守 |
| Step **completed** marker 勾 / 数字（绿圆填充）| `--text-primary-btn` | 彩底白勾。Figma `icon/Edit/Selected` 绑 `Color Type/Text/Primary Button`（4943:7248）|
| Step **active** marker 描边+数字（透明圆、在页面底上）| `--text-heading` | **非填充**——是页面底上的描边，需随主题反相才有对比 |
| 页面/卡片标题、topbar 标题（中性底）| `--text-heading` | 同上，中性底上的标题随主题反相 |

> **自检**：给某元素上色前问「它的背景是彩色填充，还是页面/中性底？」——彩色填充 → `--text-primary-btn`；页面/中性底 → `--text-heading`（或对应 text 语义 token）。
>
> **背景**：2026-06-09 owner 发现 Step completed 勾在 light 主题变黑勾（绿底黑字），根因是 marker 用了 `--text-heading`。owner 为此显式新增 `Color Type/Text/Primary Button` token（两主题一致）并 Publish，确立本规则。

## 原则 5：审计粒度
- API 层 SSOT = api-diff.md（+ api-diff-patterns.md，由 code+Figma 双向 diff 生成）
- 视觉层 SSOT = 本地 JSON + token 文件
- 视觉层 audit 用脚本，不用 AI
- API 层 audit 仅审 diff 表的 ⚠️ 行

## 原则 6：决策默认值
- 🟢 仅代码 prop：默认删除；Vue 约定 → 保留 + 登记
- 🟡 仅 Figma 设计态：不映射 + 登记
- 🟡 仅 Figma 运行时态：加进代码
- 🟠 命名不同语义同：Vue 命名 + 登记

## 原则 7：Figma 跨 theme 不对称处置（B1 semantic token）

Figma 库内部可能在 dark/light 两 theme variant 绑定**不同的 published token**（如 SelectBoxLine disabled border：dark 绑 `grey-8`，light 绑 `grey-5`）。Code 不能用单一 primitive token 同时 1:1 还原两 theme。

**处置方法**（强制路径，不走 divergence / B 归类）：

1. **在 `src/tokens/variables.css` 加 component-state-specific semantic token**，如 `--select-disabled-border`
2. 该 semantic token 在 `:root`（dark）和 `[data-theme='light']` 块下**各自解析到对应的 Figma published primitive token**
3. **Component CSS 只引用 semantic token**：`border-color: var(--select-disabled-border);` —— 保持 component **theme-agnostic**，不在 component CSS 写 `[data-theme]` 选择器（破坏分层抽象）
4. **每个新增 semantic token 必须登记到 [`docs/internal/figma-cross-theme-asymmetries.md`](./internal/figma-cross-theme-asymmetries.md)** 清单，标明 "待设计师确认两 theme 是否统一"。该清单不阻塞 code 落地——code 1:1 还原 Figma 当前状态，统一与否是 Figma 库侧决策

**不走方案**：

- ❌ 在 component CSS 加 `[data-theme='light'] .selector` 分支 —— 破坏 token / component 分层
- ❌ 登记 divergence + 归 B —— code 一 theme 不 1:1，A_TRUE_DRIFT 不真清零
- ❌ 强推单一 primitive token —— P15-1 实证：dark 修了 light 反向 drift 14 个

**判定条件**（cluster 是否 cross-theme 不对称）：
读 `figma-data/normalized/components-tokenized/<component>.json`，比较 dark variant 与 light variant 在同一 state 的 stroke/fill/text token binding。binding 的 primitive token 不同 → cross-theme asymmetric → 走本原则。

---

## 原则 8：单一统一方法优先，不为分叉形式加兼容规则（尤其 Figma 组件库 Code 化）

遇到"同一件事存在多种实现/暴露形式"时，**默认收敛到唯一统一方法**，而不是写一条额外规则 / 审计信号 / 代码分支去同时兼容这些分叉形式。**兼容是在固化不一致；统一才是消除它。**

**唯一例外**：有**无法替代**的技术原因必须保留分叉（如平台限制、第三方约束）。此时该例外必须**显式登记 + 写明原因 + 挂 backlog 追踪**（不是静默 accommodate）。

### Figma 组件库 Code 化的统一方法（强制）

把 Figma 已发布组件做成 Code，**唯一形态**是：

1. `src/components/<X>/<X>.vue` —— base 实现层
2. `src/canonical/<X>.vue` —— Figma-aligned 桥层，**import 并 wrap base**（不是 self-contained 重写）
3. 从 `src/index.ts` **export + `install()` 注册** —— 接进产物（消费者可 import）

判定一个组件"是否接进产物"的**唯一信号**：从 `src/index.ts` 做 import-graph 可达性（`audit:export-coverage`）。base 层因被 canonical wrap 而可达，无需第二信号。

**反模式（2026-06-03 实证，本原则立项来源）**：
- ❌ canonical 自包含重写、把 base 留成 orphan 文件（Badge：canonical/Badge 不 wrap base，base Badge 悬空只剩 playground 用）→ 制造"两种暴露形式"
- ❌ 组件造好却没 export（Logo：组件存在但没接 `src/index.ts`，消费者拿不到，docs 只能手搓占位）—— "有组件 ≠ 接进产物"
- ❌ **为容忍上述不一致，给 `audit:export-coverage` 加"可达 OR 名字匹配"双信号** —— 这正是本原则禁止的"加规则兼容分叉"。正确做法是消除 orphan（base 收敛回 wrap，或删除被取代的 legacy + repoint 用法），让单一可达性信号即可干净通过
- ❌ 无 Figma 源的 code-only 组件长期游离（Alert：被 Notification/PromptMessage 取代仍留着）—— 要么补 Figma 源走统一形态，要么删除 + repoint，不靠白名单养着

**落地**：`audit:export-coverage` 单信号 = 可达性；`INTERNAL_ALLOWLIST` 仅登记"无法替代的例外"且每条带原因 + backlog 指针，不作为容忍分叉的常规手段。

---

## 决策 Log

- **2026-04-28：Phase 6.1 批量登记**
  - 类别 A：~47 项 🟢 / 🟡 / 🟠 项按 Vue 生态标准 / 设计态不映射 / 命名 alias 批量登记
  - 类别 B：10 项决策（B1–B10），全部按推荐方案执行
  - 详见：`src/design-system/translation/prop-aliases.md`、`src/design-system/translation/divergences.md`、`docs/internal/runtime-additions.md`
  - 后续修复阶段计划：
    - 6.2 视觉层 token 替换
    - 6.3 删除自创内容（Notification.success / type）
    - 6.4 新增运行时能力（showCount / multiple / editable）
    - 6.5 微结构修复（Notification side pop / warning glyph / 按钮颜色 / PromptMessage L width）
    - 6.6 双形态 API（FormItem.label / Tooltip.content）
    - 6.7 Badge prop 拆分
    - 6.8 Button canonical 完成迁移

- **2026-05-09：F25 cleanup — Icon default color 决策**
  - 设计意图：单色非品牌 icon 默认色 = figma `Color Type/Icon/Default` Variable = code `--icon-default` token (dark `#9e9e9e` / light `#595959`)
  - Figma SVG export pipeline 限制：Variable 引用解析为 literal hex `#dbdbdb`（实际是 `UX/Grey/grey-4` palette 值，designer 用作占位语义）
  - **Code 端治本**：`figma-sync/export-icons.mjs` 的 `transformSvgCurrentColor` 把 `#dbdbdb` per-path 转 `currentColor`；audit 回归保护只 ERROR 残留 `#dbdbdb`
  - **消费方 responsibility**：消费组件容器自己设 `color: var(--icon-default)` 提供 fallback；**不**在 `Icon.vue` 的 `.icon` 元素层强制设 color——会破坏现有 `InputNumber:disabled` / `PromptMessage status icon` / `Notification --error` 等 cascade-based color override pattern
  - **AI 工具未来生成 figma 效果图**：单色非品牌图标 path fill 必须绑 `Color Type/Icon/Default` Variable，不写 hex literal——详见 [`docs/internal/mockup-conventions.md`](./internal/mockup-conventions.md) §M10
  - **第三方 logo / app icon hex (Facebook 蓝 / Twitch 紫 / etc.)**：保留 hex literal，**不**加进 TVU color token 表——它们封装在 catalog SVG 内，外部组件通过 `<Icon name="..." />` 引用，颜色不泄漏
  - 详见 [`docs/internal/_reports/f25-cleanup-2026-05-09.md`](./internal/_reports/f25-cleanup-2026-05-09.md) + [`src/design-system/translation/divergences.md`](../src/design-system/translation/divergences.md) §"Figma SVG export pipeline limitation"
