fix(reports): switching editions replaces the reader instead of stacking it (BUG-1126)
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:
co-authored by
Claude Opus 5.5
parent
5604c11bb4
commit
d87ec44aab
+6
-6
@@ -15034,14 +15034,14 @@
|
||||
|
||||
## BUG-1126 | 报告阅读页切换中文 / English 后正文不换
|
||||
|
||||
- 状态:investigating(根因已复现并验证修复方向;待执行,任务书 `docs/tasks/TASK-report-language-switch-stale-20260930.md`)
|
||||
- 状态:resolved(代码 + 回归测试;待部署,真机待产品)
|
||||
- 首次发现 / 最近更新:2026-09-30 / 2026-09-30
|
||||
- 影响面:`frontend/src/components/personal-report/personal-report-page.tsx`(`PersonalReportPage` markdown-ready 分支);所有带英文版的新报告。
|
||||
- 现象:点 English 后地址栏变为 `?lang=en`、开关显示已选中,但正文和目录仍是中文;带 `?lang=en` 打开后点中文,正文仍是英文。导出的英文版正常。桌面与 iPhone 均复现。
|
||||
- 触发条件:报告同时带核对表(`factTables` / `factTablesEn`,真实报告都有),在阅读页切换语言。
|
||||
- 根因:`PersonalReportMarkdownView` 与 `ReportFactTables` 是同级元素,key 都是 `shown`,发生重复;React 清不掉旧节点,旧语言正文留在最上面,新语言正文追加在后面(Claude 在 staging 线上 JS 上复现:正文区块 1→2→3 份,开发版报 `Encountered two children with the same key`)。响应里没有核对表时不复现,所以只测组件的用例没有拦住。
|
||||
- 修复:待执行(两处 key 改成互不相同;Claude 已在复现环境验证可行)。
|
||||
- 验证:待执行(整页切换回归测试,先红后绿)。
|
||||
- 根因:`PersonalReportMarkdownView` 与 `ReportFactTables` 是同级元素,key 都是 `shown`,发生重复;React 清不掉旧节点,旧语言正文留在最上面,新语言正文追加在后面(Claude 在 staging 线上 JS 上复现:正文区块 1→2→3 份,开发版报 `Encountered two children with the same key`)。响应里没有核对表时不复现,所以只测组件的用例(`report-english-edition.test.tsx`)没有拦住。引入提交 `b2ad772e`。
|
||||
- 修复:两处 key 改为 `markdown-${shown}` / `facts-${shown}`,仍随语言重新挂载,不再重复。
|
||||
- 验证:新增 `frontend/tests/report-language-switch-20260930.test.tsx`(整页渲染 + 真实引擎虚构盘 golden 的中英正文与核对表;中→英→中、英→中两条):修复前 2 条均失败(正文区块 2 份),修复后通过;前端全量失败清单与基线逐条一致;tsc / lint / build 见 `docs/tasks/PROGRESS-report-language-switch-stale-20260930.md`。浏览器级走查见 `docs/testing/report-english-edition-20260929.md` 第 10、11 步。
|
||||
- 防复发:同一父级下按语言重新挂载的多个子元素,key 必须带各自前缀;语言切换要有整页测试,数据带齐核对表。
|
||||
- 相关记录:BUG-1106~1108(跨语言保留章节);引入提交 `b2ad772e`
|
||||
- 修复版本:—
|
||||
- 相关记录:BUG-1106~1108(跨语言保留章节)
|
||||
- 修复版本:分支 `codex/report-language-switch-stale-20260930`
|
||||
|
||||
@@ -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 上复现缺陷(报告接口换成虚构盘数据),部署后用同一方法再核一次。
|
||||
@@ -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` |
|
||||
|
||||
## 命名与归档
|
||||
|
||||
|
||||
@@ -13,3 +13,5 @@
|
||||
| 7 | 打开一份**这次更新之前**生成的旧报告 | 没有「中文 / English」切换;点导出,弹窗里一行小字「这份报告生成于英文版上线前,重新生成即可获得中英两版。」 | |
|
||||
| 8 | 在旧报告地址后面手动加 `?lang=en` 打开 | 仍然显示中文,不报错 | |
|
||||
| 9 | 把第 6 步下载的英文文件发给 ChatGPT 或 Claude,让它概括 | 能正常读懂(这一步只是体感,不强求) | |
|
||||
| 10 | (BUG-1126 补充,2026-09-30)不带 `?lang=en` 打开一份新报告 → 点「English」→ 再点「中文」→ 再点「English」 | 每点一次,正文和目录都跟着换(目录标题在「目录」和「Contents」之间变);页面往下拉,正文只有一份,不会出现两份不同语言的正文 | |
|
||||
| 11 | (BUG-1126 补充)直接带 `?lang=en` 打开同一份报告 → 点「中文」 | 正文和目录变成中文 | |
|
||||
|
||||
Reference in New Issue
Block a user