@teamix-evo/skills 0.4.0 → 0.6.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 (97) hide show
  1. package/README.md +7 -3
  2. package/manifest.json +3 -2
  3. package/package.json +2 -2
  4. package/src/teamix-evo-code-opentrek/SKILL.md +12 -10
  5. package/src/teamix-evo-code-opentrek/api-layering.md +8 -5
  6. package/src/teamix-evo-code-opentrek/checklist.md +2 -0
  7. package/src/teamix-evo-code-opentrek/error-and-loading.md +38 -25
  8. package/src/teamix-evo-code-opentrek/file-structure.md +63 -54
  9. package/src/teamix-evo-code-opentrek/forms-and-validation.md +14 -12
  10. package/src/teamix-evo-code-opentrek/reuse-first.md +2 -0
  11. package/src/teamix-evo-code-opentrek/routing-and-codesplit.md +23 -21
  12. package/src/teamix-evo-code-opentrek/testing.md +32 -28
  13. package/src/teamix-evo-code-uni-manager/SKILL.md +12 -10
  14. package/src/teamix-evo-code-uni-manager/api-layering.md +2 -0
  15. package/src/teamix-evo-code-uni-manager/checklist.md +2 -0
  16. package/src/teamix-evo-code-uni-manager/error-and-loading.md +3 -1
  17. package/src/teamix-evo-code-uni-manager/file-structure.md +2 -0
  18. package/src/teamix-evo-code-uni-manager/forms-and-validation.md +2 -0
  19. package/src/teamix-evo-code-uni-manager/reuse-first.md +3 -1
  20. package/src/teamix-evo-code-uni-manager/routing-and-codesplit.md +3 -1
  21. package/src/teamix-evo-code-uni-manager/testing.md +2 -0
  22. package/src/teamix-evo-design-opentrek/SKILL.md +213 -52
  23. package/src/teamix-evo-design-opentrek/boundaries.md +25 -5
  24. package/src/teamix-evo-design-opentrek/brand.md +7 -7
  25. package/src/teamix-evo-design-opentrek/checklist.md +15 -13
  26. package/src/teamix-evo-design-opentrek/components.md +89 -39
  27. package/src/teamix-evo-design-opentrek/examples/detail-ai-gateway-1.html +1069 -0
  28. package/src/teamix-evo-design-opentrek/examples/detail-ai-gateway-instance.html +941 -0
  29. package/src/teamix-evo-design-opentrek/examples/detail-page-api-doc.html +906 -0
  30. package/src/teamix-evo-design-opentrek/examples/detail-page-config.html +993 -0
  31. package/src/teamix-evo-design-opentrek/examples/detail-page-monitor.html +1339 -0
  32. package/src/teamix-evo-design-opentrek/examples/detail-page.html +933 -0
  33. package/src/teamix-evo-design-opentrek/examples/settings-page.html +1119 -0
  34. package/src/teamix-evo-design-opentrek/examples/standard-card-list.html +1094 -0
  35. package/src/teamix-evo-design-opentrek/examples/standard-table-list.html +1361 -0
  36. package/src/teamix-evo-design-opentrek/examples/wizard-form-page.html +877 -0
  37. package/src/teamix-evo-design-opentrek/flows.md +85 -12
  38. package/src/teamix-evo-design-opentrek/foundations.md +12 -9
  39. package/src/teamix-evo-design-opentrek/generation-flow.md +84 -14
  40. package/src/teamix-evo-design-opentrek/pages/detail-page/SKILL.md +260 -0
  41. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/api-doc-detail.md +163 -0
  42. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/comparison-detail.md +100 -0
  43. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/monitor-detail.md +190 -0
  44. package/src/teamix-evo-design-opentrek/pages/detail-page/patterns/resource-detail.md +148 -0
  45. package/src/teamix-evo-design-opentrek/pages/form-page/SKILL.md +362 -0
  46. package/src/teamix-evo-design-opentrek/pages/list-page/SKILL.md +286 -0
  47. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/action-column-spec.md +60 -0
  48. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/column-meta-rules.md +117 -0
  49. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/search-combo-spec.md +194 -0
  50. package/src/teamix-evo-design-opentrek/pages/list-page/_shared/state-action-pattern.md +51 -0
  51. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/advanced-filter-list.md +94 -0
  52. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/card-list.md +558 -0
  53. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/drawer-list.md +76 -0
  54. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/expandable-list.md +70 -0
  55. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/l2-sidebar-list.md +73 -0
  56. package/src/teamix-evo-design-opentrek/pages/list-page/patterns/standard-list.md +198 -0
  57. package/src/teamix-evo-design-opentrek/patterns/color-mapping.md +96 -0
  58. package/src/teamix-evo-design-opentrek/patterns/dashboard.md +2 -0
  59. package/src/teamix-evo-design-opentrek/patterns/detail-page.md +218 -152
  60. package/src/teamix-evo-design-opentrek/patterns/form-page.md +437 -228
  61. package/src/teamix-evo-design-opentrek/patterns/list-page.md +221 -260
  62. package/src/teamix-evo-design-opentrek/patterns/page-types.md +40 -125
  63. package/src/teamix-evo-design-opentrek/philosophy.md +7 -5
  64. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AGENT RUNTIME.svg +1 -0
  65. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI GATEWAY.svg +1 -0
  66. package/src/teamix-evo-design-opentrek/rules/_assets/OP_AI STUDIO.svg +1 -0
  67. package/src/teamix-evo-design-opentrek/rules/_assets/OP_DEV-2.svg +1 -0
  68. package/src/teamix-evo-design-opentrek/rules/_assets/OP_LOGO.svg +1 -0
  69. package/src/teamix-evo-design-opentrek/rules/_assets/OP_OPS.svg +1 -0
  70. package/src/teamix-evo-design-opentrek/rules/boundaries.rules.json +3 -3
  71. package/src/teamix-evo-design-opentrek/rules/business-mapping.json +124 -0
  72. package/src/teamix-evo-design-opentrek/rules/common-components.json +924 -0
  73. package/src/teamix-evo-design-opentrek/rules/component-specs.json +1083 -0
  74. package/src/teamix-evo-design-opentrek/rules/design-tokens.css +433 -0
  75. package/src/teamix-evo-design-opentrek/rules/design-tokens.json +2798 -0
  76. package/src/teamix-evo-design-opentrek/rules/layout-rules.json +218 -0
  77. package/src/teamix-evo-design-opentrek/rules/page-flow.json +351 -0
  78. package/src/teamix-evo-design-opentrek/rules/page-frame.json +241 -0
  79. package/src/teamix-evo-design-opentrek/rules/page-header-spec.md +123 -0
  80. package/src/teamix-evo-design-opentrek/rules/page-types.json +206 -0
  81. package/src/teamix-evo-design-opentrek/rules/sidebar-spec.md +217 -0
  82. package/src/teamix-evo-design-opentrek/rules/styling.json +188 -0
  83. package/src/teamix-evo-design-opentrek/rules/token-mapping.md +284 -0
  84. package/src/teamix-evo-design-uni-manager/SKILL.md +18 -27
  85. package/src/teamix-evo-design-uni-manager/boundaries.md +7 -4
  86. package/src/teamix-evo-design-uni-manager/brand.md +1 -1
  87. package/src/teamix-evo-design-uni-manager/components.md +33 -28
  88. package/src/teamix-evo-design-uni-manager/foundations.md +24 -21
  89. package/src/teamix-evo-design-uni-manager/generation-flow.md +46 -8
  90. package/src/teamix-evo-design-uni-manager/patterns/dashboard.md +3 -1
  91. package/src/teamix-evo-design-uni-manager/patterns/detail-page.md +42 -13
  92. package/src/teamix-evo-design-uni-manager/patterns/form-page.md +67 -30
  93. package/src/teamix-evo-design-uni-manager/patterns/list-page.md +73 -40
  94. package/src/teamix-evo-design-uni-manager/patterns/page-types.md +14 -12
  95. package/src/teamix-evo-design-uni-manager/philosophy.md +4 -2
  96. package/src/teamix-evo-design-uni-manager/rules/boundaries.rules.json +3 -3
  97. package/src/teamix-evo-manage/SKILL.md +74 -66
