@teamix-evo/skills 0.12.1 → 0.13.0

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 (82) hide show
  1. package/README.md +1 -1
  2. package/manifest.json +11 -28
  3. package/package.json +2 -2
  4. package/src/teamix-evo-code-opentrek/SKILL.md +13 -13
  5. package/src/teamix-evo-code-opentrek/api-layering.md +53 -44
  6. package/src/teamix-evo-code-opentrek/checklist.md +24 -24
  7. package/src/teamix-evo-code-opentrek/file-structure.md +55 -36
  8. package/src/teamix-evo-code-opentrek/forms-and-validation.md +17 -16
  9. package/src/teamix-evo-code-opentrek/reuse-first.md +6 -9
  10. package/src/teamix-evo-code-opentrek/testing.md +14 -14
  11. package/src/teamix-evo-code-uni-manager/SKILL.md +15 -15
  12. package/src/teamix-evo-code-uni-manager/api-layering.md +74 -58
  13. package/src/teamix-evo-code-uni-manager/checklist.md +28 -28
  14. package/src/teamix-evo-code-uni-manager/error-and-loading.md +2 -2
  15. package/src/teamix-evo-code-uni-manager/file-structure.md +77 -62
  16. package/src/teamix-evo-code-uni-manager/forms-and-validation.md +17 -15
  17. package/src/teamix-evo-code-uni-manager/reuse-first.md +7 -10
  18. package/src/teamix-evo-code-uni-manager/routing-and-codesplit.md +1 -1
  19. package/src/teamix-evo-code-uni-manager/testing.md +37 -37
  20. package/src/teamix-evo-design-opentrek/SKILL.md +17 -20
  21. package/src/teamix-evo-design-opentrek/boundaries.md +1 -1
  22. package/src/teamix-evo-design-opentrek/checklist.md +5 -5
  23. package/src/teamix-evo-design-opentrek/components.md +19 -19
  24. package/src/teamix-evo-design-opentrek/examples/standard-card-list.html +1 -1
  25. package/src/teamix-evo-design-opentrek/examples/standard-table-list.html +1 -1
  26. package/src/teamix-evo-design-opentrek/foundations.md +6 -6
  27. package/src/teamix-evo-design-opentrek/pages/dashboard-page/SKILL.md +18 -19
  28. package/src/teamix-evo-design-opentrek/pages/dashboard-page/patterns/dashboard-opentrek.md +6 -6
  29. package/src/teamix-evo-design-opentrek/pages/detail-page/SKILL.md +24 -25
  30. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/api-doc-detail.md +3 -7
  31. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/comparison-detail.md +3 -7
  32. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/monitor-detail.md +3 -7
  33. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/resource-detail.md +10 -10
  34. package/src/teamix-evo-design-opentrek/pages/form-page/SKILL.md +26 -27
  35. package/src/teamix-evo-design-opentrek/pages/list-page/SKILL.md +35 -36
  36. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/item-card-spec.md +41 -32
  37. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/card-list-opentrek.md +10 -10
  38. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/card-list.md +23 -23
  39. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/standard-list-opentrek.md +8 -8
  40. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/standard-list.md +8 -8
  41. package/src/teamix-evo-design-opentrek/patterns/color-mapping.md +2 -2
  42. package/src/teamix-evo-design-opentrek/patterns/dashboard.md +1 -1
  43. package/src/teamix-evo-design-opentrek/patterns/detail-page.md +9 -9
  44. package/src/teamix-evo-design-opentrek/patterns/form-page.md +6 -6
  45. package/src/teamix-evo-design-opentrek/patterns/list-page.md +9 -9
  46. package/src/teamix-evo-design-opentrek/patterns/page-types.md +3 -3
  47. package/src/teamix-evo-design-opentrek/principles.md +541 -0
  48. package/src/teamix-evo-design-opentrek/rules/common-components.json +206 -76
  49. package/src/teamix-evo-design-opentrek/rules/component-specs.json +2 -2
  50. package/src/teamix-evo-design-opentrek/rules/page-frame.json +197 -193
  51. package/src/teamix-evo-design-opentrek/{generation-flow.md → workflow.md} +141 -22
  52. package/src/teamix-evo-design-uni-manager/SKILL.md +5 -5
  53. package/src/teamix-evo-design-uni-manager/boundaries.md +2 -2
  54. package/src/teamix-evo-design-uni-manager/brand.md +1 -1
  55. package/src/teamix-evo-design-uni-manager/checklist.md +2 -2
  56. package/src/teamix-evo-design-uni-manager/components.md +11 -11
  57. package/src/teamix-evo-design-uni-manager/foundations.md +7 -7
  58. package/src/teamix-evo-design-uni-manager/generation-flow.md +3 -3
  59. package/src/teamix-evo-manage/SKILL.md +111 -709
  60. package/src/teamix-evo-manage/init.md +98 -0
  61. package/src/teamix-evo-manage/migrate.md +100 -0
  62. package/src/teamix-evo-manage/rearchitect-capture-guide.md +174 -0
  63. package/src/teamix-evo-manage/rearchitect.md +373 -0
  64. package/src/teamix-evo-manage/update-component-staging.md +188 -0
  65. package/src/teamix-evo-manage/update-token-rename.md +126 -0
  66. package/src/teamix-evo-manage/update-token-treatment.md +116 -0
  67. package/src/teamix-evo-manage/update.md +213 -0
  68. package/src/teamix-evo-design-opentrek/brand.md +0 -154
  69. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/search-combo-spec.md +0 -194
  70. package/src/teamix-evo-design-opentrek/philosophy.md +0 -98
  71. package/src/teamix-evo-design-opentrek/rules/README.md +0 -39
  72. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AGENT RUNTIME.svg +0 -1
  73. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI GATEWAY.svg +0 -1
  74. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI STUDIO.svg +0 -1
  75. package/src/teamix-evo-design-opentrek/rules/_assets/OP_DEV-2.svg +0 -1
  76. package/src/teamix-evo-design-opentrek/rules/_assets/OP_LOGO.svg +0 -1
  77. package/src/teamix-evo-design-opentrek/rules/_assets/OP_OPS.svg +0 -1
  78. package/src/teamix-evo-design-opentrek/rules/layout-rules.json +0 -218
  79. package/src/teamix-evo-design-opentrek/rules/page-header-spec.md +0 -123
  80. package/src/teamix-evo-design-opentrek/rules/sidebar-spec.md +0 -217
  81. package/src/teamix-evo-upgrade/SKILL.md +0 -431
  82. /package/src/teamix-evo-design-opentrek/{rules/boundaries.rules.json → boundaries.json} +0 -0
