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
9.9 KiB
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
失败的那一句:
// 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 只报了上面那条):
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
失败的那一句:
assert.equal(pageSource.match(/window\.location\.assign\("\/login"\)/g)?.length, 4);
8144fca2 删掉了拉建议问题的那个 useEffect,其中包含:
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. 决策记录
- 改测试,不改产品代码。 两处的新行为都是任务书明确要求的结果:两层可见性必然有两个
message-markdown包裹;删除建议问题入口必然少一处硬跳转。回退产品代码去迁合测试是反的(CLAUDE.md:不要为迎合工具改业务代码)。 - 两条断言都保持"数数"的形式,只改数字并补三栏说明。不要改成模糊匹配(
>= 1、assert.ok)——它们数的就是"有几个包裹 / 有几次硬跳转",放宽等于把防线拆了。
4. 硬红线
- 不得修改
frontend/src/下任何文件。 本单只动frontend/tests/与文档。 tsc --noEmit0 错;npm run lint0 error。- 改完全量
npm test的失败数必须回到 27,且失败清单与ebd5ba68基线逐条一致。 - 不得放宽断言形式(见 §3.2)。
- 不得顺手修 120 条既有 warning。
5. 任务分解
任务 1 · 修 chat-bundle-splitting-contract.test.ts
把那两条断言更新为当前架构,并在紧邻的注释里写三栏:
// 原值: 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 内部的回退路径
// 原因: 口语层与报告层共用 renderProse,plainParagraphs 只在它内部调用一次。
assert.match(contentSource, /\(plainParagraphs\(text\) \?\? \[\]\)\.map/);
补一条新断言,把这条 test 原本守的东西守回来——"两个包裹用的是同一套渲染路径":
// 两层必须都走 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
// 原值: 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 跑全量并逐条比对:
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 的"新断言"若想不出既精确又稳的写法,先把两条数字断言改对并写满三栏,把新断言的缺口写进 PROGRESS 交回。数字改对是底线,加强是加分。
- 绝不让步:不得改
frontend/src/;不得把精确计数放宽成模糊匹配;全量失败数必须回到 27。
7. 开工前置命令
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 的另一半(本命第一步未强制计算工具) | 与主题卡无关,仍未处理 |