# 前端功能性 QA 报告 — 2026-06-24 > 方法:多 agent 并行 QA(15 个 area agent)。每个 area 读组件 + `lib/api` hook,直接 curl 打 `:8000` API(happy path + 404/422/503/空集/幂等边界),交叉核对组件逻辑 vs OpenAPI 契约;外加单浏览器 render/runtime 冒烟。LLM 流程按要求**完整端到端**跑真 Kimi。**仅记录、不改码。** > 原始数据:`frontend-qa-errors-2026-06-24.json`(全部错误)+ `frontend-qa-2026-06-24.md`(agent 合成稿,注意其严重度排序受下述环境故障污染)。 ## 执行摘要 - 测试覆盖:15 个 area,14 路由 + 52 组件。 - 计数(**剔除环境故障后**):**CRITICAL 1 · HIGH 3 · MEDIUM 11 · LOW ~26**。 - ⚠️ **环境故障(非源码缺陷,已修复)**:QA 期间运行环境有**两个并发 `next dev` 写同一个 `.next`**(我多次重启 `pnpm dev` 未杀旧进程所致),导致 `.next` 构建缓存损坏 → 全部 `/projects/[id]/*` 路由 500、静态 chunk 404、页面不 hydrate。这污染了 agent 报的 **2 个 CRITICAL + ~4 个 HIGH**("全站 500/不可交互")。**已 kill 重复进程 + `rm -rf .next` + 单实例重启**,复验:10 个 project 路由全部 **200**、静态 chunk **200**。这些不计入真实缺陷。 ## 真实缺陷(按严重度) ### CRITICAL (1) | # | 位置 | 问题 | 复现 | |---|---|---|---| | C1 | `apps/api/.../routers/projects.py::stream_draft`(`POST /projects/{pid}/chapters/{n}/draft`) | 对**不存在的 project** 流式写章返回 **200 + 真生成整章**,而非 404。入口未校验项目存在性就调网关——非法 id 会**静默烧掉一次付费、限流的 LLM 调用**。 | `curl -sN -X POST .../projects/00000000-.../chapters/7/draft -H 'Accept: text/event-stream'` → 200 + token/done 事件 | ### HIGH (3) | # | 位置 | 问题 | 复现 | |---|---|---|---| | H1 | `apps/web/components/ProjectWizard.tsx`(StepPremise + StepProtagonist) | 第3步「立意」与第4步「主角/金手指」**绑定同一个 `form.premise`**,后填覆盖先填 → 提交时丢数据,二者不能共存。 | /projects/new 填立意→下一步填主角→提交,`project.premise` 只剩主角文本 | | H2 | `apps/api/.../routers/rules.py` + `rule_repo.py` | 给**不存在的 project** POST 规则返回 **500(INTERNAL)** 而非 404——repo 直接 insert+flush,FK 违例逃逸成 500(真后端 bug,curl 直测,与 next dev 无关)。 | `curl -X POST .../projects/{随机uuid}/rules -d '{"level":"project","content":"x"}'` → 500 | | H3 | `review/ReviewReport.tsx`(segmentText) + `prompts/style.md` | 文风 drift 段 `idx`(LLM 按自己的分段)与前端段落 index(另一套切分)口径不一致 → 「一键回炉」可能改**错段落**。(需已学指纹+drift 才能必现;当前 PID 无指纹故未必现。) | 学指纹→写章→style 返回 idx>0 段→点回炉→改的不是被标段 | ### MEDIUM (11) - **outline**:卷号选择器只参与 POST 生成;`GET /outline?volume=N` **忽略过滤**,编辑器永远渲染全部卷,用户无法只看某卷。 - **outline**:`useOutline` 读 `error.error.code`,但 FastAPI 422 用 `{detail:[...]}` → 校验错误细节丢失,永远显示通用「大纲生成失败」。 - **rules**:纯空白 content 被接受持久化(201)——`min_length:1` 在 trim 前校验,服务端不 strip。 - **rules**:`GET /projects/{不存在}/rules` 返 **200 `{"rules":[]}`** 而非 404,掩盖坏 id。 - **rules**:**无 DELETE/编辑端点**——规则只能增+读,误加的规则(如上面的空白行)无法删除。 - **settings/providers**:`PUT /settings/providers` 接受**任意未知 tier**(如 `bogus`)和未知 provider 名,无枚举/白名单校验。 - **codex**:角色 relations 在读路径被**静默丢弃**,CodexPage 也从不展示已存角色的 relations。 - **codex**:角色入库**非幂等**——同名卡重复入库生成重复行,UI 渲染重复 chip。 - **review/style**:drift 段 `idx` 超过本章段落数时,渲染一个可点的「第 N 段/一键回炉」但点击**静默 no-op**。 - **command palette**:listbox/combobox a11y 契约不完整,屏幕阅读器读不全。 - **chain**:run/resume 失败统一塌成通用 toast「出错了,请稍后重试」,丢弃 API envelope 的结构化 code/message。 ### LOW(~26,摘要) 通用错误 toast 丢弃后端 detail(多处:wizard/chain/outline);服务端接受纯空白标题(201);空大纲生成仍报「已生成」成功 toast 却显示空态;outline 全程只读(无 PUT 编辑,QA 预期与实现的范围差);多处表单缺 `