---
title: "Tanrei 是如何构建的"
description: "两个唯一可信来源、规格先行的工作流，以及让五个平台、文档和状态矩阵保持一致的漂移检查。"
url: "https://tanrei.chnotchy.com/zh-cn/project/how-tanrei-is-built/"
last_updated: 2026-10-18T00:51:38.000Z
---

# Tanrei 是如何构建的

> 两个唯一可信来源、规格先行的工作流，以及让五个平台、文档和状态矩阵保持一致的漂移检查。

Tanrei 从一个仓库把一套设计系统交付到五个平台。要让这种方式行得通，仓库本身必须让人很难出错：在一处所做的修改，要么传播到所有地方，要么明明白白地失败。本页介绍做到这一点的那套机制。

## 两个唯一可信来源

只有两个目录包含决策：

| 目录 | 内容 | 格式 |
| --- | --- | --- |
| `tokens/` | 每一个设计值 | Design Tokens Community Group 格式，2025.10 |
| `specs/` | 每一个组件的契约 | JSON，由 `specs/component.schema.json` 校验 |

其余一切都是推导出来的：为各平台生成的令牌文件、各平台的实现、这份文档中的表格和状态矩阵。当推导出的东西与唯一可信来源不一致时，按定义，错的是推导出的东西。

## 规格包含什么

组件规格与平台无关。它记录组件的构成、属性、插槽、事件和状态；以带编号的需求写成的行为；无障碍契约，包括角色、键盘操作表和标注方式；它所绑定的令牌分组；一组**测试样例**（fixtures），即每个平台都以相同 ID 截取快照的基准渲染；以及一个 **platforms** 块，把中立的名称映射到各平台的惯用写法。

行为以 EARS 形式书写，这是一种受约束的句式，便于据此编写测试：

```text
WHEN the user activates the button by pointer, touch, Enter or Space
THE button SHALL emit click exactly once.
```

每条需求都有一个类似 `BTN-1` 的 ID，各平台上的测试都引用这些 ID。

## 工作流

1. **提议。** 编写或修改规格，必要时也修改令牌。新组件和破坏性变更由维护者在修改规格之前决定。
2. **校验。** `pnpm validate` 检查 schema、对令牌做 lint，并针对每个主题和对比度级别验证对比度不变式。
3. **生成。** `pnpm generate` 重新构建令牌输出、脚手架和状态矩阵。
4. **实现。** 在规格列出的每个平台上实现，并附带单元测试、快照测试和无障碍测试。
5. **检查。** `pnpm check` 按顺序运行全部检查，包括漂移检查，并且必须全部通过。
6. **记录。** 添加 changeset。所有包的版本同步推进，因为对等本身就是产品。

任何平台的公开接口，都不得在规格没有在同一次提交中做出相同修改的情况下发生变化。

## 漂移就是构建失败

- **重新生成的差异。** CI 会重新生成所有派生文件，只要结果与已提交的内容不同就失败。手工改动生成的文件是留不住的。
- **对等检查。** 对规格声明的每个平台，检查器都要求有实现、有测试、每个测试样例都有快照，并且有该组件的英文和日文文档页面。站点的其他各语言必须有哪些页面，由文档站点另行检查。状态矩阵由同样的事实计算得出，因此它所声称的不可能多于实际存在的。
- **对比度不变式。** 对比度不达标的配色方案根本不会被输出。
- **快照纪律。** 快照出现差异就是一项发现。只有当视觉变化正是规格或令牌变更的预期结果时，基准图才会更新。

## 对等的定义

对等意味着**相同的令牌**、**等效的视觉结果**、**相同的状态**和**相同的无障碍语义**，并不意味着完全相同的 API。Compose 接受 `enabled`，Flutter 用值为 null 的 `onPressed` 来禁用按钮，SwiftUI 使用 `.disabled(_:)`。每一处对应关系都写在规格的 platforms 块中，并显示在每个组件页面上。

## 对代理而言安全的工作流

同样的规则同时约束人和 AI 代理，并且其中尽可能多的规则由工具强制执行，而不是依靠信任。

- **分层的规则。** 根目录一份简短的 `AGENTS.md` 写明项目的根本规则；嵌套的文件为令牌、规格和各个包补充本地规则。嵌套的规则可以在根规则之上追加，但绝不能削弱它们。
- **分道。** 平台相关的工作被限制在该平台的包内。实现 SwiftUI 的代理没有理由去碰 Compose。
- **钩子。** 对生成目录的编辑、`--no-verify` 以及更新快照基准图的操作，都在工具层面被拦截；会话结束时还会运行漂移检查。
- **技能。** 添加组件或令牌这类反复出现的流程，被写成按步骤执行的技能，因此每次的做法都一样。
- **不做发布。** 向注册表发布由维护者决定。任何自动化的执行者都不得进行发布。

> [!NOTE]
> **注意**
>
> 这份文档也遵循同样的规则。状态、令牌参考和组件规格表格都在构建时由仓库数据渲染而来。只有正文是手写的。