@@ -10,13 +10,13 @@
10
10
 
11
11
  ```
12
12
  src/
13
+ ├── features/ # 业务领域(每个子目录聚合 api/hooks/types/mocks/schema)
13
14
  ├── pages/ # 路由页面(每个目录对应一条路由)
14
15
  ├── components/ # 跨页面复用的业务组件
15
- ├── services/ # 后端通信(纯函数,见 api-layering.md)
16
- ├── hooks/ # 自定义 Hook(包含数据 hook、UI hook)
16
+ ├── hooks/ # 跨领域共享的通用 Hook(极少数,如 useDebounce / useDangerConfirm)
17
17
  ├── contexts/ # React Context(跨子树共享、低频更新的全局态;含 TenantContext / RegionContext)
18
18
  ├── stores/ # 高频全局态(zustand / jotai / redux,首次需要时引入,只用一种)
19
- ├── types/ # Domain / API 类型声明(.ts 仅类型,无运行时)
19
+ ├── types/ # 跨领域通用类型(ApiResponse<T>、Pagination<T>)
20
20
  ├── utils/ # 纯函数工具(无 React、无副作用)
21
21
  ├── lib/ # 三方 SDK 包装、http 实例、active-context、monitor
22
22
  ├── routes/ # 路由配置 + 守卫(若用 react-router 集中式声明)
@@ -29,22 +29,22 @@ src/
29
29
  └── App.tsx # 根组件
30
30
  ```
31
31
 
32
- > 没列出的目录按需补,但**不要**新增同义层(如同时有 `utils/` 和 `helpers/`、`api/` 和 `services/`、`views/` 和 `pages/`)。`*.test.ts(x)` 文件**就近**放在被测文件同目录,**不要**集中堆到顶层 `tests/`(见 [`testing.md`](testing.md))。
32
+ > 没列出的目录按需补,但**不要**新增同义层(如同时有 `utils/` 和 `helpers/`、`services/` 和 `features/`、`views/` 和 `pages/`)。`*.test.ts(x)` 文件**就近**放在被测文件同目录,**不要**集中堆到顶层 `tests/`(见 [`testing.md`](testing.md))。
33
33
 
34
34
  ---
35
35
 
36
36
  ## uni-manager 增量目录约定
37
37
 
38
- | 路径 | 用途 |
39
- | -------------------------------- | ------------------------------------------------------------------------------- |
40
- | `src/contexts/TenantContext.tsx` | 租户上下文 Provider + `useTenant()` |
41
- | `src/contexts/RegionContext.tsx` | 区域上下文 Provider + `useRegion()` |
42
- | `src/lib/active-context.ts` | vanilla TS 实现的 active tenant/region/cloud 单例(供 interceptor 与 React 共享) |
43
- | `src/lib/http.ts` | 集中注入 `X-Tenant-Id` / `X-Region-Id` / `X-Cloud-Provider` 请求头 |
44
- | `src/components/cloud/` | 跨云通用组件占位:`CloudBadge`、`RegionBadge`、`ProviderIcon` |
45
- | `src/components/danger/` | 危险操作组件占位:`DangerConfirmDialog`、`useDangerConfirm` |
46
- | `src/components/context/` | 上下文切换组件占位:`ContextSwitcher`(切租户/区域时弹 AlertDialog) |
47
- | `src/services/<domain>.cloud.ts` | 跨云查询服务(显式覆盖 interceptor,并发多 cloud) |
38
+ | 路径 | 用途 |
39
+ | ------------------------------------- | ------------------------------------------------------------------------------- |
40
+ | `src/contexts/TenantContext.tsx` | 租户上下文 Provider + `useTenant()` |
41
+ | `src/contexts/RegionContext.tsx` | 区域上下文 Provider + `useRegion()` |
42
+ | `src/lib/active-context.ts` | vanilla TS 实现的 active tenant/region/cloud 单例(供 interceptor 与 React 共享) |
43
+ | `src/lib/http.ts` | 集中注入 `X-Tenant-Id` / `X-Region-Id` / `X-Cloud-Provider` 请求头 |
44
+ | `src/components/cloud/` | 跨云通用组件占位:`CloudBadge`、`RegionBadge`、`ProviderIcon` |
45
+ | `src/components/danger/` | 危险操作组件占位:`DangerConfirmDialog`、`useDangerConfirm` |
46
+ | `src/components/context/` | 上下文切换组件占位:`ContextSwitcher`(切租户/区域时弹 AlertDialog) |
47
+ | `src/features/<domain>/api.cloud.ts` | 跨云查询服务(显式覆盖 interceptor,并发多 cloud) |
48
48
 
49
49
  > 这些目录是"概念占位拼装"的标准落点(详见 [reuse-first.md](reuse-first.md))。biz-ui/uni-manager 已实装 `um-topbar`,其余先以本工程内组件兜底,后续逐步抽包。
50
50
 
@@ -73,7 +73,7 @@ src/pages/
73
73
 
74
74
  - 页面组件**默认 default export**,与文件名一致
75
75
  - `_components/` `_hooks/` 用下划线前缀标记**私有**,**禁止跨页面 import**
76
- - 跨页面用得到的组件/hook → 升到 `src/components/` `src/hooks/`
76
+ - 跨页面用得到的组件 → 升到 `src/components/`;领域 hook → 放 `src/features/<domain>/hooks.ts`
77
77
 
78
78
  ### `src/components/`
79
79
 
@@ -104,36 +104,51 @@ src/components/
104
104
  - **不要**写"小工具组件"如 `Wrapper`、`Container` —— 用 div + tailwind 即可
105
105
  - 跨云/危险/上下文相关组件统一放 `cloud/` `danger/` `context/` 子目录,便于后续抽到 biz-ui/uni-manager
