# Figma Conformance First

这份说明只服务一个优先级：

**先解决“生成出来的代码不符合 TVU Figma 设计系统”，再考虑 AI 扩展、自动补组件、自动衍生。**

## 当前偏离的真实原因

从当前仓库实现来看，主要不是 token 同步失败，而是下面三类问题叠在一起：

### 1. 生成器不是 manifest-driven

`figma-sync/generate-vue.mjs` 目前仍然是一个偏手写、偏单组件的生成器：

- 内置 `BUTTON_SPEC`
- 内置 `VAR_TO_CSS`
- 输出路径直接写到 `src/components/Button/Button.vue`

这意味着它不是“从统一 Figma manifest 生成所有组件”，而是“针对个别组件补一段生成逻辑”。

结果：

- 扩展性差
- 容易和 Figma 演进脱节
- 无法保证所有组件遵守同一生成契约

### 2. canonical/generated 里大量是 skeleton，不是真正对齐实现

很多 `src/canonical/generated/*.vue` 目前只是：

- 保留 Figma 轴
- 挂 `data-figma-*`
- 转手把内容传给旧 runtime 组件

它们自己并没有真正把 Figma 轴映射成视觉、结构、行为。

所以这些文件更准确的身份是：

- `wrapper skeleton`

而不是：

- `Figma-conformant canonical component`

### 3. skeleton 组件已经进入公共导出

当前 `src/index.ts` 已经把部分 `canonical/generated/*` 直接作为正式组件导出。

这会带来一个很危险的认知偏差：

- 用户以为拿到的是 canonical 组件
- 实际拿到的是“带 Figma 标签的旧 runtime 壳”

## 结论

所以当前阶段最重要的不是继续补更多页面，也不是继续让 AI 生成新组件。

最重要的是先建立下面这个红线：

1. skeleton 不能当完成组件发布
2. manifest 之外的手写特例生成器不能当主生成链路
3. 未完成 Figma 轴映射的 wrapper 不能宣称符合设计系统

## 2026-04-27 新增执行规则

下面这些规则已经不再是临时讨论结论，而是新的 Session 必须直接遵守的 conformance 约束。

### 1. 图标也属于 canonical 契约的一部分

当前图标链不再只是零散 registry，而是完整的 category-first 体系：

1. Figma 图标导出链应直接生成：
   - `manifest`
   - `generated/<category>.ts`
   - `loaders`
   - `esm/<category>.js`
   - `svg/<category>/<name>.svg`
2. 正式命名统一为 `category/name`
3. 运行时普通图标统一通过共享 `Icon` 组件消费
4. 运行时组件不得：
   - 直接 import `icons/catalog/generated/*`
   - 直接 import `icons/raw`
   - 重新塞一段独立内联 SVG 作为“局部修补”
5. 品牌 Logo 是单独资产组件，不与普通语义图标混用

也就是说，图标如果没有接入共享 `Icon` 体系，就不能算符合当前设计系统实现约束。

### 2. docs shell 的主题规则属于全站规则，不属于单页特例

从这轮开始，组件页中凡是涉及主题展示的内容，默认必须遵守：

1. 页面跟随网站全局主题切换
2. 不要在单个组件页里再复制一套局部 dark / light toggle
3. 只有当页面本身就是在审计“主题切换组件”时，才允许把主题切换本身作为页面内容

### 3. Tooltip 这类交互组件的审计优先级高于静态铺排

像 `Tooltip` 这类组件，文档页不应该再以“把所有气泡都强行摆出来”作为主要审核方式。

优先规则应改为：

1. 当前站点主题下只展示一套结果
2. 使用 hover / focus 触发真实交互
3. 气泡位置必须贴近 anchor
4. 如果静态矩阵仍然存在，也只能作为辅助，不应盖过真实交互结果

### 4. 页面外层白边属于站点壳层缺陷，不是浏览器默认可忽略差异

`html / body / #app` 的默认 margin 必须在入口层清零。

如果组件页四周露出浏览器默认白边，这应视为 docs shell conformance 问题，而不是“非组件区域可忽略”。

## 当前建议的修复顺序

### 第一步

先把“不符合”的东西识别出来并阻止继续扩散：

- 审计 skeleton wrapper
- 审计公共导出是否暴露 skeleton
- 审计生成器是否仍然依赖手写特例逻辑

### 第二步

再按组件优先级逐步修正：

