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

9.9 KiB
Raw Blame History

TASK · 验收修复单:两条既有契约测试被本轮改动打红且未更新

  • 日期:2026-09-15
  • 基线 commitorigin/staging @ 6c748d86
  • 执行分支:codex/chat-reading-load-fix-20260915
  • 来源:Claude 对 8144fca2chat-reading-load)与 6c748d86mobile-touch-and-breakpoints)的验收
  • 规模:两条断言 + 两段三栏说明。不改产品代码。

1. 验收结论摘要

两单绝大部分通过。门禁在 Linux 上实测:

结果
tsc --noEmit 0 错
npm run lint 0 error / 120 warning(全部既有)
测试总数 基线 ebd5ba68 3214 → 改后 3232+18
npm run buildTurbopack exit 0/○ Static
首屏 JS gzip-9 585,246 B → 583,660 B1,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. 决策记录

  1. 改测试,不改产品代码。 两处的新行为都是任务书明确要求的结果:两层可见性必然有两个 message-markdown 包裹;删除建议问题入口必然少一处硬跳转。回退产品代码去迁合测试是反的(CLAUDE.md:不要为迎合工具改业务代码)。
  2. 两条断言都保持"数数"的形式,只改数字并补三栏说明。不要改成模糊匹配(>= 1assert.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

把那两条断言更新为当前架构,并在紧邻的注释里写三栏:

// 原值: 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 原本守的东西守回来——"两个包裹用的是同一套渲染路径":

// 两层必须都走 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 里其余断言在当前代码下仍然成立(redirectToLoginpersistLoginSessionReturnsignOut 三段),成立就别动

验收标准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 文档:

  • 两份既有 PROGRESSPROGRESS-chat-reading-load-20260915.mdPROGRESS-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. 开工前置命令

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.mddocs/testing/mobile-touch-and-breakpoints-20260915.md
iOS 键盘遮挡输入框 docs/testing/ios-keyboard-composer-20260915.md,结论出来前不立单
staging 部署核对 线上仍是 8d56d0ab,两单尚未部署;部署后 /api/healthdeployment.gitCommit 应等于含门禁路径改动的最新 staging 提交
设置弹窗尺寸与分区菜单强调条 TASK-settings-dialog-size-and-nav-20260915.mdBUG-698),待领取
BUG-630 的另一半(本命第一步未强制计算工具) 与主题卡无关,仍未处理