106
106
 
107
- ### `src/services/`
107
+ ### `src/features/`
108
108
 
109
- **职责**:后端通信纯函数。详见 [`api-layering.md`](api-layering.md)。
109
+ **职责**:业务领域代码。每个子目录聚合一个领域的全部代码。详见 [`api-layering.md`](api-layering.md)。
110
110
 
111
- uni-manager 强约定:
111
+ ```
112
+ src/features/
113
+ ├── instance/
114
+ │ ├── api.ts # 请求纯函数(listInstances, createInstance, ...)
115
+ │ ├── api.cloud.ts # 跨云查询(显式覆盖 interceptor,并发多 cloud)
116
+ │ ├── hooks.ts # react-query 封装(useInstanceList, useCreateInstance, ...)
117
+ │ ├── types.ts # 领域类型(Instance, InstanceStatus, ...)
118
+ │ ├── mocks.ts # 本地 mock 数据(开发阶段)
119
+ │ └── schema.ts # zod schema(CreateInstanceInputSchema, ...)
120
+ ├── tenant/
121
+ │ └── ...
122
+ ├── region/
123
+ │ └── ...
124
+ └── audit-log/
125
+ └── ...
126
+ ```
127
+
128
+ 约束:
112
129
 
113
- - service **不显式拼 tenantId / regionId**(走 `lib/http.ts` interceptor 自动注入)
114
- - 例外:跨云/跨租户查询接口 `*.cloud.ts`,可显式 `headers: { 'X-Tenant-Id': tenantId }` 覆盖
130
+ - 一个领域 = 一个子目录,**不按技术类型拆**
131
+ - api.ts 是纯函数层,**不依赖 React**;**不显式拼 tenantId / regionId**(走 `lib/http.ts` interceptor 自动注入)
132
+ - 例外:跨云/跨租户查询接口 `api.cloud.ts`,可显式 `headers: { 'X-Tenant-Id': tenantId }` 覆盖
133
+ - hooks.ts 调同目录 api.ts,**禁止**直接 fetch
134
+ - types.ts 仅类型,无运行时代码
135
+ - 同领域内用相对路径 `./types`、`./api`
115
136
 
116
137
  ### `src/hooks/`
117
138
 
118
- **职责**:跨页面复用的自定义 Hook
139
+ **职责**:跨领域共享的通用 Hook(极少数)。
119
140
 
120
141
  ```
121
142
  src/hooks/
122
- ├── useInstanceList.ts # 数据 hook(queryKey 必须含 tenantId/regionId)
123
- ├── useCreateInstance.ts
124
- ├── useTenant.ts # 读取 TenantContext + 切换 active tenant
125
- ├── useRegion.ts
126
- ├── useCloudProvider.ts
127
- ├── useDangerConfirm.ts # 危险操作(输入资源名才执行)
128
- └── useDebounce.ts
143
+ ├── useDangerConfirm.ts # 危险操作(输入资源名才执行,跨领域通用)
144
+ ├── useDebounce.ts # 通用 UI hook
145
+ └── useMediaQuery.ts
129
146
  ```
130
147
 
131
148
  约束:
132
149
 
133
- - 命名必须 `use` 开头
134
- - hook = 单文件
135
- - 数据 hook 调 `src/services/`,**禁止**直接 fetch
136
- - queryKey **必须**包含 `tenantId` / `regionId`(见 [`api-layering.md`](api-layering.md) §3)
150
+ - 领域数据 hook **不放这里**,放 `src/features/<domain>/hooks.ts`
151
+ - 只放与业务领域无关的通用 hook(如 useDebounce、useMediaQuery)及跨领域通用业务 hook(如 useDangerConfirm)
137
152
 
138
153
  ### `src/contexts/`
139
154
 
@@ -180,13 +195,13 @@ src/contexts/
180
195
 
181
196
  ### `src/types/`
182
197
 
183
- **职责**:类型声明。
198
+ **职责**:跨领域通用类型声明。
184
199
 
185
200
  约束:
186
201
 
187
202
  - 只放 `.ts` 类型文件,**不导出运行时代码**
188
- - domain 拆:`instance.ts`、`tenant.ts`、`region.ts`、`cloud.ts`
189
- - 通用类型放 `api.ts`、`common.ts`
203
+ - Domain 类型放 `src/features/<domain>/types.ts`,**不放这里**
204
+ - 仅放跨领域通用类型:`api.ts`(ApiResponse<T>、Pagination<T>)、`common.ts`
190
205
 
191
206
  ### `src/utils/`
192
207
 
@@ -194,9 +209,9 @@ src/contexts/
194
209
 
195
210
  约束:
196
211
 
197
- - **不依赖 React**(用了 React 应该是 hook,放 `src/hooks/`)
212
+ - **不依赖 React**(用了 React 应该是 hook,放 `src/features/<domain>/hooks.ts` 或 `src/hooks/`)
198
213
  - **不依赖 DOM**(用了 DOM 应该是 lib 层包装)
199
- - **不发请求**(发请求是 service)
214
+ - **不发请求**(发请求是 features/<domain>/api.ts)
200
215
  - 一个文件一个主题:`format.ts`、`validate.ts`、`array.ts`、`cloud.ts`(云厂商显示名映射)
201
216
 
202
217
  ### `src/lib/`
@@ -215,7 +230,7 @@ src/lib/
215
230
  约束:
216
231
 
217
232
  - 这一层**与 React 解耦**,可以在 Node / 测试中跑
218
- - **不放业务逻辑** —— 业务逻辑去 service / hook
233
+ - **不放业务逻辑** —— 业务逻辑去 features/<domain>/api.ts / hooks.ts
219
234
  - `active-context.ts` 是 Context 与 interceptor 的桥梁:Context Provider 内 setter 双向同步它
220
235
 
221
236
  ### `src/routes/`
@@ -240,7 +255,7 @@ src/routes/
240
255
  | 目录 | kebab-case | `src/pages/instance-detail/` |
241
256
  | React 组件文件 | PascalCase.tsx | `InstanceStatusBadge.tsx` |