1. `Button`
2. `Input / Select / DateTime`
3. `CheckBox / Radio / Switch`
4. `Pagination / Tooltip / Notification`
5. `Slider / Rating / Table / TopBar`

### 第三步

等 canonical 映射真正稳定后，再做：

- change impact audit
- AI authoring contract
- 自动补组件

## 新增机器审计

仓库里新增了：

```bash
pnpm audit:figma-conformance
```

它目前会直接检查三类高风险问题：

1. `src/canonical/generated/*.vue` 是否仍包含 skeleton 标记
2. `src/index.ts` 是否把这些 skeleton 组件公开导出
3. `figma-sync/generate-vue.mjs` 是否仍然是手写组件特例生成器

只要这些问题还存在，就不应该说“生成 code 已经符合 TVU Figma 设计系统”。

## 当前审计快照（2026-04-24）

本轮已实际执行：

```bash
pnpm audit:figma-conformance
```

结果（初始基线）：

- `skeletonGeneratedWrappers = 16`
- `publiclyExportedSkeletons = 11`
- `generatorErrors = 3`
- `errors = 30`
- `overallPass = false`

### 当前已确认事实

1. `src/canonical/generated/*.vue` 里仍有 16 个 skeleton wrapper  
   它们不是完成组件，只是保留 Figma 轴并把后续映射留给人工补齐。

2. `src/index.ts` 仍公开导出了 11 个 skeleton wrapper  
   这会让外部使用方误以为这些组件已经 canonical 且符合 Figma。

3. `figma-sync/generate-vue.mjs` 仍然不是统一 manifest-driven 主链  
   当前审计明确抓到了 3 个问题：
   - `BUTTON_SPEC`
   - `VAR_TO_CSS`
   - 直接写入 `src/components/Button/Button.vue`

### 当前阻塞点

当前阻塞不在 token 是否同步，而在生成契约本身：

1. **生成器阻塞**  
   仍以 `Button` 的手写特例为主，而不是统一消费 manifest / canonical spec。

2. **canonical 阻塞**  
   `generated/*` 大量是 wrapper skeleton，没有把 Figma 轴真正映射成视觉、结构、行为。

3. **公共导出阻塞**  
   skeleton 已进入公共 API，继续扩散会放大错误认知。

### 本轮优先修复层

本轮先修这 3 层，不继续扩散生成：

1. `figma-sync/generate-vue.mjs`
2. `src/canonical/generated/*`
3. `src/index.ts`

也就是先修：

- 生成入口
- canonical 契约
- 对外暴露边界

再谈新增组件。

### 为什么先从 Button 开始

`Button` 是当前最适合作为样板的组件，原因不是它最简单，而是它最能暴露主链问题：

1. 它目前正被手写特例生成器直接控制  
   所以最适合拿来拆掉 “组件特例生成 = 主生成链” 这件事。

2. 它已经进入公共使用范围  
   修它的收益高，也最能减少继续扩散错误实现。

3. 它具备清晰的 Figma 变体轴和高频使用场景  
   适合验证：
   - manifest 是否足够表达
   - canonical 组件是否真正消费 Figma 轴
   - 生成器是否可以回到统一契约

## 当前执行红线

从这一轮开始，默认遵守以下红线：

1. 不再把 skeleton wrapper 当作完成组件叙述或扩散
2. 不再把 `generate-vue.mjs` 当前这套 Button 特例模式复制到更多组件
3. 所有关键结论先写回 `docs/`，再继续实现
4. 如果需要试做，只允许先拿 `Button` 做样板

## Button 样板链当前动作（2026-04-24）

本轮已开始真正处理 `generate-vue.mjs`、`src/canonical/ButtonBridge.vue`、`src/index.ts` 这条 Button 样板链。

### 已执行调整

1. `figma-sync/generate-vue.mjs`
   - 不再直写 `src/components/Button/Button.vue`
   - 不再内嵌 `BUTTON_SPEC`
   - 不再内嵌 `VAR_TO_CSS`
   - 当前改为只从：
     - `figma-data/normalized/canonical-components.json`
     - `figma-data/normalized/components.manifest.json`
     读取数据
   - 输出一个过渡性的 manifest-driven 样板工件：
     - `figma-data/normalized/button-generation-sample.json`

2. `src/canonical/ButtonBridge.vue`
   - 已从 `canonical/generated` 目录移出，改成手工维护的 bridge 文件
   - 这样 generated 目录里剩下的就都是仍待晋升的 skeleton，本体语义更清晰
   - 仍保留为 Button 样板适配草稿
   - 但已明确标记为 **non-public draft**
   - 不再把它描述成完成组件或 skeleton 可发布实现

