fix(chat): keep latest-turn actions under the reply (BUG-1042)

The BUG-930 pin spacer sat on the last assistant row, pushing its sibling
.message-actions (and follow-ups / delivery card) below half a screen of
empty space. Move the same min-height onto the last turn's entry
(`:last-child:has(.message-assistant)`) with align-content: start so the
rectification grid wrap does not stretch its rows. Hook logic unchanged.

Records BUG-1043 / BUG-1044 (pre-existing, investigating) found during
browser verification.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-26 10:19:00 +08:00
co-authored by Claude Opus 5.5
parent ac224cef6a
commit 83cce7ad6a
13 changed files with 189 additions and 7 deletions
+45
View File
@@ -13943,3 +13943,48 @@
- 相关记录:BUG-163(入口摘要由服务端 Case 驱动)、BUG-1040(暖快照缓存的正是解析后的 summary,快照结构不变)。
- 复发自:无。
- 修复版本:`codex/starter-home-polish-20260926`,未部署。
## BUG-1042 | 最后一轮回答与点赞 / 踩 / 复制 / 重试之间空出大半屏
- 状态:investigating(实现已完成、本地真实浏览器验证通过,等待 Claude 验收与 staging 部署后真机复核)
- 首次发现 / 最近更新:2026-09-26 / 2026-09-26
- 影响面:`frontend/src/app/globals.css` 末轮留白规则;普通咨询 `.message-entry`(`chat-transcript.tsx` `MessageEntry`)与生时校正 `.rectification-message-wrap`(`rectification-agentic-chat.tsx` + `rectification-message-entry.tsx`)两处最后一轮。
- 用户现象:产品 09-26 iPhone Safari 生时校正会话:助手正文结束后,点赞 / 踩 / 复制 / 重试一排被推到屏幕下部,中间空出大半屏。普通咨询同样存在,回答越短空白越大。
- 触发条件:本轮经 `pinLatestTurn` 钉顶(发问、校正点选项 / 开场)后,最后一轮回答短于一屏。
- 根因:BUG-930 的钉顶留白写成 `.conversation .message-list > :last-child .message-assistant { min-height: calc(var(--conversation-viewport) - var(--latest-turn-head-height)) }`,加在助手行本身;而 `.message-actions`(以及普通咨询的追问建议、校正的交付卡)是助手行之后、同一个 `:last-child` 容器里的兄弟节点,于是被顶到留白之后。实测(无头 Chrome 151,虚构数据)正文末行到操作行的距离:普通咨询 375px 526px / 1280px 608px;生时校正 375px 452px / 1280px 554px。
- 为何 BUG-930 验收没发现:当时的 Chrome 骨架页只验证「本轮开头钉在视口顶部」(S1–S4),骨架里没有操作按钮行,也没人看按钮落在哪里;合同测试只锁选择器字符串,不看操作行的位置。
- 修复:留白改加在整轮外层 `.conversation .message-list > :last-child:has(.message-assistant)`,计算式与两个 CSS 变量口径不变(`useConversationScrollAnchor` 逻辑未改,只更新了 `turnHeadHeightForSpacer` 的注释);外加 `align-content: start`,防止生时校正外层(`display: grid`)把多出来的高度分摊到各行、把间距又撑开。头行与末行同一元素时(BUG-931)仍写 `0px`:外层从这条助手行开始,最小高度等于整个视口正好把它留在顶部。
- 验证:`frontend/tests/chat-notice-and-scroll-contract.test.ts`「turn spacer uses conversation viewport variables and is not sticky」(选择器三栏改写 + 断言 `align-content: start`)与新增「the turn spacer trails the actions row…(BUG-1042)」(任何规则不得把 `--conversation-viewport` 写在 `.message-assistant` 上;两处 DOM 中操作行与助手行同在末轮外层且在其后)。本地 `next build --webpack` + `next start` + Chrome 151 CDP、`/api/*` 虚构数据:短回答正文末行到操作行 23px(即 `.message-actions` 自身间距),375 / 1280、普通咨询 / 生时校正四种组合一致;本轮开头仍停在滚动容器顶部下方约 10px;普通咨询长回答「跳到最新」出现、点击后落底并继续跟随。截图 `docs/testing/latest-turn-actions-gap-20260926/`。全量 3961 条 / 61 失败,失败名单与基线逐条一致(新增 1 条通过)。
- 防复发:留白只加在末轮外层;新增合同测试禁止把视口留白写回助手行。改末轮 DOM(给外层加兄弟、把操作行挪出外层)必须同时看操作行位置。
- 相关记录:BUG-930(钉顶留白来源)、BUG-931(头行即末行时写 0)、BUG-932(闲置不写留白);验证中另查出 BUG-1043、BUG-1044(均为既有问题,与本次改动无关,前后表现一致)。
- 复发自:无(BUG-930 的遗漏)。
- 修复版本:`codex/latest-turn-actions-gap-20260926`,未部署。
## BUG-1043 | 直接打开普通咨询会话后,滚动锚的监听从未挂上
- 状态:investigating(仅诊断;本轮任务书禁止改 `useConversationScrollAnchor`,未修)
- 首次发现 / 最近更新:2026-09-26 / 2026-09-26
- 影响面:`frontend/src/hooks/use-conversation-scroll-anchor.ts` 两个 effect(scroll 监听、ResizeObserver 跟随)与首页 `page.tsx` 的加载闸门;普通咨询面。生时校正面不受影响(组件在揭幕后才挂载)。
- 用户现象(本地复现):刷新页面或经 `?c=` / 启动落点直接进入一个已有的普通咨询会话后,发一个短问题,回答很短也会出现「跳到最新」;上下滚动该按钮不消失;点它之后回答继续变长也不会跟随。
- 触发条件:首页揭幕时当前会话就是一条有消息的普通咨询(不是揭幕后再切换过去)。
- 已确认事实:无头 Chrome 151、生产构建、虚构数据下给 `EventTarget.addEventListener` 与 `ResizeObserver.observe` 打点:该路径下 `.conversation` 上 scroll 监听 0 次、ResizeObserver 观察 0 次;先开 `/?new=1` 再点侧栏进入同一会话,则各挂上 1 次,短回答不再出现「跳到最新」。`pinLatestTurn` 走 rAF 不依赖 effect,所以钉顶本身正常,但 `latestBelowFold` 停在钉顶那一刻算出的值。
- 推测根因(未证实):加载闸门期间 `page.tsx` 早退渲染 `.app-loading`,`.conversation` 尚未挂载,effect 因 `container.current` 为空直接返回;揭幕后 `active` 与 `resetKey`(`activeSessionId`)都没变,effect 不会重跑。
- 修复:未修。需要单独任务书授权改 hook(例如依赖里加入容器是否已挂载的状态)。
- 验证:见上,复现脚本在执行方 scratchpad,未入库。
- 防复发:待定。
- 相关记录:BUG-930、BUG-932、BUG-1042。
- 复发自:无。
- 修复版本:无。
## BUG-1044 | 生时校正长回答钉顶后又被拉到底部
- 状态:investigating(仅诊断;本轮任务书禁止改 `useConversationScrollAnchor`,未修)
- 首次发现 / 最近更新:2026-09-26 / 2026-09-26
- 影响面:`useConversationScrollAnchor` 的 `holdUnpin` 解除条件 × 生时校正 `.message-list` 的底部留白(`padding-bottom: calc(var(--rectification-jump-clearance) + var(--space-3))`)。
- 用户现象(本地复现):生时校正里发一句话,回答超过一屏时,本轮开头先被钉到顶部,回答一到视口就跳到最后一个字(BUG-930 想解决的现象)。
- 已确认事实:CDP 逐 100ms 采样:平滑滚动结束时本轮开头距顶 10px、`distanceFromBottom` = 94(≤ `conversationAnchorThreshold` 96),下一帧回答撑高后 `scrollTop` 直接到底。改动前(旧留白规则)表现完全相同(开头位置 −1323px,落底),与 BUG-1042 无关。普通咨询同一时刻距离约 198,不触发。
- 推测根因(未证实):钉顶后的静止距离等于该面底部留白 + 钉顶间距,校正面恰好 ≤ 96,`measure()` 把它当成「读者回到底部」,解除 `holdUnpin` 并把 `anchored` 翻回真,随后 ResizeObserver 贴底跟随。BUG-931 修的是留白坍塌导致的同一症状,这里留白正常但阈值判断仍然命中。
- 修复:未修。需单独任务书决定改阈值判定还是改该面的留白口径。
- 相关记录:BUG-930、BUG-931、BUG-1042。
- 复发自:无(BUG-931 同症状、不同触发条件)。
- 修复版本:无。