package/README.md CHANGED
@@ -42,15 +42,19 @@ pnpm --filter @teamix-evo/skills scaffold:skill
42
42
  由 `teamix-evo skills` 子命令族管理(source-mirror 模型见 [ADR 0013](../../docs/adr/0013-skills-source-mirror.md)):
43
43
 
44
44
  ```bash
45
- teamix-evo skills add # 全量装入 manifest 内所有 skill
46
- teamix-evo skills add <name...> # 增量:仅装入指定 skill(可多个)
45
+ teamix-evo skills init # 自举:按 variant + scope 全装符合条件的 skill(ADR 0034)
46
+ teamix-evo skills add <name...> # 增量:仅装入指定 skill(必填,至少一个)
47
47
  teamix-evo skills list # 列出全部 skill(默认:已装✓ / 未装○)
48
- teamix-evo skills update # 升级到最新版(managed 策略保留你的自定义)
48
+ teamix-evo skills update [name...] [--dry-run] # 升级已装 skill(ADR 0035 双闸 + version 短路)
49
49
  teamix-evo skills sync [name...] # 源 → IDE 镜像(漂移恢复用)
50
50
  teamix-evo skills doctor # 检测源 / 镜像漂移
51
51
  teamix-evo skills uninstall # 卸载(源 + 镜像 + lock)
52
52
  ```
53
53
 
54
+ > **verb 分工(ADR 0034)**:`init` 自举(无 ids)/ `add` 增量(必填 ids)。`skills add` 不传 id → commander 报错并输出 help。
55
+ >
56
+ > **scope 过滤(ADR 0033)**:`skills init` 跳过 manifest 里 `scope` 与当前 install scope 不匹配的 entry。entry skill `teamix-evo-manage` 标 `scope: "global"`,因此项目级 init 不会重复装入 —— 它由「全局装机」段一次性装到全局。`skills add` 显式指名 global-only skill 时仍可装入,但会得到 warning。
57
+
54
58
  ### 全局装机(`--scope global`)
55
59
 