3. `src/index.ts`
   - 已移除 `Button` 的公共导出
   - 已移除 `install()` 里的 `Button` 注册

4. `src/canonical/generated/index.ts`
   - 已移除 `Button` 的 generated 导出

5. `src/canonical/index.ts`
   - 已移除对 `./generated` 的全量再导出
   - 稳定 canonical 入口现在只保留手工完成的组件

### 本轮复跑审计结果

本轮改动后，已重新执行：

```bash
pnpm audit:figma-conformance
```

结果变为：

- `skeletonGeneratedWrappers = 16`
- `publiclyExportedSkeletons = 0`
- `generatorErrors = 0`
- `generatedIndexViolations = 0`
- `errors = 16`
- `overallPass = false`

### 这代表什么

1. **公共导出边界问题已清零**  
   当前 stable public entry 已不再泄漏 generated skeleton。

2. **生成器显式特例问题已清零**  
   `generate-vue.mjs` 已不再以内嵌 Button 特例的方式直写运行时代码。

3. **剩余问题已收敛到 generated 本体**  
   当前 conformance 错误的主体，已经明确收敛为：
   - `15` 个普通 generated skeleton
   - `1` 个仍处于 under-repair 的 `Button` draft

4. **generated index 也已纳入审计**  
   当前 `src/canonical/generated/index.ts` 没有继续违背晋升计划：
   - 没有重新导出 `structural-rebuild` 组件
   - 没有重新导出 under-repair 的 `Button`

## Button bridge 第二轮进展（2026-04-24）

这轮 `Button` 已从“只有 resolvedContract 和 draft wrapper”继续推进到了 **runtime bridge 真正开始消费 canonical icon / width 轴** 的阶段。

### 已新增的桥接能力

1. `src/components/Button/Button.vue`
   - 已接入运行时 `Icon` 组件
   - `left / right / loading / loading button / light` 开始映射到真实图标资产
   - `light` 当前使用已发布 Figma `icon/Picture/light` SVG 作为 bridge asset，而不是继续空转为字符串轴

2. `fixedWidth`
   - 已不再使用粗略的 `size -> width` 单值映射
   - 当前改为 bridge-level 的 `size × icon-presence × fixedWidth` 桶化策略
   - 目的是先消除“所有 Button 只靠一个 size 宽度占位”的明显偏差

3. `src/canonical/ButtonBridge.vue`
   - 继续保持 non-public canonical bridge 身份
   - 但它现在已经不是只透传 `data-figma-*` 的 wrapper，而是会把 canonical Button 轴真正落到 runtime bridge props

4. `playground/docs/pages/ButtonPage.vue`
   - 不再继续沿用旧的 runtime-only `variant / size / disabled / loading` 演示页
   - 改成直接渲染 canonical bridge 轴的可视化验证页
   - 当前页面分成：
     - `Family Contract`
     - `Geometry Contract`
     - `State & Icon Review`
   - 目的不是宣称 `Button` 已 pixel-perfect，而是先让桥接效果真正可见、可评估

### 这一步还不代表什么

这一步仍然只能视为 **bridge-level conformance**，不能宣称 `Button` 已完成像素级还原：

- `light` 图标虽然已经接入，但还只是单个已发布 SVG 资产的 bridge，不代表整个 Button icon family 已 canonical 完成
- `fixedWidth` 当前还是桶化 bridge 值，不是逐 frame 校准后的最终 Figma 几何值
- 红/橙 filling 以及部分 ghost / rimless 状态，仍是 token-level bridge，不是 pixel parity 完成态

### 当前阶段判断

从这一轮开始，`Button` 已进入：

- **可视化 bridge 审核阶段**

也就是说，后续是否把这套方法复制到下一个组件，不应只看 audit 结果，而应先看 `Button` 页面里的桥接效果是否足够接近预期。

## Button canonical 主 family 已真实收口

本轮继续推进后，`Button` 不再只是“在 `resolvedContract` 里口头排除 `url link`”，而是主 canonical spec 本身已经开始按这个规则收口：

- `memberCount = 8`
- `rawMemberCount = 9`
- `totalVariants = 3200`

当前主 `Button` canonical spec 只保留：

