@bovinphang/frontend-craft 2.5.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.
- package/.claude-plugin/marketplace.json +61 -0
- package/.claude-plugin/plugin.json +32 -0
- package/.mcp.json +44 -0
- package/CHANGELOG.md +229 -0
- package/CHANGELOG.zh-CN.md +228 -0
- package/CODE_OF_CONDUCT.md +34 -0
- package/CODE_OF_CONDUCT.zh-CN.md +34 -0
- package/CONTRIBUTING.md +356 -0
- package/CONTRIBUTING.zh-CN.md +356 -0
- package/LICENSE +21 -0
- package/README.md +467 -0
- package/README.openclaw.md +262 -0
- package/README.openclaw.zh-CN.md +262 -0
- package/README.zh-CN.md +467 -0
- package/SECURITY.md +44 -0
- package/SECURITY.zh-CN.md +44 -0
- package/agents/fec-architect.md +125 -0
- package/agents/fec-code-reviewer.md +152 -0
- package/agents/fec-debugger.md +44 -0
- package/agents/fec-design-token-mapper.md +75 -0
- package/agents/fec-doc-updater.md +30 -0
- package/agents/fec-e2e-runner.md +107 -0
- package/agents/fec-figma-implementer.md +104 -0
- package/agents/fec-performance-optimizer.md +293 -0
- package/agents/fec-refactor-cleaner.md +32 -0
- package/agents/fec-security-reviewer.md +116 -0
- package/agents/fec-test-planner.md +50 -0
- package/agents/fec-typescript-reviewer.md +142 -0
- package/agents/fec-ui-checker.md +120 -0
- package/commands/fec-debug.md +30 -0
- package/commands/fec-doc-sync.md +18 -0
- package/commands/fec-init.md +70 -0
- package/commands/fec-plan.md +94 -0
- package/commands/fec-refactor-clean.md +19 -0
- package/commands/fec-review.md +66 -0
- package/commands/fec-scaffold.md +205 -0
- package/commands/fec-tdd.md +21 -0
- package/dist/bin/frontend-craft.js +82 -0
- package/dist/hooks/fec-cleanup-claude-cache.js +4 -0
- package/dist/hooks/fec-format-changed-file.js +1 -0
- package/dist/hooks/fec-notify.js +1 -0
- package/dist/hooks/fec-run-tests.js +1 -0
- package/dist/hooks/fec-security-check.js +2 -0
- package/dist/hooks/fec-session-start.js +1 -0
- package/docs/example-prompts.md +124 -0
- package/docs/ja-JP/README.md +467 -0
- package/docs/ko-KR/README.md +467 -0
- package/docs/project-structure.md +119 -0
- package/docs/runtimes/antigravity.md +10 -0
- package/docs/runtimes/augment.md +10 -0
- package/docs/runtimes/claude.md +171 -0
- package/docs/runtimes/claude.zh-CN.md +169 -0
- package/docs/runtimes/cline.md +10 -0
- package/docs/runtimes/codebuddy.md +10 -0
- package/docs/runtimes/codex.md +10 -0
- package/docs/runtimes/copilot.md +10 -0
- package/docs/runtimes/cursor.md +10 -0
- package/docs/runtimes/gemini.md +10 -0
- package/docs/runtimes/kilo.md +10 -0
- package/docs/runtimes/openclaw.md +10 -0
- package/docs/runtimes/opencode.md +10 -0
- package/docs/runtimes/qoder.md +19 -0
- package/docs/runtimes/trae.md +10 -0
- package/docs/runtimes/windsurf.md +10 -0
- package/docs/superpowers/plans/2026-05-29-fec-debug.md +491 -0
- package/docs/superpowers/specs/2026-05-29-fec-debug-design.md +340 -0
- package/docs/zh-CN/example-prompts.md +124 -0
- package/docs/zh-CN/project-structure.md +119 -0
- package/docs/zh-TW/README.md +467 -0
- package/hooks/hooks.json +71 -0
- package/openclaw.plugin.json +26 -0
- package/package.json +101 -0
- package/skills/TEMPLATE.md +155 -0
- package/skills/eval_queries.json +890 -0
- package/skills/fec-accessibility-check/SKILL.md +39 -0
- package/skills/fec-accessibility-check/references/report-template.md +23 -0
- package/skills/fec-accessibility-check/references/screen-reader-testing.md +33 -0
- package/skills/fec-api-integration/SKILL.md +78 -0
- package/skills/fec-api-integration/references/integration-patterns.md +80 -0
- package/skills/fec-browser-storage/SKILL.md +34 -0
- package/skills/fec-browser-storage/references/storage-patterns.md +65 -0
- package/skills/fec-canvas-threejs/SKILL.md +66 -0
- package/skills/fec-canvas-threejs/references/rendering-patterns.md +67 -0
- package/skills/fec-canvas-threejs/references/shader-webgl-patterns.md +67 -0
- package/skills/fec-code-review/SKILL.md +135 -0
- package/skills/fec-code-review/references/report-template.md +29 -0
- package/skills/fec-component-testing/SKILL.md +119 -0
- package/skills/fec-data-fetching/SKILL.md +62 -0
- package/skills/fec-data-fetching/references/query-patterns.md +77 -0
- package/skills/fec-debug-framework/SKILL.md +162 -0
- package/skills/fec-dependency-upgrade/SKILL.md +34 -0
- package/skills/fec-dependency-upgrade/references/dependency-upgrade-workflow.md +56 -0
- package/skills/fec-doc-sync/SKILL.md +37 -0
- package/skills/fec-e2e-testing/SKILL.md +40 -0
- package/skills/fec-e2e-testing/references/e2e-advanced.md +176 -0
- package/skills/fec-e2e-testing/references/playwright-patterns.md +95 -0
- package/skills/fec-form-handling/SKILL.md +76 -0
- package/skills/fec-form-handling/references/advanced-form-patterns.md +72 -0
- package/skills/fec-implement-from-design/SKILL.md +122 -0
- package/skills/fec-implement-from-design/references/design-plan-template.md +36 -0
- package/skills/fec-legacy-to-modern-migration/SKILL.md +180 -0
- package/skills/fec-legacy-to-modern-migration/references/migration-report-template.md +41 -0
- package/skills/fec-legacy-web-standard/SKILL.md +35 -0
- package/skills/fec-legacy-web-standard/references/legacy-patterns.md +85 -0
- package/skills/fec-list-virtualization/SKILL.md +34 -0
- package/skills/fec-list-virtualization/references/virtualization-patterns.md +52 -0
- package/skills/fec-monorepo-project-standard/SKILL.md +142 -0
- package/skills/fec-motion-interaction/SKILL.md +66 -0
- package/skills/fec-motion-interaction/references/motion-patterns.md +112 -0
- package/skills/fec-nextjs-project-standard/SKILL.md +117 -0
- package/skills/fec-nuxt-project-standard/SKILL.md +122 -0
- package/skills/fec-performance-optimization/SKILL.md +72 -0
- package/skills/fec-pwa-implementation/SKILL.md +36 -0
- package/skills/fec-pwa-implementation/references/manifest-and-icons.md +53 -0
- package/skills/fec-pwa-implementation/references/service-worker-workbox.md +68 -0
- package/skills/fec-react-project-standard/SKILL.md +83 -0
- package/skills/fec-react-project-standard/references/react-project-details.md +642 -0
- package/skills/fec-refactor-clean/SKILL.md +48 -0
- package/skills/fec-responsive-layout/SKILL.md +34 -0
- package/skills/fec-responsive-layout/references/responsive-layout-patterns.md +59 -0
- package/skills/fec-route-protection/SKILL.md +135 -0
- package/skills/fec-security-review/SKILL.md +38 -0
- package/skills/fec-security-review/references/report-template.md +27 -0
- package/skills/fec-security-review/references/security-checklist.md +64 -0
- package/skills/fec-source-driven-development/SKILL.md +61 -0
- package/skills/fec-state-management/SKILL.md +140 -0
- package/skills/fec-state-management/references/state-patterns.md +109 -0
- package/skills/fec-storybook-component-doc/SKILL.md +37 -0
- package/skills/fec-storybook-component-doc/references/story-patterns.md +118 -0
- package/skills/fec-svg-animation/SKILL.md +148 -0
- package/skills/fec-tailwind-design-system/SKILL.md +34 -0
- package/skills/fec-tailwind-design-system/references/tailwind-system-patterns.md +55 -0
- package/skills/fec-tdd-workflow/SKILL.md +52 -0
- package/skills/fec-testing-strategy/SKILL.md +45 -0
- package/skills/fec-typescript-type-safety/SKILL.md +157 -0
- package/skills/fec-typescript-type-safety/references/type-safety-patterns.md +102 -0
- package/skills/fec-ui-design/SKILL.md +90 -0
- package/skills/fec-ui-design/data/product-rules.json +110 -0
- package/skills/fec-ui-design/data/stack-ui-rules.json +14 -0
- package/skills/fec-ui-design/data/style-archetypes.json +74 -0
- package/skills/fec-ui-design/data/ux-quality-rules.json +56 -0
- package/skills/fec-ui-design/references/design-intelligence.md +37 -0
- package/skills/fec-ui-design/references/master-page-overrides.md +30 -0
- package/skills/fec-ui-design/references/pre-delivery-checklist.md +33 -0
- package/skills/fec-ui-design/scripts/design-system.mjs +530 -0
- package/skills/fec-validation-fix/SKILL.md +48 -0
- package/skills/fec-vite-project-standard/SKILL.md +83 -0
- package/skills/fec-vue3-project-standard/SKILL.md +49 -0
- package/skills/fec-vue3-project-standard/references/vue3-project-details.md +547 -0
- package/skills/fec-web-workers/SKILL.md +36 -0
- package/skills/fec-web-workers/references/advanced-workers.md +54 -0
- package/skills/fec-web-workers/references/basic-worker.md +67 -0
- package/skills/metadata.json +1422 -0
- package/skills/relations.json +644 -0
- package/templates/claude/CLAUDE.md +106 -0
- package/templates/claude/settings.json +62 -0
- package/templates/codex/AGENTS.md +105 -0
- package/templates/codex/config.toml +32 -0
- package/templates/openclaw/AGENTS.md +101 -0
- package/templates/openclaw/OPENCLAW-CONFIG.md +12 -0
- package/templates/shared/rules/fec-agent-workflow.md +54 -0
- package/templates/shared/rules/fec-api-layer.md +99 -0
- package/templates/shared/rules/fec-ci-cd.md +128 -0
- package/templates/shared/rules/fec-code-comments.md +33 -0
- package/templates/shared/rules/fec-design-system.md +248 -0
- package/templates/shared/rules/fec-error-handling.md +200 -0
- package/templates/shared/rules/fec-git-conventions.md +73 -0
- package/templates/shared/rules/fec-i18n.md +112 -0
- package/templates/shared/rules/fec-naming-conventions.md +120 -0
- package/templates/shared/rules/fec-performance.md +101 -0
- package/templates/shared/rules/fec-react.md +82 -0
- package/templates/shared/rules/fec-refactoring.md +43 -0
- package/templates/shared/rules/fec-rendering-patterns.md +183 -0
- package/templates/shared/rules/fec-responsive-design.md +227 -0
- package/templates/shared/rules/fec-source-driven-development.md +31 -0
- package/templates/shared/rules/fec-state-management.md +163 -0
- package/templates/shared/rules/fec-testing.md +98 -0
- package/templates/shared/rules/fec-typescript.md +355 -0
- package/templates/shared/rules/fec-vue.md +64 -0
- package/templates/shared/rules/fec-working-modes.md +50 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-doc-sync
|
|
3
|
+
description: Use when synchronizing frontend project documentation with source-of-truth files such as package.json, runtime templates, CLI commands, public skills, agents, reports, environment examples, or repository structure. Do not use for general prose polishing only; Chinese triggers include 文档同步, 更新 README, docs sync, 命令清单, 能力表同步.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 文档同步
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
从代码、模板和元数据同步公开文档,避免 README、runtime 文档、能力表和报告说明与实际安装内容漂移。
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
1. 找到事实来源:
|
|
15
|
+
- `package.json`:版本、脚本、bin、files。
|
|
16
|
+
- `skills/metadata.json`、`skills/eval_queries.json`:公开 skill 清单。
|
|
17
|
+
- `agents/`、`commands/`、`templates/shared/rules/`:安装内容。
|
|
18
|
+
- `docs/project-structure.md` 和 runtime docs:分发说明。
|
|
19
|
+
2. 比对文档中的数量、名称、命令、报告文件名和运行时支持矩阵。
|
|
20
|
+
3. 更新所有受影响语言版本;若仓库有多语言 README,保持信息一致。
|
|
21
|
+
4. 对生成型报告或示例,只更新稳定约定,不把临时报告内容写入公开文档。
|
|
22
|
+
5. 若变更来自依赖升级、架构决策或公共能力调整,同步轻量 ADR、变更说明、示例 prompt 和迁移注意事项。
|
|
23
|
+
6. 运行文档/元数据一致性测试或相关打包检查。
|
|
24
|
+
|
|
25
|
+
## Constraints
|
|
26
|
+
|
|
27
|
+
- 不把 README 变成完整实现细节;公开文档保留高信号摘要。
|
|
28
|
+
- 不引入与事实来源不一致的命令、路径或能力名称。
|
|
29
|
+
- 不修改用户项目私有文档,除非用户明确指定。
|
|
30
|
+
- 多语言文档若无法完整翻译,至少保持数量、命令名、skill id、agent id 和报告格式一致。
|
|
31
|
+
- 不把 ADR 当作长篇复盘;只记录背景、决策、取舍、影响范围、验证和回滚线索。
|
|
32
|
+
|
|
33
|
+
## Expected Output
|
|
34
|
+
|
|
35
|
+
- README、runtime docs、项目结构和 changelog 中的公开能力清单与代码一致。
|
|
36
|
+
- 相关 metadata、marketplace 描述和测试期望同步。
|
|
37
|
+
- 总结事实来源、更新范围、验证命令和仍需人工校对的翻译风险。
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-e2e-testing
|
|
3
|
+
description: Use when creating, maintaining, debugging, or reviewing real-browser end-to-end tests with Playwright or Cypress, including Page Object models, CI artifacts, traces, flaky tests, cross-page visual regression, and critical user journeys such as login, payment, permissions, or CRUD. For layer planning or tests close to UI components, choose the matching testing workflow first; Chinese triggers include E2E, 端到端测试, Playwright, Cypress.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# E2E 测试规范
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
用真实浏览器验证关键用户旅程,发现单元测试无法覆盖的集成风险。
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
1. 明确关键旅程:登录、购买、创建、搜索、权限、支付等跨页面流程优先;若只是选择测试层级,先做测试分层规划。
|
|
15
|
+
2. 新项目优先 Playwright;已有 Cypress 项目沿用 Cypress,不为迁移而迁移。
|
|
16
|
+
3. 用 Page Object 或 fixtures 封装登录、页面定位器和共享数据,spec 中避免裸选择器。
|
|
17
|
+
4. 断言用户可见结果,依赖 Locator 自动等待、网络响应或可见状态,不使用固定 sleep。
|
|
18
|
+
5. CI 必须上传 HTML report、截图、trace 或视频;flaky 用例要隔离、复现、标注来源并关联 issue。
|
|
19
|
+
6. 调试失败时先读 trace、console、network 和截图,再改测试;不要把真实产品缺陷误判成 flaky。
|
|
20
|
+
7. 对关键浏览器能力、支付、权限、上传和实时通信流程,记录测试环境、种子数据和清理策略。
|
|
21
|
+
8. 测试数据必须可重复:优先使用 API seed、数据库 fixture、测试账号或 mock 服务;随机数据需带唯一前缀并在 teardown 清理。
|
|
22
|
+
|
|
23
|
+
## Detailed References
|
|
24
|
+
|
|
25
|
+
- Load [references/playwright-patterns.md](references/playwright-patterns.md) for directory layout, Page Object, spec structure, config, flaky debugging, and artifact examples.
|
|
26
|
+
- Load [references/e2e-advanced.md](references/e2e-advanced.md) when the task needs CI examples, Markdown report templates, Web3/wallet flows, high-risk financial flows, or visual regression setup.
|
|
27
|
+
|
|
28
|
+
## Constraints
|
|
29
|
+
|
|
30
|
+
- 禁止依赖 `sleep` / 固定 `setTimeout` 作为主要同步手段。
|
|
31
|
+
- 不在生产环境跑真实 E2E。
|
|
32
|
+
- 避免在 E2E 中断言组件内部像素级样式;跨页面截图/视觉回归仅覆盖用户旅程中的关键页面状态。
|
|
33
|
+
- 失败必须能靠截图、trace 或视频定位。
|
|
34
|
+
- 组件内部契约测试分流到组件测试 workflow。
|
|
35
|
+
- 不把不稳定等待、随机数据或依赖测试顺序带入 CI。
|
|
36
|
+
- 不把重试次数当作 flaky 治理;重试只能保留证据,根因仍需定位。
|
|
37
|
+
|
|
38
|
+
## Expected Output
|
|
39
|
+
|
|
40
|
+
关键用户旅程有稳定 E2E 覆盖,失败产物可定位问题,CI 中报告和 trace 可下载;flaky 用例有复现命令、隔离策略和后续 issue。
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# E2E 高级测试细节
|
|
2
|
+
|
|
3
|
+
## CI 集成(示例)
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
# .github/workflows/e2e.yml
|
|
7
|
+
name: E2E Tests
|
|
8
|
+
on: [push, pull_request]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
test:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npx playwright install --with-deps
|
|
20
|
+
- run: npx playwright test
|
|
21
|
+
env:
|
|
22
|
+
CI: true
|
|
23
|
+
BASE_URL: ${{ vars.STAGING_URL }}
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
if: always()
|
|
26
|
+
with:
|
|
27
|
+
name: playwright-report
|
|
28
|
+
path: playwright-report/
|
|
29
|
+
retention-days: 30
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Cypress:固定 `CYPRESS_baseUrl` 或配置文件中的 `baseUrl`,失败截图/视频同样建议上传 artifact。
|
|
33
|
+
|
|
34
|
+
## E2E 报告模板(Markdown)
|
|
35
|
+
|
|
36
|
+
输出或归档测试报告时可采用下列结构,便于复盘:
|
|
37
|
+
|
|
38
|
+
```markdown
|
|
39
|
+
# E2E 测试报告
|
|
40
|
+
|
|
41
|
+
**日期:** YYYY-MM-DD HH:mm
|
|
42
|
+
**耗时:** X 分 Y 秒
|
|
43
|
+
**结果:** 通过 / 失败
|
|
44
|
+
|
|
45
|
+
## 摘要
|
|
46
|
+
|
|
47
|
+
- 总计 X | 通过 Y | 失败 Z | 不稳定/跳过 说明
|
|
48
|
+
|
|
49
|
+
## 失败用例
|
|
50
|
+
|
|
51
|
+
### 用例名
|
|
52
|
+
|
|
53
|
+
- **文件:** `tests/e2e/xxx.spec.ts:行号`
|
|
54
|
+
- **错误:** 简述断言或超时信息
|
|
55
|
+
- **截图/Trace:** 路径或 artifact 名称
|
|
56
|
+
- **建议修复:** 可执行结论
|
|
57
|
+
|
|
58
|
+
## 产物
|
|
59
|
+
|
|
60
|
+
- HTML 报告:`playwright-report/index.html`
|
|
61
|
+
- Trace / 视频 / 截图:CI artifact 或本地 `test-results/`
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 特殊场景(按需)
|
|
65
|
+
|
|
66
|
+
### Web3 / 钱包注入(Playwright)
|
|
67
|
+
|
|
68
|
+
在 **`page.goto` 之前**通过 `context.addInitScript` 注入 mock,避免依赖真实插件:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
test("连接钱包", async ({ page, context }) => {
|
|
72
|
+
await context.addInitScript(() => {
|
|
73
|
+
(window as unknown as { ethereum?: unknown }).ethereum = {
|
|
74
|
+
isMetaMask: true,
|
|
75
|
+
request: async ({ method }: { method: string }) => {
|
|
76
|
+
if (method === "eth_requestAccounts")
|
|
77
|
+
return ["0x1234567890123456789012345678901234567890"];
|
|
78
|
+
if (method === "eth_chainId") return "0x1";
|
|
79
|
+
return null;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await page.goto("/");
|
|
85
|
+
await page.getByTestId("connect-wallet").click();
|
|
86
|
+
await expect(page.getByTestId("wallet-address")).toContainText("0x1234");
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
链 ID、账户列表与业务契约保持一致即可。
|
|
91
|
+
|
|
92
|
+
### 金融或高风险操作
|
|
93
|
+
|
|
94
|
+
- **禁止**在真实生产环境执行真实资金操作;使用 **staging、mock 或 `test.skip`**。
|
|
95
|
+
- 断言**预览金额、成功态**与**关键接口 200** 后再结束用例,超时给足(如链上确认场景)。
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
test("下单预览与成功态", async ({ page }) => {
|
|
99
|
+
test.skip(process.env.NODE_ENV === "production", "生产环境跳过真实交易");
|
|
100
|
+
|
|
101
|
+
await page.goto("/markets/demo");
|
|
102
|
+
await page.getByTestId("position-yes").click();
|
|
103
|
+
await page.getByTestId("trade-amount").fill("1.0");
|
|
104
|
+
|
|
105
|
+
const preview = page.getByTestId("trade-preview");
|
|
106
|
+
await expect(preview).toContainText("1.0");
|
|
107
|
+
|
|
108
|
+
await page.getByTestId("confirm-trade").click();
|
|
109
|
+
await page.waitForResponse(
|
|
110
|
+
(resp) => resp.url().includes("/api/trade") && resp.status() === 200,
|
|
111
|
+
{ timeout: 30_000 },
|
|
112
|
+
);
|
|
113
|
+
await expect(page.getByTestId("trade-success")).toBeVisible();
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 视觉回归测试
|
|
118
|
+
|
|
119
|
+
通过截图快照对比检测非预期的 UI 变化。适用于 CSS 重构、设计 Token 更新、组件改版等场景。
|
|
120
|
+
|
|
121
|
+
### 基础用法
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { test, expect } from "@playwright/test";
|
|
125
|
+
|
|
126
|
+
test("首页布局与基线一致", async ({ page }) => {
|
|
127
|
+
await page.goto("/");
|
|
128
|
+
await page.waitForLoadState("networkidle");
|
|
129
|
+
|
|
130
|
+
// 全页截图对比 — 首次运行自动生成基线
|
|
131
|
+
await expect(page).toHaveScreenshot("homepage-full.png", {
|
|
132
|
+
fullPage: true,
|
|
133
|
+
maxDiffPixels: 100, // 允许的轻微像素差异(抗锯齿等)
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("按钮组件视觉一致", async ({ page }) => {
|
|
138
|
+
await page.goto("/components/buttons");
|
|
139
|
+
const submitButton = page.getByTestId("submit-button");
|
|
140
|
+
await expect(submitButton).toHaveScreenshot("submit-button.png");
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 屏蔽动态内容
|
|
145
|
+
|
|
146
|
+
时间、随机 ID、第三方广告等动态元素会导致误报,使用 `mask` 屏蔽:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
await expect(page).toHaveScreenshot("dashboard.png", {
|
|
150
|
+
mask: [
|
|
151
|
+
page.locator(".live-clock"),
|
|
152
|
+
page.locator(".random-id"),
|
|
153
|
+
page.locator(".ad-banner"),
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 禁用动画
|
|
159
|
+
|
|
160
|
+
CSS 动画会导致截图不一致,注入样式禁用动画后再截图:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
await page.addStyleTag({
|
|
164
|
+
content:
|
|
165
|
+
"*, *::before, *::after { animation-duration: 0s !important; transition-duration: 0s !important; }",
|
|
166
|
+
});
|
|
167
|
+
await expect(page).toHaveScreenshot("animated-page.png");
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 基线管理
|
|
171
|
+
|
|
172
|
+
- **首次运行**: `npx playwright test` 失败但自动生成基线到 `tests/` 目录
|
|
173
|
+
- **更新基线**: UI 有意变更时 `npx playwright test --update-snapshots`
|
|
174
|
+
- **CI 一致性**: 基线必须在与 CI 相同的 OS(Linux/Ubuntu)上生成,字体渲染在 macOS 与 Linux 间存在差异
|
|
175
|
+
- **多浏览器**: 每个 project 独立生成基线(Chromium/Firefox/WebKit 渲染结果不同)
|
|
176
|
+
- **基线文件**: 纳入 `.gitignore` 或 Git LFS 管理,避免仓库膨胀
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Playwright / Cypress E2E 模式
|
|
2
|
+
|
|
3
|
+
## 目录结构
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
tests/
|
|
7
|
+
├── e2e/
|
|
8
|
+
│ ├── auth/login.spec.ts
|
|
9
|
+
│ └── features/search.spec.ts
|
|
10
|
+
├── fixtures/
|
|
11
|
+
│ └── auth.ts
|
|
12
|
+
└── pages/
|
|
13
|
+
├── LoginPage.ts
|
|
14
|
+
└── ItemsPage.ts
|
|
15
|
+
playwright.config.ts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Page Object
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { type Locator, type Page } from "@playwright/test";
|
|
22
|
+
|
|
23
|
+
export class ItemsPage {
|
|
24
|
+
readonly searchInput: Locator;
|
|
25
|
+
readonly itemCards: Locator;
|
|
26
|
+
|
|
27
|
+
constructor(private readonly page: Page) {
|
|
28
|
+
this.searchInput = page.getByTestId("search-input");
|
|
29
|
+
this.itemCards = page.getByTestId("item-card");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async goto() {
|
|
33
|
+
await this.page.goto("/items");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async search(keyword: string) {
|
|
37
|
+
await this.searchInput.fill(keyword);
|
|
38
|
+
await this.page.waitForResponse((response) => response.url().includes("/api/search"));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Spec
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { expect, test } from "@playwright/test";
|
|
47
|
+
import { ItemsPage } from "../pages/ItemsPage";
|
|
48
|
+
|
|
49
|
+
test.describe("商品搜索", () => {
|
|
50
|
+
test("按关键词搜索有结果", async ({ page }) => {
|
|
51
|
+
const itemsPage = new ItemsPage(page);
|
|
52
|
+
await itemsPage.goto();
|
|
53
|
+
await itemsPage.search("test");
|
|
54
|
+
await expect(itemsPage.itemCards.first()).toContainText(/test/i);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Config
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
63
|
+
|
|
64
|
+
export default defineConfig({
|
|
65
|
+
testDir: "./tests/e2e",
|
|
66
|
+
forbidOnly: !!process.env.CI,
|
|
67
|
+
retries: process.env.CI ? 2 : 0,
|
|
68
|
+
reporter: [["html"], ["junit", { outputFile: "playwright-results.xml" }]],
|
|
69
|
+
use: {
|
|
70
|
+
baseURL: process.env.BASE_URL || "http://localhost:3000",
|
|
71
|
+
trace: "on-first-retry",
|
|
72
|
+
screenshot: "only-on-failure",
|
|
73
|
+
video: "retain-on-failure",
|
|
74
|
+
},
|
|
75
|
+
projects: [
|
|
76
|
+
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
|
|
77
|
+
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
|
|
78
|
+
],
|
|
79
|
+
webServer: {
|
|
80
|
+
command: "npm run dev",
|
|
81
|
+
url: "http://localhost:3000",
|
|
82
|
+
reuseExistingServer: !process.env.CI,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Flaky 调试
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx playwright test path/to/spec.ts --repeat-each=10
|
|
91
|
+
npx playwright test path/to/spec.ts --retries=3
|
|
92
|
+
npx playwright show-trace path/to/trace.zip
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
常见原因:竞态、网络时序、动画、测试数据污染、跨测试状态泄露。
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-form-handling
|
|
3
|
+
description: Use when building or reviewing substantial forms with React Hook Form, Zod schemas, typed validation, dynamic fields, controlled third-party inputs, file upload, multi-step flows, dependent validation, or form performance. Do not use for trivial 1-3 field forms without validation; Chinese triggers include 表单, 表单校验, 动态字段.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 表单处理
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
管理表单状态、校验和提交,避免复杂表单输入卡顿。
|
|
11
|
+
|
|
12
|
+
## Procedure
|
|
13
|
+
|
|
14
|
+
1. 先判断复杂度:10+ 字段、动态字段、联动校验、文件上传、多步流程或输入卡顿时使用 React Hook Form + Zod;极简表单可用原生受控组件。
|
|
15
|
+
2. 用 Zod 定义运行时 schema,并用 `z.infer` 推导 TypeScript 类型;schema 是外部输入边界,不要只写 TS 类型。
|
|
16
|
+
3. `useForm` 必须提供 `defaultValues`,通过 `zodResolver` 统一校验;错误提示用 `aria-invalid`、`aria-describedby` 和 `role="alert"`。
|
|
17
|
+
4. 第三方受控组件用 `Controller`,原生 input 优先 `register`;不要混用两套状态源。
|
|
18
|
+
5. 提交时处理 loading、服务端错误、重复提交和 reset;大型表单用局部订阅和子组件隔离控制重渲染。
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
24
|
+
import { useForm } from "react-hook-form";
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
|
|
27
|
+
const loginSchema = z.object({
|
|
28
|
+
email: z.string().email("请输入有效的邮箱地址"),
|
|
29
|
+
password: z.string().min(8, "密码至少 8 个字符"),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
type LoginForm = z.infer<typeof loginSchema>;
|
|
33
|
+
|
|
34
|
+
export function LoginFormView() {
|
|
35
|
+
const {
|
|
36
|
+
register,
|
|
37
|
+
handleSubmit,
|
|
38
|
+
formState: { errors, isSubmitting },
|
|
39
|
+
} = useForm<LoginForm>({
|
|
40
|
+
resolver: zodResolver(loginSchema),
|
|
41
|
+
defaultValues: { email: "", password: "" },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<form onSubmit={handleSubmit((data) => api.login(data))} noValidate>
|
|
46
|
+
<label htmlFor="email">邮箱</label>
|
|
47
|
+
<input id="email" {...register("email")} aria-invalid={!!errors.email} />
|
|
48
|
+
{errors.email && <span role="alert">{errors.email.message}</span>}
|
|
49
|
+
|
|
50
|
+
<label htmlFor="password">密码</label>
|
|
51
|
+
<input id="password" type="password" {...register("password")} />
|
|
52
|
+
{errors.password && <span role="alert">{errors.password.message}</span>}
|
|
53
|
+
|
|
54
|
+
<button disabled={isSubmitting}>
|
|
55
|
+
{isSubmitting ? "提交中..." : "提交"}
|
|
56
|
+
</button>
|
|
57
|
+
</form>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Detailed References
|
|
63
|
+
|
|
64
|
+
涉及 `Controller`、`useFieldArray`、联动校验、文件上传、多步表单、异步校验和性能模式时,加载 [references/advanced-form-patterns.md](references/advanced-form-patterns.md)。
|
|
65
|
+
|
|
66
|
+
## Constraints
|
|
67
|
+
|
|
68
|
+
- React Hook Form 默认非受控;只有第三方受控组件才用 `Controller`。
|
|
69
|
+
- `defaultValues` 必须完整,避免 undefined 触发受控/非受控警告。
|
|
70
|
+
- `reset()` 应传服务器返回或明确的完整默认对象。
|
|
71
|
+
- 异步校验必须 debounce 或放到提交边界,避免每次键入请求。
|
|
72
|
+
- Zod `refine` / `superRefine` 的 `path` 必须指向实际字段。
|
|
73
|
+
|
|
74
|
+
## Expected Output
|
|
75
|
+
|
|
76
|
+
产出类型安全、可访问、提交状态明确的表单;复杂字段和文件上传有 schema 约束,输入过程无明显卡顿,服务端错误能回填到用户可理解的位置。
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# 高级表单模式
|
|
2
|
+
|
|
3
|
+
## Controller
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
<Controller
|
|
7
|
+
name="role"
|
|
8
|
+
control={control}
|
|
9
|
+
rules={{ required: "请选择角色" }}
|
|
10
|
+
render={({ field, fieldState }) => (
|
|
11
|
+
<>
|
|
12
|
+
<Select {...field} options={roleOptions} />
|
|
13
|
+
{fieldState.error && <span role="alert">{fieldState.error.message}</span>}
|
|
14
|
+
</>
|
|
15
|
+
)}
|
|
16
|
+
/>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
用于不暴露原生 `ref` 的 Select、DatePicker、RichEditor 等组件。
|
|
20
|
+
|
|
21
|
+
## 动态字段
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
const { fields, append, remove } = useFieldArray({
|
|
25
|
+
control,
|
|
26
|
+
name: "contacts",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
{fields.map((field, index) => (
|
|
30
|
+
<div key={field.id}>
|
|
31
|
+
<input {...register(`contacts.${index}.name`)} />
|
|
32
|
+
<button type="button" onClick={() => remove(index)}>删除</button>
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`key` 必须使用 `field.id`,不要使用数组 index。
|
|
38
|
+
|
|
39
|
+
## 联动校验
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const schema = z
|
|
43
|
+
.object({
|
|
44
|
+
password: z.string().min(8),
|
|
45
|
+
confirmPassword: z.string().min(8),
|
|
46
|
+
})
|
|
47
|
+
.refine((data) => data.password === data.confirmPassword, {
|
|
48
|
+
message: "两次密码不一致",
|
|
49
|
+
path: ["confirmPassword"],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 文件上传
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
const schema = z.object({
|
|
57
|
+
avatar: z
|
|
58
|
+
.instanceof(FileList)
|
|
59
|
+
.refine((files) => files.length > 0, "请上传头像")
|
|
60
|
+
.refine((files) => files[0]?.size <= 5 * 1024 * 1024, "文件大小不超过 5MB")
|
|
61
|
+
.refine((files) => ["image/jpeg", "image/png"].includes(files[0]?.type), "仅支持 JPG/PNG"),
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
提交时用 `FormData`,不要把 `File` JSON 序列化。
|
|
66
|
+
|
|
67
|
+
## 性能
|
|
68
|
+
|
|
69
|
+
- 用 `useWatch` 或字段级订阅替代全表单 watch。
|
|
70
|
+
- 大表单拆成 memoized 子组件。
|
|
71
|
+
- 高频输入不要每次键入触发网络校验。
|
|
72
|
+
- 多步表单要明确每步 schema 和跨步最终 schema。
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fec-implement-from-design
|
|
3
|
+
description: Use when implementing UI from Figma, Sketch, MasterGo, Pixso, Modao, MockingBot, screenshots, design selections, design tokens, or design-to-code tasks for production frontend components/pages; Chinese triggers include 设计稿, 按设计实现.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 按设计稿实现
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
基于设计工具(Figma、Sketch、MasterGo、Pixso、墨刀、摹客)的设计上下文,高保真实现页面或组件,强调组件复用、设计 Token 映射、可访问性和面向生产的前端实现。
|
|
11
|
+
|
|
12
|
+
## 支持的设计工具
|
|
13
|
+
|
|
14
|
+
| 工具 | MCP 集成 | 获取设计数据方式 |
|
|
15
|
+
| -------- | ------------------------- | ----------------------------------------- |
|
|
16
|
+
| Figma | `figma` / `figma-desktop` | API 获取设计结构、变量定义 |
|
|
17
|
+
| Sketch | `sketch` | MCP 获取设计选区截图 |
|
|
18
|
+
| MasterGo | `mastergo` | API 获取 DSL 结构数据 |
|
|
19
|
+
| Pixso | `pixso` | 本地 MCP 获取帧数据和代码 |
|
|
20
|
+
| 墨刀 | `modao` | MCP 获取原型数据、生成设计描述 |
|
|
21
|
+
| 摹客 | 无 MCP | 通过用户提供的截图、标注或导出的 CSS 获取 |
|
|
22
|
+
|
|
23
|
+
## 目标
|
|
24
|
+
|
|
25
|
+
- 尽量高还原地实现设计稿
|
|
26
|
+
- 在创建新组件前优先复用现有项目组件
|
|
27
|
+
- 尽可能把设计变量映射到现有 Token
|
|
28
|
+
- 保持实现结果可维护、类型明确、可测试且具备可访问性
|
|
29
|
+
- 避免引入重复的基础组件或并行设计系统
|
|
30
|
+
|
|
31
|
+
## 必需工作流
|
|
32
|
+
|
|
33
|
+
### 1. 识别设计来源
|
|
34
|
+
|
|
35
|
+
按优先级选择设计数据获取方式:
|
|
36
|
+
|
|
37
|
+
- `figma` — Figma API 集成
|
|
38
|
+
- `figma-desktop` — Figma 桌面端集成
|
|
39
|
+
- `mastergo` — MasterGo DSL 数据
|
|
40
|
+
- `pixso` — Pixso 本地 MCP
|
|
41
|
+
- `modao` — 墨刀原型数据
|
|
42
|
+
- `sketch` — Sketch 选区截图
|
|
43
|
+
- 如以上 MCP 均不可用,请求用户提供设计截图或标注(适用于摹客等无 MCP 工具)
|
|
44
|
+
|
|
45
|
+
### 2. 读取设计上下文
|
|
46
|
+
|
|
47
|
+
通过 MCP 或用户提供的设计数据读取设计上下文:
|
|
48
|
+
|
|
49
|
+
- 检查布局结构
|
|
50
|
+
- 检查间距、字体、颜色、变量、状态、图标和组件层级
|
|
51
|
+
- 如果 MCP 提供了资源文件或 SVG / 图片源,直接使用
|
|
52
|
+
- 如果 MCP 已提供真实资源,不要自行造占位资源
|
|
53
|
+
- 如果用户提供截图而非 MCP 数据,从截图中推断布局、颜色、字体等视觉信息
|
|
54
|
+
|
|
55
|
+
### 3. 搜索可复用组件
|
|
56
|
+
|
|
57
|
+
在创建新组件前先搜索代码库中的可复用组件,重点检查:
|
|
58
|
+
|
|
59
|
+
- Button
|
|
60
|
+
- Input / Select / Checkbox / Radio / Switch
|
|
61
|
+
- Modal / Drawer / Dialog
|
|
62
|
+
- Table / List / Card
|
|
63
|
+
- Tabs / Breadcrumb / Pagination
|
|
64
|
+
- 页面容器 / 区块容器 / 空状态 / Loading 状态
|
|
65
|
+
|
|
66
|
+
### 4. 产出实现计划
|
|
67
|
+
|
|
68
|
+
在改文件前先产出一份简短实现计划,计划必须包含:
|
|
69
|
+
|
|
70
|
+
- 需要改动的文件列表
|
|
71
|
+
- 组件拆分方案
|
|
72
|
+
- 状态 / 数据流
|
|
73
|
+
- 响应式行为
|
|
74
|
+
- 复用还是新建的决策
|
|
75
|
+
- 设计缺口或歧义点
|
|
76
|
+
|
|
77
|
+
### 5. 按框架实现
|
|
78
|
+
|
|
79
|
+
按仓库当前使用的前端框架进行实现:
|
|
80
|
+
|
|
81
|
+
- 严格遵循仓库现有约定
|
|
82
|
+
- 优先使用明确类型的 props 和 interfaces/types
|
|
83
|
+
- 保持组件小而可组合
|
|
84
|
+
- 将重复逻辑提取为 hooks / composables / utilities
|
|
85
|
+
|
|
86
|
+
### 6. 设计 Token 映射
|
|
87
|
+
|
|
88
|
+
- 优先使用现有 design token、CSS 变量、主题变量或工具类
|
|
89
|
+
- 除非确实没有对应 Token,否则不要硬编码颜色、圆角和间距
|
|
90
|
+
- 如果设计使用了新 Token,要明确指出,不要悄悄到处硬编码
|
|
91
|
+
|
|
92
|
+
### 7. 可访问性保障
|
|
93
|
+
|
|
94
|
+
- 优先使用语义化 HTML
|
|
95
|
+
- 确保交互控件具有可访问名称
|
|
96
|
+
- 保留可见的焦点样式
|
|
97
|
+
- 检查对话框、菜单、标签页、表单控件的键盘可操作性
|
|
98
|
+
|
|
99
|
+
### 8. 编码后验证
|
|
100
|
+
|
|
101
|
+
- 如有 lint,执行 lint
|
|
102
|
+
- 如有测试,执行测试
|
|
103
|
+
- 如果缺少测试,说明建议补充的最小测试范围
|
|
104
|
+
|
|
105
|
+
## Detailed References
|
|
106
|
+
|
|
107
|
+
撰写实现计划报告时,加载 [references/design-plan-template.md](references/design-plan-template.md)。
|
|
108
|
+
|
|
109
|
+
## Constraints
|
|
110
|
+
|
|
111
|
+
- 如果已有设计上下文(MCP 或截图),不要靠猜来实现 UI
|
|
112
|
+
- 如果项目已有 UI 体系,不要再引入一套新的 UI Kit
|
|
113
|
+
- 除非有合理理由,不要用硬编码替代已 Token 化的样式
|
|
114
|
+
- 不要忽略 hover、active、disabled、loading、empty、error 等状态
|
|
115
|
+
- 摹客等无 MCP 工具场景下,主动向用户索要关键截图和标注信息,不要凭空编造视觉数据
|
|
116
|
+
|
|
117
|
+
## Expected Output
|
|
118
|
+
|
|
119
|
+
- 设计实现计划报告保存为 `reports/design-plan-YYYY-MM-DD-HHmmss.md`,包含实现概要、组件拆分方案、状态/数据流、变更文件清单
|
|
120
|
+
- 代码实现高保真还原设计稿,组件复用率最大化
|
|
121
|
+
- 设计变量映射到现有 Token,无硬编码颜色/间距/圆角
|
|
122
|
+
- 交互元素具备可访问名称和键盘支持,焦点样式可见
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 设计实现计划模板
|
|
2
|
+
|
|
3
|
+
```markdown
|
|
4
|
+
# 设计实现计划
|
|
5
|
+
|
|
6
|
+
> 生成时间: YYYY-MM-DD HH:mm
|
|
7
|
+
> 评审工具: frontend-craft
|
|
8
|
+
> 设计工具: Figma / Sketch / MasterGo / Pixso / 墨刀 / 摹客
|
|
9
|
+
|
|
10
|
+
## 实现概要
|
|
11
|
+
- 设计稿来源: ...
|
|
12
|
+
- 目标页面/组件: ...
|
|
13
|
+
|
|
14
|
+
## 复用的组件
|
|
15
|
+
- ...
|
|
16
|
+
|
|
17
|
+
## 新建的组件
|
|
18
|
+
- ...
|
|
19
|
+
|
|
20
|
+
## 组件拆分方案
|
|
21
|
+
- ...
|
|
22
|
+
|
|
23
|
+
## 状态/数据流
|
|
24
|
+
- ...
|
|
25
|
+
|
|
26
|
+
## 与设计稿的偏差
|
|
27
|
+
- ...
|
|
28
|
+
|
|
29
|
+
## 缺失的资源 / Token / 交互细节
|
|
30
|
+
- ...
|
|
31
|
+
|
|
32
|
+
## 变更文件清单
|
|
33
|
+
- ...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
实现计划确定后,将报告保存到 `reports/design-plan-YYYY-MM-DD-HHmmss.md`,并告知用户报告路径。
|