@teamix-evo/mcp 0.3.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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +225 -178
  3. package/dist/cli.js.map +1 -1
  4. package/dist/data/adr/0001-three-layer-alignment.md +54 -0
  5. package/dist/data/adr/0002-package-naming.md +50 -0
  6. package/dist/data/adr/0003-update-strategy-tri-state.md +62 -0
  7. package/dist/data/adr/0004-cli-command-structure.md +61 -0
  8. package/dist/data/adr/0005-ui-no-variant.md +67 -0
  9. package/dist/data/adr/0006-ui-upgrade-no-baseline.md +67 -0
  10. package/dist/data/adr/0007-governance-docs-at-root.md +62 -0
  11. package/dist/data/adr/0008-eslint-visual-rules-warn-baseline.md +110 -0
  12. package/dist/data/adr/0009-registry-mcp-protocol-layer.md +87 -0
  13. package/dist/data/adr/0010-design-default-and-variants.md +319 -0
  14. package/dist/data/adr/0011-mcp-single-package-multi-group.md +169 -0
  15. package/dist/data/adr/0012-lint-shared-core.md +215 -0
  16. package/dist/data/adr/0013-skills-source-mirror.md +154 -0
  17. package/dist/data/adr/0014-ui-biz-ui-templates-tier.md +274 -0
  18. package/dist/data/adr/0015-skill-description-trigger-contract.md +122 -0
  19. package/dist/data/adr/0016-design-md-brand-charter.md +93 -0
  20. package/dist/data/adr/0017-mcp-design-group.md +107 -0
  21. package/dist/data/adr/0018-ai-context-routing.md +112 -0
  22. package/dist/data/adr/0019-project-upgrade-flow.md +156 -0
  23. package/dist/data/adr/0020-design-to-tokens-skill-fusion.md +139 -0
  24. package/dist/data/adr/0021-semantic-color-api-unification.md +99 -0
  25. package/dist/data/adr/0022-preferences-css-boundary.md +75 -0
  26. package/dist/data/adr/0023-cursor-pointer-explicit-in-component-source.md +70 -0
  27. package/dist/data/adr/0024-scoped-css-radix-state-conflict.md +99 -0
  28. package/dist/data/adr/0025-component-props-explicit-declaration.md +144 -0
  29. package/dist/data/adr/0026-component-level-token-alias.md +107 -0
  30. package/dist/data/adr/0027-component-visual-token-alignment.md +127 -0
  31. package/dist/data/adr/0028-ui-component-categorization.md +111 -0
  32. package/dist/data/adr/0029-input-split-and-prefix-suffix-removal.md +62 -0
  33. package/dist/data/adr/0030-skill-uni-manager-uplift.md +56 -0
  34. package/dist/data/adr/0031-skill-templates-decoupling.md +77 -0
  35. package/dist/data/adr/0032-opentrek-v4.1-brand-token-alignment.md +129 -0
  36. package/dist/data/adr/0033-entry-skill-global-only-scope.md +64 -0
  37. package/dist/data/adr/0034-skills-cli-verb-alignment.md +61 -0
  38. package/dist/data/adr/0035-skills-update-scope-and-lock-gates.md +69 -0
  39. package/dist/data/adr/README.md +75 -0
  40. package/dist/data/adr/_template.md +36 -0
  41. package/dist/index.d.ts +64 -59
  42. package/dist/index.js +232 -186
  43. package/dist/index.js.map +1 -1
  44. package/package.json +2 -2
