Files
Jyotisha/docs/tasks/TASK-chat-reading-load-fix-20260915.md
T
Jesse_ChenandClaude Fable 5 ca5ecafc1a docs(tasks): accept the two UI rounds and file the contract-test fix
Linux 上补了执行方在 Windows 上做不到的两项:全量 npm test 与 next build。

通过:tsc 0 错;lint 0 error / 120 既有 warning;测试总数 3214 → 3232;
next build exit 0、/ 仍是 ○ Static;首屏 JS gzip-9 585,246 → 583,660 B,
-1,586 B / -0.27%(口径同 09-02、09-06)。任务书逐条对照,删除范围与保留
范围都对,guided_topic 枚举保留、录入链路未被误删、复制仍走全文。

未通过 2 条,同一类——契约测试跟着代码漂了没更新,不是产品回归:
chat-bundle-splitting-contract 断言 message-markdown 包裹恰好 1 个,两层
可见性后是 2 个(同一 test 里 plainParagraphs(spoken) 字面量也已不存在);
chat-navigation-a11y-contract 断言 window.location.assign("/login") 恰好
4 处,删建议问题入口带走一处变成 3。基线 27 条失败与改后逐条一致,新增
的就是这两条。

执行方把全量测试记成 Windows 环境缺口(SKILL.md symlink EPERM、CRLF),
判断对他们的机器成立,也因此没机会看到这两条。修复单只改测试、禁止碰
frontend/src,要求失败数回到 27。

顺带澄清:PROGRESS 里记的 membership-page「login redirects」CRLF 失败在
Linux 上通过,不是回归。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
2026-09-15 05:14:43 +00:00

