# 前端功能性 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 预期与实现的范围差）；多处表单缺 `<label>`/aria（rules textarea 等）a11y；等。完整见 errors JSON。

## 信息性观察（非缺陷）
- **skills 注册表为空**（`GET /skills` → `{"skills":[]}`）：本环境 DB 未播种内置 skill，故 SkillsPage 正确显示空态。页面本身工作正常（重建后 200）。若期望展示内置 8/21 agent，需要 skills 播种逻辑/数据。

## 各 area 状态
projects-list+create：H1 + 2 LOW · outline：1 MED×2 + 2 LOW（只读）· rules：H2 + 3 MED + 1 LOW · foreshadow：✅（环境 500 已修，功能正常）· templates：✅（空白校验此前已修）· settings/providers：1 MED · codex：2 MED · skills：✅（空态，数据未播种）· review-display：1 MED（idx）· nav/shell+command：1 MED（a11y）· write-sse：**C1** · toolbox-generators：见下 · style：H3 · chain：1 MED（已确认可跑通）

## 覆盖与缺口
- 环境故障期间**浏览器交互层**（hydration 后的真实点击/键盘）被全站 500 阻断，agent 多以源码+API 推断；建议在**已修复的环境**上重跑一次浏览器冒烟以补足 LLM 结果页的真实渲染验证。
- LLM 端到端：部分生成流程因 Kimi 限流/单 PID 数据（无 style 指纹）未能必现 H3；chain 已独立确认 count=1 跑通。