- `Button/dark L`
- `Button/dark M`
- `Button/dark S`
- `Button/dark XS`
- `Button/light L`
- `Button/light M`
- `Button/light S`
- `Button/light XS`

并显式把：

- `Button/url link`

记录为 `excludedMembers`。

这一步的意义是：

1. `Button` 的主 canonical family 已经不再和 link family 混在一起  
2. `memberCount / totalVariants` 已经回到主 family 的真实口径  
3. 后续 runtime/canonical 实现，可以直接围绕这 8 个成员继续收 contract，而不是再从混合 family 开始

## Button M 可视化审核补充结论

为了解决“页面看起来有点乱，无法一眼判断除了 size 以外是否都覆盖了”的问题，本轮又补充确认了 `Button/dark M` 与 `Button/light M` 的真实导出轴。

`Button/dark M` 当前确认到的非 size 轴为：

- `icon = left | loading | no | right`
- `style = filling | ghost | rimless`
- `color = gray 1 | green | orange | red`
- `status = default | disable | hover | loading`
- `radius = round | square`
- `fixed width = no | yes`

`Button/light M` 的值域本质一致，但存在两个源命名漂移：

- `style = rimeless`
- `Radius = round | square`

这两个 light M 差异目前应被视为 **Figma 导出命名漂移**，而不是应该继续扩散到 canonical/runtime 的正式契约。

因此，`Button` 页面现在应优先回答这件事：

- 对 `Button/dark M` 来说，**除了 size 以外的全部原生轴都已经在页面上显性展示**

同时要避免误导：

- `light` 图标资源虽然已接入 runtime bridge，但它不是 `Button/dark M` 这个组件集的原生 `icon` 取值，不应继续放在 M family 原生变体覆盖区里冒充“已覆盖的 Figma 轴值”

另外，本轮又确认了一个展示层规则：

- 仅仅在页面顶部罗列出 Figma 原始轴和值，还不够
- 页面下方的 demo 分组，也必须按同一组轴逐段对应展示

因此 `Button` 页当前的主审核结构应为：

1. `Figma M Coverage`
2. `Axis-by-Axis Review`
   - `style`
   - `color`
   - `status`
   - `radius`
   - `fixed width`
   - `icon`

这意味着后续别的组件页也不应再出现：

- 上面列的是 Figma 轴
- 下面却按 `family / geometry / state` 这类二次概念重新打散

否则审核者仍然无法一眼判断“页面展示是否真的对应上了原始 Figma 轴”。

## 组件页还需要承担开发引用说明，不应只停留在视觉审核

参考 Element 的组件页结构，`Button` 本轮又补充确认了一个文档层要求：

- 组件页除了要能看出 Figma 轴覆盖和视觉效果
- 还需要让开发一眼知道：
  - 当前如何引用这个组件
  - 当前可用的属性有哪些
  - 当前可用的 slots 有哪些
  - 这个引用方式是不是稳定公开 API

因此 `Button` 页新增了：

1. `Development Usage`
   - 记录当前 bridge 阶段的引用方式
   - 明确说明它目前是项目内审核用引用，不是稳定公开 API

2. `Button API`
   - `Button Attributes`
   - `Button Slots`

这一步的目的不是把 `Button` 伪装成已经发布完成，而是：

- 让设计审核页同时兼顾开发引用
- 让后续其它组件页在进入审查阶段时，也有统一的“视觉 + 用法 + API”三层结构

## Loading 审核必须直接按真实 Figma loading 子集组织

这轮继续确认了 `Button/dark M` / `Button/light M` 的 `status=loading` 真实值域，当前结论是：

- 所有 loading 变体都使用 `icon=loading`
- `square` 支持：
  - `filling`
  - `ghost`
  - `rimless`
- `round` 仅支持：
  - `filling`
  - `ghost`
- `round + rimless + loading` 并不存在于当前 M 组件集中

因此 `Button` 页里的 `Loading Review` 不应再只是一个普通轴示例，而应直接按真实 loading 子集做矩阵：

- 行：
  - `color`
- 列：
  - `square / filling`
  - `square / ghost`
  - `square / rimless`
  - `round / filling`
  - `round / ghost`

并显式说明：

- 当前矩阵使用：
  - `status=loading`
  - `icon=loading`
  - `size=M`
  - `fixed width=yes`

这样审核者看到的就是：

- Figma 里真实存在的 loading 组合
- 而不是“为了页面整齐而补齐出来”的伪组合

