Files
Jyotisha/docs/tasks/PROGRESS-latest-turn-actions-gap-20260926.md
T
Jesse_ChenandClaude Opus 5.5 83cce7ad6a 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
2026-09-26 10:19:00 +08:00

93 lines
8.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.
# PROGRESS · 最后一轮回答与操作按钮之间的大段空白(2026-09-26)
任务书:`TASK-latest-turn-actions-gap-20260926.md`。执行方:Claude 子代理(直接执行模式)。分支 `codex/latest-turn-actions-gap-20260926`,worktree `.worktrees/latest-turn-actions-gap-20260926`,本地提交未推送。
## 基线
- `origin/staging` = `a57c4310`。
- 基线全量前端测试(Node 20.19.2):3960 条,pass 3872 / fail 61(均为环境缺口:Node 20 `mock.module` 的 `ERR_MODULE_NOT_FOUND @/…`、无 Docker)。
- 基线 rootMainFiles gzip 130933 B。
- BUG 编号:开工时 `docs/BUG_HISTORY.md` 最大 BUG-1041,`docs/tasks/` 无更大号 → 本单 BUG-1042;验证中另查出的两个既有问题记为 BUG-1043、BUG-1044(investigating,未修)。
## DOM 核对(D2)
| 面 | 最后一轮的 `.message-list > :last-child` | 内容 | 用户行 |
| --- | --- | --- | --- |
| 普通咨询 | `div.message-entry`(`chat-transcript.tsx` `MessageEntry`) | `article.message-assistant` → `.message-actions` → `.conversation-follow-ups` | 单独一个 `.message-entry`,不在末轮外层里 |
| 生时校正 | `div.rectification-message-wrap.rectification-message-entry`(`display: grid; gap: var(--space-1)`) | `article.message-assistant` → `.message-actions` → 交付卡 / 只读区间(若有) | 单独一个 wrap,不在末轮外层里 |
两处外层都**从助手行开始**、都不含用户行,所以 `--latest-turn-head-height` 的口径原样可用:头是用户行时扣用户行高度;头就是这条助手行(BUG-931)时写 `0px`,外层最小高度 = 整个视口,正好把它留在顶部。hook 不需要改逻辑。
末轮外层不是助手外层的情况(校正面的 persisted-question / pending-note / error 是最后一个子节点、普通咨询 `error-message`):旧规则 `:last-child .message-assistant` 也匹配不到,新规则 `:last-child:has(.message-assistant)` 同样不匹配,行为不变。
## 改动
| 文件 | 内容 |
| --- | --- |
| `frontend/src/app/globals.css` | `.conversation .message-list > :last-child .message-assistant { min-height: … }` → `.conversation .message-list > :last-child:has(.message-assistant) { min-height: …; align-content: start; }`。计算式、变量不变。`align-content: start` 是必要的:生时校正外层是 grid,给了最小高度后 `align-content: normal` 会把多余高度分摊给各行,操作行又会离开正文。 |
| `frontend/src/hooks/use-conversation-scroll-anchor.ts` | 只改 `turnHeadHeightForSpacer` 的注释(留白承载者从助手行变为末轮外层,BUG-931 写 0 的理由随之更新)。逻辑未动。 |
| `frontend/tests/chat-notice-and-scroll-contract.test.ts` | 见下「改动既有断言」;新增 1 条。 |
| `frontend/DESIGN.md` | Answer start anchor · Spacer 条目改写。 |
| 记录 | `docs/BUG_HISTORY.md`(BUG-1042 / 1043 / 1044)、`CHANGELOG.md`、`docs/testing/latest-turn-actions-gap-20260926.md` + 4 张截图、`docs/tasks/README.md`。 |
`:has()` 兼容性:Safari 15.4+ / Chrome 105+ / Firefox 121+,低于本站语法下限 Safari 16.4;`globals.css` 已有多处 `:has()`。
### 改动既有断言(三栏也写在测试文件内)
| 测试 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| 「turn spacer uses conversation viewport variables and is not sticky」 | 选择器 `.conversation .message-list > :last-child .message-assistant` 带该 `min-height` 计算式 | 选择器 `.conversation .message-list > :last-child:has(.message-assistant)` 带同一计算式,并断言 `align-content: start` | BUG-1042:留白从助手行移到整轮外层;计算式与变量口径不变,`sticky` 否定断言保留,变量写入 / 清除的行为断言原样保留 |
新增「the turn spacer trails the actions row instead of sitting between text and actions (BUG-1042)」:任何 `.message-assistant { … }` 规则里不得出现 `--conversation-viewport`;普通咨询 `MessageEntry` 里 `ChatMessageRow` → `ChatMessageActions` → `ConversationFollowUps` 顺序同在 `.message-entry`;校正面 `ChatMessageRow` 在 `ChatMessageActions` 之前且 `RectificationMessageEntry` 直接挂在 `.rectification-message-wrap` 里。
BUG-930 / 931 / 932 的其余断言(`resolveTurnHead`、头即尾写 0、闲置不写留白、`pinLatestTurn` 源码断言、`lastTurnTail` 假 DOM)一条未改。
## 测试
| 项 | 结果 |
| --- | --- |
| `tsc --noEmit` | 0 错 |
| `npm run lint` | 0 error(126 warning,均为既有) |
| 全量 `npm test` | 3961 条,pass 3873 / fail 61 |
| 失败名单 vs 基线 | 逐条一致,新增失败 0、修好 0 |
| 测试名单 vs 基线 | +1 新名(上面新增那条),消失 0 |
| Python 源码合同 | `test_session_management_entrypoints.py`、`test_daily_and_rectification_entrypoints.py`、`test_birth_time_journey_contract.py` 全过;`test_frontend_theme_contract.py::test_lightweight_warm_palette_contract` 失败,**改动前同样失败**(`--color-ink-secondary` 色值不符,与本单无关,未动) |
| `npm run build -- --webpack` | 通过;`/` ○ Static |
| gzip | rootMainFiles 130933 → 130933 B(0,本单只改 CSS);`index.html` 引用的全部 js/css 合计 656748 B |
## 真实浏览器验证
环境:本地 `next build --webpack` + `next start`,无头 Chrome 151(`--headless=new --disable-dev-shm-usage`),CDP `Fetch` 拦截 `/api/*` 回虚构数据(5 轮虚构历史撑出一屏以上;普通咨询 `/api/consult` 回纯文本,校正 `/api/rectification/agent` 回 ndjson `answer.delta` + `run.completed`)。「改动前」= 同一构建里注入旧规则(新规则 `min-height: 0 !important` + 旧选择器原样),因为本单只改了这一条 CSS。输入框用 CDP `Input.insertText` + Enter 真发问,钉顶由真实 `pinLatestTurn` 完成。
「正文末行 → 操作行」= `.message-actions` 顶边 − 末轮回答文本节点最后一行的底边。「开头距顶」= 本轮用户行顶边 − 滚动容器顶边。
| 面 | 宽度 | 改动前 间距 | 改动后 间距 | 改动后 开头距顶 | 改动后 外层高度 / 助手行高度 |
| --- | --- | --- | --- | --- | --- |
| 普通咨询 短回答 | 375 | 526px(操作行在视口外) | **23px** | 10px | 589 / 86 |
| 普通咨询 短回答 | 1280 | 608px(操作行在视口外) | **23px** | 10px | 671 / 86 |
| 生时校正 短回答 | 375 | 452px(且开头被顶出视口,见下) | **23px** | 10px | 495 / 66 |
| 生时校正 短回答 | 1280 | 554px(同上) | **23px** | 10px | 597 / 66 |
23px 即操作行自身间距:同一页面里历史轮(非末轮,从未带留白)的同一量测得也是 23px(普通咨询、生时校正各测一次)。
长回答(14 段):
- 普通咨询 375 / 1280:开头距顶 10 / 9px,「跳到最新」出现;点击后落底(`distanceFromBottom` 0)、按钮消失、操作行可见;随后往末轮插入 400px 内容,仍保持落底(跟随正常)。
- 生时校正 375 / 1280:本轮开头**没有**停在顶部,回答一到就落底——改动前完全相同(开头位置同为 −1323 / −482px)。定性为既有问题 BUG-1044,见下。
截图(入库,375 宽):`docs/testing/latest-turn-actions-gap-20260926/consult-375-{before,after}.png`、`rectification-375-{before,after}.png`。其余(1280、长回答、跳到最新后)在执行方 scratchpad `gap/`,未入库。校正截图底部的「合盘历史暂时无法读取」提示是虚构数据没回合盘接口所致,与本单无关。
附带改善:改动前校正面短回答结算时,操作行追加在已撑满视口的助手行之后,内容变高触发贴底跟随,把本轮开头顶出视口(上表「改动前」开头距顶 −118px);改动后操作行落在外层的最小高度之内,内容总高不变,开头留在顶部。
## 验证中查出的既有问题(未修,前后表现一致)
1. **BUG-1043**:刷新或经 `?c=` / 启动落点直接进入已有消息的普通咨询会话时,`useConversationScrollAnchor` 的 scroll 监听与 ResizeObserver 一次都没挂上(给 `addEventListener` / `ResizeObserver.observe` 打点实测为 0),结果短回答也显示「跳到最新」、滚动不更新、点击后不跟随。先 `/?new=1` 再点侧栏进入同一会话则正常(本单的普通咨询测量都走这条路径)。推测:加载闸门期间容器未挂载、effect 早退,揭幕后依赖不变不重跑。
2. **BUG-1044**:生时校正钉顶后静止时 `distanceFromBottom` = 94 ≤ 阈值 96,`measure()` 当成「回到底部」解除 `holdUnpin`、`anchored` 翻真,长回答随后贴底。普通咨询同一时刻约 198,不触发。
两条都在 hook 里,任务书红线禁止改 hook,只记为 investigating,建议另开单。
## 未做 / 环境缺口
- 登录态 iPhone Safari 真机:留给 `docs/testing/latest-turn-actions-gap-20260926.md`。
- 未部署、未推送。