@@ -0,0 +1,139 @@
1
+ # 0020. Design 包降级为 Tokens 包,设计知识全面迁入 Skills
2
+
3
+ - **Status**: Accepted
4
+ - **Date**: 2026-05-27
5
+ - **Region**: 0100–0999 协议与工具
6
+ - **Related ADR**: 0010 (supersedes design default-and-variants), 0016 (supersedes DESIGN.md brand charter ownership), 0017 (supersedes MCP design group)
7
+
8
+ ## Context
9
+
10
+ ### 问题
11
+
12
+ 1. **design 包职责过重**:同时承载构建时资产(tokens)和 AI 知识(philosophy / patterns / scenarios),两类内容的消费者完全不同(CSS bundler vs AI assistant)。
13
+ 2. **内容重复**:design 包的 patterns/page-types.md 等内容与 skills 包的 design-rules skill 有概念重复,维护时需要双处同步。
14
+ 3. **装机产物臃肿**:`design init` 向消费者项目写入大量 `.teamix-evo/design/**` 文件,而这些文件只有 AI 在运行时才读取——业务开发者实际只需要 tokens。
15
+ 4. **设计师侧已有完整规则体系**(`teamix-evo-design` 独立仓库),需要融合吸收到我们的工程体系中。
16
+ 5. **Tailwind v4 的 CSS-native 配置**使得 `tailwind.preset.ts` 已不需要,token 传递只需一个 `theme.css`。
17
+
18
+ ### 约束
19
+
20
+ - 消费者的 Tailwind CSS v4 构建工具链必须能 `@import` 到真实的 token CSS 文件
21
+ - AI 技能(skills)是跨 IDE 工作的唯一知识载体(Qoder / Claude / Cursor 均支持)
22
+ - 变体(opentrek / uni-manager)需要独立的完整技能包,不依赖"基线 + overlay"模式
23
+ - 与设计师侧 `teamix-evo-design` 仓库的融合路径要明确
24
+
25
+ ## Options Considered
26
+
27
+ | 选项 | 优点 | 缺点 |
28
+ | ---------------------------------------------------------- | ------------------ | ---------------------------------------- |
29
+ | A. 保持 design 包现有架构,手动同步 | 零改动 | 永远双处维护、新人困惑 |
30
+ | B. design 包保留但 "瘦身"(移除 patterns 等) | 改动较小 | 包名暗示它承载"设计",新人仍会在此找规则 |
31
+ | **C. design 改名 tokens,设计知识全量迁入 skills(选定)** | 包名即职责、零歧义 | 改动面大、需废弃多个 ADR |
32
+
33
+ ## Decision
34
+
35
+ ### 1. 包重命名与精简
36
+
37
+ - `packages/design/` → `packages/tokens/`,npm 包名 `@teamix-evo/design` → `@teamix-evo/tokens`
38
+ - tokens 包只保留:
39
+ - `tokens/` — token 源文件(theme.css + overrides.css 模板),按变体分目录
40
+ - `manifest.json` — 变体列表(供 CLI 枚举)
41
+ - 各变体的 `pack.json`(身份标识)
42
+ - 移除:`default/philosophy/`、`default/foundations/`(非 token 文件如 spacing.md)、`default/patterns/`、`default/scenarios/`、`variants/*/brand/`、所有 AGENTS.md/CLAUDE.md/DESIGN.md 模板
43
+
44
+ ### 2. Skills 目录重命名
45
+
46
+ - `packages/skills/skills/` → `packages/skills/src/`,消除冗余嵌套
47
+
48
+ ### 3. 设计基线 Skill 废弃,变体 Skill 自包含
49
+
50
+ - **删除** `teamix-evo-design-rules`(原基线 skill)
51
+ - **删除** `teamix-evo-design-rules-uni-manager`(暂不研发)
52
+ - `teamix-evo-design-rules-opentrek` 升级为**完整自包含技能**,含:
53
+ - SKILL.md(trigger + dispatcher)
54
+ - generation-flow.md(AI 行为流程)
55
+ - boundaries.md(硬约束)
56
+ - checklist.md(自检清单)
57
+ - rules/(页面类型、布局、组件规格、样式规则等)
58
+ - patterns/(列表页、详情页、表单页等具体模式)
59
+ - Skill 中**不硬写 token 具体值**,统一写"参照 `tokens/theme.css` 中的 `--spacing` 等变量"——让 AI 运行时读 token 文件获取实际值
60
+
61
+ ### 4. DESIGN.md 归属变更
62
+
63
+ - DESIGN.md 模板从 design 包移入 `packages/create/`,在 `npm create teamix-evo` 时生成到项目根目录
64
+ - 内容:品牌身份 + 设计理念 + 工具链指引(skills / eslint / ui / tokens 索引)
65
+
66
+ ### 5. Token 装机位置
67
+
68
+ - `teamix-evo design init <variant>` 改为 `teamix-evo tokens init <variant>`(或保留 design 作为 CLI alias)
69
+ - 装机产物位于**业务项目根目录**:
70
+ ```
71
+ my-project/
72
+ ├── tokens/
73
+ │ ├── theme.css ← @theme 声明(regenerable)
74
+ │ └── overrides.css ← 用户覆盖(frozen)
75
+ └── ...
76
+ ```
77
+ - 不再有 `.teamix-evo/design/` 和 `.teamix-evo/tokens/` 嵌套目录,tokens 直接在根 `tokens/`
78
+
79
+ ### 6. MCP 精简
80
+
81
+ - **移除** `design_list_principles`、`design_list_patterns`、`design_get_pattern`、`design_get_brand`
82
+ - **保留并重命名** `design_get_tokens` → `tokens_get`(从项目根 `tokens/` 读取)
83
+ - 设计知识 AI 直接从 skill 文件读取,不绕 MCP
84
+
85
+ ### 7. 通用 vs 变体内容治理
86
+
87
+ - 通过 ADR 文档约束研发时哪些内容是"通用知识"(可跨变体复用),哪些是"变体独有"
88
+ - 通用知识(如"间距必须 4px 倍数""危险操作必须二次确认")在 ADR 中定义,各变体 skill 引用 ADR 编号
89
+ - 变体独有内容(如"OpenTrek 品牌色用法""AI 副驾驶场景")仅在对应 skill 中维护
90
+ - 新增变体时,从现有变体 skill 复制并修改,通过 ADR 引用确保通用规则一致
91
+
92
+ ### 8. preferences.css 归属
93
+
94
+ - `preferences.css` 从 design 包迁入 `packages/ui/`,随 `ui init` 装机
95
+
96
+ ## Consequences
97
+
98
+ - **Positive**:
99
+ - 包名即职责(tokens 包只有 tokens,skills 包只有 AI 知识)
100
+ - 消费者项目更干净(根 tokens/ + DESIGN.md,无 `.teamix-evo/design/` 树)
101
+ - AI 单次读取 skill 即获得完整上下文(无需先读 skill 再跳转 `.teamix-evo/design/` 读内容)
102
+ - 设计师侧内容融合路径明确(→ opentrek skill 的 rules/ 和 patterns/)
103
+ - Tailwind v4 对齐(只需 `@import "../../tokens/theme.css"`)
104
+ - **Negative**:
105
+ - 改动面大(20+ 文件,多个 CLI/MCP 源码文件需修改)
106
+ - ADR 0010 / 0016 / 0017 废弃,需要时间收敛文档
107
+ - 旧版本消费者项目升级只需 `mv .teamix-evo/tokens tokens`
108
+ - **Trade-off**:
109
+ - 为了包名语义清晰和 AI-first 体验,放弃了 design 包作为"设计体系知识中心"的传统定位
110
+ - 为了变体 skill 自包含,放弃了"基线 + overlay"的 DRY 复用,接受少量跨变体内容重复(通过 ADR 引用治理)
111
+
112
+ ## Migration Path
113
+
114
+ ### Phase 1: 结构变更(本次执行)
115
+
116
+ 1. `packages/design/` → `packages/tokens/`,更新 package.json name
117
+ 2. tokens 包精简内容(只留 token 文件 + manifest)
118
+ 3. `packages/skills/skills/` → `packages/skills/src/`
119
+ 4. 删除 `teamix-evo-design-rules` 和 `teamix-evo-design-rules-uni-manager`
120
+ 5. 重构 `teamix-evo-design-rules-opentrek` 为完整技能(吸收设计师规则)
121
+ 6. MCP design group 精简为 tokens_get
122
+ 7. CLI design-init 核心逻辑适配
123
+
124
+ ### Phase 2: 引用收敛
125
+
126
+ 8. 更新所有文档(AGENTS.md、architecture.md、ADR 索引)
127
+ 9. 更新 CLI 命令实现(design → tokens 或保持 design alias)
128
+ 10. 更新 registry schema(design-pack → tokens-pack)
129
+ 11. 更新 check-variant-sync 等脚本
130
+
131
+ ### Phase 3: 消费者侧迁移
132
+
133
+ 12. CLI 新增 `tokens migrate` 命令(`.teamix-evo/tokens/` → 根 `tokens/`,已实现核心路径变更)
134
+ 13. create 包 preset 模板直接使用根 `tokens/` 结构
135
+ 14. 文档站更新安装指引
136
+
137
+ ## Source
138
+
139
+ 对话决策(2026-05-27)— 设计师侧 `teamix-evo-design` 仓库与工程侧 design 包融合分析中,确认了"按消费者分包"原则:tokens 归构建工具链,设计知识归 AI skills。
@@ -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,因此显式声明优先