56
60
  ```bash
package/manifest.json CHANGED
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "id": "teamix-evo-manage",
12
12
  "name": "teamix-evo-manage",
13
- "description": "Single entry point for the teamix-evo lifecycle: scaffold a new project, install the AI coding system into an existing repo, run / update / inspect / remove any teamix-evo package, and drive the placeholder→real UI migration loop after `npm create teamix-evo`.\nTRIGGER when: (CLI) user runs or asks about `teamix-evo tokens ...` / `teamix-evo skills ...` / `teamix-evo ui ...` / `teamix-evo biz-ui ...` / `teamix-evo templates ...` / `teamix-evo logs ...`, or `npm create teamix-evo` / `pnpm create teamix-evo`; (模糊初始化) \"初始化一个项目\"、\"初始化一个工程\"、\"初始化一个 teamix-evo 工程\"、\"初始化一个 Teamix Evo 项目\"、\"create a teamix-evo project\"、\"set up teamix-evo from scratch\"、\"new teamix-evo app\"; (具名变体初始化) \"初始化一个 opentrek 工程 / op 工程 / OpenTrek 项目 / 探索者项目\"、\"初始化一个云管 / 云管控制台 / 云管项目 / uni-manager 工程 / 云管工程\"、\"new opentrek/uni-manager project\"; (AI coding 接入) \"给现有仓库装 teamix-evo\"、\"现有项目装一下 skills + ui\"、\"接入 AI coding 体系\"、\"装 teamix-evo 进这个项目\"、\"add teamix-evo to existing repo\"、\"install AI coding system\"、\"接入 opentrek 研发体系\"、\"接入 op 研发体系\"、\"接入 OpenTrek 研发体系\"、\"接入 opentrek 研发系统\"、\"接入 op 研发系统\"、\"接入云管研发体系\"、\"接入云管研发系统\"、\"接入 uni-manager 研发体系\"、\"接入 uni-manager 研发系统\"、\"接入统一管理研发体系\"; (更新检测) \"升级 teamix-evo\"、\"看看哪些要升级\"、\"update teamix-evo\"、\"check what needs updating\"、\"refresh installed teamix-evo packages\"; (卸载 / 清单) \"卸载 teamix-evo\"、\"看看装了哪些 teamix-evo 资源\"、\"remove the design system\"、\"list installed\"; (placeholder→real 升级) \"升级 UI\"、\"接入真组件\"、\"替换 placeholder\"、\"upgrade UI\"、\"replace placeholders\"、\"swap in real components\"、\"make the UI real\", or user opens / edits `src/components/_placeholder/**`, project contains `.teamix-evo/create/pending-ui.json`, literal `@teamix-evo:placeholder` tag in code; (状态文件) user touches `.teamix-evo/config.json`、`.teamix-evo/manifest.json`、`.teamix-evo/create/pending-ui.json`.\nSKIP: any content task — generating components, pages, services, or reviewing screens; changes to `src/` files outside the migration loop, design tokens, or business logic. Those go to teamix-evo-code-opentrek or teamix-evo-design-opentrek. SKIP if the user is mid-flow inside an already-initialized project asking to \"新增页面 / 加按钮 / 调接口\" — that's coding work, not lifecycle. SKIP pure styling / token tweaks — those go to ESLint + `tokens.overrides.css`.\nCoordinates with: teamix-evo-design-opentrek (visual side after a screen is generated)、teamix-evo-code-opentrek (file placement / reuse rules) — manage is the entry point and precedes content skills, never co-triggers.",
13
+ "description": "Single entry point for the teamix-evo lifecycle: scaffold a new project, install the AI coding system into an existing repo, run / update / inspect / remove any teamix-evo package, and drive the placeholder→real UI migration loop after `npm create teamix-evo`.\nTRIGGER when: (CLI) user runs or asks about `teamix-evo tokens ...` / `teamix-evo skills ...` / `teamix-evo ui ...` / `teamix-evo biz-ui ...` / `teamix-evo templates ...` / `teamix-evo lint ...` / `teamix-evo logs ...`, or `npm create teamix-evo` / `pnpm create teamix-evo`; (模糊初始化) \"初始化一个项目\"、\"初始化一个工程\"、\"初始化一个 teamix-evo 工程\"、\"初始化一个 Teamix Evo 项目\"、\"create a teamix-evo project\"、\"set up teamix-evo from scratch\"、\"new teamix-evo app\"; (具名变体初始化) \"初始化一个 opentrek 工程 / op 工程 / OpenTrek 项目 / 探索者项目\"、\"初始化一个云管 / 云管控制台 / 云管项目 / uni-manager 工程 / 云管工程\"、\"new opentrek/uni-manager project\"; (AI coding 接入) \"给现有仓库装 teamix-evo\"、\"现有项目装一下 skills + ui\"、\"接入 AI coding 体系\"、\"装 teamix-evo 进这个项目\"、\"add teamix-evo to existing repo\"、\"install AI coding system\"、\"接入 opentrek 研发体系\"、\"接入 op 研发体系\"、\"接入 OpenTrek 研发体系\"、\"接入 opentrek 研发系统\"、\"接入 op 研发系统\"、\"接入云管研发体系\"、\"接入云管研发系统\"、\"接入 uni-manager 研发体系\"、\"接入 uni-manager 研发系统\"、\"接入统一管理研发体系\"; (更新检测) \"升级 teamix-evo\"、\"看看哪些要升级\"、\"update teamix-evo\"、\"check what needs updating\"、\"refresh installed teamix-evo packages\"; (卸载 / 清单) \"卸载 teamix-evo\"、\"看看装了哪些 teamix-evo 资源\"、\"remove the design system\"、\"list installed\"; (placeholder→real 升级) \"升级 UI\"、\"接入真组件\"、\"替换 placeholder\"、\"upgrade UI\"、\"replace placeholders\"、\"swap in real components\"、\"make the UI real\", or user opens / edits `src/components/_placeholder/**`, project contains `.teamix-evo/create/pending-ui.json`, literal `@teamix-evo:placeholder` tag in code; (状态文件) user touches `.teamix-evo/config.json`、`.teamix-evo/manifest.json`、`.teamix-evo/create/pending-ui.json`.\nSKIP: any content task — generating components, pages, services, or reviewing screens; changes to `src/` files outside the migration loop, design tokens, or business logic. Those go to teamix-evo-code-opentrek or teamix-evo-design-opentrek. SKIP if the user is mid-flow inside an already-initialized project asking to \"新增页面 / 加按钮 / 调接口\" — that's coding work, not lifecycle. SKIP pure styling / token tweaks — those go to ESLint + `tokens.overrides.css`.\nCoordinates with: teamix-evo-design-opentrek (visual side after a screen is generated)、teamix-evo-code-opentrek (file placement / reuse rules) — manage is the entry point and precedes content skills, never co-triggers.",
14
14
  "version": "0.2.0",
15
15
  "source": "src/teamix-evo-manage",
16
16
  "ides": [
@@ -21,7 +21,8 @@
21
21
  "managedRegions": [
22
22
  "core"
23
23
  ],
24
- "template": false
24
+ "template": false,
25
+ "scope": "global"
25
26
  },
26
27
  {
27
28
  "id": "teamix-evo-design-opentrek",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamix-evo/skills",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Skills (AI IDE capabilities) for Teamix Evo",
5
5
  "type": "module",
6
6
  "files": [
@@ -12,7 +12,7 @@
12
12
  "devDependencies": {
13
13
  "@clack/prompts": "^0.8.0",
14
14
  "tsx": "^4.0.0",
15
- "@teamix-evo/registry": "0.3.0"
15
+ "@teamix-evo/registry": "0.7.0"
16
16
  },
17
17
  "publishConfig": {
18
18
  "access": "public",
@@ -29,16 +29,18 @@ If the task is purely about _visual design_ of a screen (layout / colors / spaci
29
29
 
30
30
  ## What this skill does
31
31
 
32
- Before AI writes or commits code, it performs an **8-step gated flow**. Steps 1-4 are baseline (always run); steps 5-7 are topic gates (run when the task touches that topic); step 8 is the final self-review.
32
+ Before AI writes or commits code, it performs an **8-step gated flow**. Steps 1-4 are baseline (**必须执行,不得跳过**); steps 5-7 are topic gates (run when the task touches that topic); step 8 is the final self-review (**必须执行,不得跳过**).
33
33
 
34
- 1. **Reuse-first check** — read [`reuse-first.md`](reuse-first.md). Before creating any new component, query the `@teamix-evo/ui` registry (via MCP `list_components` / `find_components`) and grep the local project. Only write new code when no reuse path exists.
35
- 2. **Layering check** — read [`api-layering.md`](api-layering.md). Any code that talks to a backend goes under `src/services/<domain>.ts`. Components never call `fetch` / `axios` directly. Data hooks live in `src/hooks/` and consume services.
36
- 3. **Directory check** — read [`file-structure.md`](file-structure.md). Place the new file under the right top-level folder (pages / components / services / hooks / types / utils / lib / stores / contexts). Each folder has a single responsibility and an import boundary.
37
- 4. **Forms gate** — if the task involves a form, read [`forms-and-validation.md`](forms-and-validation.md). `react-hook-form` + `zod`; schema lives at `src/services/<domain>.schema.ts`. Never wire forms with raw `useState`.
38
- 5. **Error/loading gate** — if the task adds a page or data hook, read [`error-and-loading.md`](error-and-loading.md). Ensure global ErrorBoundary, page-level fallback, and three-state handling (`isPending` / `isError` / data) are in place; mutations report success/error via `toast`.
39
- 6. **Routing gate** — if the task adds a route or page-entry, read [`routing-and-codesplit.md`](routing-and-codesplit.md). Pages use `React.lazy`; auth/role guards live in `src/routes/guards.tsx`; 404 / 403 / 500 fallbacks exist.
40
- 7. **Testing gate** — read [`testing.md`](testing.md). Pure functions and zod schemas are **mandatory** to test; hooks and reusable components are recommended. Test files sit next to the source as `*.test.ts(x)`; `vitest` + `@testing-library/react` + `msw`.
41
- 8. **Self-review** — run [`checklist.md`](checklist.md). Every item must pass before declaring the change done. Anything failing must be fixed or surfaced to the user.
34
+ 1. **🚧 Reuse-first check** — **MUST READ** [`reuse-first.md`](reuse-first.md). Before creating any new component, query the `@teamix-evo/ui` registry (via MCP `list_components` / `find_components`) and grep the local project. Only write new code when no reuse path exists.
35
+ 2. **🚧 Layering check** — **MUST READ** [`api-layering.md`](api-layering.md). Any code that talks to a backend goes under `src/services/<domain>.ts`. Components never call `fetch` / `axios` directly. Data hooks live in `src/hooks/` and consume services.
36
+ 3. **🚧 Directory check** — **MUST READ** [`file-structure.md`](file-structure.md). Place the new file under the right top-level folder (pages / components / services / hooks / types / utils / lib / stores / contexts). Each folder has a single responsibility and an import boundary.
37
+ 4. **🚧 Forms gate** — if the task involves a form, **MUST READ** [`forms-and-validation.md`](forms-and-validation.md). `react-hook-form` + `zod`; schema lives at `src/services/<domain>.schema.ts`. Never wire forms with raw `useState`.
38
+ 5. **Error/loading gate** — if the task adds a page or data hook, **MUST READ** [`error-and-loading.md`](error-and-loading.md). Ensure global ErrorBoundary, page-level fallback, and three-state handling (`isPending` / `isError` / data) are in place; mutations report success/error via `toast`.
39
+ 6. **Routing gate** — if the task adds a route or page-entry, **MUST READ** [`routing-and-codesplit.md`](routing-and-codesplit.md). Pages use `React.lazy`; auth/role guards live in `src/routes/guards.tsx`; 404 / 403 / 500 fallbacks exist.
40
+ 7. **Testing gate** — **MUST READ** [`testing.md`](testing.md). Pure functions and zod schemas are **mandatory** to test; hooks and reusable components are recommended. Test files sit next to the source as `*.test.ts(x)`; `vitest` + `@testing-library/react` + `msw`.
41
+ 8. **🚧 Self-review** — **MUST READ** [`checklist.md`](checklist.md). Every item must pass before declaring the change done. Anything failing must be fixed or surfaced to the user.
42
+
43
+ > ⚠️ **禁止行为**: 跳过 Step 1 (reuse-first) 直接写新组件、跳过 Step 8 (checklist) 不做自检、凭"已有知识"省略任何文件读取。
42
44
 
43
45
  ## Inputs the user provides
44
46
 
@@ -81,7 +83,7 @@ Before AI writes or commits code, it performs an **8-step gated flow**. Steps 1-
81
83
  ## Relationship to other skills
82
84
 
83
85
  - [`teamix-evo-design-opentrek`](../teamix-evo-design-opentrek/SKILL.md) — visual / interaction rules for screen generation. Run **alongside** this skill when the task includes UI; this skill never overrides design on visual concerns.
84
- - [`teamix-evo-manage`](../teamix-evo-manage/SKILL.md) — lifecycle (`init` / `update` / `uninstall` / `skills add`). Out of scope here.
86
+ - [`teamix-evo-manage`](../teamix-evo-manage/SKILL.md) — lifecycle (`init` / `update` / `uninstall` / `skills init` / `skills add`). Out of scope here.
85
87
 
86
88
  ## Why these conventions
87
89
 
@@ -1,5 +1,7 @@
1
1
  # API 数据层规范
2
2
 
3
+ > ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 2 的强制读取项。任何涉及后端通信的代码必须先读本文件。
4
+
3
5
  > **核心原则**:组件不直接 fetch。所有与后端通信的代码都落在 `src/services/`,通过 `src/hooks/` 暴露给组件。
4
6
 
5
7
  ---
@@ -80,6 +82,7 @@ export async function createOrder(input: CreateOrderInput): Promise<Order> {
80
82
  ```
81
83
 
82
84
  要点:
85
+
83
86
  - **纯函数**(不依赖 React 上下文,可以在 SSR / Node 测试中单独跑)
84
87
  - **类型来自 `src/types/`**,不要在 service 文件里就地定义 domain 类型
85
88
  - **错误不在 service 层处理** —— 抛出去给 hook / 全局 interceptor 处理(归一化在 `src/lib/http.ts`)
@@ -201,11 +204,11 @@ src/types/
201
204
 
202
205
  某些场景**允许**绕过本规范,但需要在代码注释里说明原因:
203
206
 
204
- | 场景 | 允许 |
205
- | --- | --- |
206
- | 静态资源 fetch(读 `public/` 下的 json) | 组件直接 `fetch` 可,不用进 service |
207
- | 第三方 SDK(地图、支付)有自己的客户端 | 包装层放 `src/lib/<sdk>.ts`,不强制走 services |
208
- | 一次性的诊断 / 调试代码 | 临时 fetch 可,但 PR 合并前必须清理或归位 |
207
+ | 场景 | 允许 |
208
+ | -------------------------------------- | --------------------------------------------- |
209
+ | 静态资源 fetch(读 `public/` 下的 json) | 组件直接 `fetch` 可,不用进 service |
210
+ | 第三方 SDK(地图、支付)有自己的客户端 | 包装层放 `src/lib/<sdk>.ts`,不强制走 services |
211
+ | 一次性的诊断 / 调试代码 | 临时 fetch 可,但 PR 合并前必须清理或归位 |
209
212
 
210
213
  ---
211
214
 
@@ -1,5 +1,7 @@
1
1
  # 编码自检清单
2
2
 
3
+ > ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 8 的强制读取项。AI 写完/改完代码后必须读取并逐项核对。不得跳过。
4
+
3
5
  > AI 写完 / 改完代码后,**必须**逐项核对。任何一项未通过都不应交付,需要先修复或显式告诉用户哪条不通过、为什么。
4
6
 
5
7
  ---
@@ -1,6 +1,8 @@
1
1
  # 错误与加载态规范
2
2
 
3
- > **核心约定**:渲染错误用 `ErrorBoundary`,异步加载用 `react-query` 的 `isPending/isError`(必要时叠 `Suspense`),全局兜底必须存在。**不允许**白屏 / 红屏直出给用户。
3
+ > ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 5 的条件读取项。任务涉及页面或数据 hook 时必须读取。
4
+
5
+ > **核心约定**:渲染错误用 `ErrorBoundary`,异步加载用 `react-query` 的 `isPending/isError`(必要时叠 `Suspense`),全局兗底必须存在。**不允许**白屏 / 红屏直出给用户。
4
6
 
5
7
  ---
6
8
 
@@ -62,13 +64,21 @@ createRoot(document.getElementById('root')!).render(
62
64
  // src/components/GlobalErrorFallback.tsx
63
65
  import { Button } from '@teamix-evo/ui';
64
66
 
65
- export function GlobalErrorFallback({ resetErrorBoundary }: { resetErrorBoundary: () => void }) {
67
+ export function GlobalErrorFallback({
68
+ resetErrorBoundary,
69
+ }: {
70
+ resetErrorBoundary: () => void;
71
+ }) {
66
72
  return (
67
73
  <div className="flex min-h-screen items-center justify-center">
68
74
  <div className="text-center">
69
75
  <h1 className="text-2xl font-semibold">页面出错了</h1>
70
- <p className="text-muted-foreground mt-2">已记录,可尝试刷新或返回首页</p>
71
- <Button className="mt-4" onClick={resetErrorBoundary}>重新加载</Button>
76
+ <p className="text-muted-foreground mt-2">
77
+ 已记录,可尝试刷新或返回首页
78
+ </p>
79
+ <Button className="mt-4" onClick={resetErrorBoundary}>
80
+ 重新加载
81
+ </Button>
72
82
  </div>
73
83
  </div>
74
84
  );
@@ -130,19 +140,19 @@ function OrderListPage() {
130
140
 
131
141
  ### 三种状态都要处理
132
142
 
133
- | 状态 | 必须输出 |
134
- | --- | --- |
135
- | `isPending`(首次加载) | Skeleton / Spinner,**不允许**显示空 div |
136
- | `isError` | 错误面板 + 重试按钮,**不允许**直接 `throw` |
137
- | `isFetching`(后台刷新) | 表格右上角小转圈即可,不要遮罩 |
143
+ | 状态 | 必须输出 |
144
+ | ---------------------- | ------------------------------------------ |
145
+ | `isPending`(首次加载) | Skeleton / Spinner,**不允许**显示空 div |
146
+ | `isError` | 错误面板 + 重试按钮,**不允许**直接 `throw` |
147
+ | `isFetching`(后台刷新) | 表格右上角小转圈即可,不要遮罩 |
138
148
 
139
149
  ### Skeleton vs Spinner 怎么选
140
150
 
141
- | 场景 | 用 |
142
- | --- | --- |
143
- | 列表 / 卡片 / 详情 | Skeleton(撑住布局,避免 CLS) |
144
- | 短动作(按钮提交、< 500ms 加载) | Spinner |
145
- | 全屏切换 | PageSkeleton 或 logo loading |
151
+ | 场景 | 用 |
152
+ | ------------------------------ | ---------------------------- |
153
+ | 列表 / 卡片 / 详情 | Skeleton(撑住布局,避免 CLS) |
154
+ | 短动作(按钮提交、< 500ms 加载) | Spinner |
155
+ | 全屏切换 | PageSkeleton 或 logo loading |
146
156
 
147
157
  ---
148
158
 
@@ -177,14 +187,17 @@ react-query v5 支持 `useSuspenseQuery`,可把 loading 转给 `<Suspense>`:
177
187
 
178
188
  ```tsx
179
189
  function OrderListPage() {
180
- const { data } = useSuspenseQuery({ queryKey: ['orders'], queryFn: listOrders });
190
+ const { data } = useSuspenseQuery({
191
+ queryKey: ['orders'],
192
+ queryFn: listOrders,
193
+ });
181
194
  return <OrderTable data={data} />; // 不需要 isPending 分支
182
195
  }
183
196
 
184
197
  // 父级:
185
198
  <Suspense fallback={<TableSkeleton rows={10} />}>
186
199
  <OrderListPage />
187
- </Suspense>
200
+ </Suspense>;
188
201
  ```
189
202
 
190
203
  **何时用**:
@@ -232,15 +245,15 @@ window.addEventListener('error', (e) => reportError(e.error));
232
245
 
233
246
  ## 反模式速查
234
247
 
235
- | 反模式 | 为什么禁 | 应该 |
236
- | --- | --- | --- |
237
- | 没有全局 ErrorBoundary | 异常直接白屏 | App 根挂一层 |
238
- | `useState(false)` + `useEffect` 管 loading | 重复造轮,易漏 cancel | 走 react-query / useSuspenseQuery |
239
- | 只判 `isLoading` 不判 `isError` | 错误态显示空白 | 三态都处理 |
240
- | `if (data) return <>...</> else return null` | 无 skeleton,布局抖动 + CLS | 加 Skeleton |
241
- | `alert('保存失败')` | 阻断用户、丑、不可样式化 | `toast.error()` |
242
- | ErrorBoundary 不上报 | 错过线上 bug | `onError` 接 `reportError` |
243
- | service 里 `try/catch` 吞错 | 上层失去判断依据 | 让 error 抛上来,hook / boundary 决定 |
248
+ | 反模式 | 为什么禁 | 应该 |
249
+ | -------------------------------------------- | -------------------------- | ------------------------------------ |
250
+ | 没有全局 ErrorBoundary | 异常直接白屏 | App 根挂一层 |
251
+ | `useState(false)` + `useEffect` 管 loading | 重复造轮,易漏 cancel | 走 react-query / useSuspenseQuery |
252
+ | 只判 `isLoading` 不判 `isError` | 错误态显示空白 | 三态都处理 |
253
+ | `if (data) return <>...</> else return null` | 无 skeleton,布局抖动 + CLS | 加 Skeleton |
254
+ | `alert('保存失败')` | 阻断用户、丑、不可样式化 | `toast.error()` |
255
+ | ErrorBoundary 不上报 | 错过线上 bug | `onError` 接 `reportError` |
256
+ | service 里 `try/catch` 吞错 | 上层失去判断依据 | 让 error 抛上来,hook / boundary 决定 |
244
257
 
245
258
  ---
246
259
 
@@ -1,5 +1,7 @@
1
1
  # 业务工程目录约定
2
2
 
3
+ > ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 3 的强制读取项。AI 确定新文件放置位置前必须读完本文件。
4
+
3
5
  > 业内主流 React/Vite 业务工程的标准目录骨架。本文件是 AI 决定"新建文件应该放在哪"的唯一参考。
4
6
 
5
7
  ---
@@ -51,6 +53,7 @@ src/pages/
51
53
  ```
52
54
 
53
55
  约束:
56
+
54
57
  - 页面组件**默认 default export**,与文件名一致
55
58
  - `_components/` `_hooks/` 用下划线前缀标记**私有**,**禁止跨页面 import**
56
59
  - 跨页面用得到的组件/hook → 升到 `src/components/` `src/hooks/`
@@ -69,6 +72,7 @@ src/components/
69
72
  ```
70
73
 
71
74
  约束:
75
+
72
76
  - 命名 PascalCase,文件名 = 组件名
73
77
  - 单组件 = 单文件;复杂组件可建子目录 `<Name>/{index.tsx, types.ts, hooks.ts}`
74
78
  - **不要**重复 `@teamix-evo/ui` 已经提供的能力(见 [reuse-first.md](reuse-first.md))
@@ -91,6 +95,7 @@ src/hooks/
91
95
  ```
92
96
 
93
97
  约束:
98
+
94
99
  - 命名必须 `use` 开头
95
100
  - 单 hook = 单文件
96
101
  - 数据 hook 调 `src/services/`,**禁止**直接 fetch
@@ -99,12 +104,12 @@ src/hooks/
99
104
 
100
105
  **装机时默认不预装任何状态库**。按"最小够用"逐级升级,不要直接跳到 zustand:
101
106
 
102
- | 场景 | 用什么 |
103
- | --- | --- |
104
- | 组件内状态 | `useState` / `useReducer` |
105
- | 跨少量组件、低频更新(当前用户、主题、语言、租户) | React `Context` |
106
- | 跨域、多页面、**高频**更新的客户端态 | `src/stores/`(zustand / jotai / redux 选一) |
107
- | 服务端数据(列表、详情、表单提交) | `react-query` / `swr`,**永远不进 Context、也不进 store** |
107
+ | 场景 | 用什么 |
108
+ | ------------------------------------------------ | -------------------------------------------------------- |
109
+ | 组件内状态 | `useState` / `useReducer` |
110
+ | 跨少量组件、低频更新(当前用户、主题、语言、租户) | React `Context` |
111
+ | 跨域、多页面、**高频**更新的客户端态 | `src/stores/`(zustand / jotai / redux 选一) |
112
+ | 服务端数据(列表、详情、表单提交) | `react-query` / `swr`,**永远不进 Context、也不进 store** |
108
113
 
109
114
  「高频」的粗略判定:一个状态被 10+ 处读取、且每秒可能变化一次以上 —— Context 会触发整子树重渲染,这时才有引 store 的收益。
110
115
 
@@ -131,6 +136,7 @@ Context 文件位置:
131
136
  **职责**:类型声明。
132
137
 
133
138
  约束:
139
+
134
140
  - 只放 `.ts` 类型文件,**不导出运行时代码**
135
141
  - 按 domain 拆:`order.ts`、`user.ts`
136
142
  - 通用类型放 `api.ts`、`common.ts`
@@ -140,6 +146,7 @@ Context 文件位置:
140
146
  **职责**:纯函数工具。
141
147
 
142
148
  约束:
149
+
143
150
  - **不依赖 React**(用了 React 应该是 hook,放 `src/hooks/`)
144
151
  - **不依赖 DOM**(用了 DOM 应该是 lib 层包装)
145
152
  - **不发请求**(发请求是 service)
@@ -158,6 +165,7 @@ src/lib/
158
165
  ```
159
166
 
160
167
  约束:
168
+
161
169
  - 这一层**与 React 解耦**,可以在 Node / 测试中跑
162
170
  - **不放业务逻辑** —— 业务逻辑去 service / hook
163
171
 
@@ -177,16 +185,16 @@ src/routes/
177
185
 
178
186
  ## 命名约定
179
187
 
180
- | 类型 | 风格 | 例 |
181
- | --- | --- | --- |
182
- | 目录 | kebab-case | `src/pages/order-detail/` |
183
- | React 组件文件 | PascalCase.tsx | `OrderStatusBadge.tsx` |
184
- | Hook 文件 | camelCase.ts,`use` 前缀 | `useOrderList.ts` |
185
- | Service 文件 | camelCase.ts,domain 名 | `order.ts` |
186
- | 工具文件 | camelCase.ts | `formatDate.ts` |
187
- | 类型文件 | camelCase.ts,domain 名 | `order.ts` |
188
- | 常量文件 | camelCase.ts 或 SCREAMING_SNAKE 导出 | `constants.ts` |
189
- | 测试文件 | `*.test.ts(x)` 紧邻被测文件 | `formatDate.test.ts` |
188
+ | 类型 | 风格 | 例 |
189
+ | -------------- | ------------------------------------ | ------------------------- |
190
+ | 目录 | kebab-case | `src/pages/order-detail/` |
191
+ | React 组件文件 | PascalCase.tsx | `OrderStatusBadge.tsx` |
192
+ | Hook 文件 | camelCase.ts,`use` 前缀 | `useOrderList.ts` |
193
+ | Service 文件 | camelCase.ts,domain 名 | `order.ts` |
194
+ | 工具文件 | camelCase.ts | `formatDate.ts` |
195
+ | 类型文件 | camelCase.ts,domain 名 | `order.ts` |
196
+ | 常量文件 | camelCase.ts 或 SCREAMING_SNAKE 导出 | `constants.ts` |
197
+ | 测试文件 | `*.test.ts(x)` 紧邻被测文件 | `formatDate.test.ts` |
190
198
 
191
199
  ---
192
200
 
@@ -220,6 +228,7 @@ lib ──▶ types only
220
228
  ```
221
229
 
222
230
  要点:
231
+
223
232
  - **services 不依赖 React** —— 它是纯函数层
224
233
  - **types / utils / lib 是叶子层** —— 不依赖业务层,谁都能引
225
234
  - **反向依赖直接否决**(component 不能 import page,service 不能 import hook)
@@ -228,46 +237,46 @@ lib ──▶ types only
228
237
 
229
238
  ## 反模式速查
230
239
 
231
- | 反模式 | 为什么禁 | 应该怎么做 |
232
- | --- | --- | --- |
233
- | `src/views/`、`src/screens/` 与 `src/pages/` 并存 | 同义层,认知爆炸 | 二选一,统一叫 `pages/` |
234
- | `src/api/` 与 `src/services/` 并存 | 同义层 | 统一叫 `services/` |
235
- | `src/utils/` 与 `src/helpers/` 并存 | 同义层 | 统一叫 `utils/` |
236
- | 在 `src/components/` 里写 page-only 组件 | 抽象层级别错位 | 放 `src/pages/<id>/_components/` |
237
- | 在 `src/utils/` 里写 React Hook | 边界错位 | 放 `src/hooks/` |
238
- | 一个文件 export 5 个不相关的工具 | 命名失焦 | 拆成多文件,一个主题一个文件 |
239
- | `index.ts` 大量 `export *` 当 barrel | 影响 tree-shaking、循环依赖 | 显式 named export 需要的几个 |
240
- | 为消除一处 props drilling 直接引 zustand | 升级过度,徒增心智 | 用 React `Context` |
241
- | 服务端数据塞进 Context / store | 与 query 缓存形成双源,易漂移 | 一律走 `react-query` / `swr` |
242
- | 表单字段用一堆 `useState` 拼 | 字段越多越糟,校验散落 | `useForm` + `zodResolver`(见 [`forms-and-validation.md`](forms-and-validation.md)) |
243
- | 页面顶部 `import` 不 lazy + 无 Suspense | 首屏 bundle 爆炸 / 进入即崩 | `React.lazy` + Layout 挂 Suspense(见 [`routing-and-codesplit.md`](routing-and-codesplit.md)) |
244
- | 鉴权写在每个 page 顶部 | 散乱、易漏、UI 与 URL 两层不一致 | `src/routes/guards.tsx` 集中,URL 与 sidebar 两层都判 |
245
- | 全局没有 ErrorBoundary | 任何未捕获异常直接白屏 | App 根挂 `ErrorBoundary` + `reportError` |
246
- | 测试堆在顶层 `tests/` 目录 | 改文件忘记移动测试,易漂移 | `*.test.ts(x)` 就近紧邻被测文件 |
247
- | `alert()` / `window.location.href = ...` | 阻断、丑、丢状态 | `toast.error()` / `useNavigate()` |
240
+ | 反模式 | 为什么禁 | 应该怎么做 |
241
+ | ------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------- |
242
+ | `src/views/`、`src/screens/` 与 `src/pages/` 并存 | 同义层,认知爆炸 | 二选一,统一叫 `pages/` |
243
+ | `src/api/` 与 `src/services/` 并存 | 同义层 | 统一叫 `services/` |
244
+ | `src/utils/` 与 `src/helpers/` 并存 | 同义层 | 统一叫 `utils/` |
245
+ | 在 `src/components/` 里写 page-only 组件 | 抽象层级别错位 | 放 `src/pages/<id>/_components/` |
246
+ | 在 `src/utils/` 里写 React Hook | 边界错位 | 放 `src/hooks/` |
247
+ | 一个文件 export 5 个不相关的工具 | 命名失焦 | 拆成多文件,一个主题一个文件 |
248
+ | `index.ts` 大量 `export *` 当 barrel | 影响 tree-shaking、循环依赖 | 显式 named export 需要的几个 |
249
+ | 为消除一处 props drilling 直接引 zustand | 升级过度,徒增心智 | 用 React `Context` |
250
+ | 服务端数据塞进 Context / store | 与 query 缓存形成双源,易漂移 | 一律走 `react-query` / `swr` |
251
+ | 表单字段用一堆 `useState` 拼 | 字段越多越糟,校验散落 | `useForm` + `zodResolver`(见 [`forms-and-validation.md`](forms-and-validation.md)) |
252
+ | 页面顶部 `import` 不 lazy + 无 Suspense | 首屏 bundle 爆炸 / 进入即崩 | `React.lazy` + Layout 挂 Suspense(见 [`routing-and-codesplit.md`](routing-and-codesplit.md)) |
253
+ | 鉴权写在每个 page 顶部 | 散乱、易漏、UI 与 URL 两层不一致 | `src/routes/guards.tsx` 集中,URL 与 sidebar 两层都判 |
254
+ | 全局没有 ErrorBoundary | 任何未捕获异常直接白屏 | App 根挂 `ErrorBoundary` + `reportError` |
255
+ | 测试堆在顶层 `tests/` 目录 | 改文件忘记移动测试,易漂移 | `*.test.ts(x)` 就近紧邻被测文件 |
256
+ | `alert()` / `window.location.href = ...` | 阻断、丑、丢状态 | `toast.error()` / `useNavigate()` |
248
257
 
249
258
  ---
250
259
 
251
260
  ## 决策速查表
252
261
 
253
- | AI 收到的请求 | 文件应该放在 |
254
- | --- | --- |
255
- | 新增订单列表页 | `src/pages/orders/index.tsx` |
256
- | 新增订单状态徽章(跨页面用) | `src/components/OrderStatusBadge.tsx` |
257
- | 新增订单状态徽章(只在订单页用) | `src/pages/orders/_components/OrderStatusBadge.tsx` |
258
- | 新增"取消订单"接口调用 | `src/services/order.ts` 加 `cancelOrder` |
259
- | 新增"取消订单"的 mutation hook | `src/hooks/useCancelOrder.ts` |
260
- | 新增日期格式化函数 | `src/utils/format.ts`(先 grep 是否已存在) |
261
- | 新增鉴权拦截器 | `src/lib/http.ts` 加 interceptor |
262
- | 新增订单 domain 类型 | `src/types/order.ts` |
263
- | 跨少量组件分享当前用户 / 主题 | `src/contexts/UserContext.tsx`(用 React Context) |
264
- | 跨域高频全局态(首次需要) | `src/stores/useXxxStore.ts`(按需引 zustand,固定后只用一种) |
265
- | 列表 / 详情等服务端数据 | `src/hooks/useXxx.ts` 包 `react-query`,**不进** store / Context |
266
- | 新增"下单表单"(react-hook-form + zod) | `src/pages/orders/new/_components/CreateOrderForm.tsx` + `src/services/order.schema.ts` |
267
- | 跨页面复用表单(地址 / 客户搜索) | `src/components/forms/<Name>Form.tsx` + 对应 schema |
268
- | 新增路由 / 页面入口 | `src/routes/index.tsx` 注册 + `src/pages/<id>/index.tsx`(default export,被 `React.lazy` 引入) |
269
- | 新增鉴权 / 角色守卫 | `src/routes/guards.tsx` |
270
- | 全局 ErrorBoundary fallback 组件 | `src/components/GlobalErrorFallback.tsx` |
271
- | Sentry / 日志上报包装 | `src/lib/monitor.ts`(`reportError(err, extra?)`) |
272
- | 测试基础设施(msw handler / 自定义 render) | `src/test/handlers.ts` / `src/test/render.tsx` |
273
- | 纯函数 / service / hook 的测试 | 同目录 `<name>.test.ts(x)`(就近,不集中) |
262
+ | AI 收到的请求 | 文件应该放在 |
263
+ | ----------------------------------------- | --------------------------------------------------------------------------------------------- |
264
+ | 新增订单列表页 | `src/pages/orders/index.tsx` |
265
+ | 新增订单状态徽章(跨页面用) | `src/components/OrderStatusBadge.tsx` |
266
+ | 新增订单状态徽章(只在订单页用) | `src/pages/orders/_components/OrderStatusBadge.tsx` |
267
+ | 新增"取消订单"接口调用 | `src/services/order.ts` 加 `cancelOrder` |
268
+ | 新增"取消订单"的 mutation hook | `src/hooks/useCancelOrder.ts` |
269
+ | 新增日期格式化函数 | `src/utils/format.ts`(先 grep 是否已存在) |
270
+ | 新增鉴权拦截器 | `src/lib/http.ts` 加 interceptor |
271
+ | 新增订单 domain 类型 | `src/types/order.ts` |
272
+ | 跨少量组件分享当前用户 / 主题 | `src/contexts/UserContext.tsx`(用 React Context) |
273
+ | 跨域高频全局态(首次需要) | `src/stores/useXxxStore.ts`(按需引 zustand,固定后只用一种) |
274
+ | 列表 / 详情等服务端数据 | `src/hooks/useXxx.ts` 包 `react-query`,**不进** store / Context |
275
+ | 新增"下单表单"(react-hook-form + zod) | `src/pages/orders/new/_components/CreateOrderForm.tsx` + `src/services/order.schema.ts` |
276
+ | 跨页面复用表单(地址 / 客户搜索) | `src/components/forms/<Name>Form.tsx` + 对应 schema |
277
+ | 新增路由 / 页面入口 | `src/routes/index.tsx` 注册 + `src/pages/<id>/index.tsx`(default export,被 `React.lazy` 引入) |
278
+ | 新增鉴权 / 角色守卫 | `src/routes/guards.tsx` |
279
+ | 全局 ErrorBoundary fallback 组件 | `src/components/GlobalErrorFallback.tsx` |
280
+ | Sentry / 日志上报包装 | `src/lib/monitor.ts`(`reportError(err, extra?)`) |
281
+ | 测试基础设施(msw handler / 自定义 render) | `src/test/handlers.ts` / `src/test/render.tsx` |
282
+ | 纯函数 / service / hook 的测试 | 同目录 `<name>.test.ts(x)`(就近,不集中) |
@@ -1,17 +1,19 @@
1
1
  # 表单与校验规范
2
2
 
3
+ > ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 4 的条件读取项。任务涉及表单时必须读取。
4
+
3
5
  > **核心约定**:表单状态用 `react-hook-form`,schema 用 `zod`,**永远不要**用一堆 `useState` 拼表单。
4
6
 
5
7
  ---
6
8
 
7
9
  ## 为什么选这个组合(业界事实标准)
8
10
 
9
- | 维度 | react-hook-form + zod | 多 `useState` 拼 |
10
- | --- | --- | --- |
11
- | 重渲染 | 字段级订阅,只渲染变化的字段 | 任意字段变化整个表单重渲染 |
12
- | 校验 | schema 驱动,前后端共用 | 散落在 onChange 里,易漏 |
13
- | 复杂表单 | resolver + watch + array fields 内建 | 自己实现一遍 |
14
- | TypeScript | 从 schema 自动推导类型 | 手写类型,易漂移 |
11
+ | 维度 | react-hook-form + zod | 多 `useState` 拼 |
12
+ | ---------- | ------------------------------------ | -------------------------- |
13
+ | 重渲染 | 字段级订阅,只渲染变化的字段 | 任意字段变化整个表单重渲染 |
14
+ | 校验 | schema 驱动,前后端共用 | 散落在 onChange 里,易漏 |
15
+ | 复杂表单 | resolver + watch + array fields 内建 | 自己实现一遍 |
16
+ | TypeScript | 从 schema 自动推导类型 | 手写类型,易漂移 |
15
17
 
16
18
  参考:[React Hook Form](https://react-hook-form.com/) + [Zod](https://zod.dev/) 是 2024+ React 生态默认组合,被 shadcn/ui、Vercel、Linear、Cal.com 等广泛采用。
17
19
 
@@ -141,12 +143,12 @@ export function CreateOrderForm() {
141
143
 
142
144
  ## 校验规则
143
145
 
144
- | 场景 | 写在哪 |
145
- | --- | --- |
146
- | 字段格式(邮箱 / 手机号 / URL) | zod schema(`z.string().email()`) |
147
- | 业务规则(库存检查 / 唯一性) | service 层,服务端返回错误 → hook 暴露 → UI 显示 |
148
- | 跨字段约束(开始日期 < 结束日期) | zod `.refine()` / `.superRefine()` |
149
- | 异步校验(用户名是否被占用) | `useQuery` + debounce,**不放 schema** |
146
+ | 场景 | 写在哪 |
147
+ | ------------------------------- | ----------------------------------------------- |
148
+ | 字段格式(邮箱 / 手机号 / URL) | zod schema(`z.string().email()`) |
149
+ | 业务规则(库存检查 / 唯一性) | service 层,服务端返回错误 → hook 暴露 → UI 显示 |
150
+ | 跨字段约束(开始日期 < 结束日期) | zod `.refine()` / `.superRefine()` |
151
+ | 异步校验(用户名是否被占用) | `useQuery` + debounce,**不放 schema** |
150
152
 
151
153
  ### 反模式
152
154
 
@@ -1,5 +1,7 @@
1
1
  # Reuse-First · 组件 / 工具复用决策流
2
2
 
3
+ > ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 1 的强制读取项。AI 写任何新代码前必须先读完本文件并执行复用查询。
4
+
3
5
  > 在写**任何**新组件 / Hook / 工具函数之前,AI 必须按本流程跑一遍。"没找到现成的"必须是查询的结论,不能是默认假设。
4
6
 
5
7
  ---
@@ -1,6 +1,8 @@
1
1
  # 路由与代码分包规范
2
2
 
3
- > **核心约定**:页面级 `React.lazy` 默认分包,鉴权 / 角色守卫集中在 `src/routes/`,404 / 401 / 403 必有兜底。
3
+ > ⚠️ **Prerequisites**: 本文件是 [SKILL.md](./SKILL.md) Step 6 的条件读取项。任务涉及路由或页面入口时必须读取。
4
+
5
+ > **核心约定**:页面级 `React.lazy` 默认分包,鉴权 / 角色守卫集中在 `src/routes/`,404 / 401 / 403 必有兗底。
4
6
 
5
7
  ---
6
8
 
@@ -8,11 +10,11 @@
8
10
 
9
11
  teamix-evo console preset 默认 `react-router-dom@6`(Data Router 模式,业界主流)。
10
12
 
11
- | 候选 | 何时选 |
12
- | --- | --- |
13
- | `react-router-dom@6` Data Router | **默认**。中后台 / SPA / 多页应用 |
14
- | `@tanstack/react-router` | 类型安全要求极高、复杂 search params | (替换需在 ADR 记录)
15
- | 文件路由(Next.js / Remix) | 不在本 preset 范围 |
13
+ | 候选 | 何时选 |
14
+ | -------------------------------- | ------------------------------------ | ------------------- |
15
+ | `react-router-dom@6` Data Router | **默认**。中后台 / SPA / 多页应用 |
16
+ | `@tanstack/react-router` | 类型安全要求极高、复杂 search params | (替换需在 ADR 记录) |
17
+ | 文件路由(Next.js / Remix) | 不在本 preset 范围 |
16
18
 
17
19
  **一个项目只用一种**。已经用了 react-router 就不要混入 tanstack-router。
18
20
 
@@ -181,11 +183,11 @@ export function RoleGuard({
181
183
 
182
184
  每个项目**必须**有这三个页面,放在 `src/pages/_xxx/`(下划线前缀,表示非业务路由):
183
185
 
184
- | 路径 | 文件 | 触发场景 |
185
- | --- | --- | --- |
186
- | `*` (404) | `src/pages/_not-found/index.tsx` | 任何未匹配的 URL |
187
- | `/_forbidden` (403) | `src/pages/_forbidden/index.tsx` | 角色无权访问 |
188
- | `/_error` (500) | `src/pages/_error/index.tsx` | router `errorElement` 兜底 |
186
+ | 路径 | 文件 | 触发场景 |
187
+ | ------------------- | -------------------------------- | -------------------------- |
188
+ | `*` (404) | `src/pages/_not-found/index.tsx` | 任何未匹配的 URL |
189
+ | `/_forbidden` (403) | `src/pages/_forbidden/index.tsx` | 角色无权访问 |
190
+ | `/_error` (500) | `src/pages/_error/index.tsx` | router `errorElement` 兜底 |
189
191
 
190
192
  未登录(401)由 `AuthGuard` 重定向到 `/login`,**不**单独建页面。
191
193
 
@@ -229,7 +231,7 @@ function OrderListPage() {
229
231
  import { useNavigate, Link } from 'react-router-dom';
230
232
 
231
233
  // 声明式(链接)
232
- <Link to={`/orders/${order.id}`}>查看</Link>
234
+ <Link to={`/orders/${order.id}`}>查看</Link>;
233
235
 
234
236
  // 命令式(mutation success)
235
237
  const navigate = useNavigate();
@@ -249,15 +251,15 @@ mutation.mutate(values, {
249
251
 
250
252
  ## 反模式速查
251
253
 
252
- | 反模式 | 为什么禁 | 应该 |
253
- | --- | --- | --- |
254
- | 全部页面顶部 `import` 不 lazy | 首屏 bundle 爆炸 | `React.lazy` 每页 |
255
- | Lazy 但没 Suspense 兜底 | 进入页面报错 | Layout 挂一次 Suspense |
256
- | 鉴权写在每个 page 里 | 散乱、易漏 | 守卫组件 + 路由层 |
257
- | 路由声明分散在多处 | 难维护、易循环 | 唯一在 `src/routes/index.tsx` |
258
- | 列表筛选状态不进 URL | 刷新 / 分享 / 后退失效 | `useSearchParams` |
259
- | `window.location.href` 跳转 | 整页刷新 | `useNavigate` / `<Link>` |
260
- | 路由 path 写魔法字符串 | 改路径漏改 | 抽 `src/routes/paths.ts` 集中 |
254
+ | 反模式 | 为什么禁 | 应该 |
255
+ | ----------------------------- | ---------------------- | ----------------------------- |
256
+ | 全部页面顶部 `import` 不 lazy | 首屏 bundle 爆炸 | `React.lazy` 每页 |
257
+ | Lazy 但没 Suspense 兜底 | 进入页面报错 | Layout 挂一次 Suspense |
258
+ | 鉴权写在每个 page 里 | 散乱、易漏 | 守卫组件 + 路由层 |
259
+ | 路由声明分散在多处 | 难维护、易循环 | 唯一在 `src/routes/index.tsx` |
260
+ | 列表筛选状态不进 URL | 刷新 / 分享 / 后退失效 | `useSearchParams` |
261
+ | `window.location.href` 跳转 | 整页刷新 | `useNavigate` / `<Link>` |
262
+ | 路由 path 写魔法字符串 | 改路径漏改 | 抽 `src/routes/paths.ts` 集中 |
261
263
 
262
264
  ---
263
265