242
257
  | Hook 文件 | camelCase.ts,`use` 前缀 | `useInstanceList.ts` |
243
- | Service 文件 | camelCase.ts,domain 名 | `instance.ts` / `instance.cloud.ts` |
258
+ | API 文件 | camelCase.ts,domain 名 | `api.ts` / `api.cloud.ts` |
244
259
  | 工具文件 | camelCase.ts | `formatDate.ts` |
245
260
  | 类型文件 | camelCase.ts,domain 名 | `instance.ts` |
246
261
  | 常量文件 | camelCase.ts 或 SCREAMING_SNAKE 导出 | `constants.ts` |
@@ -256,11 +271,11 @@ src/routes/
256
271
  // ✅
257
272
  import { Button } from '@teamix-evo/ui';
258
273
  import { Topbar } from 'biz-ui/uni-manager/um-topbar';
259
- import { listInstances } from '@/services/instance';
274
+ import { listInstances } from '@/features/instance/api';
260
275
  import { CloudBadge } from '@/components/cloud/CloudBadge';
261
276
 
262
277
  // ❌
263
- import { listInstances } from '../../../services/instance';
278
+ import { listInstances } from '../../../features/instance/api';
264
279
  ```
265
280
 
266
281
  ---
@@ -268,22 +283,22 @@ import { listInstances } from '../../../services/instance';
268
283
  ## Import 边界(单向依赖)
269
284
 
270
285
  ```
271
- pages ──▶ components, hooks, services, types, utils, lib, stores, contexts
272
- components ──▶ hooks, services, types, utils, lib, contexts (NOT pages)
273
- hooks ──▶ services, types, utils, lib, stores, contexts (NOT components, NOT pages)
274
- services ──▶ types, lib (NOT hooks, NOT components, NOT pages, NOT contexts)
275
- contexts ──▶ types, utils, lib (NOT services, NOT hooks)
276
- stores ──▶ types, utils, lib (NOT services, NOT hooks)
277
- types ──▶ (no runtime imports)
278
- utils ──▶ types only
279
- lib ──▶ types only
286
+ pages ──▶ components, features, hooks, types, utils, lib, stores, contexts
287
+ components ──▶ features, hooks, types, utils, lib, contexts (NOT pages)
288
+ features/* ──▶ lib, types (内部 api→types, hooks→api+types; NOT components, NOT pages)
289
+ hooks ──▶ types, utils, lib (NOT features, NOT components, NOT pages)
290
+ contexts ──▶ types, utils, lib (NOT features, NOT hooks)
291
+ stores ──▶ types, utils, lib (NOT features, NOT hooks)
292
+ types ──▶ (no runtime imports)
293
+ utils ──▶ types only
294
+ lib ──▶ types only
280
295
  ```
281
296
 
282
297
  要点:
283
298
 
284
- - **services 不依赖 React** —— 它是纯函数层(tenant/region 走 interceptor,不在 service 显式读 Context)
299
+ - **features/*/api.ts 不依赖 React** —— 它是纯函数层(tenant/region 走 interceptor,不在 api 显式读 Context)
285
300
  - **types / utils / lib 是叶子层** —— 不依赖业务层,谁都能引
286
- - **反向依赖直接否决**(component 不能 import page,service 不能 import hook)
301
+ - **反向依赖直接否决**(component 不能 import page,api 不能 import hook)
287
302
 
288
303
  ---
289
304
 
@@ -292,10 +307,10 @@ lib ──▶ types only
292
307
  | 反模式 | 为什么禁 | 应该怎么做 |
293
308
  | ----------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
294
309
  | `src/views/`、`src/screens/` 与 `src/pages/` 并存 | 同义层,认知爆炸 | 二选一,统一叫 `pages/` |
295
- | `src/api/` 与 `src/services/` 并存 | 同义层 | 统一叫 `services/` |
310
+ | `src/services/` 与 `src/features/` 并存 | 同义层 | 统一用 `features/` |
296
311
  | `src/utils/` 与 `src/helpers/` 并存 | 同义层 | 统一叫 `utils/` |
297
312
  | 在 `src/components/` 里写 page-only 组件 | 抽象层级别错位 | 放 `src/pages/<id>/_components/` |
298
- | 在 `src/utils/` 里写 React Hook | 边界错位 | `src/hooks/` |
313
+ | 在 `src/utils/` 里写 React Hook | 边界错位 | 领域 hook → `features/<domain>/hooks.ts`;通用 hook → `src/hooks/` |
299
314
  | 一个文件 export 5 个不相关的工具 | 命名失焦 | 拆成多文件,一个主题一个文件 |
300
315
  | `index.ts` 大量 `export *` 当 barrel | 影响 tree-shaking、循环依赖 | 显式 named export 需要的几个 |
301
316
  | 为消除一处 props drilling 直接引 zustand | 升级过度,徒增心智 | 用 React `Context` |
@@ -307,7 +322,7 @@ lib ──▶ types only
307
322
  | 测试堆在顶层 `tests/` 目录 | 改文件忘记移动测试,易漂移 | `*.test.ts(x)` 就近紧邻被测文件 |
308
323
  | `alert()` / `window.location.href = ...` | 阻断、丑、丢状态 | `toast.error()` / `useNavigate()` |
309
324
  | 自建 `Topbar.tsx` 替代 `biz-ui/uni-manager/um-topbar` | 一致性三件套 UM1 红线 | 直接用 `um-topbar`(见 [reuse-first.md](reuse-first.md)) |
310
- | service 函数手动拼接 `tenantId` | 上下文与传输层耦合,散点漂移 | `lib/http.ts` interceptor 集中注入(见 [`api-layering.md`](api-layering.md)) |
325
+ | api 函数手动拼接 `tenantId` | 上下文与传输层耦合,散点漂移 | `lib/http.ts` interceptor 集中注入(见 [`api-layering.md`](api-layering.md)) |
311
326
  | 危险删除接口只用 `confirm("确定?")` | UM 红线,误操作概率高 | `useDangerConfirm`(见 [`forms-and-validation.md`](forms-and-validation.md) §10) |
312
327
 
313
328
  ---