另外，本轮又确认了一个更细的视觉约束：

- `status=loading` 的视觉，不应单独发明一套 palette
- 对 `Button/dark M` / `Button/light M` 来说，loading 应理解为：
  - **对应颜色与样式的禁用态视觉**
  - 再叠加 `icon=loading`

因此实现层当前应遵守：

- `loading` 的背景 / 文本 / 描边 palette，直接复用同组合下的 `disable`
- `loading` 与 `disable` 的差异，不在颜色，而在：
  - 前导 loading 图标
  - 交互禁用语义

这条规则是后续继续做其他带 loading 组件时必须复用的，不应再重新猜一套“loading 专属颜色”。

## Button 审核页的展示顺序也要服从审阅心智，而不只是服从轴完整性

虽然 `Button` 页已经按 Figma 原始轴完成了数据覆盖，但本轮又确认了一个页面结构规则：

- 仅仅“轴对齐”还不够
- 审核页的 section 顺序，也必须更接近审核者实际阅读心智

当前 `Button` 页应优先按下面的顺序组织 review：

1. `Basic Usage`
   - 先看 `style`
2. `Color Axis`
3. `Status Axis`
4. `Icon Review`
5. `Loading Review`
6. `Radius Axis`
7. `Size Review`
8. `Fixed Width Axis`

这样页面的阅读顺序会更接近：

- 先看颜色
- 再看状态
- 再看有无图标
- 再单独看 loading
- 再看圆角
- 最后再看尺寸 / 固定宽度

这比“虽然数据完整，但 section 排列仍然不符合审阅习惯”的页面更容易一眼判断是否真的对齐 Figma。

## generated wrapper 生成器也已收口

本轮继续处理了：

- `/Users/nancy/Documents/AICoding/VS_Code/tvu-design-system/figma-sync/generate-canonical-wrappers.mjs`

当前它已经不再把所有 canonical spec 都继续壳化成 generated wrapper，而是开始遵守晋升计划：

1. `Button`
   - 作为正在修复中的样板组件，被显式排除

2. `structural-rebuild` 组件
   - 被显式跳过，不再重新生成 wrapper

3. 当前只保留 `spec-normalization` 候选进入 generated index：
   - `Badge`
   - `BreadcrumbItem`
   - `InputNumber`
   - `Progress`
   - `Rating`
   - `Tooltip`

也就是说，generated wrapper 的未来扩散面已经被收窄成 6 个候选，不再继续把结构型组件壳化后制造“已经 canonical”的错觉。

## 统一问题台账

从这一轮开始，所有跨 Session 的问题、根因、修复和复用规则，统一沉淀到：

- `/Users/nancy/Documents/AICoding/VS_Code/tvu-design-system/docs/conformance-issue-log.md`

后续如果发现：

- 新阻塞点
- 新修复决策
- 可复用的约束或经验

必须优先更新这份台账，再继续实现。

这意味着本轮至少完成了 3 件可量化的事情：

1. `Button` 不再被 skeleton 审计计数
2. `Button` 不再通过稳定公共入口暴露
3. `generate-vue.mjs` 已不再命中“手写 Button 特例生成器”那 3 个审计错误

### 稳定公共入口本轮新增收口

本轮还继续执行了一刀更彻底的边界收口：

- `src/index.ts` 现在只保留手工完成的 canonical 组件
- 剩余 10 个 generated skeleton：
  - `Badge`
  - `InputNumber`
  - `Notification`
  - `Pagination`
  - `Progress`
  - `Rating`
  - `Slider`
  - `Table`
  - `Tooltip`
  - `TopBar`
  已全部从稳定公共导出和 `install()` 注册中移除

这一步的意义是：

- conformance 问题不再继续通过根包公共 API 对外扩散
- 后续这些组件如果继续处理，也必须先修 canonical 契约，再考虑重新晋升回稳定入口

### Button 样板工件当前新增能力

`button-generation-sample.json` 当前已经不只是在重复旧 spec，而是开始输出一份 **resolved contract**，用于明确告诉后续实现层：

1. 哪些 member 应继续保留在主 Button family 内
2. 哪些 member 应从主 Button family 中剥离
3. 哪些轴需要被归一化后再成为 canonical prop

当前它已经明确给出：

- `Button/url link` 应从主 Button canonical family 中剥离
- `style` 应统一为 `rimless`
- `size` 应从 member family 名中导出为 canonical 轴
- 当前主 Button family 只应先处理 8 个成员，而不是 9 个成员混在一起

