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
This commit is contained in:
co-authored by
Claude Fable 5
parent
848e39e61f
commit
5565b6324e
@@ -9318,3 +9318,18 @@
|
||||
- 复发自:BUG-039(全球地点列漏授 service_role 的同类权限缺口)
|
||||
- 修复版本:迁移 `20260909010000`,待发布
|
||||
|
||||
## BUG-601 | 报告生成进度后端已产出,前端解析后一字未渲染,8 分钟只见转圈
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-09-09
|
||||
- 最近更新:2026-09-09
|
||||
- 影响面:`/reports/[reportId]` 生成等待屏、`personal-report-page.tsx`、`personal-report-route-core.ts`
|
||||
- 用户现象:生成个人报告后停在等待页,屏幕上只有一个 `InlineSpinner`、一句固定文案「正在生成报告,大约 10–30 秒」和「已等待 X 分 Y 秒」。实际耗时以分钟计,轮询预算 8 分钟。用户无从判断是在推进还是已经卡死,普遍在三分钟左右刷新或离开。
|
||||
- 触发条件:任何一次个人报告生成,必现。
|
||||
- 根因:两段断链。其一,`personal-report-page.tsx` 的 `classifyReportEnvelope` 已经把 `progressPercent` / `progressPhase` 解析进 `generating` 状态对象(原第 97–102 行),但该分支的渲染(原第 231–246 行)完全没有引用这两个字段,等待屏因此与后端进度无关。其二,`resolveReportRead` 只在 `row.status === "failed"` 时读取分章行,`reportView` 也不含分章字段,所以生成中根本没有任何章节状态离开服务端——即便前端想渲染也无数据可用。worker 侧的阶梯(`loading_context` 10 → `generating_report` 30 → `section:<id>` 30–85 → `persisting_report` 90)一直在正常写库,只是没有读者。
|
||||
- 修复:分章进度成为等待屏的主体。服务端在 `generating` 与 `failed` 两种状态下读取分章行(`ready` 路径不变,不增加查询);新增 `personal-report-progress.ts` 把行状态映射为只读的四态 `done / failed / writing / waiting` 并随 `reportView` 下发,只带 `id` 与 `state`。等待屏按 phase 分三段:准备阶段保留 spinner,写作阶段换成"已完成 N / M 章"加按章分格的进度条与章节清单,收尾阶段回到 spinner。停滞满 90 秒(`REPORT_PROGRESS_STALL_MS`)时当前章文案改为「用时较长,仍在写」,该计时复用既有的一秒 tick,未新增定时器或 effect。
|
||||
- 验证:`frontend/tests/personal-report-progress.test.ts` 16 项全绿,覆盖:认领态判定为「正在写」而非按完成数顺推(字典序返回时顺推会指向 `timing` 而正确答案是 `marriage`)、phase 命名的章已完成、blocked 计入完成数且 `hasFailure` 为真、全 ready 与部分 blocked 两种收尾、90 秒停滞文案切换且不出现「第 N 次尝试」、准备/写作/收尾三分支、分章行缺失时回落准备态而非渲染「已完成 0 / 0 章」、面板无 percent/定时器/预计剩余、`attemptCount` 等后台字段不出服务端。全量 `npm test` 2929→2945(pass 2887→2903),失败 28 条与基线逐条一致(全部为无 Docker 的数据库/部署套件)。`tsc --noEmit` 0 错,`npm run lint` 0 error。
|
||||
- 防复发:三条写进 `frontend/DESIGN.md` §9「报告生成等待态」并由上述测试锁死。其一,进度条按章分格,不得改画百分比条——job 的 percent 在 0→30 与 90→100 是瞬间跳变,线性条会演出后端没做的动作。其二,"正在写哪一章"只能由行状态(`pending` 且已认领)判定,不得由 `section:<id>` 的 phase 名或"已完成数 + 1"推导:前者命名的是刚写完的那一章,后者在字典序列表上会指错。其三,界面上不得出现按定时器推进的插值动画或预计剩余时间。
|
||||
- 相关记录:BUG-043(禁止把后台评分状态渲染成用户需要管理的面板。本处边界不同且不冲突:等待屏是只读的,展示的是用户交付物自身的章节结构,`attemptCount`、原始错误码、lease、job id、payload 一律不出服务端)、BUG-576、BUG-574
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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` 第 102–111 行),所以"正在写"精确等于 `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(按会话纪律,代码分支不自行推送)。
|
||||
@@ -0,0 +1,67 @@
|
||||
# 报告生成进度:真实环境走查清单(2026-09-09,BUG-601)
|
||||
|
||||
执行环境没有登录态、没有 Chrome、没有模型凭据,因此**没有任何一次真实报告生成被观察过**。下面每一条都需要一位有真实账号的人在 staging 上照做。自动化替代证据见 `frontend/tests/personal-report-progress.test.ts`(16 项,纯函数 + 源码锁)。
|
||||
|
||||
前置:staging 已部署含本轮改动的 SHA;账号点数足以生成一份个人报告。
|
||||
|
||||
## 1. 三个阶段各自出现过
|
||||
|
||||
生成一份个人报告,停在 `/reports/<id>` 不要离开,全程录屏或每 30 秒截一张。
|
||||
|
||||
- [ ] **准备阶段**:屏幕上是一个转圈 + 「正在准备你的星盘证据」。没有章节清单,没有「已完成 0 / 0 章」。
|
||||
- [ ] **写作阶段**:转圈消失,换成「已完成 N / M 章」大字 + 一条按章分格的进度条 + 章节清单。M 等于本次生成的主题数(默认 5:事业、婚恋、财富、应期、健康)。
|
||||
- [ ] **收尾阶段**:清单消失,回到转圈 + 「正在整理成文」。
|
||||
- [ ] 完成后自动跳到报告正文。
|
||||
|
||||
如果**从头到尾没有出现过写作阶段**,说明分章行没有下发,这是本轮最可能的失败模式,请把 `/api/reports/<id>` 的响应体(去掉正文)贴给开发。
|
||||
|
||||
## 2. 章节名对得上,而且不错位
|
||||
|
||||
这是本轮最容易做错的一条,请特别留意。
|
||||
|
||||
- [ ] 清单里同一时刻**至多一章**显示「正在写」。
|
||||
- [ ] 那一章的名字,与几十秒后变成「已完成」的那一章,**是同一章**。(如果它写完后变成已完成的却是清单里的另一行,就是错位,必须报回来。)
|
||||
- [ ] 已完成的章按完成先后逐个变成「已完成」,不会一次跳变两章以上(除非确实很快)。
|
||||
- [ ] 章节名是中文(事业 / 婚恋 / 财富 / 应期 / 健康),不是 `career` 这样的英文 id。
|
||||
|
||||
## 3. 进度条与清单不打架
|
||||
|
||||
- [ ] 进度条的格数 **等于** 清单的行数。
|
||||
- [ ] 深色格数 = 清单里「已完成」+「写作失败」的行数。
|
||||
- [ ] 进度条不会自己平滑地往前爬——它只在某一章状态变化时跳一格。盯着看 30 秒,没有变化时它应该纹丝不动。
|
||||
|
||||
## 4. 慢章节的文案
|
||||
|
||||
需要碰到一次某章写得久(超过 90 秒)。可遇不可求,遇到就记下来。
|
||||
|
||||
- [ ] 该章文案从「正在写」变成「用时较长,仍在写」。
|
||||
- [ ] 该文案**没有**出现「第 2 次尝试」「重试」这类字样。
|
||||
- [ ] 恢复推进后,下一章回到正常的「正在写」。
|
||||
|
||||
## 5. 有章节失败时
|
||||
|
||||
需要一次部分失败的生成(不易构造,若一直没遇到请在清单上写「未遇到」,不要打勾)。
|
||||
|
||||
- [ ] 失败的那一章显示「写作失败」。
|
||||
- [ ] 进度条对应格仍然是深色(失败也算完成的工作),整体继续前进。
|
||||
- [ ] 最终页面出现「M 个主题中 N 个写作失败:…」这类说明,**不是**只说一句「完成」就跳走。
|
||||
|
||||
## 6. 长等待与离开
|
||||
|
||||
- [ ] 等到超过 8 分钟:页面出现「生成时间超出预期」,有「继续等待」和「返回报告中心」两个按钮。
|
||||
- [ ] 点「继续等待」后,等待计时归零,进度重新开始显示(不是空白)。
|
||||
- [ ] 中途切到别的标签页再切回来:进度继续,没有重复计时或倒退。
|
||||
- [ ] 中途刷新页面:进度接着显示,不是从头开始。
|
||||
|
||||
## 7. 手机上
|
||||
|
||||
- [ ] iPhone Safari 竖屏:章节清单每行「主题名 — 状态」左右对齐,不换行错位、不溢出。
|
||||
- [ ] 进度条在窄屏下每格仍然可见(5 格不会挤成一条)。
|
||||
|
||||
## 8. 深色模式
|
||||
|
||||
- [ ] 系统切到深色:已完成 / 正在写 / 待写 三种颜色仍然区分得开,进度条深色格与底色对比足够。
|
||||
|
||||
---
|
||||
|
||||
未打勾的项一律写成「未验证」,不要因为"看起来应该没问题"就打勾。发现错位、卡住不动、或章节名不对,直接把现象和 `/api/reports/<id>` 响应(去掉正文)贴回。
|
||||
Reference in New Issue
Block a user