@@ -319,15 +334,15 @@ lib ──▶ types only
319
334
  | 新增实例列表页 | `src/pages/instances/index.tsx` |
320
335
  | 新增实例状态徽章(跨页面用) | `src/components/InstanceStatusBadge.tsx` |
321
336
  | 新增实例状态徽章(只在实例页用) | `src/pages/instances/_components/InstanceStatusBadge.tsx` |
322
- | 新增"释放实例"接口调用 | `src/services/instance.ts` 加 `releaseInstance` |
323
- | 新增"释放实例"的 mutation hook | `src/hooks/useReleaseInstance.ts`(用 useDangerConfirm 包装) |
337
+ | 新增"释放实例"接口调用 | `src/features/instance/api.ts` 加 `releaseInstance` |
338
+ | 新增"释放实例"的 mutation hook | `src/features/instance/hooks.ts` 加 `useReleaseInstance`(用 useDangerConfirm 包装) |
324
339
  | 新增日期格式化函数 | `src/utils/format.ts`(先 grep 是否已存在) |
325
340
  | 新增鉴权 + 租户 header 拦截器 | `src/lib/http.ts` 加 interceptor;active 值放 `src/lib/active-context.ts` |
326
- | 新增实例 domain 类型 | `src/types/instance.ts` |
341
+ | 新增实例 domain 类型 | `src/features/instance/types.ts` |
327
342
  | 跨少量组件分享当前用户 / 主题 / 租户 / 区域 | `src/contexts/<Name>Context.tsx`(同时同步 `lib/active-context.ts`) |
328
343
  | 跨域高频全局态(首次需要) | `src/stores/useXxxStore.ts`(按需引 zustand,固定后只用一种) |
329
- | 列表 / 详情等服务端数据 | `src/hooks/useXxx.ts` 包 `react-query`,queryKey 含 tenantId/regionId,**不进** store / Context |
330
- | 新增"创建实例表单" | `src/pages/instances/new/_components/CreateInstanceForm.tsx` + `src/services/instance.schema.ts` |
344
+ | 列表 / 详情等服务端数据 | `src/features/<domain>/hooks.ts` 包 `react-query`,queryKey 含 tenantId/regionId,**不进** store |
345
+ | 新增"创建实例表单" | `src/pages/instances/new/_components/CreateInstanceForm.tsx` + `src/features/instance/schema.ts` |
331
346
  | 跨页面复用表单(地址 / 标签编辑) | `src/components/forms/<Name>Form.tsx` + 对应 schema |
332
347
  | 新增路由 / 页面入口 | `src/routes/index.tsx` 注册 + `src/pages/<id>/index.tsx`(default export,被 `React.lazy` 引入) |
333
348
  | 新增鉴权 / 角色守卫 | `src/routes/guards.tsx` |
@@ -335,7 +350,7 @@ lib ──▶ types only
335
350
  | 跨云资源 404 兜底(资源被删除/迁移) | `src/components/cloud/CloudResourceNotFound.tsx`(见 [`error-and-loading.md`](error-and-loading.md) §7) |
336
351
  | Sentry / 日志上报包装 | `src/lib/monitor.ts`(`reportError(err, extra?)`) |
337
352
  | 测试基础设施(msw handler / 自定义 render) | `src/test/handlers.ts` / `src/test/render.tsx` |
338
- | 纯函数 / service / hook 的测试 | 同目录 `<name>.test.ts(x)`(就近,不集中) |
339
- | 跨云查询(并发多 cloud / 显式 tenantId 覆盖) | `src/services/<domain>.cloud.ts`(显式传 header,**不**走 active-context) |
353
+ | 纯函数 / api / hook 的测试 | 同目录 `<name>.test.ts(x)`(就近,不集中) |
354
+ | 跨云查询(并发多 cloud / 显式 tenantId 覆盖) | `src/features/<domain>/api.cloud.ts`(显式传 header,**不**走 active-context) |
340
355
  | 危险确认对话框(输入资源名才执行) | `src/components/danger/DangerConfirmDialog.tsx` + `src/hooks/useDangerConfirm.ts` |
341
356
  | 全局 topbar(品牌 / 租户切换 / 区域切换 / 用户菜单) | **直接用** `biz-ui/uni-manager/um-topbar`,**不要**自建 |
@@ -22,22 +22,24 @@
22
22
  ## Schema 落点
23
23
 
24
24
  ```
25
- src/services/
26
- ├── instance.ts # 请求函数:listInstances、createInstance
27
- └── instance.schema.ts # zod schema:CreateInstanceInput、UpdateInstanceInput
25
+ src/features/instance/
26
+ ├── api.ts # 请求函数:listInstances、createInstance
27
+ ├── schema.ts # zod schema:CreateInstanceInput、UpdateInstanceInput
28
+ ├── hooks.ts # react-query 封装
29
+ └── types.ts # 领域类型
28
30
  ```
29
31
 
30
32
  约定:
31
33
 
32
- - **每个 domain 一个 `<domain>.schema.ts`**,与 service 同目录
34
+ - **每个 domain 一个 `schema.ts`**,与 api / hooks / types 同目录
33
35
  - 从 schema 派生类型:`export type CreateInstanceInput = z.infer<typeof CreateInstanceInputSchema>`
34
- - service 的入参 / 返回类型**优先用** schema 推导,而不是手写 interface
36
+ - api 的入参 / 返回类型**优先用** schema 推导,而不是手写 interface
35
37
  - **不要**把 schema 写在页面 / 组件文件里 —— schema 是 domain 资产,跨页面复用
36
38
 
37
39
  ### 示例
38
40
 
39
41
  ```ts
40
- // src/services/instance.schema.ts
42
+ // src/features/instance/schema.ts
41
43
  import { z } from 'zod';
42
44
 
43
45
  export const CreateInstanceInputSchema = z.object({
@@ -55,10 +57,10 @@ export type CreateInstanceInput = z.infer<typeof CreateInstanceInputSchema>;
55
57
  ```
56
58
 
