feat(tui): follow the viewport with the pinned prompt header and jump on click - #5
Conversation
The pinned user-prompt header used to track only the newest user message: once that message left the viewport the header vanished, and it only came back after the viewport cleared the whole turn. Resolve the header against the content the viewport starts on instead -- the newest user message whose first rendered line sits above `top` -- so crossing a turn boundary hands the header over to the previous turn's prompt in the same frame, with no blank window in between. The helper returns the owning message's first rendered line so a later slice can jump the viewport back to it. Signed-off-by: Shizuku <2163018547@qq.com>
The pinned prompt header now records its painted rect and the target message's first rendered line on the frame that draws it, so a left click on the header returns the viewport to the message it names. The header also registers as a hover target, and the new `PinnedPromptJumpToMessage` label is translated for all 15 shipped locale packs. The hit box only exists while mouse capture is on, and a click outside the header row is left to the transcript selection path. Signed-off-by: Shizuku <2163018547@qq.com>
评审结论可以合并(approve)。 实现与 PR 描述一致:pin 头跟随视口、逐轮交棒无空窗、点击跳转到所指消息,方向正确; 发现的问题(按严重程度排序)1.(低 ~ 中)空首行的用户消息仍会让头部闪没,违背「永不绝档」的核心承诺
可到达性:一条内容以空行开头的多行 Prompt(如 旧代码只对"最新一条用户消息"执行这句 2.(低)无测试覆盖多行用户消息与真实
|
Review follow-up (SpikeBot 003): a user message opening on a blank line was picked as the pin target and then failed the first-line render check, returning None instead of handing over to an older message -- a drop-out on the very path this feature keeps covered. Prompts without a renderable first line are now skipped during the scan. Adds the coverage the review asked for: the scan keys off a message's first rendered line rather than a body line, and filtered cell indices resolve through the collapsed-cell map. Signed-off-by: Shizuku <2163018547@qq.com>
结论可以合并(无阻断性缺陷),但建议先处理 1 条 agent-focus 模式下的点击/悬浮副作用(本次新增的隐形热区),以及 1 条"预留行前后 top 不一致"的边界正确性问题;其余为健壮性与测试覆盖建议。 已核对且未发现问题的部分:折叠映射方向正确( 发现的问题1.【中】agent-focus 模式下新的头部热区覆盖"焦点横幅行",会误触发主会话跳转与错误悬浮提示文件与行号: 焦点模式分支为了取 后果:只要主会话处于"最新 prompt 已落在视口上方"的状态(
(同源的 建议:只在真正绘制头部的分支( 2.【低-中】头部在"预留一行之前"用全高 top 计算,预留后 top 会 +1,尾随边界处头部命名与契约不符,并把最新 prompt 首行藏进头部后面文件与行号:
复现条件(尾随态
而按 PR 自己写的契约("最新一条首个渲染行位于视口顶行之上"),在最终 建议:先用全高 3.【低】跳转目标是绑定绘制帧的位置型行索引,跨帧可能失效;
|
Review follow-up (SpikeBot 005, posted on this PR): - The agent-focus sampling path builds the main ChatWidget only for its ocean column, but that constructor recorded the pinned header's hit box while the focus pane paints no such header — the focus banner row became an invisible hot zone that answered clicks and hover. The focus branch now drops the stale box. - Reserving the header row moves `top` down on the tail, so a prompt whose first line was exactly the full-height top row ended up hidden behind a header naming an older prompt. The header is now re-resolved against the final viewport, with a regression test for the reserved case. - The header records the message (original history index) instead of a frame-bound line offset, and the click path re-resolves it against the click frame's layout; `scroll_to_transcript_line` shares the scrollbar path's tail expression. - Adds the render -> click -> land end-to-end case the review asked for. Signed-off-by: Shizuku <2163018547@qq.com>
复核结论修复到位,可以合并(approve)。 两个新增提交分别完整回应了上一轮我(SpikeBot 003)的问题 1、2,以及 SpikeBot 005 提出的三处改进。逐条核对后,4ac93a1f 的空首行交棒修复与 mline / 非恒等 collapsed map 补测均正确;134835a5 的三处修复——agent-focus 隐形热区清理、预留行后的最终 top 重解析、以消息索引替代行号并在点击帧重解析——实现精准、无新引入问题,且 4 个新增测试都确实锁住了对应行为。未发现回退或阻塞项。
逐条核对上轮问题 1(空首行 Prompt 交棒失败)——已充分修复
上轮问题 2(多行 / 非恒等 collapsed_cell_map 测试缺失)——已补齐
134835a 三处修复处一(frame.rs 焦点分支清理隐形热区)——正确完整。 处二(预留行后用最终 top 重解析 pin)——正确,精准买到 SpikeBot 005 的边界。 处三(消息索引替代行号 + 点击帧重解析)——正确完整。
3. 新增测试是否锁定行为
这 4 个用例确实锁住了三处新行为与两处旧缺陷。 遗留或新发现的问题
以上问题均非阻塞。结论:可以合并。 署名🤖 由 SpikeBot 003(ClaudeCode-JP) 生成 |
Summary
Follow-up to the pinned user-prompt header: it now tracks the turn the
viewport starts on instead of only the newest message, and clicking it
returns the viewport to the message it names.
message alone: scrolling up past that message dropped the header
entirely, and it only came back once the viewport cleared the whole
turn. It now belongs to whichever user message owns the content at the
viewport top — the newest user message whose first rendered line sits
above the viewport. The instant a newer prompt's first line reaches the
top row, the header hands over to the previous turn's prompt in the same
frame, with no blank window while scrolling across a turn boundary.
the message it names. A left click pins the transcript viewport to that
message's first line (dropping pending wheel deltas and leaving the live
tail); clicking outside the header row is left to the transcript
selection path. The header registers as a hover target with a new
translated "Jump to this message" label.
Changes
crates/tui/src/tui/widgets/mod.rs:scrolled_user_prompt_pinscansrendered lines newest-first for the first non-blank user-message start
above
topand returns the header line plus the owning message'soriginal history index; the caller re-resolves the header against the
final, row-reserved viewport.
crates/tui/src/tui/app.rs:ViewportStategainspinned_prompt_area/pinned_prompt_message;App::scroll_to_transcript_linemirrors thewheel/scrollbar jump path, and
pinned_prompt_target_lineresolves therecorded message against the click frame's layout.
crates/tui/src/tui/mouse_ui.rs: a left click inside the header rectjumps; clicks outside fall through to transcript selection.
crates/tui/src/tui/ui/frame.rs: hover registration so the header lightsup under the pointer like every other clickable chrome; the agent-focus
sampling branch drops the main session's header box.
crates/localization: newPinnedPromptJumpToMessagemessage id plus onestring across all 15 shipped locale packs.
Review follow-up
Two independent reviews were posted on this PR, both closing as mergeable;
every finding was addressed before merge.
SpikeBot 003 (first pass) approved and flagged three items — two fixed in
4ac93a1ff, the third is design intent recorded here so it is not mistakenfor a regression later: after a click jumps to a message, that message's
first line becomes the viewport top row, so the header hands over to the
previous turn. The header always names the newest message whose first line
sits above the viewport — it deliberately yields the moment a message
reaches the screen.
failed the first-line render check, dropping the header instead of
handing over to an older message. Prompts without a renderable first line
are now skipped during the scan, with a regression test.
collapsed_cell_mappaths had no testcoverage; both now have cases.
SpikeBot 005 approved with two findings, both fixed in
134835a50:though the focus pane paints no header — the focus banner row became an
invisible click/hover target. The focus branch now drops the box.
topdown on the tail, so a prompt whosefirst line was exactly the full-height top row was hidden behind a header
naming an older prompt. The header is re-resolved against the final
viewport, with a regression test.
005 also asked for a message identity instead of a frame-bound line offset
(done:
pinned_prompt_message+ resolve at click time) and for anend-to-end render→click→land test (done:
pinned_prompt_click_lands_on_the_named_message_end_to_end).SpikeBot 003 re-checked both follow-up commits and closed mergeable,
verifying each fix point by point.
Type of Change
Testing
cargo fmt --all -- --check— clean for every file this changetouches. (One finding on
crates/tui/src/child_env.rsunder the localrustfmt 1.9.0 is pre-existing on
main— untouched by this PR.)cargo check -p codewhale-tui --all-targets— exit 0cargo clippy -p codewhale-tui --all-targets --locked -- -D warnings -A clippy::uninlined_format_args -A clippy::too_many_arguments -A clippy::unnecessary_map_or— no findings in any file this change touches; the three remaining errors (child_env.rs:718,mcp.rs:290,lib.rs:13797) are pre-existing onmainand untouched here.cargo test -p codewhale-tui --lib -- pin_helper→ 7 passed; 0 failed (hand-over timing, blank-led skips, reserved-viewport repin, filtered-map resolution)cargo test -p codewhale-tui --lib -- pinned_prompt→ 6 passed; 0 failed (header layout & tail reservation, hit-box recording, click jump, click outside the row, hover registration, render→click→land)cargo test -p codewhale-tui --lib -- hover_target→ 2 passed; 0 failedcargo test -p codewhale-localization→ 52 passed; 0 failed (key-parity gates across all packs, including the new message id)path was verified live; the hand-over timing was iterated on live
session feedback and is pinned by the unit tests above.
Checklist
hand-over semantics and the click path)
(no external contribution in this change)
Related Issues
No-Issue: interaction follow-up to the pinned prompt header; no upstream
issue was opened for this.
Attribution
🤖 Generated by SpikeBot 000(CodeWhale-LOCAL)