这一步的意义是：

- 生成器现在开始产出 **修正后的解释层**
- 不再只是机械复述当前有问题的 canonical spec

### 当前意义

这一步的目的不是“Button 已经修好”，而是先把最危险的错误扩散点切断：

1. 不再让手写 Button 特例生成器继续直写 runtime 组件
2. 不再让 Button skeleton / draft 继续通过公共入口对外承诺 canonical 完成态
3. 用 `button-generation-sample.json` 先建立一个可审计、可讨论、可追溯的 manifest-driven 样板工件

### 下一步仍然要做的事

Button 还没有完成，接下来仍需继续：

1. 收口 Button canonical 轴设计  
   重点包括：
   - `size` 仍分散在 member family
   - `rimeless / rimless` 拼写不一致
   - `Button/url link` 混入主 Button canonical spec

2. 再决定 Button 的正式 canonical 实现文件放在哪里  
   在此之前，不重新公开导出。

### 本轮继续后的新增结果

这一步已经不再停留在“contract 解释层”，而是推进到了实现桥接层：

1. `src/components/Button/Button.vue`
   - 已升级成 dual-stack runtime bridge
   - 同时兼容：
     - legacy API：`variant / size / disabled / loading`
     - canonical bridge props：`canonicalColor / canonicalStyle / canonicalRadius / canonicalFixedWidth / canonicalStatus / canonicalIcon / canonicalSize`

2. `src/canonical/ButtonBridge.vue`
   - 已从“只透传 `data-figma-*` 的 wrapper skeleton”推进成真正的 canonical bridge draft
   - 现在会把 canonical Button 轴真正传给 runtime Button bridge props
   - 已修正 runtime 引用路径到：
     - `../../components/Button/Button.vue`

3. conformance 数字进一步收口为：
   - `skeletonGeneratedWrappers = 15`
   - `publiclyExportedSkeletons = 0`
   - `generatorErrors = 0`
   - `generatedIndexViolations = 0`
   - `errors = 15`

### 当前仍然没有完成的部分

这一步不应被误解成 “Button 已经 pixel-perfect”。

当前仍明确未完成：

1. `fixedWidth`
   - 已完成 contract-level 映射
   - 但当前宽度值仍属于 bridge-level provisional implementation

2. `icon=left/right/light`
   - 还未接入正式的 Figma 已发布图标资产

3. 颜色映射
   - 红/橙的 filling
   - 以及部分 ghost/rimless 的视觉
   目前属于 bridge-level token mapping，不可宣称已经完成像素级校准

### Button Review 与运行时本轮补充（2026-04-24）

本轮又继续收了两层，而且不是只动页面：

1. `ButtonPage.vue`
   - Review 区不再使用散卡片式 wall
   - 改成更接近 Element 的 demo panel 结构：
     - 左侧轴标签
     - 右侧横向实例
     - 每节只审核一条主轴
   - 这样审核者可以直接按：
     - `Color`
     - `Status`
     - `Icon`
     - `Loading`
     - `Radius`
     - `Size`
     - `Fixed Width`
     顺序往下看，不必自己在卡片堆里重新理解分类

2. `src/components/Button/Button.vue`
   - `loading` 已显式复用 `disable` palette
   - `fixedWidth=no` 已不再继续强行复用 width bucket 的 `min-width`
   - `XS / S / M / L` 的高度、字号、行高与 square radius 已进一步向 Figma 几何值靠拢

3. `tests/Button.test.ts`
   - 已补：
     - `fixedWidth=no` 真实自适应
     - `loading=disable palette`
   - 避免页面规则和运行时实现再次分叉

4. 当前验证方式
   - `Button` 这一轮用定向测试先验：
     - `pnpm exec vitest run tests/Button.test.ts`
   - 当前结果：
     - `16 passed`
   - 这样做的原因是：
     - 全量 `prepare/build` 仍会被仓库里其他 generated skeleton 的既有类型错误拦住
     - 这些错误不应被误判成当前 Button 样板链回归失败

5. Button loading 的修正规则
   - 不能把 `loading` 简化成“所有颜色都复用 disable palette”
   - 当前从 `Button/dark M` 原始 JSON 已确认：
     - `green / gray 1` 的 loading 视觉接近 disable
     - `red / orange` 的 loading 仍保持饱和色
   - 所以 runtime 和页面都必须按真实 member 收，而不是套一个统一口号