208 lines
9.9 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.
# TASK · 验收修复单:两条既有契约测试被本轮改动打红且未更新
- 日期:2026-09-15
- 基线 commit`origin/staging` @ `6c748d86`
- 执行分支:`codex/chat-reading-load-fix-20260915`
- 来源:Claude 对 `8144fca2`chat-reading-load)与 `6c748d86`mobile-touch-and-breakpoints)的验收
- 规模:两条断言 + 两段三栏说明。**不改产品代码。**
---
## 1. 验收结论摘要
两单绝大部分通过。门禁在 Linux 上实测:
| 项 | 结果 |
| --- | --- |
| `tsc --noEmit` | 0 错 |
| `npm run lint` | **0 error** / 120 warning(全部既有) |
| 测试总数 | 基线 `ebd5ba68` 3214 → 改后 3232**+18** |
| `npm run build`Turbopack | exit 0`/``○ Static` |
| 首屏 JS gzip-9 | 585,246 B → 583,660 B**1,586 B / 0.27%**(口径同 09-02 / 09-06:预渲染 `index.html` 引用脚本去重求和) |
| 全量测试失败 | 基线 27 → 改后 **29** |
**两条新增失败就是本单要修的东西。** 基线 27 条失败与改后逐条一致(Docker / DB / 部署 / rsync 环境缺口),没有一条消失也没有一条变形。
执行方两份 PROGRESS 都把全量测试与 `next build` 记成了 Windows 环境缺口(`SKILL.md` symlink `EPERM`、CRLF),这个判断对他们的机器成立,但也因此**没有机会看到这两条**。本单把它们补上。
顺带澄清:`PROGRESS-mobile-touch-and-breakpoints` 里记的 `membership-page`「login redirects…」CRLF 失败,**在 Linux 上通过**,不是回归,无需处理。
---
## 2. 事故实证
### 2.1 `the pre-markdown fallback renders prose but never raw markdown`
- 文件:`frontend/tests/chat-bundle-splitting-contract.test.ts`
- 断点:`TestContext.<anonymous> (…:47:10)`
- 报错:`Expected values to be strictly equal: 2 !== 1`
失败的那一句:
```js
// And: one stable wrapper serves both paths, so no styling or caret is lost on upgrade.
assert.equal(contentSource.match(/className="message-markdown"/g)?.length, 1);
```
`8144fca2` 把回答拆成口语层与折叠层,两层各有一个 `<div className="message-markdown">``chat-message-content.tsx` 里现在实测 2 处)。
**同一个 test 里还有第二条会接着红**(node:test 首个断言失败即中止,所以 TAP 只报了上面那条):
```js
assert.match(contentSource, /plainParagraphs\(spoken\) \?\? \[\]/);
```
口语层改走 `renderProse(spoken, renderMarkdown)``plainParagraphs` 现在只出现在 `renderProse` 内部的 `(plainParagraphs(text) ?? []).map(…)`,字面量 `plainParagraphs(spoken) ?? []` 已不存在。
同一个 test 里的 `assert.match(contentSource, /<p key=\{index\}>\{paragraph\}<\/p>/)` **仍然成立**`renderProse` 里还是这一行),不要动。
### 2.2 `auth redirects stay hard document loads so stale session state cannot survive`
- 文件:`frontend/tests/chat-navigation-a11y-contract.test.ts`
- 断点:`TestContext.<anonymous> (…:68:10)`
- 报错:`Expected values to be strictly equal: 3 !== 4`
失败的那一句:
```js
assert.equal(pageSource.match(/window\.location\.assign\("\/login"\)/g)?.length, 4);
```
`8144fca2` 删掉了拉建议问题的那个 `useEffect`,其中包含:
```js
if (caught instanceof OnboardingAuthenticationError) {
window.location.assign("/login");
return;
}
```
调用点从 4 处变成 3 处。这条断言数的是"硬跳转的次数",入口删了所以少一次——**是正确的减少,不是把跳转换成了 `router.push`**。同一个 test 里的 `doesNotMatch(/router\.push\("\/login"\)/)``doesNotMatch(/router\.replace\("\/login"\)/)` 仍然成立,红线本身没破。
### 2.3 性质
两条都是**契约测试跟着代码漂了但没更新**,不是产品回归。但 `frontend/AGENTS.md` §7 两条红线各破一次:
- §7.1「相关测试套件 fail=0」——两套在基线是绿的。
- §7.3「改任何既有断言必须写『原值 / 新值 / 原因』三栏」——这两条既有断言被改动打红,既没更新也没说明。
---
## 3. 决策记录
1. **改测试,不改产品代码。** 两处的新行为都是任务书明确要求的结果:两层可见性必然有两个 `message-markdown` 包裹;删除建议问题入口必然少一处硬跳转。回退产品代码去迁合测试是反的(`CLAUDE.md`:不要为迎合工具改业务代码)。
2. 两条断言都**保持"数数"的形式**,只改数字并补三栏说明。不要改成模糊匹配(`>= 1``assert.ok`)——它们数的就是"有几个包裹 / 有几次硬跳转",放宽等于把防线拆了。
---
## 4. 硬红线
1. **不得修改 `frontend/src/` 下任何文件。** 本单只动 `frontend/tests/` 与文档。
2. `tsc --noEmit` 0 错;`npm run lint` 0 error。
3. 改完全量 `npm test` 的失败数必须回到 **27**,且失败清单与 `ebd5ba68` 基线逐条一致。
4. 不得放宽断言形式(见 §3.2)。
5. 不得顺手修 120 条既有 warning。
---
## 5. 任务分解
### 任务 1 · 修 `chat-bundle-splitting-contract.test.ts`
把那两条断言更新为当前架构,并在**紧邻的注释里**写三栏:
```js
// 原值: contentSource.match(/className="message-markdown"/g)?.length === 1
// 新值: 2
// 原因: 回答改为两层可见性(口语层 + 折叠的完整分析),两层各有一个包裹。
// 这条仍然数包裹个数,不得放宽成 >= 1——它防的是"升级到 markdown 时换了壳"。
assert.equal(contentSource.match(/className="message-markdown"/g)?.length, 2);
// 原值: assert.match(contentSource, /plainParagraphs\(spoken\) \?\? \[\]/)
// 新值: 断言 renderProse 内部的回退路径
// 原因: 口语层与报告层共用 renderProseplainParagraphs 只在它内部调用一次。
assert.match(contentSource, /\(plainParagraphs\(text\) \?\? \[\]\)\.map/);
```
**补一条新断言**,把这条 test 原本守的东西守回来——"两个包裹用的是同一套渲染路径":
```js
// 两层必须都走 renderProse,不得一层 markdown 一层裸文本。
assert.equal(contentSource.match(/renderProse\(/g)?.length >= 2, true);
```
具体写法执行方可调,但**必须让这条 test 重新具备"检测到两层渲染路径分叉"的能力**,不能只是把 1 改成 2 就完事。
**验收标准**`npx tsx --test tests/chat-bundle-splitting-contract.test.ts` fail=0;三栏注释齐全;断言仍是精确计数。
### 任务 2 · 修 `chat-navigation-a11y-contract.test.ts`
```js
// 原值: pageSource.match(/window\.location\.assign\("\/login"\)/g)?.length === 4
// 新值: 3
// 原因: 8144fca2 删除建议问题请求,连同它的 OnboardingAuthenticationError 分支里
// 那一次 window.location.assign("/login")。少的是调用点,不是把硬跳转换成了
// router.push——同 test 里两条 doesNotMatch 仍然成立。
assert.equal(pageSource.match(/window\.location\.assign\("\/login"\)/g)?.length, 3);
```
顺手核对同一个 test 里其余断言在当前代码下仍然成立(`redirectToLogin``persistLoginSessionReturn``signOut` 三段),**成立就别动**。
**验收标准**`npx tsx --test tests/chat-navigation-a11y-contract.test.ts` fail=0。
### 任务 3 · 全量复核与文档
**3.1** 跑全量并逐条比对:
```bash
npm test > /tmp/head.tap 2>&1
grep -oE "^ *not ok [0-9]+ - .*" /tmp/head.tap | sed 's/^ *not ok [0-9]* - //' | sort
```
必须得到 **27** 条,且与 `ebd5ba68` 的 27 条一字不差。数字与清单写进 PROGRESS。
**3.2** 文档:
- 两份既有 PROGRESS`PROGRESS-chat-reading-load-20260915.md``PROGRESS-mobile-touch-and-breakpoints-20260915.md`)各补一段「验收补正」,写明本单修了哪两条、为什么原来的 Windows 环境缺口判断让它们漏网,并把 Linux 实测的门禁数字抄进去(tsc 0 / lint 0 error / 3232 tests / build exit 0 / `/` Static / gzip 0.27%)。
- `docs/tasks/PROGRESS-chat-reading-load-fix-20260915.md`:本单自己的进度。
- **不新增 BUG 编号**:两条都是测试契约漂移,没有用户可感知现象,不进 `docs/BUG_HISTORY.md`。若执行中发现真实回归,从 **BUG-699** 起(BUG-698 已由 `TASK-settings-dialog-size-and-nav-20260915.md` 占用)。
- 不动 `CHANGELOG.md`(无用户可见行为变化)。
---
## 6. 让步顺序
1. 任务 1 的"新断言"若想不出既精确又稳的写法,**先把两条数字断言改对并写满三栏**,把新断言的缺口写进 PROGRESS 交回。数字改对是底线,加强是加分。
2. **绝不让步**:不得改 `frontend/src/`;不得把精确计数放宽成模糊匹配;全量失败数必须回到 27。
---
## 7. 开工前置命令
```bash
cd /workspace/Jyotisha
git status -sb | head -1
git fetch origin --prune
git worktree add -b codex/chat-reading-load-fix-20260915 \
.worktrees/chat-reading-load-fix-20260915 origin/staging
cd .worktrees/chat-reading-load-fix-20260915/frontend
npm ci
npx tsx --test tests/chat-bundle-splitting-contract.test.ts tests/chat-navigation-a11y-contract.test.ts # 应先看到两条红
```
交付:`git push origin HEAD:staging`,推完核对远端 SHA。
---
## 8. 未纳入本单的欠账
以下在验收中确认仍然欠着,**不属于本单范围**,列在这里免得被当成已完成:
| 欠账 | 归属 |
| --- | --- |
| 浏览器 / iPhone 真机走查(折叠块、宽表横滑、输入框 16px、44px 触控、断点目视) | `docs/testing/chat-reading-load-20260915.md``docs/testing/mobile-touch-and-breakpoints-20260915.md` |
| iOS 键盘遮挡输入框 | `docs/testing/ios-keyboard-composer-20260915.md`,结论出来前不立单 |
| staging 部署核对 | 线上仍是 `8d56d0ab`,两单尚未部署;部署后 `/api/health``deployment.gitCommit` 应等于含门禁路径改动的最新 staging 提交 |
| 设置弹窗尺寸与分区菜单强调条 | `TASK-settings-dialog-size-and-nav-20260915.md`BUG-698),待领取 |
| BUG-630 的另一半(本命第一步未强制计算工具) | 与主题卡无关,仍未处理 |