57
59
  ```ts
58
- // src/services/instance.ts
60
+ // src/features/instance/api.ts
59
61
  import { http } from '@/lib/http';
60
- import type { CreateInstanceInput } from './instance.schema';
61
- import type { Instance } from '@/types/instance';
62
+ import type { CreateInstanceInput } from './schema';
63
+ import type { Instance } from './types';
62
64
 
63
65
  export async function createInstance(
64
66
  input: CreateInstanceInput,
@@ -100,11 +102,11 @@ src/components/forms/
100
102
  // src/pages/instances/new/_components/CreateInstanceForm.tsx
101
103
  import { useForm } from 'react-hook-form';
102
104
  import { zodResolver } from '@hookform/resolvers/zod';
103
- import { useCreateInstance } from '@/hooks/useCreateInstance';
105
+ import { useCreateInstance } from '@/features/instance/hooks';
104
106
  import {
105
107
  CreateInstanceInputSchema,
106
108
  type CreateInstanceInput,
107
- } from '@/services/instance.schema';
109
+ } from '@/features/instance/schema';
108
110
  import { Button, Input, Form, FormField, FormItem } from '@/components/ui';
109
111
 
110
112
  export function CreateInstanceForm() {
@@ -160,7 +162,7 @@ export function CreateInstanceForm() {
160
162
  - ❌ 在 `onChange` 里手写 `if (value.length > 20) setError(...)`
161
163
  - ❌ 校验报错信息硬编码在组件里("请输入正确的实例名") —— 写在 schema 里,UI 只读
162
164
  - ❌ 表单状态用 `useState`,然后再加 `useEffect` 同步校验 —— 直接用 `useForm`
163
- - ❌ schema 在组件文件里就地定义 —— 抽到 `<domain>.schema.ts`
165
+ - ❌ schema 在组件文件里就地定义 —— 抽到 `features/<domain>/schema.ts`
164
166
  - ❌ 提交逻辑写在 `<form onSubmit>` 里直接 fetch —— 走 mutation hook
165
167
 
166
168
  ---
@@ -452,10 +454,10 @@ pnpm add react-hook-form zod @hookform/resolvers
452
454
  ```
453
455
  ## 表单改动
454
456
 
455
- - src/services/instance.schema.ts: 新增 CreateInstanceInputSchema(zod)、派生类型
456
- - src/services/instance.ts: createInstance 入参用派生类型(不显式拼 tenantId)
457
+ - src/features/instance/schema.ts: 新增 CreateInstanceInputSchema(zod)、派生类型
458
+ - src/features/instance/api.ts: createInstance 入参用派生类型(不显式拼 tenantId)
457
459
  - src/pages/instances/new/_components/CreateInstanceForm.tsx: useForm + zodResolver + useBlocker(脏态拦截)
458
- - src/hooks/useCreateInstance.ts: useMutation 包 createInstance,onError 区分字段级 / 通用
460
+ - src/features/instance/hooks.ts: useCreateInstance — useMutation 包 createInstance,onError 区分字段级 / 通用
459
461
  - 危险操作: ✅ 释放实例走 useDangerConfirm(输入 instance.id 校验)
460
462
  - 切租户保护: ✅ 表单 isDirty 时拦截 search 变化
