Files
Jyotisha/docs/tasks/PROGRESS-report-progress-20260909.md
T
Jesse_ChenandClaude Fable 5 5565b6324e
Independent Staging Quality Gate / validate (push) Successful in 9m50s
Independent Staging Quality Gate / publish (push) Successful in 1m59s
docs(reports): record BUG-601, progress notes and the manual walkthrough
Renumbered from BUG-599: staging took 599 and 600 from other sessions
while this branch was in review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016P5RoqzmUQEbeC2qjAkeGr
2026-09-09 03:30:39 +00:00

73 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PROGRESS · 报告生成分章进度(2026-09-09
- 分支:`codex/report-progress-20260909`,基线 `origin/staging` = `a43a6db8`
- 模式:直接执行模式(用户指定 subagent 执行),无独立任务书;设计口径来自会话内定稿
- BUG 编号:**BUG-601**(开工核对当时最大号 598,原写 599;交付前 rebase 发现 staging 已有别的会话占用 599「回访登录自动打开生时校正」与 600「新用户保存账户资料 500」,故顺延到 601)
## 一、做了什么
`/reports/[reportId]` 的生成等待屏从「spinner + 秒表」改成分章进度。
### 后端(本轮唯一的服务端改动)
1. `app/api/reports/[reportId]/route.ts``listSections` 原先只返回 `{status, lastErrorCode}`,现补上 `sectionId``attemptCount`。两者都只在服务端消费。
2. `lib/personal-report-route-core.ts`
- 分章行原先**只在 `row.status === "failed"` 时才读**`reportView` 也不含任何分章字段——也就是说生成中根本没有章节数据离开服务端。现改为 `failed``generating` 两种状态都读;`ready` 路径不变,不增加查询。
- 新增 `sectionProgress` 推导,随 `reportView` 第四参数下发,只带 `{id, state}`
### 新增共享层
3. `lib/personal-report-progress.ts`(新):
- `REPORT_THEME_LABELS` / `reportThemeLabel`:从 `personal-report-document-view.tsx` 提取,原处改为 import,**不留第二份副本**。
- `deriveSectionProgressState(status, attemptCount)`:行状态 → `done / failed / writing / waiting`。这是全仓唯一读 `attemptCount` 的地方,且只在服务端跑。
- `classifyReportProgressStage(phase)``preparing / writing / finishing`
- `describeReportProgress()`:纯函数,产出标题句与章节清单。
- `reportProgressSignature()`:进度指纹,用于停滞判定。
- `REPORT_PROGRESS_STALL_MS = 90_000`
### 前端
4. `components/personal-report/personal-report-progress-panel.tsx`(新):按章分格的进度条 + 章节清单。无 percent、无定时器、无 transition。
5. `components/personal-report/personal-report-page.tsx`:解析 `sections`(带白名单校验,形状不对就丢弃而不是渲染成谜之行);用 `progressMark` 记录进度指纹与时刻,停滞判定复用既有的一秒 tick(`waitStartedAt + waitedMs` 即"现在"),**未新增定时器、未新增 effect**;写作阶段隐藏 spinner 换成面板。
6. `app/globals.css`:新增等待屏进度样式。
7. `frontend/DESIGN.md` §9 新增「报告生成等待态」小节。
## 二、三条实现事实(做错任一条功能就是错的)
1. **`section:<id>` 命名的是刚写完的那一章。** `emitProgress``complete()`/`block()` 之后调用(`personal-report-generation.ts:3459 / 3485 / 3496`)。
2. **"已完成数 + 1"同样会指错。** `sectionService.list()``section_id` **字典序**返回(`personal-report-section-service-core.ts:113`),不是写作顺序。所以顺推出来的"下一章"与真正在写的那一章无关。
→ 因此本轮改用**行状态**判定:`start_personal_report_section``attempt_count + 1` 并保持 `status='pending'`(迁移 `20260830020000` 第 102111 行),所以"正在写"精确等于 `pending && attemptCount > 0`,不是推断。测试里专门构造了字典序会指错的用例(正确答案 `marriage`,顺推会得到 `timing`)。
3. **blocked 计入完成数**,进度条照常前进;收尾走既有 `summarizePersonalReportFailure()`
## 三、测试数字
| 项 | 基线(a43a6db8 | 改后 | 结论 |
| --- | --- | --- | --- |
| `tsc --noEmit` | 0 错 | 0 错 | 通过 |
| `npm run lint` | 0 error / 108 warning | 0 error / 108 warning | 通过;warning 数不变,且无一条在本轮改动的文件里 |
| `npm test` tests | 2929 | 2945 | +16 |
| `npm test` pass | 2887 | 2903 | +16 |
| `npm test` fail | 28 | 28 | 清单 `diff` 逐条一致 |
| `npm test` skipped | 14 | 14 | 不变 |
| `next build` `/` | `○ Static` | `○ Static` | 通过 |
| `globals.css` gzip | 33,324 B | 33,641 B | +317 B / +0.95%,在 ±2% 内 |
28 条失败全部是无 Docker 的数据库/部署/迁移套件,基线即红。失败清单已逐条 `diff` 比对,**完全一致,无新增、无消失**。
新增测试:`frontend/tests/personal-report-progress.test.ts` 16 项,覆盖任务要求的六类——off-by-one(含字典序陷阱)、blocked 计入进度、全 ready 与部分 blocked 两种收尾、90 秒停滞文案切换且不出现「第 N 次尝试」、准备/写作/收尾三分支渲染、以及后台字段不出服务端。
**未改动任何既有断言**,因此无「原值 / 新值 / 原因」三栏。特别说明一条:`tests/report-polling-contract.test.ts:105``assert.doesNotMatch(pageSource, /章节/)`,本轮把章节渲染整体放进独立组件 `personal-report-progress-panel.tsx``pageSource` 里确实不出现「章节」,该断言原样通过,未被弱化或删除。
## 四、偏离与判断
1. **任务交代「`sections[]` 目前被剥掉了 `sectionId`,把它加回去」,实际情况更严重一层。** 分章数组根本不在 `generating` 的响应里——`resolveReportRead` 只在 `failed` 时读分章行,`reportView` 也没有该字段。所以只补 `sectionId` 不够,必须同时让生成中也读分章行并下发。这是比交代范围略大的服务端改动,但不做则整个功能无数据可用。`ready` 路径未加查询。
2. **停滞判定没有用 `progressPhase` 的持续时长,而是用完整进度指纹**phase + percent + 各章状态)。理由:重试时 phase 与 percent 都不动,但若同期别的信号变了,仍不该报"停滞"。指纹变化才重置计时,更严格。
3. **面板不重复渲染标题句。** 标题句(「已完成 N / M 章」)只出现在页面的 `<p role="status">` 里,面板只画条与清单。这样读屏软件只播报一次,也不会出现同一句话上下各一遍。
4. **`--type-body-xs` 这个 token 不存在**,写样式时误用后已改掉,改用继承字号。未新增 token。
## 五、没做的
- **真实报告生成一次都没跑过**(无登录态、无 Chrome、无模型凭据)。写作阶段是否真的出现、章节名是否错位、慢章节文案是否真的切换,全部无运行时证据。已写成 `docs/testing/report-progress-20260909.md` 八节清单,并记入 `BLOCKED.md`
- **`npm run test:db` 未跑**(无 Docker,基线同样红)。本轮未动数据库结构、未加迁移。
- 未 push(按会话纪律,代码分支不自行推送)。