Sprint 1 G0 闸门通过 (pnpm install / typecheck / lint / build / turbo 全部 exit 0) 实际完成: - T-A-01 monorepo 根骨架: pnpm-workspace.yaml + turbo.json + 根 package.json - T-A-02 收编原型: apps/web/ 30 个 src/ 文件 (4948 行) 首次入库 - T-A-03 lint/format: eslint flat config (根 + apps/web) + prettier - T-A-23 web-ci.yml: GitHub Actions 4-step CI (typecheck/lint/build/bundle) 勘误 (前置假设错误): - 原型 4948 行 (不是 4114 行) - 原型含 33 个 any + 16 个未用变量 (Sprint 2 修) - 原型从未 commit, 用 cp -r + git add, 不是 git mv 推迟到 Sprint 2+: - T-A-04/05 tsconfig packages 抽离 - T-A-06/07/08 design tokens 抽离 - T-A-09~13 5 基础组件 依赖隔离: - .gitignore 加 11 行 monorepo 规则 - apps/web/.gitignore 补 .npm-cache/next-dev.log - 删除 apps/web 内嵌 .git/ (cp -r 复制时携带) 验证: - pnpm install: 1m55s, 456 packages - typecheck: 0 errors - lint: 0 errors, 49 warnings (any + unused) - next build: 8 routes compiled, 9/9 static pages - turbo run build: 1 successful, 12.9s
9.9 KiB
9.9 KiB
Sprint 2 任务拆解(W2-3 · 18 人天 · 58 子任务)
主任务:T-A-14 ~ T-A-22, T-B-01 ~ T-B-05 目标:Vitest/Playwright 骨架 + OpenAPI Codegen + 后端 5 端点对接 闸门:G1(OpenAPI 类型生成无
any)
0. Sprint 2 概览
| 任务 | 主任务 | 估时 | 子任务数 |
|---|---|---|---|
| T-A-14 | Vitest + RTL + MSW 配置 | 0.5d | 4 |
| T-A-15 | 示范单测:ModeIndicator | 0.25d | 3 |
| T-A-16 | 示范单测:SafeMarkdown | 0.25d | 3 |
| T-A-17 | 示范单测:FormCard | 0.5d | 4 |
| T-A-18 | Playwright 配置 | 0.5d | 3 |
| T-A-19 | 示范 e2e:theme.spec | 0.5d | 3 |
| T-A-20 | 示范 e2e:navigation.spec | 0.5d | 3 |
| T-A-21 | OpenAPI Codegen 接入 | 1.0d | 5 |
| T-A-22 | 示范调用:mock server | 0.5d | 4 |
| T-B-01 | POST /api/chat/send |
1.5d | 6 |
| T-B-02 | GET /api/chat/history |
0.5d | 4 |
| T-B-03 | 3 个 consultations 端点 | 1.0d | 5 |
| T-B-04 | 4 个 plans 端点 | 1.0d | 5 |
| T-B-05 | assessment + audit/upload | 2.0d | 6 |
| 合计 | 15 任务 | 10.5d + 缓冲 7.5d = 18d | 58 |
T-A-14 · Vitest + RTL + MSW 配置(0.5d · 4 子任务)
ST-S2-A-14.1 写 vitest.config.ts(0.125d)
- 产出:
apps/web/vitest.config.ts(jsdom env + setup 文件 + alias)
ST-S2-A-14.2 写 setup.ts(0.125d)
- 产出:
@testing-library/jest-dom/vitest+ MSW server 启动/关闭
ST-S2-A-14.3 写 renderWithProviders.tsx(0.125d)
- 产出:
packages/test-utils/renderWithProviders.tsx(QueryClient + Theme + Router + Intl) - 验收:
pnpm --filter web test跑通
ST-S2-A-14.4 占位测试验证(0.125d)
- 产出:
Math.test.ts(1+1=2) - 验收:
- 占位测试 1 绿
T-A-15 · 示范单测:ModeIndicator(0.25d · 3 子任务)
ST-S2-A-15.1 写 4 mode 渲染测试(0.125d)
- 覆盖:
- explore: "探索模式"
- generating: "生成中"
- auditing: "审核中"
- adjusting: "调整中"
- 验收:
- 4 模式 label 正确
ST-S2-A-15.2 写 deriveMode 决策树测试(0.0625d → 0d)
- 覆盖:
- isAuditActive → auditing
- currentPlan + no audit → adjusting
- province + score → generating
- 其他 → explore
- 验收:
- 100% branch coverage
ST-S2-A-15.3 验证 100% 覆盖(0.0625d → 0d)
- 命令:
pnpm test --coverage - 验收:
ModeIndicator.tsxline + branch 各 100%
T-A-16 · 示范单测:SafeMarkdown(0.25d · 3 子任务)
ST-S2-A-16.1 写 XSS 注入测试(0.125d)
- 覆盖:
<script>alert(1)</script>→ 渲染为文本<img src=x onerror=alert(1)>→ onerror 剥<a href="javascript:alert(1)">→ href sanitize
- 验收:
- 3 注入用例全过
ST-S2-A-16.2 写正常 markdown 渲染(0.0625d → 0d)
- 覆盖:h1/ul/code/blockquote 渲染正确
ST-S2-A-16.3 验证(0.0625d → 0d)
T-A-17 · 示范单测:FormCard(0.5d · 4 子任务)
ST-S2-A-17.1 字段 touched 显示验证(0.125d)
- 验收:
- 字段未 touch 时不显示错误
- touch 后显示错误
ST-S2-A-17.2 跳步守卫测试(0.125d)
- 验收:
- basic 未完成时点击 subjects 步骤 → 阻止 + 提示
ST-S2-A-17.3 subjects 数组拼装测试(0.125d)
- 验收:
- 选物理/化学/生物 → 提交时
subjects: ["physics", "chemistry", "biology"]
- 选物理/化学/生物 → 提交时
ST-S2-A-17.4 验证 80% 覆盖(0.125d)
- 命令:
pnpm test --coverage --testPathPattern=FormCard
T-A-18 · Playwright 配置(0.5d · 3 子任务)
ST-S2-A-18.1 写 playwright.config.ts(0.25d)
- 产出:
apps/web/e2e/playwright.config.ts- chromium + webkit
- viewports: 375 (mobile) / 768 (tablet) / 1280 (desktop)
- baseURL: http://localhost:3000
- 验收:
pnpm --filter web e2e启动浏览器
ST-S2-A-18.2 写 smoke.spec.ts(0.125d)
- 验收:
- 访问
/看到 Sidebar
- 访问
ST-S2-A-18.3 写 webServer 配置(0.125d)
- 产出:自动启
pnpm dev
T-A-19 · 示范 e2e:theme.spec(0.5d · 3 子任务)
ST-S2-A-19.1 写 light/dark/system 切换(0.25d)
- 覆盖:
- 切到 dark →
data-theme="dark"出现 - 切到 system →
data-theme移除
- 切到 dark →
- 验收:
- 3 场景全绿
ST-S2-A-19.2 写持久化测试(0.125d)
- 覆盖:
- 刷新页面后 localStorage 保留选择
- 验收:
expect(localStorage.getItem('theme')).toBe('dark')
ST-S2-A-19.3 写闪白防护测试(0.125d)
- 覆盖:
page.waitForLoadState('domcontentloaded')在 100ms 内
- 验收:
- DOMContentLoaded → theme 已应用
T-A-20 · 示范 e2e:navigation.spec(0.5d · 3 子任务)
ST-S2-A-20.1 写桌面导航(0.125d)
- 覆盖:viewport 1280 → Sidebar 可见,MobileNav 隐藏
ST-S2-A-20.2 写移动导航(0.125d)
- 覆盖:viewport 375 → MobileNav 可见,Sidebar 隐藏
ST-S2-A-20.3 写跳转测试(0.25d)
- 覆盖:
- Sidebar /plans → 跳转
- MobileNav /plans → 跳转
- 验收:
- 4 场景全绿
T-A-21 · OpenAPI Codegen 接入(1.0d · 5 子任务)
ST-S2-A-21.1 启动后端 OpenAPI(0.125d)
- 命令:
curl http://localhost:8000/openapi.json > openapi.json - 验收:
- 看到 ~30+ 端点(V2 增至 30+)
ST-S2-A-21.2 写 codegen.config.ts(0.25d)
- 产出:
packages/api-client/codegen.config.ts- schema: openapi.json
- generator: typescript-fetch
- output: src/gen
ST-S2-A-21.3 写 turbo.json generate 任务(0.125d)
- 产出:
generate-api-client任务依赖^build-admin - 验收:
pnpm turbo run generate-api-client跑通
ST-S2-A-21.4 跑 codegen 看产物(0.25d)
- 验收:
packages/api-client/src/gen/services/30+ 服务类- 0 个
any(G1 闸门)
ST-S2-A-21.5 写 packages/api-client/src/index.ts barrel(0.25d)
- 验收:
import { AuthService } from '@gaokao/api-client'可用
T-A-22 · 示范调用:mock server(0.5d · 4 子任务)
ST-S2-A-22.1 写 apps/web/lib/api/chat.ts(0.125d)
- API:
export const apiClient = { chat: { send: (consultationId: string | undefined, body: { message: string }) => Promise<ChatResponse>, getHistory: (consultationId: string) => Promise<{ messages: Message[] }>, } };
ST-S2-A-22.2 写 MSW handler(0.125d)
- 产出:
apps/web/mocks/handlers/chat.ts拦截/api/chat/send
ST-S2-A-22.3 在 page.tsx 临时接 apiClient(0.125d)
- 验收:
- 类型管道通(不替换 useChat,仅验证)
ST-S2-A-22.4 写 Zod 校验 match 字段(0.125d)
- 约束:§0.1 R-NEW-3(
z.number().int().min(1).max(5)) - 验收:
- match=6 → Zod 抛错
T-B-01 · POST /api/chat/send(1.5d · 6 子任务)
ST-S2-B-01.1 对接 OpenAPI 类型(0.25d)
- 产出:
import type { ChatSendRequest, ChatResponse } from '@gaokao/api-client/gen'
ST-S2-B-01.2 写 useChatStore.sendMessage(0.5d)
- 产出:
apps/web/stores/chatStore.ts - API:
sendMessage(consultationId, body)→ 调用apiClient.chat.send - 验收:
- 401/403 错误码正确捕获
ST-S2-B-01.3 写错误码 E03001/03002/03003 处理(0.25d)
- 验收:
- 3 错误码 → 对应 Toast 提示
ST-S2-B-01.4 写单测(0.25d)
- 覆盖:成功 / 401 / 403 / 500
ST-S2-B-01.5 写 e2e 草稿(0.125d)
- 产出:
chat-send.spec.ts(Sprint 3 完成)
ST-S2-B-01.6 写 Storybook 故事(0.125d)
- 产出:
ChatSendButton.stories.ts
T-B-02 · GET /api/chat/history(0.5d · 4 子任务)
ST-S2-B-02.1 对接 OpenAPI(0.125d)
ST-S2-B-02.2 写 useQuery(0.125d)
- 验收:
- cursor 分页支持
ST-S2-B-02.3 写 auth 校验(0.125d)
- 验收:
- consultation_id 无访问令牌 → 401
ST-S2-B-02.4 写单测(0.125d)
T-B-03 · 3 个 consultations 端点(1.0d · 5 子任务)
ST-S2-B-03.1 列表(0.25d)
- 出参:
{consultations: [{id, title, updated_at, profile, has_plan, has_audit}]}
ST-S2-B-03.2 详情(0.25d)
- 出参:完整 messages + profile + plan + audit
ST-S2-B-03.3 DELETE 软删除(0.25d)
- 验收:
- 数据保留 30 天
ST-S2-B-03.4 写 useConsultation hook(0.125d)
ST-S2-B-03.5 写单测(0.125d)
T-B-04 · 4 个 plans 端点(1.0d · 5 子任务)
ST-S2-B-04.1 列表(0.25d)
- 出参:
{plans: [{id, name, created_at, profile, distribution: {rush,stable,safe}}]}
ST-S2-B-04.2 详情(0.125d)
ST-S2-B-04.3 PATCH name 修改(0.25d)
- 验收:
- 仅支持 name,其他字段 PATCH 拒绝
ST-S2-B-04.4 DELETE 软删除(0.25d)
ST-S2-B-04.5 写单测(0.125d)
T-B-05 · assessment + audit/upload(2.0d · 6 子任务)
ST-S2-B-05.1 写 useAssessment hook(0.25d)
- API:
POST /api/assessment(Holland RIAS)
ST-S2-B-05.2 写 useAudit.upload multipart(0.5d)
- 验收:
- Excel 5MB / Image 10MB / PDF 10MB 限制
- MIME 校验
ST-S2-B-05.3 写上传进度条(0.25d)
- API:
XMLHttpRequest+progress事件
ST-S2-B-05.4 写 AI 处理进度(0.5d)
- API:轮询
/api/audit/{id}/status
ST-S2-B-05.5 错误码 E04001 处理(0.25d)
- 验收:
- 上游 AI 失败 → Toast 重试
ST-S2-B-05.6 写单测(0.25d)
Sprint 2 收口验收
- 15 主任务 / 58 子任务全部完成
- G1 通过:OpenAPI 生成 0 个
any - 测试覆盖率:ModeIndicator/SafeMarkdown/FormCard 100% / 100% / 80%+
- E2E:theme.spec / navigation.spec 全绿
- 进入 Sprint 3 前 commit:
<feat(s2): vitest+playwright+openapi+5 endpoints>