461
463
  ```
@@ -40,8 +40,8 @@
40
40
 
41
41
  ### 查询顺序(从快到慢)
42
42
 
43
- 1. **MCP `list_components`** —— 一次列全部,扫描是否有同名 / 近义。
44
- 2. **MCP `find_components` query=<关键词>** —— 子串匹配 id / name / description
43
+ 1. **读 `.teamix-evo/meta/ui/manifest.json`** —— 一次列全部,扫描是否有同名 / 近义。
44
+ 2. **`grep <关键词> .teamix-evo/meta/ui/manifest.json`** —— 子串匹配 id / name / description;需要详细 Props/示例时读 `.teamix-evo/meta/ui/<id>.md`。
45
45
  3. **本项目 grep** —— `grep -r "from '@/components/ui'" src/` 看哪些已经装机。
46
46
  4. **本项目 `.teamix-evo/ui/manifest.lock.json`** —— 已装机的 ui entry 清单。
47
47
 
@@ -111,21 +111,18 @@
111
111
 
112
112
  ### 页面骨架来源(列表 / 详情 / 表单 / Console Home)
113
113
 
114
- > ⚠️ **AI 默认不调用 `@teamix-evo/templates` 包**(见 ADR 0031 skill-templates-decoupling)。页面骨架按下列优先级生成:
115
-
116
114
  1. **首选** —— 读 `teamix-evo-design-uni-manager` skill 的 [`patterns/{list|detail|form|dashboard}-page.md`](../teamix-evo-design-uni-manager/patterns/),按 Zone Map 与决策树用 ui 原子件 + biz-ui/uni-manager 实物件(`um-topbar`) + 概念占位拼装表**直接拼装**到 `src/pages/<id>/`
117
- 2. **patterns/ 未覆盖时** —— 按业界流行的云管 / 控制台页面架构(antd Pro / aliyun-console / shadcn Examples 等)与用户描述自由实现,**不要回退到 templates 包**
118
- 3. **`@teamix-evo/templates` 包与 CLI 命令仍保留**,但仅在用户**显式**要求时才走 `teamix-evo templates add`
115
+ 2. **patterns/ 未覆盖时** —— 按业界流行的云管 / 控制台页面架构(antd Pro / aliyun-console / shadcn Examples 等)与用户描述自由实现
119
116
 
120
117
  ### 决策表
121
118
 
122
119
  | 场景 | 选择 |
123
120
  | ---------------------------------------------------- | -------------------------------------------------------------- |
124
121
  | 列表页 / 详情页 / 表单页 / Console Home 骨架 | 读 design skill `patterns/*.md` → ui + biz-ui/uni-manager 拼装 |
125
- | “带筛选的资源表格”、“带审批流的工单卡片” | 优先 `biz-ui` |
122
+ | “带筛选的资源表格”、”带审批流的工单卡片” | 优先 `biz-ui` |
126
123
  | 一次性的、强领域绑定的组合 | 写在 `src/components/<domain>/` 里,**不要污染 biz-ui** |
127
124
  | uni-manager 跨云专属(CloudBadge / OperationLog 等) | 走 §2 概念占位拼装表 |
128
- | patterns/ 未覆盖的特殊页面 | 按业界流行架构 + 用户描述自由实现,**不调用 templates 包** |
125
+ | patterns/ 未覆盖的特殊页面 | 按业界流行架构 + 用户描述自由实现 |
129
126
 
130
127
  ---
131
128
 
@@ -135,7 +132,7 @@
135
132
 
136
133
  1. **本项目 grep**:
137
134
  ```bash
138
- grep -rn "function <近义名>" src/utils/ src/lib/ src/hooks/
135
+ grep -rn "function <近义名>" src/utils/ src/lib/ src/features/
139
136
  ```
140
137
  2. **`@teamix-evo/*` 是否提供**(少数情况,如格式化、token 工具)
141
138
  3. **依赖里的成熟库** —— 例如:
@@ -167,7 +164,7 @@
167
164
  1. **命名要能让下次复用查得到** —— 起 `InstanceStatusBadge` 而不是 `MyBadge`;`useInstanceList` 而不是 `useList`
168
165
  2. **抽象层级别低于一次性页面** —— 跨页面会用的才放 `src/components/`,只在一个页面用的写在 `src/pages/<id>/_components/`
169
166
  3. **暴露面尽量窄** —— 默认只 `export` 这次需要的 API,别一次性 `export *`
170
- 4. **写完登记** —— 在 PR 描述里明确“新增 `<path>`,因为 ui / biz-ui / patterns / 本项目均未提供 X 能力”(注:不再以 `templates` 包作为兑底依据,见 ADR 0031 skill-templates-decoupling)
167
+ 4. **写完登记** —— 在 PR 描述里明确”新增 `<path>`,因为 ui / biz-ui / patterns / 本项目均未提供 X 能力”
171
168
  5. **uni-manager 专属**:跨云 / 跨租户的 component 必须放 `src/components/cloud/` 或 `src/components/<domain>/`,方便后续抽到 biz-ui/uni-manager
172
169
 
173
170
  ---
@@ -321,7 +321,7 @@ mutation.mutate(values, {
321
321
  | ---------- | --------------------------- | ---------------- | ------------------------------------ |
322
322
  | `?tenant=` | tenantId | um-topbar 切换时 | TenantContextGuard / TenantProvider |
323
323
  | `?region=` | regionId | um-topbar 切换时 | TenantContextGuard / RegionProvider |
324
- | `?cloud=` | cloudProvider(aws/aliyun..) | 跨云页面 toolbar | 跨云 service hook(覆盖 active cloud) |
324
+ | `?cloud=` | cloudProvider(aws/aliyun..) | 跨云页面 toolbar | 跨云 api hook(覆盖 active cloud) |
325
325
 
326
326
  ### um-topbar 集成示例
327
327
 
@@ -29,12 +29,12 @@ src/
29
29
  ├── utils/
30
30
  │ ├── format.ts
31
31
  │ └── format.test.ts # ← 紧邻
32
- ├── services/
33
- ├── instance.ts
34
- └── instance.test.ts
35
- ├── hooks/
36
- ├── useInstanceList.ts
37
- └── useInstanceList.test.ts
32
+ ├── features/
33
+ └── instance/
34
+ ├── api.ts
35
+ ├── api.test.ts # ← 紧邻
36
+ ├── hooks.ts
37
+ └── hooks.test.tsx
38
38
  └── components/
39
39
  ├── InstanceStatusBadge.tsx
40
40
  └── InstanceStatusBadge.test.tsx
@@ -60,19 +60,19 @@ src/test/
60
60
 
61
61
  按 ROI 排序:
62
62
 
63
- | 优先级 | 类型 | 例子 | 必测? |
64
- | ------ | --------------------------------------- | ---------------------------------------------- | -------------- |
65
- | P0 | 纯函数(`src/utils/`、`src/services/`) | `formatMoney`、`isValidInstance` | ✅ **必测** |
66
- | P0 | zod schema | `CreateInstanceInputSchema.parse(...)` | ✅ **必测** |
67
- | P0 | 关键业务路径(创建实例 / 释放 / 切租户) | E2E-style 组件测 | ✅ **必测** |
68
- | P0 | **interceptor / active-context 切换** | 切租户后请求带新 X-Tenant-Id,旧缓存 invalidate | ✅ **必测** |
69
- | P0 | **danger 流程(useDangerConfirm)** | 输入正确名才 enable 按钮、关闭后状态重置 | ✅ **必测** |
70
- | P1 | Hook(数据 hook + 自定义 UI hook) | `useInstanceList`、`useDebounce` | 推荐 |
71
- | P1 | 复用业务组件(`src/components/`) | `CloudBadge` 各 cloud 渲染 | 推荐 |
72
- | P2 | 页面组件 | 用 RTL 渲染 + msw mock | 可选 |
73
- | ❌ | ui 包源码 | 已在 `@teamix-evo/ui` 包内测过 | **不要重复测** |
74
- | ❌ | biz-ui/uni-manager 包源码(如 um-topbar) | 已在 biz-ui 包内测过 | **不要重复测** |
75
- | ❌ | 三方库 | `react-router`、`react-query` | **不要测** |
63
+ | 优先级 | 类型 | 例子 | 必测? |
64
+ | ------ | -------------------------------------------- | ---------------------------------------------- | -------------- |
65
+ | P0 | 纯函数(`src/utils/`、`features/*/api.ts`) | `formatMoney`、`isValidInstance` | ✅ **必测** |
66
+ | P0 | zod schema | `CreateInstanceInputSchema.parse(...)` | ✅ **必测** |
67
+ | P0 | 关键业务路径(创建实例 / 释放 / 切租户) | E2E-style 组件测 | ✅ **必测** |
68
+ | P0 | **interceptor / active-context 切换** | 切租户后请求带新 X-Tenant-Id,旧缓存 invalidate | ✅ **必测** |
69
+ | P0 | **danger 流程(useDangerConfirm)** | 输入正确名才 enable 按钮、关闭后状态重置 | ✅ **必测** |
70
+ | P1 | Hook(数据 hook + 自定义 UI hook) | `useInstanceList`、`useDebounce` | 推荐 |
71
+ | P1 | 复用业务组件(`src/components/`) | `CloudBadge` 各 cloud 渲染 | 推荐 |
72
+ | P2 | 页面组件 | 用 RTL 渲染 + msw mock | 可选 |
73
+ | ❌ | ui 包源码 | 已在 `@teamix-evo/ui` 包内测过 | **不要重复测** |
74
+ | ❌ | biz-ui/uni-manager 包源码(如 um-topbar) | 已在 biz-ui 包内测过 | **不要重复测** |
75
+ | ❌ | 三方库 | `react-router`、`react-query` | **不要测** |
76
76
 
77
77
  **红线**:不要为了凑覆盖率数字测 `Button` 能不能渲染、`React.useState` 能不能用。
78
78
 
@@ -161,9 +161,9 @@ beforeEach(() => {
161
161
  ```
