@teamix-evo/mcp 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,99 @@
1
+ # 0021. 语义色 API 统一治理(双 prop:`variant`(形态) + `color`(颜色))
2
+
3
+ - **Status**: Accepted
4
+ - **Date**: 2026-05-29
5
+ - **Region**: 0001–0099 工程哲学
6
+ - **Related ADR**: [0005](./0005-ui-no-variant.md) ui 不感知 variant / [0010](./0010-design-default-and-variants.md) design default-and-variants / [0008](./0008-eslint-visual-rules-warn-baseline.md) eslint visual rules
7
+
8
+ ## Context
9
+
10
+ `@teamix-evo/ui` 在 antd ∪ shadcn 并集策略下逐步落地 ~90 个组件。引入"语义色"能力时,各组件按各自源头沿用了不同的 prop 名与枚举值,出现两类不一致:
11
+
12
+ **一、prop 名混用(5 种)**
13
+
14
+ | 当前 prop 名 | 出处 | 组件示例 |
15
+ | ------------ | ---------------- | ----------------------------------------------- |
16
+ | `color` | antd | Tag / PageHeaderDataItem / Timeline |
17
+ | `type` | antd | Typography Text / Alert / Notification |
18
+ | `status` | antd | Result / Progress / Steps / Upload / TimePicker |
19
+ | `tone` | shadcn / Polaris | Spinner / Icon |
20
+ | `variant` | shadcn | Button / Badge |
21
+
22
+ **二、"错误"语义同义词(5 种)**
23
+
24
+ | 字面值 | 出处 | 底层 token |
25
+ | ----------------------- | ---------------------------------------------------------- | --------------------------- |
26
+ | `error` | antd Tag/Alert/Result/Notification/Steps/Upload/TimePicker | `--destructive`(API 层翻译) |
27
+ | `danger` | antd Typography Text | `--destructive` |
28
+ | `exception` | antd Progress | `--destructive` |
29
+ | `destructive` | shadcn Button/Badge/Spinner/Icon | `--destructive`(直对) |
30
+ | `secondary`(语义"次级") | antd Typography Text | `--muted` |
31
+
32
+ **底层 token 只有一套** —— shadcn 命名(`--destructive` / `--muted` / `--success` / `--warning`,**没有** `--error` / `--info` / `--danger` / `--exception`)。这意味着 antd 命名的字面值在 className 层一律要做"业务话术 → token 字面值"的翻译,引入了不必要的认知层。
33
+
34
+ **关于 prop 命名 `tone` 的回退**:本 ADR 初版(2026-05-29 上午)曾采用 Polaris 的 `tone`,落地一轮后发现:`tone` 在业界几乎是 Polaris 孤本(MUI / antd / Mantine / Chakra 主流派均用 `color`,shadcn 用 `variant`),且不在 antd 消费方的认知词典内。**本次正式版回退到 `color`** —— 它是业界最大公约数,对 antd 用户零迁移成本,字面值仍走 shadcn token(`destructive` / `muted`)以保持与底层 OpenTrek tokens 单一权威源对齐。
35
+
36
+ ## Options Considered
37
+
38
+ | 选项 | 优点 | 缺点 |
39
+ | ----------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
40
+ | A. 单 `variant` 笛卡尔展开(纯 shadcn) | 与 shadcn Button/Badge 完全同构 | `outline-success` / `solid-destructive` / ... 笛卡尔积爆炸,Tag/Icon/Timeline 形态 ⊥ 颜色场景 API 体验差 |
41
+ | B. **`variant`(形态) + `color`(颜色) 双 prop(本 ADR 采纳)** | 形态轴与颜色轴正交;`color` 是业界最大公约数;字面值仍走 token | Spinner/Icon 已落地 `tone` 需要二次改名(范围已知,小) |
42
+ | C. 保留 `tone`(初版 ADR) | Spinner/Icon 不动 | Polaris 孤本,与 shadcn 基线 prop 命名习惯不一致(shadcn 一律 `variant`,扩展派一律 `color`) |
43
+ | D. 全仓 antd 风:`type` / `color` 双轨 | 与 antd 消费习惯零摩擦 | 字面值脱离 token 单一权威;`destructive` 表达"破坏性操作"是 shadcn 视觉语义,优于 antd 偏运行态的 `error` |
44
+
45
+ ## Decision
46
+
47
+ ### 一、prop 二分:`variant`(形态) + `color`(颜色)
48
+
49
+ | 作用面 | prop 名 | 取值 | 适用组件 |
50
+ | ------ | ------------- | ------------------------------------------ | ------------------------------------------------------------------ |
51
+ | 形态 | **`variant`** | `solid` / `outline` / `ghost` 等(组件自定) | 形态决定整体外观档位的组件:Button / Badge / Tag / Alert |
52
+ | 颜色 | **`color`** | 6 档标准枚举(见下) | 所有需要语义色的组件:Tag / Icon / Spinner / Timeline / Alert / ... |
53
+
54
+ 形态 ⊥ 颜色,正交组合。例:`<Tag variant="outline" color="success">` / `<Alert variant="solid" color="warning">`。组件若不需要形态轴(如 Icon / Spinner),仅暴露 `color`。
55
+
56
+ **业务状态机** prop(`status`,如 Upload `status="uploading|done|error"`、Result `status`)不属于本 ADR 治理范围 —— 它表达业务状态不表达视觉颜色,各组件按行业惯例自然落地。但若某 `status` 取值仅作用于颜色(如 Progress `status="exception"` 只是变红,无状态机语义),则统一并入 `color="destructive"`。
57
+
58
+ ### 二、`color` 标准 6 档枚举
59
+
60
+ ```
61
+ default | muted | primary | success | warning | destructive
62
+ ```
63
+
64
+ - 字面值与 OpenTrek tokens 一一对应:`text-foreground` / `text-muted-foreground` / `text-primary` / `text-success` / `text-warning` / `text-destructive`
65
+ - **不收 `info`**:见 ADR 0010 与决策记忆「语义色标准档位不包含 info 色」 —— Tag/Alert 等组件中信息类展示用 `primary` 表达,或通过 typography 层级区分。如确需信息色,在 design 哲学层单独论证,不进基础 token 集
66
+ - **不收 `error` / `danger` / `exception`**:统一 `destructive`(对齐 shadcn 与 token)
67
+ - **不收 `secondary`** 作为颜色枚举:统一 `muted`(对齐 token `--muted`)
68
+ - **填充背景类组件可省略 `muted`** —— 实色背景下 `default` 本身就是次级灰,无 `muted` 区分点(如 Tag、PageHeaderDataItem 实施为 5 档)
69
+
70
+ ### 三、ESLint 规则配套
71
+
72
+ 新增 `teamix-evo/no-antd-color-alias`(packages/eslint-config),禁用以下字面值:
73
+
74
+ - `error` / `danger` / `exception`(在 `color` / `tone` / `type` 上下文)
75
+ - `secondary` 作为颜色枚举(在 typography 上下文)
76
+ - `info`(在 `color` 上下文,与 token 集裁剪联动)
77
+
78
+ 并禁用 prop 名 `tone` / `type` / `color` 的混用 —— 颜色一律 `color`。
79
+
80
+ ## Consequences
81
+
82
+ - **Positive**:
83
+ - prop 命名取业界最大公约数(MUI/antd/Mantine/Chakra 皆用 `color`),antd 消费方零迁移成本
84
+ - 字面值与底层 OpenTrek tokens 直接对应,无认知翻译层
85
+ - 形态 ⊥ 颜色的双 prop 设计满足 Tag/Alert 等组件"outline + success"的真实组合需求,无 shadcn 单 `variant` 的笛卡尔积爆炸问题
86
+ - ESLint 可执行,新组件强制走规范
87
+ - **Negative**:
88
+ - 7 个组件 break change(Icon / Spinner / Tag / PageHeaderDataItem / Timeline / Typography Text / Alert / Notification),走 changeset major + migration guide
89
+ - 砍 `info` 后 Alert / Notification 的"提示"色档位需用 `primary` 或层级表达,与 antd Alert 4 档(info/success/warning/error)有一档差异
90
+ - **Trade-off**:
91
+ - 放弃 shadcn `variant` 单 prop 的极简,换取形态/颜色正交的实用主义(社区扩展 shadcn 的主流路径)
92
+ - 放弃 antd `type`/`color` 双轨与 `error` 字面值的零迁移,换取与底层 token 的单一权威源对齐
93
+
94
+ ## Source
95
+
96
+ - 触发对话:Icon v0.1 API 收敛后,与 Tag 的 `color="error"` 不对齐讨论(2026-05-29 上午)
97
+ - 二次回退触发:用户对 `tone` 命名的业界流行度质疑(2026-05-29 下午)—— 实测 `tone` 仅 Polaris 一家,业界主流是 `color` / `variant`,本 ADR 据此从 `tone` 回退到 `color`
98
+ - 关联记忆:`shadcn基线项目语义色双prop命名决策` / `语义色标准档位不包含info色的决策` / `shadcn 新组件代码规范`
99
+ - 关联组件:Icon / Spinner / Button / Badge / Tag(本 ADR 立项前已落地的混合命名,本次统一)
@@ -0,0 +1,75 @@
1
+ # 0022. preferences.css 边界约束:仅承载 shadcn 装机偏好,禁止组件级 utility / token alias
2
+
3
+ - **Status**: Superseded by [ADR 0023](0023-cursor-pointer-explicit-in-component-source.md)(2026-06-01)
4
+ - **Date**: 2026-05-31
5
+ - **Region**: 0100–0999 协议与工具
6
+ - **Related ADR**: [ADR 0020](0020-design-to-tokens-skill-fusion.md)(§8 preferences.css 归属决策)
7
+
8
+ > **本 ADR 已被 [ADR 0023](0023-cursor-pointer-explicit-in-component-source.md) 取代**:`preferences.css` 文件已整体删除,可交互元素的 `cursor: pointer` 改为组件源码内显式声明,`<input type="search">` 原生 clear 屏蔽规则下沉到各 globals.css 模板。原 ADR 0022 关于"preferences.css 边界"的约束因文件不复存在而失效。
9
+
10
+ ## Context
11
+
12
+ [ADR 0020 §8](0020-design-to-tokens-skill-fusion.md) 把 `preferences.css` 从 tokens 包搬到了 `packages/ui/src/preferences.css`,并明确它"随 `ui init` 装机,生命周期与 UI 组件绑定"。但该 ADR 只规定了**文件归属**,没有规定**文件内容边界**。
13
+
14
+ 实操中出现了把本不属于 preferences 范畴的内容塞进来的冲动:
15
+
16
+ - **组件级 utility**:当某个组件需要消费 `var(--xxx)` token 又被 `no-arbitrary-tw-value` lint 拦截时,有人会在 preferences.css 里 `@utility rounded-btn { border-radius: var(--radius-button); }` 给组件造一个 first-class Tailwind utility 名
17
+ - **token alias**:把组件级 token 重命名暴露成另一个名字方便 cva 引用
18
+
19
+ 这两类内容会把 preferences.css 慢慢变成"杂物间",侵蚀其单一职责。更严重的是,它们让消费方业务工程在 `ui init` 装机时被动接收了**不属于"偏好"概念的样式工件**,违反"装机内容用户可理解 / 可拒绝"的最小惊讶原则。
20
+
21
+ 且这类越权写法掩盖了**真正应该做的事**:对于消费 token 的视觉属性(圆角 / 颜色 / 间距 等),优先用 **Tailwind v4 标准 namespace** 对应的 utility(`rounded-sm/md/lg/xl/2xl` ← `--radius-sm/md/lg/xl/2xl`、`bg-primary` ← `--color-primary` 等),这些在多包 `@import` 场景下是唯一可靠地被 Tailwind v4 识别并派生为 utility 的 token 命名。**非标准 namespace token**(如 `--radius-button`/`--radius-dialog` 等语义型别名) 在多包 `@import` 下 **不会被自动派生 utility**(实测 dev server 编译产物中 `.rounded-button { ... }` 规则不生成),如果代码跳过标准档写的 `rounded-button` 会静默失效(回退到浏览器默认 8px,与任何主题无关) —— 这是项目已记在案的 pitfall(见 “Tailwind 自定义 utility 在分层引入模式下不生效” 记忆)。
22
+
23
+ ## Options Considered
24
+
25
+ | 选项 | 优点 | 缺点 |
26
+ | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
27
+ | A. 允许 preferences.css 承载组件级 utility / token alias | 改组件时不需要碰 tokens 包,所有"工程性补丁"集中 | 文件职责漂移;装机方被动接收非偏好工件;掩盖 Tailwind v4 token namespace 自动派生能力;违反 ADR 0020 §8 单一职责 |
28
+ | B. **preferences.css 严格只放 shadcn 装机偏好** | 单一职责清晰;消费方装机内容可解释;倒逼正确使用 Tailwind v4 `@theme` 派生 / 组件内 inline style / 真正必要时回 tokens 包加 token | 个别极端场景(token 在 `@theme` 外/Tailwind 无自动 namespace 的属性如 `height`)需要走 inline style 或额外讨论,无 escape hatch |
29
+
30
+ ## Decision
31
+
32
+ **`packages/ui/src/preferences.css` 严格只承载下列内容,其它一律禁止:**
33
+
34
+ 1. **shadcn CLI init prompts 对应的全局偏好**(当前唯一一类):
35
+ - `Use pointer on buttons` → `button:not(:disabled) { cursor: pointer }`
36
+ - 未来 shadcn 新增 init prompts(如 `Use system font` / `Use rounded corners` 等)按需追加
37
+ 2. **跨组件的全局 base layer 重置**(若 shadcn 上游 globals.css 新增,且属于装机偏好范畴)
38
+
39
+ **明确禁止**:
40
+
41
+ - ❌ 组件级 `@utility` 声明(如 `@utility rounded-btn`)
42
+ - ❌ token alias / 重命名(如 `--btn-radius: var(--radius-button)`)
43
+ - ❌ 单组件的 base 样式补丁
44
+ - ❌ 任何 `.foo-bar { ... }` 形态的具名 class 规则
45
+ - ❌ 与单一组件强相关的修复样式(应放回组件源码或 tokens)
46
+
47
+ **正确替代路径**:
48
+
49
+ | 需求 | 正确路径 |
50
+ | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | 组件消费 token 圆角/颜色/间距/字号 | **优先**用 Tailwind v4 标准 namespace utility(`rounded-md` / `bg-primary` / `text-sm` 等),它们稳定派生且能被主题 `[data-theme]` 覆盖生效。**禁止**期望非标准 namespace token(如 `--radius-button`) 被 Tailwind v4 自动派生 utility —— 在多包 @import 场景下不可靠 |
52
+ | 某组件跨主题落在不同标准档上(如 button uni=2px=md / opentrek=8px=lg) | 三选一:(a) 都拢到同一标准档,接受其中一主题偏离设计意图(推荐最简单);(b) 在 tokens 包加显式 `@utility component-x { ... }` 注册,与 token 同源;(c) 组件内 inline style |
53
+ | 组件需要 Tailwind v4 无自动 namespace 的属性(如 `height: var(--xxx)`) | 组件内 inline `style={{ height: 'var(--xxx)' }}`,或评估是否真有必要(优先回退到 Tailwind 标准刻度 `h-6` / `h-8`) |
54
+ | 跨组件复用的语义槽(色/圆角/层级/间距) | 在 tokens 包对应 variant 的 `theme.css` `@theme {}` 块新增 token,所有组件共享 |
55
+ | ESLint `no-arbitrary-tw-value` 拦截 `[var(--xxx)]` 写法 | 这是规则在提示“你应该对齐标准 namespace utility,而不是任意值”。正确做法是换用现成标准 utility(`rounded-md`/`rounded-lg`等),**不是**在 preferences.css 造 utility 绕开 lint,也**不是**期望组件级别名 token 自动派生 utility |
56
+
57
+ ## Consequences
58
+
59
+ - **Positive**:
60
+ - preferences.css 单一职责清晰,装机方业务工程能解释“这个文件为什么必装”;
61
+ - 明确“Tailwind 标准 namespace 优先”原则,避免将来重蹈“期望组件级别名 token 自动派生 utility”的坑;
62
+ - 新组件接入时,工程师必须先回答“我消费的 token 是否能拢到 Tailwind 标准档”——而不是绕路打补丁。
63
+ - **Negative**:
64
+ - 个别属性(如 `height`)在 Tailwind v4 没有自动 namespace,极端场景下组件需要 inline style,无 utility 可写;
65
+ - 历史技术债清单产生:`dialog.tsx` / `alert-dialog.tsx` 仍使用 `rounded-[var(--radius-dialog)]` 任意值写法,应改为 `rounded-2xl`(v4 默认 `--radius-2xl: 1rem = 16px`,与 `--radius-dialog` 一致)或 `rounded-lg` 等标准 namespace utility;那些跨主题反映不一致的语义 token(`--radius-dialog`/`--radius-tag`) 需在 tokens 包额外配 `@utility` 显式注册。
66
+ - **Trade-off**:
67
+ - 为了保持 preferences.css 的**装机偏好层**单一身份,放弃"集中工程补丁"的便利性,容忍 token 体系自身演进时的小阵痛。
68
+
69
+ ## Source
70
+
71
+ - 2026-05-31 Button MVP 优化对话第一轮:用户驳回“在 preferences.css 加 `@utility rounded-btn`”方案,提出“圆角应该写进 tokens”。
72
+ - 2026-05-31 Button MVP 优化对话第二轮(圆角仍是 8px 的 bug 调查):追溯发现 `.rounded-button` utility 在 dev server 实时编译的 CSS 产物中完全**没有生成**,证实 Tailwind v4 在多包 `@import` 场景下不会为非标准 namespace token 自动派生 utility;修正本 ADR 原先错误的“自动派生”假设,明确优先使用 Tailwind 标准 namespace utility。Button 最终选择 A1 方案:`rounded-md`(消费 `--radius-md`) + 删除 `--radius-button` token。
73
+ - [ADR 0020 §8](0020-design-to-tokens-skill-fusion.md) preferences.css 文件归属决策(本 ADR 是其内容边界补丁)
74
+ - Common pitfall 记忆:“Tailwind 自定义 utility 在分层引入模式下不生效” —— 本 ADR 是同一类问题在 `@theme namespace 派生` 路径上的重复验证。
75
+ - Tailwind v4 文档:`@theme` namespace 推导规则在单入口文件中可靠,在多层 `@import` 中不保证为语义型别名 token 派生 utility(仅保证标准档 `sm/md/lg/xl/2xl/3xl/full/none`)。
@@ -0,0 +1,70 @@
1
+ # 0023. 可交互样式 `cursor: pointer` 由组件源码显式声明,移除全局 `preferences.css`
2
+
3
+ - **Status**: Accepted
4
+ - **Date**: 2026-06-01
5
+ - **Region**: 0100–0999 协议与工具
6
+ - **Supersedes**: [ADR 0020 §8](0020-design-to-tokens-skill-fusion.md)(preferences.css 归属决策)、[ADR 0022](0022-preferences-css-boundary.md)(preferences.css 边界约束)
7
+
8
+ ## Context
9
+
10
+ ADR 0020 §8 把 `preferences.css` 从 tokens 包搬到 `@teamix-evo/ui/src/preferences.css`,作为"shadcn 装机偏好"统一承载 `cursor: pointer`(可交互元素)与 `<input type="search">` 浏览器原生 clear 按钮屏蔽两类全局规则,通过 `teamix-evo ui init` 部署到消费方 `src/preferences.css`。ADR 0022 给该文件加了"严格只承载装机偏好,禁止组件级 utility / token alias"的边界。
11
+
12
+ W3 选择类波次中暴露了这个架构的内在矛盾:
13
+
14
+ 1. **shadcn 老默认在组件 cva 里硬编码 `cursor-default`**(`Command` / `DropdownMenu` / `Menubar` / `ContextMenu`),因 Tailwind utility class 优先级高于 `preferences.css` 的属性选择器(`[role='option']:not(:disabled)`),**全局规则被组件类静默覆盖** — Select 弹层选项无手型即此 bug
15
+ 2. **修法只有两条**:(a) 移除组件里的 `cursor-default` + 让全局规则生效;(b) 在组件里**显式**写 `cursor-pointer` + 让全局规则失去意义
16
+ 3. **维护负担两边并存**:不论修法 a 还是 b,以后每加一个新组件都要记得"组件里别写 `cursor-default`"或"组件里要写 `cursor-pointer`",但**没有 ESLint 规则保护**(规则会让"全局兜底 + 组件覆盖"两侧反复打架,无法静态判定)
17
+ 4. **隐性行为难调试**:消费方业务工程发现某个 `<button>` 没手型,要排查"是 preferences.css 没装、还是组件类压住了、还是 disabled 状态对了"三层
18
+
19
+ 更深的问题:`preferences.css` 这一全局文件本身**让消费方装机内容多了一份非显式工件**,违反"装机内容用户可理解 / 可拒绝"的最小惊讶原则(ADR 0022 主张过同样精神)。当组件源码已经能 / 应该自带交互样式时,全局文件失去了存在理由。
20
+
21
+ ## Options Considered
22
+
23
+ | 选项 | 优点 | 缺点 |
24
+ | --- | --- | --- |
25
+ | A. 保留 `preferences.css` + 修组件里所有 `cursor-default` | 改动量小(已修 4 个 menu 组件) | "全局兜底 + 组件不冲突"靠纪律,无静态保护;新增组件易踩同坑;`preferences.css` 仍是消费方装机的隐性工件 |
26
+ | B. **删除 `preferences.css` + 组件源码显式声明 `cursor-pointer`** | 单一来源,组件自闭环;消费方装机内容更轻;全 lint / typecheck 友好;新组件由 ADR + 设计 skill checklist 强制覆盖 | 需要批量给 ~20 个组件加 `cursor-pointer`(一次性) |
27
+
28
+ ## Decision
29
+
30
+ **采纳选项 B**:
31
+
32
+ 1. **删除 `packages/ui/src/preferences.css`** 文件
33
+ 2. **CLI `ui init` 不再部署 `preferences.css`** — `runUiInit` 移除 `deployPreferencesCss` 调用,返回类型 `RunUiInitResult` 移除 `preferencesCss` 字段
34
+ 3. **可交互组件源码内显式 `cursor-pointer`** — Button / Toggle / Tabs 等所有渲染 `<button>` 或 `[role='button']` 的组件,在其 cva base 或主 className 中直接写 `cursor-pointer`
35
+ 4. **`<input type="search">` 浏览器原生 clear 按钮屏蔽** — 这两条 CSS 规则不属于"可交互样式"范畴,改为业务侧 globals.css 模板与 storybook globals.css 各自内联(脚手架已更新)
36
+ 5. **disabled 态保持现有 `disabled:cursor-not-allowed` Tailwind 修饰符** — 与 `cursor-pointer` 共存时,`:disabled` 状态选择器优先级覆盖
37
+ 6. **ADR 0022 同步降级为 historical** — 它的全部约束在新方案里不再适用(没有"preferences.css 该不该装内容"问题,因为没有这个文件)
38
+
39
+ ## Consequences
40
+
41
+ ### 强制约束(由本 ADR 与 [`teamix-evo-design-opentrek` skill](../../packages/skills/src/teamix-evo-design-opentrek/SKILL.md) checklist 同步落地)
42
+
43
+ > 设计 skill checklist 新增条目:**"任何 onClick / hover-反馈 / Radix Trigger / role='button' 的元素必须显式带 `cursor-pointer`"**(disabled 态保留现有 `disabled:cursor-not-allowed`)。AI 生成 / 人工 review 都按此查。
44
+
45
+ - 渲染 `<button>` / `[role='button']` 的组件,**必须在源码 className 里显式写 `cursor-pointer`**;不允许"留给全局 CSS 兜底"
46
+ - Radix `*Trigger` / `*Close` 等直透传(`const Trigger = Primitive.Trigger`)若用户能直接渲染并交互,**必须包成 `forwardRef` 加默认 `cursor-pointer`**(已对 Popover / Tooltip / HoverCard / Dialog / AlertDialog / Sheet / Collapsible 等按此模式改造)
47
+ - 组件**禁止写 `cursor-default`**,除非该元素**确实不可交互**(如 Tooltip 内部纯展示文本)
48
+ - `<a href>` 锚点元素由浏览器默认 `cursor: pointer`,无需显式声明;但 `<a>` 不带 `href` 仅 `onClick` 时,需显式 `cursor-pointer`
49
+ - disabled 态用 `disabled:cursor-not-allowed`(原生 `disabled` 属性) 或 `data-[disabled]:cursor-not-allowed`(Radix `data-disabled` 属性);**禁止写 `cursor-default`** 表示 disabled
50
+ - 已知例外(故意非 pointer):`Sidebar` 的 resize rail 用 `cursor-w-resize` / `cursor-e-resize`(语义是拖拽手柄,非按钮)
51
+
52
+ ### 工程影响
53
+
54
+ - 消费方 `src/preferences.css` 不再生成;升级时 `teamix-evo ui upgrade` 不会主动删除已有用户文件(frozen 语义),消费方可手动删
55
+ - `packages/templates/dev/globals.css` / `packages/create/templates/react-ts/src/index.css` / `packages/ui/.storybook/globals.css` 已分别内联 `<input type="search">` 原生 clear 屏蔽规则
56
+ - `packages/skills/src/teamix-evo-manage/SKILL.md` / `packages/tokens/README.md` / `packages/create/README.md` / `packages/docs/docs/create/index.md` 移除对 `preferences.css` 的所有引用
57
+ - `packages/cli/src/core/ui-init.ts` 删除 `deployPreferencesCss` 函数;`packages/cli/src/commands/ui/init.ts` 删除 preferences 部署日志输出
58
+
59
+ ### 迁移路径(已存在 preferences.css 的业务工程)
60
+
61
+ 1. 删除 `src/preferences.css` 文件(或保留无影响 — 仅是冗余)
62
+ 2. 删除 `src/index.css` 顶部的 `@import './preferences.css';` 行
63
+ 3. `teamix-evo ui upgrade` 拉新组件源码,新源码自带 `cursor-pointer`,无须额外配置
64
+ 4. 如果业务侧有 `<input type="search">` 用法,**手动**把 `::-webkit-search-cancel-button { display: none; }` 等规则加到自己的 `src/index.css`(或不加 — 接受浏览器原生 clear 按钮)
65
+
66
+ ## Source
67
+
68
+ - shadcn-ui 官方 Command 默认是 `cursor-default`(为键盘优先 command palette 设计);本项目业务场景偏鼠标交互,统一改为 `cursor-pointer`
69
+ - [W3C ARIA 1.2](https://www.w3.org/TR/wai-aria-1.2/) `role='option'` / `role='menuitem'` 等没有强制 cursor 视觉规约,由设计语言决定
70
+ - [Tailwind v4 cascade ordering](https://tailwindcss.com/docs/v4-beta#layered-architecture):utilities 层晚于 base 层,组件类 utility 永远优先于 `@layer base { ... }` 的属性选择器规则
@@ -0,0 +1,99 @@
1
+ # 0024. uni-manager scoped CSS 与组件激活态 / disabled 冲突的统一规则
2
+
3
+ - **Status**: Accepted
4
+ - **Date**: 2026-06-01
5
+ - **Region**: 0100–0999 协议与工具
6
+ - **Related ADR**: [ADR 0023](0023-cursor-pointer-explicit-in-component-source.md)(可交互手型显式声明)
7
+
8
+ ## Context
9
+
10
+ ADR 0023 之后,`@teamix-evo/tokens/variants/uni-manager/theme.css` 用 scoped CSS(`[data-theme='uni-manager'] [class*='border-input']:hover/:focus-visible { ... }`)实现了对齐 cd hybridcloud 的"中性焦点 + 柔和 hover 阴影"行为。**这套规则在 form input wrapper 上很好用**(Input prefix/suffix 包壳、Select/Cascader/DatePicker trigger),但与下面两类组件**反复冲突**:
11
+
12
+ ### 冲突 1 — Radix 激活态被中性灰边框压住
13
+
14
+ **症状**:Checkbox 选中后(蓝底白勾),周围**多一圈中性灰边框**;Switch 开启 / Toggle 激活同样问题。
15
+
16
+ **原因**:
17
+ - Radix Checkbox/Radio/Switch/Toggle 等组件的 `Primitive.Root` 是 `<button>` 渲染,默认 className 含 `border-input`(中性边)
18
+ - 选中态用 Tailwind `data-[state=checked]:border-primary` 切到主色
19
+ - uni-manager scoped CSS 命中 `[class*='border-input']:focus-visible`,设置 `border-color: var(--color-input-focus)`(`#aaaaaa`)
20
+ - **CSS 特异性**:scoped CSS `(0,5,0)` > Tailwind utility `(0,2,0)` → 全局规则赢,组件 `border-primary` 失效
21
+
22
+ **已踩过的组件**:Checkbox / Radio Group Item / Switch / Toggle Group Item
23
+
24
+ ### 冲突 2 — disabled 态的 hover 仍触发样式
25
+
26
+ **症状**:Checkbox 禁用后,鼠标 hover 仍出现 border 变蓝 / 底色微变。
27
+
28
+ **原因**:
29
+ - 组件源码用 `hover:border-primary hover:bg-primary/5` 做 hover 反馈
30
+ - `:disabled` 伪类不阻止 `:hover` 触发(浏览器 spec)— 视觉效果照样应用
31
+ - 通常配 `disabled:opacity-50 disabled:cursor-not-allowed` 但都不影响 hover 样式 cascade
32
+
33
+ ## Decision
34
+
35
+ **两条统一规则,合并写入设计 skill checklist 与 token 包文档**:
36
+
37
+ ### 规则 A — uni-manager scoped CSS 必须排除 Radix 激活态
38
+
39
+ `packages/tokens/variants/uni-manager/theme.css` 中所有 `[class*='border-input']:hover` / `:focus-within` / `:focus-visible` 规则,**必须**在 `:not()` 链里排除以下 Radix `data-state` 值:
40
+
41
+ ```css
42
+ :not([data-state='checked'])
43
+ :not([data-state='indeterminate'])
44
+ :not([data-state='on'])
45
+ ```
46
+
47
+ 理由:这三个值统一表示"组件已进入激活态,有自身的主色视觉",scoped CSS 不应再叠加中性反馈。如果未来出现新的"激活态"值(例如 Tabs trigger 的 `"active"`、Tooltip 的 `"open"`),按需补到 `:not()` 链里。
48
+
49
+ > `aria-invalid='true']`(error 态)与 `[disabled]`(禁用态)已在原有 `:not()` 链中,无需重复。
50
+
51
+ ### 规则 B — 组件源码内的 hover / focus 反馈必须用 `enabled:` 前缀
52
+
53
+ 任何**视觉变化型** hover / focus 反馈(border / bg / shadow / opacity 等),写在组件源码 className 时**必须**用 `enabled:` 前缀(等价 CSS `:enabled:hover`),disabled 态不触发样式变化:
54
+
55
+ ```diff
56
+ - 'hover:border-primary hover:bg-primary/5'
57
+ + 'enabled:hover:border-primary enabled:hover:bg-primary/5'
58
+ ```
59
+
60
+ 例外:
61
+ - `cursor-pointer`(由 ADR 0023 强制) **不需要** `enabled:` 前缀 — disabled 态有 `disabled:cursor-not-allowed` 优先级覆盖
62
+ - 不渲染为 `<button>` / `<input>` 的元素(如纯 `div` 容器)无 `:enabled` / `:disabled` 概念,可不加 `enabled:`(也不会有 disabled prop 传入)
63
+
64
+ ### 规则 C — 新组件接入 `border-input` 类时检查 scoped CSS 命中范围
65
+
66
+ 新增组件如果在主 className 用 `border-input`(典型场景:form trigger),**必须**确认是否有内置 `data-state` 激活态。如果有,需要核对 [`uni-manager/theme.css`](../../packages/tokens/variants/uni-manager/theme.css) 的 `:not()` 排除链是否覆盖了对应状态值。
67
+
68
+ ## Consequences
69
+
70
+ ### 强制约束(由本 ADR 与 [`teamix-evo-design-opentrek` skill checklist](../../packages/skills/src/teamix-evo-design-opentrek/checklist.md) 同步落地)
71
+
72
+ - 设计 skill checklist 强制项 #11(可交互手型,ADR 0023)**扩展为**:
73
+ > "可交互元素显式带 `cursor-pointer`(enabled);视觉反馈(hover/focus)用 `enabled:` 前缀;Radix `data-state` 激活态视觉自管,不被 scoped CSS 覆盖"
74
+ - AI 生成 / 人工 review 都按此查
75
+ - ESLint 规则**不强制**(状态态 + 排除链组合静态识别复杂度高,依赖 skill checklist + ADR 知识库)
76
+
77
+ ### 已修组件清单(本次落地)
78
+
79
+ - ✅ Checkbox(`enabled:` 前缀 + scoped CSS `:not([data-state=checked])` 排除链)
80
+ - ✅ Radio / Switch / Toggle / ToggleGroup(自动受益于 scoped CSS `:not()` 排除链,组件源码已是 `data-[state=checked]:border-primary`)
81
+
82
+ ### 后续工序
83
+
84
+ 新增组件遵循:
85
+ 1. 渲染 `<button>` 且有 `data-state` → cva base 加 `border-input` 时同时确认 scoped CSS 排除链覆盖
86
+ 2. 视觉反馈 hover / focus → 加 `enabled:` 前缀
87
+ 3. 写入 stories 时配 disabled 演示验证
88
+
89
+ ### 反模式(踩过的坑)
90
+
91
+ - ❌ scoped CSS 用 `[class*='border-input']` 不加状态排除 — 撞 Radix 激活态
92
+ - ❌ 组件 hover 样式不加 `enabled:` 前缀 — disabled 仍响应
93
+ - ❌ 在组件 cva 里加 `cursor-default` 试图压制 preferences.css cursor 规则(已被 ADR 0023 禁止)
94
+
95
+ ## Source
96
+
97
+ - 反复踩坑 → 修补 → 再踩的根因总结(2026-05 W3 选择类波次 / W3 后置 Checkbox 视觉对齐 cd hybridcloud)
98
+ - [Radix UI primitives data-state 命名约定](https://www.radix-ui.com/primitives/docs/guides/styling#styling-states)
99
+ - [CSS Selectors Level 4 :enabled / :disabled 伪类](https://www.w3.org/TR/selectors-4/#enabled-pseudo)
@@ -0,0 +1,144 @@
1
+ # 0025. 组件 props 必须显式声明 + 数据录入类必须暴露 value/onChange 受控套
2
+
3
+ - **Status**: Accepted
4
+ - **Date**: 2026-06-01
5
+ - **Region**: 0100–0999 协议与工具
6
+ - **Related ADR**: [ADR 0023](0023-cursor-pointer-explicit-in-component-source.md) · [ADR 0024](0024-scoped-css-radix-state-conflict.md)
7
+
8
+ ## Context
9
+
10
+ `@teamix-evo/ui` 包内多数 Radix-based 组件目前是这种写法:
11
+
12
+ ```ts
13
+ export interface CheckboxProps
14
+ extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {}
15
+ ```
16
+
17
+ **空 extends + 透传 Radix Root 的 props**。优点是类型上"自动获得 Radix 全套 props",但带来三个严重缺陷:
18
+
19
+ ### 缺陷 1 — meta.md props 表为空,AI 无法快速使用
20
+
21
+ `packages/ui/scripts/generate-meta.ts` 自动从 interface 字段提取 JSDoc 生成 props 表。空 interface → 表里只剩一行 `_(no props)_`。
22
+
23
+ > 真实案例:Checkbox meta props 表显示空,新业务工程的 AI 生成代码必须**额外去翻 Radix 文档**才能知道支持哪些 prop;遇到 `onCheckedChange` vs antd `onChange` 命名差异时 AI 经常生成错误回调名。
24
+
25
+ ### 缺陷 2 — Storybook argTypes 缺数据源
26
+
27
+ Storybook 的 `argTypes: { ... }` 写控件时,如果对应 prop 不在 interface 字面里,IDE 没补全、controls 面板也不显示。结果 Storybook playground 退化为"只能看默认 demo,改不了任何参数"。
28
+
29
+ ### 缺陷 3 — 数据录入类组件可能漏 value/onChange
30
+
31
+ Radix 的命名(`onCheckedChange` / `onValueChange`)与 antd / cd / 业务习惯(`onChange`)不一致;有些组件甚至没有清晰的"受控套"。如果不显式声明,业务侧不知道该用哪一对 prop 实现受控,常常自己写一遍非受控 + state mirror。
32
+
33
+ ## Options Considered
34
+
35
+ | 选项 | 优点 | 缺点 |
36
+ | --- | --- | --- |
37
+ | A. 维持现状(空 extends 透传) | 写法少,类型自动完整 | meta props 空 / AI 不可用 / Storybook 控件缺数据源 |
38
+ | B. **显式列出每个支持的 prop + JSDoc 中文注释** | meta 自动表完整;Storybook controls 完整;AI 可读 | 每个组件多 ~20 行类型声明,需对齐 Radix 行为 |
39
+
40
+ ## Decision
41
+
42
+ **采纳选项 B** — 数据录入类与有交互意义的 props 必须显式声明。两条规则:
43
+
44
+ ### 规则 A — 所有可配置 props 必须在 interface 字面里显式声明,带 JSDoc
45
+
46
+ ```ts
47
+ // ❌ Before
48
+ export interface CheckboxProps
49
+ extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {}
50
+
51
+ // ✅ After
52
+ export interface CheckboxProps
53
+ extends Omit<
54
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,
55
+ 'checked' | 'defaultChecked' | 'onCheckedChange'
56
+ > {
57
+ /** 受控选中状态 — `true` / `false` / `'indeterminate'`(半选)。 */
58
+ checked?: boolean | 'indeterminate';
59
+ /** 非受控初始选中态。 */
60
+ defaultChecked?: boolean | 'indeterminate';
61
+ /**
62
+ * 选中状态变化回调 — Radix 命名,等价 antd `onChange(checked)`。
63
+ * @param checked - `true` / `false` / `'indeterminate'`
64
+ */
65
+ onCheckedChange?: (checked: boolean | 'indeterminate') => void;
66
+ /** 禁用整个 checkbox。 */
67
+ disabled?: boolean;
68
+ /** 必填(HTML 原生 required;表单层校验依赖此值)。 */
69
+ required?: boolean;
70
+ /** 表单提交时的字段名。 */
71
+ name?: string;
72
+ /** 表单提交时随勾选状态发送的 value。 */
73
+ value?: string;
74
+ /** id 属性,配合 `<label htmlFor>` 关联标签。 */
75
+ id?: string;
76
+ }
77
+ ```
78
+
79
+ **关键点**:
80
+ - 用 `Omit<RadixProps, 'overridden-keys'>` 排除被本地重写的字段,避免类型冲突
81
+ - 所有暴露给业务的 prop **必须**带中文 JSDoc(`generate-meta.ts` 提取的就是这部分)
82
+ - **不在 interface 暴露的 prop = 不支持** — 不允许"暗中支持但不声明"
83
+
84
+ ### 规则 B — 数据录入类组件**必须**至少暴露受控套
85
+
86
+ 数据录入类(用户输入 / 选择 / 调整某个 value 的组件)**必须**至少有这一对受控 prop:
87
+
88
+ | 组件类型 | 受控 value | 受控 onChange | 非受控初值 |
89
+ | --- | --- | --- | --- |
90
+ | 单值输入(Input / Textarea / NumberInput / DatePicker / TimePicker / ColorPicker / Select 单选 / Combobox 单选) | `value` | `onChange` 或 Radix 命名(透传时显式声明) | `defaultValue` |
91
+ | 布尔值(Checkbox / Switch) | `checked` | `onCheckedChange` | `defaultChecked` |
92
+ | 多值(Select 多选 / Combobox 多选 / CheckboxGroup) | `value: string[]` | `onChange: (next: string[]) => void` | `defaultValue: string[]` |
93
+ | 数值滑动(Slider / Rate) | `value: number[]` 或 `value: number` | `onValueChange` 或 `onChange` | `defaultValue` |
94
+ | 树形(Tree / TreeSelect) | `value` / `selectedKeys` / `checkedKeys` | 对应 onChange / onSelect / onCheck | `default*` |
95
+ | 级联(Cascader) | `value: string[]` | `onChange: (path, options) => void` | `defaultValue` |
96
+
97
+ 业务工程**必须能用受控模式跑通**;非受控只是便利封装。
98
+
99
+ ### 适用范围
100
+
101
+ - **必须**显式声明 props:`category: 'form'` 类组件 + 任何带交互的展示组件(Card hoverable / Tag closable / Avatar onClick 等)
102
+ - **可以**保持简单透传(空 extends):纯展示无交互 wrapper(Skeleton / Separator / Spinner / Progress 这种组件本身没业务可配的 prop)
103
+
104
+ ## Consequences
105
+
106
+ ### 强制约束(由本 ADR + [`teamix-evo-design-opentrek` skill checklist](../../packages/skills/src/teamix-evo-design-opentrek/checklist.md) 同步落地)
107
+
108
+ 设计 skill checklist 强制项 #13:**"数据录入类组件 props 必须显式声明 + 至少暴露 value/onChange 受控套"**(具体类型见上表)。AI 生成 / 人工 review 都按此查。
109
+
110
+ ### 工程影响
111
+
112
+ - **`pnpm gen:meta`** 自动从 interface JSDoc 生成 props 表 — interface 越完整,meta 越好用
113
+ - **Storybook `argTypes`** 完整 → playground controls 完整 → AI 用 storybook-mcp 读 args 可直接生成代码
114
+ - **业务工程升级** — 现有组件本来就支持这些 prop(Radix 透传),只是声明不显式;升级后**0 行业务代码改动**
115
+
116
+ ### 已落地清单(本次 ADR 配套)
117
+
118
+ - ✅ Checkbox(本次升级,作为首个示例)
119
+
120
+ ### 后续工序
121
+
122
+ 按以下优先级**逐组件升级**(W4 / W5 / W6 等波次时一并做):
123
+
124
+ 1. **W2 已交付组件回补**:Switch / RadioGroup / Slider / Rate / Mentions / InputNumber / Combobox(已合并入 Select,需补)
125
+ 2. **W3 已交付**:Select(本次合并已含完整 props)/ Cascader / TreeSelect / Tree
126
+ 3. **W4+ 待开**:DatePicker / TimePicker / Calendar / ColorPicker / Upload / Form
127
+
128
+ 完成后所有 form 类组件都满足 ADR 0025。
129
+
130
+ ### 验证方式
131
+
132
+ - `pnpm --filter @teamix-evo/ui gen:meta` 后检查 `<id>.meta.md` 的 `<!-- auto:props:begin -->` 块,确保非空且字段完整
133
+ - Storybook 打开任一 form 组件 Story,Controls 面板应能调整 value / onChange / disabled 等核心 prop
134
+
135
+ ### 反模式(踩过的坑)
136
+
137
+ - ❌ `extends React.ComponentPropsWithoutRef<typeof Primitive.Root> {}` 空 extends — meta 表空,AI 不可用
138
+ - ❌ 只在 README / 注释里写 prop 文档,不进 TypeScript interface — 被业务工程升级 / IDE 提示忽略
139
+ - ❌ 数据录入类只支持 `defaultValue` 不支持 `value` — 业务做不了表单层 controlled 状态
140
+
141
+ ## Source
142
+
143
+ - 真实案例:用户对比 cd hybridcloud 截图(Checkbox 暴露 ~10 个 props 含 onChange / value 等),与我们 meta 显示 `_(no props)_` 形成强烈对比 → 反推这是装机即用 / AI 可读维度的 P0 缺陷
144
+ - shadcn-ui 官方组件普遍使用空 extends 写法 — 这是为开源体验最大化设计的(类型自动完整),但**对 AI 友好度不是设计目标**;teamix-evo 主张 AI-first,因此显式声明优先
@@ -0,0 +1,107 @@
1
+ # 0026. 组件级 token 别名(`--radius-{component}` 等)的创建准则
2
+
3
+ - **Status**: Accepted
4
+ - **Date**: 2026-06-02
5
+ - **Region**: 0100–0999 协议与工具
6
+ - **Related ADR**: [ADR 0020](0020-design-to-tokens-skill-fusion.md)(tokens 包 / skill 边界) · [ADR 0022](0022-preferences-css-boundary.md)(已 superseded,曾约束 preferences.css 不放 token alias)
7
+
8
+ ## Context
9
+
10
+ W4–W5 优化时多次遇到"组件圆角该怎么定义"的问题。早期默认做法是给每个组件加一个语义别名(`--radius-button` / `--radius-dialog` / `--radius-tag` / ...),组件 className 写 `rounded-[var(--radius-X)]` 取值。
11
+
12
+ 后来 Button 优化时发现:**这种 arbitrary value 写法在 Tailwind v4 多包 `@import` 场景下不被自动派生为 utility**(实测 dev server 编译产物中 `.rounded-button { ... }` 规则不生成),组件回退到浏览器默认 8px(脱离 token 链路)。为修这个 bug,Button 改用 Tailwind v4 标准 namespace token `--radius-md`,直接走 `rounded-md` utility;`--radius-button` 被移除。
13
+
14
+ 此后又因 Dialog 跨变体视觉差异大(opentrek 16px vs uni-manager 2px),发现**纯 scale 无法满足**:无论选哪个 `rounded-X` utility,都没有任何一档能让 opentrek=16 且 uni-manager=2 同时成立。因此 `--radius-dialog` 作为组件级别名被保留下来,组件内部使用 `rounded-[var(--radius-dialog)]` 语法。
15
+
16
+ 这暴露了两类完全不同的 token 角色:
17
+
18
+ | 层 | 类型 | 例子 | 谁定义 | 谁消费 |
19
+ | --- | --- | --- | --- | --- |
20
+ | Layer 1 | scale primitives | `--radius-sm` / `--radius-md` / `--radius-lg` / `--radius-xl` / `--radius-2xl` | 各 variant 可独立覆盖 | 所有组件,通过 Tailwind 标准 utility `rounded-{sm\|md\|lg\|xl\|2xl}` |
21
+ | Layer 2 | 组件语义别名 | `--radius-dialog` / `--radius-tag` | 各 variant 可独立覆盖 | 仅该组件,通过 `rounded-[var(...)]` arbitrary value |
22
+
23
+ ADR 0022(已 superseded)曾约束"preferences.css 边界",但没有规范"什么时候可以加 Layer 2 别名"。本 ADR 补这一条。
24
+
25
+ ## Options Considered
26
+
27
+ | 选项 | 优点 | 缺点 |
28
+ | --- | --- | --- |
29
+ | A. 严格一层 — 所有组件走 scale,不允许 Layer 2 | token 架构最简 / 一致性最强 | 跨变体视觉差异大的组件被强制对齐,失去变体差异化能力(opentrek Dialog 必须从 16 收到 6,失去"圆润"视觉特征) |
30
+ | B. **二层模式 — 优先 scale,严控 Layer 2 创建条件(推荐)** | scale 是默认路径(简单);Layer 2 作为 escape hatch,有明确触发条件 | 需要 ADR 守门;否则 Layer 2 会无序蔓延变成"杂物间" |
31
+ | C. 全自由 — 任何组件都可加 `--radius-{name}` | 最大灵活性 | 跟 ADR 之前踩坑的"AI / 业务工程不知道该用哪个 token"反模式重合;token 数量爆炸 |
32
+
33
+ ## Decision
34
+
35
+ **采纳选项 B** — 二层模式,Layer 2 创建有硬性触发条件。
36
+
37
+ ### 规则 A — 默认走 Layer 1 scale(Tailwind v4 namespace)
38
+
39
+ 新增组件 / 优化组件时,**圆角默认**写:
40
+
41
+ ```tsx
42
+ className="rounded-md" // 走 --radius-md (uni-manager 2 / opentrek 6 / Tailwind 默认 6)
43
+ className="rounded-lg" // 走 --radius-lg (uni-manager 4 / opentrek 8 / Tailwind 默认 8)
44
+ ```
45
+
46
+ 各 variant 在 `@theme {}` 块里覆盖 `--radius-sm/md/lg/xl/2xl` 满足该 variant 的视觉调性。
47
+
48
+ **禁止** `rounded-[6px]` / `rounded-[var(--xxx)]` 等 arbitrary value(除非满足规则 B 的硬条件)。
49
+
50
+ ### 规则 B — 满足以下**全部**条件才可创建 Layer 2 别名
51
+
52
+ | # | 条件 | 备注 |
53
+ | --- | --- | --- |
54
+ | 1 | 跨变体的目标值**无法被同一个 scale 档同时满足** | 例:Dialog 在 opentrek 想要 16px,uni-manager 想要 2px。无任何 `rounded-{sm\|md\|lg\|xl\|2xl}` 在两个 variant 同时落到这两个值 |
55
+ | 2 | 该组件视觉**确实有跨变体差异化的设计意图**,不是 token 命名洁癖 | 例:Dialog 跨变体差异是"圆润感 vs 锐利感"的设计语言,非可省略 |
56
+ | 3 | 不能通过组合 `cn()` / 变体 className 实现 | 同一 `<Dialog>` 源码不能写"变体感知 className",所以 Dialog 必须有 token alias |
57
+ | 4 | 命名清晰指向具体组件 | `--radius-{component-id}`,如 `--radius-dialog` / `--radius-tag` |
58
+
59
+ 不满足全部 4 条 → **走 Layer 1 scale**。
60
+
61
+ ### 规则 C — 现存 Layer 2 别名 audit(本 ADR 落地时)
62
+
63
+ | Token | 状态 | 理由 |
64
+ | --- | --- | --- |
65
+ | `--radius-button` | **已移除**(W1-Button 优化时) | 走 `rounded-md` 即可(uni 2 / opentrek 6 / 标准 6),无变体差异化需求 |
66
+ | `--radius-dialog` | **保留** | uni 2 / opentrek 16 跨档不可对齐;有"圆润 vs 锐利"设计语言差异;Dialog/AlertDialog/Sheet 共享 |
67
+ | `--radius-tag` | **保留** | uni 4 / opentrek 4(同值但不在任一 variant 的同一 scale 档:opentrek `rounded-sm`=4 ✓ / uni `rounded-sm`=0 ✗);需 alias 才能跨变体对齐 |
68
+
69
+ ### 规则 D — 同样模式适用于其他 token namespace
70
+
71
+ 本 ADR 围绕 `--radius-*` 描述,但同样规则适用于 `--shadow-*` / `--spacing-*` / `--color-*` 等:
72
+
73
+ - **Layer 1 scale** 是首选(`--shadow-sm/md/lg/xl/2xl`、Tailwind v4 标准 spacing scale)
74
+ - **Layer 2 组件语义别名** 仅在满足规则 B 全部 4 条时创建
75
+ - 既有的 `--shadow-form-hover` / `--color-input-focus`(ADR 0023 引入)按此审视:它们是"form 控件 hover/focus 共用语义",跨多组件使用 → 不是单组件别名 → **算 Layer 1.5(semantic primitive)**,不违反本 ADR
76
+
77
+ ## Consequences
78
+
79
+ ### 工程影响
80
+
81
+ - 新增组件 / 优化既有组件时,**圆角默认走 scale**,不再随手加 `--radius-{name}`
82
+ - 已存在的 `--radius-dialog` / `--radius-tag` 保留(满足规则 B);未来 audit 时按规则审视
83
+ - ADR 设计 skill checklist 不新增条目(本 ADR 是架构层规则,不需要逐组件检查)
84
+ - AI / 人工新增组件 review 时:看到 `--radius-{name}` 自定义别名 → 必须能在 PR 描述里讲清楚满足规则 B 哪 4 条;说不清 → 改走 scale
85
+
86
+ ### 反模式(后续 PR 不接受)
87
+
88
+ ```tsx
89
+ // ❌ 无变体差异化诉求的随手 alias
90
+ --radius-input-group: 4px;
91
+ className="rounded-[var(--radius-input-group)]"
92
+
93
+ // ✅ 应该写
94
+ className="rounded-md" // uni 2 / opentrek 6
95
+ ```
96
+
97
+ ### 与现有 ADR 关系
98
+
99
+ - 取代 [ADR 0022](0022-preferences-css-boundary.md) 中"preferences.css 不放 token alias"的零散约束(那个文件已删,但精神保留)
100
+ - 与 [ADR 0023](0023-cursor-pointer-explicit-in-component-source.md) 关于 cursor 的显式声明、[ADR 0025](0025-component-props-explicit-declaration.md) 关于 props 显式声明并列,共同构成"AI-first 组件库"的工程约束
101
+
102
+ ## Source
103
+
104
+ - 真实案例 1:Button `--radius-button` Tailwind v4 自定义 namespace 无 utility 派生 bug(W1-Button 优化时记录的"Tailwind 自定义 utility 在分层引入模式下不生效" 记忆)
105
+ - 真实案例 2:Dialog 跨变体视觉差异(opentrek 16 / uni-manager 2)无 scale 单档可对齐
106
+ - 真实案例 3:Tag 跨变体同值(4)但 scale 档不对齐(opentrek `rounded-sm`=4 / uni `rounded-sm`=0)
107
+ - 用户 review 反馈(2026-06-02):"按照理念不应该定义一批圆角的变量供所有组件使用,而不是单独定义某个组件?"