Files
Jyotisha/docs/tasks/TASK-consultation-answer-start-anchor-fix-20260917.md
T

69 lines
6.6 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.
# 验收修复单 · 回答定位到开头:无用户行的一轮钉不住、闲置会话被留白(2026-09-17)
## 0. 基线
- 验收对象:`11c0028d`(`origin/staging` head,执行方变基后快进推送)。任务书:`TASK-consultation-answer-start-anchor-20260917.md`。
- 分支:`codex/consultation-answer-start-anchor-fix-20260917`,从 `origin/staging` 起。
- 范围:只有 `frontend/src/hooks/use-conversation-scroll-anchor.ts` 与其测试、`docs/BUG_HISTORY.md`、`docs/tasks/`。不动 `page.tsx`、不动组件、不动 CSS。
- BUG 段:**BUG-931 起**(基线最大号 BUG-930)。
- **前置**:staging 自 `e4e73f56`(会话列表单)起 `tsc --noEmit` 与 `next build` 都红(`src/lib/session-sidebar-row.ts(30,41)`、`tests/chart-library-session.test.ts(89,42)`:`Expected 1 arguments, but got 2`),门禁过不了,`/api/health` 仍停在 `dc2f2a16`。那两处属于会话列表单的验收,另行处理;本单执行时若 staging 仍红,先在本分支顺手修这两行并在进度记录写明,否则本单也发不出去。
## 1. 验收实证
验收环境:Linux,Node 20.19,Google Chrome 151 无头(本机有 Chrome,真实布局;无登录态,所以用真实 hook + 与 `globals.css` 同参数的骨架页做场景,不是 staging 页面)。
| 项 | 结果 |
| --- | --- |
| `tsc --noEmit` | 2 错,**均继承自 `e4e73f56`**,本提交 0 新增 |
| `npm run lint` | 0 error / 117 warning |
| `npm test` | 3455 条,39 红;与父提交 `e4e73f56`(3450 条,39 红)逐条一致,**0 新红、+5 新测试全绿**。`e4e73f56` 自身比 `5203f9f0` 多 4 条红(会话列表单的事,另记) |
| `next build --webpack` | 编译通过,`Failed to type check`(同上两处继承错误);`/` Static 与 gzip 因此未核 |
| `page.tsx` 行数 | 1830 → 1830 |
| 文档 | BUG-930、BUG-041/048 各补指回行、CHANGELOG、DESIGN「Answer start anchor」、真机清单、PROGRESS 齐 |
| T4 思考块 | 执行方核实「按步骤条数有界、结算后折一行、未改」,与代码一致,接受 |
真实布局场景(容器 600px 高,`.message-list` 与 `.message` 用 `globals.css` 同一组 padding,最后一轮的 `min-height` 规则原样):
| 场景 | 结果 | 结论 |
| --- | --- | --- |
| S1 长历史、读者在底部、发问 | 钉顶后用户行距容器顶 16px;流式增高 6 步 `scrollTop` 纹丝不动;尾部超出 96px 后 `latestBelowFold` 为真;按「跳到最新」后贴底并跟随 | 通过 |
| S2 短历史(不溢出)、发问 | 留白让用户行到顶(16px),增高不动 | 通过 |
| S3 读者先滚到顶部再发问 | 钉顶 16px,增高不动 | 通过 |
| S4 切换会话 | 落底,`anchored` 为真 | 通过 |
| S5 闲置历史会话,读者上滑 400px,某条旧消息高度变化 30px | **最后一条助手行被写入留白**(底部从 936 变 1320,`--conversation-viewport` / `--latest-turn-head-height` 被写上),`scrollTop` 漂 30px | **P2,BUG-932** |
| S6 本轮没有用户行(校正点选项 / 开场 / 自动续轮):追加新助手行后 `pinLatestTurn()` | **钉不住**:新助手行停在距顶 480px;`--latest-turn-head-height` 被写成 740px;随后第一步增高 `anchored` 翻成真,视口回到贴底跟随 | **P1,BUG-931** |
S6 的机理(按符号):`resolveTurnHead` 正确选到新助手行;但 `applyTurnSpacer(element, head)` 用 `head.offsetHeight` 写 `--latest-turn-head-height`,而这个 head **就是**承载 `min-height: calc(viewport − head)` 的最后一条助手行——前一帧 `follow()` 的非贴底分支已经给它写过一次留白,于是量到的高度是「内容 + 留白」,留白公式自指后坍缩为 0,`scrollTo` 目标被 `scrollHeight` 夹住,头钉不到顶。接着 `measure()` 见 `distance ≤ 96` 解除 `holdUnpin`,`nextAnchorState` 判回 `anchored = true`,后续增高就是 BUG-930 之前的贴底行为。校正面用 `ChatMessageRow`,行也是 `.message-assistant`,所以真实页面同样命中。
S5 的机理:`follow()` 的非贴底分支对**任何** `anchored = false` 的容器都 `applyTurnSpacer`,不管有没有正在进行的一轮。读者在旧会话里上滑后,任何子元素尺寸变化(窗口宽度、图片、思考块折叠)都会给最后一条助手行补一段留白,还会把 `latestBelowFold` 顶成真。
## 2. 任务分解
### F1 头就是留白行时,留白按整视口算(BUG-931)
- `applyTurnSpacer(container, head)`:若 `head` 与 `lastTurnTail(container)` 是同一个元素(本轮无用户行),`--latest-turn-head-height` 写 `0px`,让该行 `min-height` = 整个视口;否则照旧写 `head.offsetHeight`。或者在量高之前 `clearTurnSpacer` 再量,两者取其一,进度记录写明选哪种及原因。
- `pinLatestTurn` 在 `applyTurnSpacer` 之后、`scrollTo` 之前必须能把 head 钉到 `offset − space4`;不得靠 `holdUnpin` 之外的新状态。
- 验收:
- 行为测试(fake scroller 或 jsdom)新增:容器内 `[u][a][u][a][a-new]`,`pinLatestTurn()` 后 `scrollTop === offset(a-new) − 16`,`--latest-turn-head-height === "0px"`;随后增高 3 步 `anchored` 仍为假、`scrollTop` 不变。
- 现有主会话场景(S1–S3)断言不变。
### F2 留白只在本轮钉住期间存在(BUG-932)
- `follow()` 的非贴底分支只在 `pinnedHeadRef.current !== null` 时 `applyTurnSpacer`;读者单纯上滑(无 pin)不写留白。`anchorToLatest()` 与 `resetKey` 变化时清 `pinnedHeadRef` 与留白(现在已清)。
- `latestBelowFold` 在无 pin、读者上滑时仍按尾部超出 96px 判定(保留「读者上滑后可跳到最新」)。
- 验收:行为测试新增:长历史落底 → 上滑 400px → 改某条旧消息高度 → 容器 `style` 无 `--conversation-viewport`,`scrollTop` 不变(允许 ±1px)。
### F3 记录
- `docs/BUG_HISTORY.md` 新增 BUG-931、BUG-932(关联 BUG-930);BUG-930 的「验证」补一句真实布局场景已由验收方跑过。
- `docs/tasks/PROGRESS-consultation-answer-start-anchor-fix-20260917.md`;状态板行。
- 真机清单第 5 条拆成两条:点选项后新助手行在顶部;开场轮在顶部。
## 3. 硬红线
沿用原单 §4:滚动逻辑只在 hook 内;单一 hook、单一按钮;不 sticky;改断言写三栏;`page.tsx` 不增行。
## 4. 验收口径
`tsc --noEmit` 0 错(含前置两行修复后);`npm run lint` 0 error;`npm test` 失败清单与 `11c0028d` 逐条一致、新增测试全绿;`next build` 通过、`/` Static、gzip ±2%(此项因门禁红一直没核,本单必须补上)。验收方会用同一套 Chrome 骨架页复跑 S1–S6。