diff --git a/CHANGELOG.md b/CHANGELOG.md index a58b3a9e..7030b425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 印度占星 Skill 更新日志 +## 2026-09-30 — 报告阅读页的「中文 / English」切换恢复正常 + +- 之前点切换只有按钮和地址栏在变,正文和目录停在页面打开时的语言(旧语言的正文没被换掉)。现在每次切换,正文、目录、核对表都会跟着换,页面上只保留一份正文。报告内容和导出不受影响。Skill 版本不 bump。 + ## 2026-09-30 — 「我的报告」进得更快,过往报告可以一直往前翻 - 点「我的报告」不再先等一轮服务器渲染;侧栏悬停时已开始读的列表会直接用上,不再重读一遍。之前读过的列表进页即显示,后台再刷新。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 4927d05a..b9eac0b0 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -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` diff --git a/docs/tasks/PROGRESS-report-language-switch-stale-20260930.md b/docs/tasks/PROGRESS-report-language-switch-stale-20260930.md new file mode 100644 index 00000000..1ad7e8ef --- /dev/null +++ b/docs/tasks/PROGRESS-report-language-switch-stale-20260930.md @@ -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 上复现缺陷(报告接口换成虚构盘数据),部署后用同一方法再核一次。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index d81a3ded..e28278d8 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -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` | ## 命名与归档 diff --git a/docs/testing/report-english-edition-20260929.md b/docs/testing/report-english-edition-20260929.md index 8e3e2081..7b88cde2 100644 --- a/docs/testing/report-english-edition-20260929.md +++ b/docs/testing/report-english-edition-20260929.md @@ -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` 打开同一份报告 → 点「中文」 | 正文和目录变成中文 | | diff --git a/frontend/src/components/personal-report/personal-report-page.tsx b/frontend/src/components/personal-report/personal-report-page.tsx index b7ae6ca9..2e03b29b 100644 --- a/frontend/src/components/personal-report/personal-report-page.tsx +++ b/frontend/src/components/personal-report/personal-report-page.tsx @@ -564,9 +564,10 @@ export function PersonalReportPage({ reportId, initialLanguage = "zh" }: { repor onLanguageChange={switchLanguage} />