162
162
 
163
163
  ```ts
164
- // src/services/instance.test.ts
164
+ // src/features/instance/api.test.ts
165
165
  import { describe, it, expect } from 'vitest';
166
- import { listInstances } from './instance';
166
+ import { listInstances } from './api';
167
167
  import { setActiveTenant } from '@/lib/active-context';
168
168
 
169
169
  describe('listInstances', () => {
@@ -224,10 +224,10 @@ export function renderHookWithProviders<R, P>(
224
224
  ```
225
225
 
226
226
  ```tsx
227
- // src/hooks/useInstanceList.test.tsx
227
+ // src/features/instance/hooks.test.tsx
228
228
  import { waitFor } from '@testing-library/react';
229
229
  import { renderHookWithProviders } from '@/test/render';
230
- import { useInstanceList } from './useInstanceList';
230
+ import { useInstanceList } from './hooks';
231
231
 
232
232
  it('加载实例列表(queryKey 含 tenantId)', async () => {
233
233
  const { result } = renderHookWithProviders(() =>
@@ -320,18 +320,18 @@ it('切换租户:invalidate 旧 query + 新请求带新 tenantId', async () => {
320
320
 
321
321
  ## §5 · 反模式速查
322
322
 
323
- | 反模式 | 为什么禁 | 应该 |
324
- | ---------------------------------------- | --------------------------------- | ----------------------------------------------- |
325
- | 测 `setState` 是否被调用 | 测了实现,重构即崩 | 测渲染结果 / 用户行为 |
326
- | `getByTestId` 当默认查询 | data-testid 跟可访问性绑不上 | `getByRole` / `getByLabelText` 优先 |
327
- | 一个 it 里 expect 10 次无关断言 | 失败定位难 | 拆多个 it |
328
- | mock 整个 hook(`vi.mock('@/hooks/...')`) | 失去集成价值 | msw mock 后端,hook 真跑 |
329
- | 自己 mock fetch / axios | 与生产代码路径不一致 | 用 msw |
330
- | msw handler 不校验 X-Tenant-Id | interceptor 失效不会被发现 | 每个 handler `assertTenantHeader(request)` |
331
- | 测试间不重置 active-context | 上一 test 污染下一 test 的 tenant | `beforeEach` 重置 |
332
- | 测试里写 `setTimeout(..., 1000)` 等异步 | 慢且不稳定 | `await waitFor` / `findBy*` |
333
- | 全局 mock console.error 静默 | 错过真实告警 | 让测试输出 console.error,但 setup 里 fail on it |
334
- | 追求 80% 覆盖率刷数字 | 测了没价值的代码 | 按"优先级表"测,不看覆盖率盲冲 |
323
+ | 反模式 | 为什么禁 | 应该 |
324
+ | --------------------------------------------- | --------------------------------- | ----------------------------------------------- |
325
+ | 测 `setState` 是否被调用 | 测了实现,重构即崩 | 测渲染结果 / 用户行为 |
326
+ | `getByTestId` 当默认查询 | data-testid 跟可访问性绑不上 | `getByRole` / `getByLabelText` 优先 |
327
+ | 一个 it 里 expect 10 次无关断言 | 失败定位难 | 拆多个 it |
328
+ | mock 整个 hook(`vi.mock('@/features/...')`) | 失去集成价值 | msw mock 后端,hook 真跑 |
329
+ | 自己 mock fetch / axios | 与生产代码路径不一致 | 用 msw |
330
+ | msw handler 不校验 X-Tenant-Id | interceptor 失效不会被发现 | 每个 handler `assertTenantHeader(request)` |
331
+ | 测试间不重置 active-context | 上一 test 污染下一 test 的 tenant | `beforeEach` 重置 |
332
+ | 测试里写 `setTimeout(..., 1000)` 等异步 | 慢且不稳定 | `await waitFor` / `findBy*` |
333
+ | 全局 mock console.error 静默 | 错过真实告警 | 让测试输出 console.error,但 setup 里 fail on it |
334
+ | 追求 80% 覆盖率刷数字 | 测了没价值的代码 | 按"优先级表"测,不看覆盖率盲冲 |
335
335
 
336
336
  ---
337
337
 
@@ -388,8 +388,8 @@ pnpm exec msw init public/ --save # 浏览器 worker
388
388
  ## 测试覆盖
389
389
 
390
390
  - 新增 src/utils/cloud.ts → 同目录 cloud.test.ts(formatCloudProvider 4 case)
391
- - 新增 src/services/instance.ts:releaseInstance → instance.test.ts(成功 / 422 / tenant header 校验)
392
- - 新增 src/hooks/useReleaseInstance.ts → useReleaseInstance.test.tsx(success / error / 未确认 dangerName 不调 mutate)
391
+ - 新增 src/features/instance/api.ts:releaseInstance → api.test.ts(成功 / 422 / tenant header 校验)
392
+ - 新增 src/features/instance/hooks.ts:useReleaseInstancehooks.test.tsx(success / error / 未确认 dangerName 不调 mutate)
393
393
  - 复用 src/components/CloudBadge.tsx → 已有 test 覆盖 aws/aliyun/azure,无需补
394
394
  - 切租户集成测试: ✅ TenantContext.test.tsx(invalidate + 新 tenantId header)
395
395
  - 跳过测: src/pages/instances/new/index.tsx(纯组合,关键逻辑已在 hook / form 层测)