fix(reports): switching editions replaces the reader instead of stacking it (BUG-1126)
Independent Staging Quality Gate / validate (push) Successful in 14m44s
Independent Staging Quality Gate / publish (push) Successful in 3m32s

The reader and the fact tables were siblings keyed by the same language, so
React left the first edition's reader on screen and appended the new one.
Distinct keys per sibling; whole-page switch test with real engine fact tables.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4f2nya58RoRu4yEmJgRGE
This commit is contained in:
Jesse_Chen
2026-09-30 17:13:28 +08:00
co-authored by Claude Opus 5.5
parent 5604c11bb4
commit d87ec44aab
7 changed files with 160 additions and 9 deletions
@@ -0,0 +1,33 @@
# PROGRESS · 报告阅读页切换语言后正文不换 · 2026-09-30
> 任务书:`docs/tasks/TASK-report-language-switch-stale-20260930.md`。产品 2026-09-30 说「请你执行」,由 Claude 直接执行。
> 分支 `codex/report-language-switch-stale-20260930`,基线 `origin/staging` = `5604c11b`(其中代码与已部署的 `da3ed2f3` 相同)。BUG-1126。
## 做了什么
| 任务 | 结果 |
| --- | --- |
| T1 去掉重复 key | `personal-report-page.tsx`:`PersonalReportMarkdownView` 的 key 改为 `markdown-${shown}`,`ReportFactTables` 的 key 改为 `facts-${shown}`,加一行注释。同一父级下的 `ReportCalculationCharts` 本来就没有 key,不受影响。 |
| T2 整页回归测试 | 新增 `frontend/tests/report-language-switch-20260930.test.tsx`,共 2 条:用 `SidebarProvider` 包住 `PersonalReportPage` 整页渲染,mock 的报告接口带真实引擎虚构盘 golden 的中英正文(`report-reader-main-fictional-zh-pair.json` / `-en.json`),以及由 `report-density-fictional-engine.json` 组装的中英核对表。**修复前 2 条都失败**(`expected 1, actual 2`:页面上有两份正文,React 报 `Encountered two children with the same key`);修复后通过。 |
| T3 记录 | BUG-1126 改为 resolved;CHANGELOG 加一条;`docs/testing/report-english-edition-20260929.md` 加第 10、11 步;任务板改状态。 |
测试的两处环境补丁(只在测试文件里):`react-client-lifecycle-test-support` 只在 `window` 上提供 `requestAnimationFrame`,懒渲染章节调用的是全局的,所以测试期间把它挂到全局上,结束后恢复;「无汉字」断言只检查 `.personal-report-md-article`,因为它旁边的「到底部」按钮属于应用外壳,按英文版决策 6 保持中文。
既有断言零改动。
## 验收数字(Linux,Node 22 `/exec-daemon/node`,本 worktree 自己 `npm ci`)
| 项 | 基线 | 交付 |
| --- | --- | --- |
| `tsc --noEmit` | — | 0 错 |
| `npm run lint` | — | 0 error(126 warning,均为既有) |
| `npm test` 总数 | 4487 | 4489(+2,按测试名比对只多了本单 2 条,没有消失的) |
| 失败 | 24 | 24,名单逐条一致(全是需要 Docker 的数据库 / 部署 / 备份套件,属环境缺口) |
| `cancelled` | 0 | 0 |
| `next build` | — | 通过,`/` 仍是 `○ Static` |
首屏 gzip 没有测:改动只在 `/reports/[reportId]` 的客户端组件里,首页不引用这个组件,所以不影响首屏体积。
## 环境缺口
- 没有真实登录态:浏览器级走查留给产品,见 `docs/testing/report-english-edition-20260929.md` 第 10、11 步。诊断阶段已在 staging 线上 JS 上复现缺陷(报告接口换成虚构盘数据),部署后用同一方法再核一次。
+1 -1
View File
@@ -382,7 +382,7 @@
| `TASK-rectification-midnight-final-grok-20260921.md` | `PROGRESS-rectification-midnight-date-anchor-20260920.md` | **日期锚点最终验收接续(Grok)**:测试契约复验、扩展身份四模块、真实新 freeze 与 20/900、final-3 全门。不 commit/push/deploy。 | **已验收**(并入 `b85c4a68`,见下一行):隔离 Linux 门通过(tsc/lint0、前端3649、DB56、quick、AA 21/21、0 新增 Python 失败);四既存失败仍在;真人缺口见 BLOCKED | `b85c4a68`(分支 `codex/rectification-midnight-date-anchor-20260920` 提交后合入 staging) |
| `TASK-rectification-midnight-date-anchor-20260920.md` | `PROGRESS-rectification-midnight-date-anchor-20260920.md` | **跨午夜的日期锚点与簇跨度(BUG-982 + BUG-983 合并一单)**:两条同根——线上契约只传钟点、日期不在契约里,后端按钟点大小**猜**哪端跨日。**BUG-983 是静默算错盘**:`_candidate_datetimes()` 把起始钟点无条件绑在申报日期上,前端零补偿。Claude 实测(申报日 2000-06-15):申报 `00:10` → 候选 `06-15 23:55`…`06-16 00:25`,**申报分钟本身落在 `06-16 00:10`,+1 天**;`00:02` 同样 +1 天;`12:00` 对照正确。受影响:申报落在午夜后半径内者(±15≈1.0%、±30≈2.1%、±60≈4.2%、±120≈8.3%);`late_night`(23:00–03:59) 更重,其 `00:00`–`03:59` 共 240 分钟**全部落在用户没申报过的日期上**。**BUG-982 影响面比原记录大**:同缺陷在链路上出现三处(`_cluster_span` / `unionStillValidRange` / `indistinguishableWidthMinutes`),原记录只写了 Python 两处。实测跨午夜簇 `23:58/23:59/00:00` 报告宽度 **1440 分钟**(真实 3)、`23:50/23:55/00:05` 报 **1431**(真实 16);该链经 `reportWidth` 直达 **交付卡「范围 X 分钟」**,深夜用户会看到候选其实差几分钟却被告知范围一千多分钟。**与 BUG-981 的关系**:981 让每个候选按自己日期算 Dasha 是对的,但 983 给的日期本身就错,**983 不修则 981 在申报近午夜的场景等于没上线**。**产品 2026-09-20 三点全部拍板**:D1 `late_night` **先问用户「午夜前还是午夜后」,答不上退「同日两段」`[D 00:00–03:59] ∪ [D 23:00–23:59]`,一律不得跨到 `D+1`**(连锁范围:退路产生**不连续候选集**,枚举/聚类/簇跨度/交付区间/宽度/交付卡都要支持两段,**不得用「取两段最小到最大」糊成一段**——那正是 982 的同型错误);D2 **允许窗口跨到前一天**,但交付层必须说明「若落在 23:5x 则出生日期是前一天」,不得静默改用户的出生日期(依据 Part B B4 真值覆盖率优先);D3 **加窗口相对序号**,排序/取首尾/算跨度一律用序号、钟点只作展示 —— 序号入契约会改候选身份与缓存指纹,**必须核对 BUG-984 的 `scoringIdentityMatches` 并确认历史结果只读不被重标**。硬红线:不调打分常数/确认门阈值;不得以排除真值换窄宽度;不得只修 `_cluster_span` 就宣称 982 已修;非跨午夜必须逐位不变且用同机 A/B(不得写死跨机浮点哈希,见 BUG-985)。沿用 BUG-982/983,不新开号 | **已验收通过并合入 staging**(2026-09-21,`b85c4a68`);已部署 staging(run 2831);真人验收待完成 | Claude 独立复核(用当初复现两条 Bug 的同一探针重跑):**BUG-983** 新契约下申报 `00:10`/`00:02` 的申报分钟落在**申报日**(+0 天),`late_night` 300 个候选**全在同一日**、两段;**BUG-982** 跨午夜 3 分钟簇 → **3 分钟**(原 1440),同日两段 → **300 分钟**(未糊成 1440)。三处(`decision_policy.py`/`credible-range.ts`/`candidate-plateau.ts`)均已改。**非跨午夜分数同机 A/B 6/6 逐位相同**;新测试在基线 6 条红、修复后全绿;Python `test_rectification_*` 基线 216 → **272 passed / 0 failed**;`tsc --noEmit` 退出 0。红线全清:打分常数零改动、`status=not_ready`、coverage 0、`holdout_passed()` False、Skill 未动、`official_eval_trial_count` 仍 0。D1 追问 4 选项含「不知道」「跳过」,均退同日两段、从不去 `D+1`,明写不计分可跳过;D2 交付层显示「(前一天)」;D3 用 `window_index`/`segment_index`,`interval_union_width` 按段求和(注释 “never fill a declared segment gap”)。超出要求的设计:`candidate_intervals` 拒绝重复钟点;engine-client 对 dated 路径 fail-closed 抛 `engine_invalid_candidate_decisions`。环境缺口:3 条前端测试在 Claude 机器红,但**同样的 `@/` 别名错误在既有测试上一模一样复现**(`ephemeris-route`、`api-service-unavailable`),非回归;无 Docker 故 `test:db` 未跑。**遗留待确认**:无 `cluster_intervals` 的行仍走旧按钟点算法(宽度仍 1440),应确认除修复前旧缓存外没有别的生产路径会产生无日期行 |
| (直接执行,无任务书) | `PROGRESS-notice-loading-polish-20260930.md` | 性别只在档案(1119)、星盘气泡频闪与表格圆角(1120)、全站顶部浮层提示(1121/1122)、我的报告提速与分页(1123/1124)、宫位点亮等待与今日一句不跳(1125) | **已验收,推 staging**;真机清单 `docs/testing/notice-loading-polish-20260930.md` 待产品 | 合并分支 `codex/polish-integrate-20260930` |
| `TASK-report-language-switch-stale-20260930.md` | — | 报告阅读页切换中文 / English 后正文不换:正文与核对表同级重复 key(`key={shown}`),旧语言节点残留(BUG-1126);改 key + 整页切换回归测试 | 待领取 | — |
| `TASK-report-language-switch-stale-20260930.md` | `PROGRESS-report-language-switch-stale-20260930.md` | 报告阅读页切换中文 / English 后正文不换:正文与核对表同级重复 key(`key={shown}`),旧语言节点残留(BUG-1126);改 key + 整页切换回归测试 | **已验收,推 staging**(Claude 直接执行);真机第 10、11 步待产品 | 分支 `codex/report-language-switch-stale-20260930` |
## 命名与归档