fix(#3852): jump-to-question scrolls to response start instead of user message#3934
fix(#3852): jump-to-question scrolls to response start instead of user message#3934rodboev wants to merge 2 commits into
Conversation
…d of user message
|
| Filename | Overview |
|---|---|
| static/ui.js | Adds assistantRawIdx parameter to jump button and scroll function; builds a reverse map to find the first assistant segment per question turn; querySelector('[data-msg-idx]') correctly targets the right DOM element. Logic is sound — the reverse map is properly used at the call site with a safe ??rawIdx fallback. |
| static/i18n.js | Updates jump_to_question and jump_to_question_label strings across all 13 supported locales to reflect the new "scroll to response start" behavior. All locale entries have been updated consistently. |
| tests/test_issue2246_question_jump.py | Updates existing signature-match assertions to the new two-parameter signatures and adds a new test test_multi_segment_turn_jumps_to_first_assistant_segment that verifies the reverse map presence and the correct call site expression. |
Sequence Diagram
sequenceDiagram
participant User
participant Button as ↑ to response button
participant jumpToTurnQuestion
participant DOM
User->>Button: click
Button->>jumpToTurnQuestion: jumpToTurnQuestion(questionRawIdx, assistantRawIdx)
jumpToTurnQuestion->>DOM: "querySelector('[data-msg-idx="assistantRawIdx"]')"
alt assistant segment found
DOM-->>jumpToTurnQuestion: seg element
jumpToTurnQuestion->>DOM: "seg.scrollIntoView({block:'start', behavior:'smooth'})"
else segment not in DOM (windowed)
jumpToTurnQuestion->>DOM: expand render window via renderMessages()
jumpToTurnQuestion->>DOM: requestAnimationFrame → retry querySelector
else no assistantRawIdx (fallback)
jumpToTurnQuestion->>DOM: getElementById(userMessageDomId)
DOM-->>jumpToTurnQuestion: user row
jumpToTurnQuestion->>DOM: row.scrollIntoView + _highlightQuestionRow
end
Reviews (2): Last reviewed commit: "fix(#3852): update question-jump pins, w..." | Re-trigger Greptile
SummaryRead the diff at this PR against One concrete problem, though: the reverse map you added is dead code, and the actual behavior diverges from your stated intent. Code referenceThe reverse map at const assistantRawIdxByQuestionRawIdx=new Map();
for(const [aIdx,qIdx] of questionRawIdxByAssistantRawIdx){
if(!assistantRawIdxByQuestionRawIdx.has(qIdx)) assistantRawIdxByQuestionRawIdx.set(qIdx,aIdx);
}But the call site at const _qJumpTarget=(!isUser&&!m._live)?questionRawIdxByAssistantRawIdx.get(rawIdx):undefined;
const questionJumpBtn = (_qJumpTarget!==undefined&&_qJumpTarget!==null)
? _questionJumpButtonHtml(_qJumpTarget, rawIdx) // <- rawIdx, not the reverse-map lookup
DiagnosisThis matters for multi-segment turns. A button is rendered on every non-live assistant segment (
They don't — the reverse map that would make them all point to the turn's first segment is computed and discarded. Two clean resolutions, pick one:
Either is fine, but right now the code does (2) while shipping the unused machinery for (1), which will confuse the next reader. Test planThe diff is JS-only; the |
…, and align button labeling
|
Absorbed and shipped in v0.51.352 (Release LP, deployed live) via the batched release #3948 — rebased onto fresh master with attribution, content verified, full-suite (8570) + Codex + Opus gated. A folded-Worklog hidden-segment edge the gate caught was fixed inline (visible-target guard + regression test) before merge. Closes #3852. Thanks @rodboev! 🙏 |
Thinking Path
jumpToTurnQuestion()at ui.js:502 scrolls todocument.getElementById(_userMessageDomId(questionRawIdx)), which is the user message row — so the button puts the question into view, not the answer.div.assistant-segmentelements identified bydataset.msgIdx, notidattributes; they must be resolved viacontainer.querySelector('[data-msg-idx="..."]').questionRawIdxByAssistantRawIdx(assistant rawIdx → question rawIdx). Inverting it givesassistantRawIdxByQuestionRawIdx(question rawIdx → first assistant rawIdx for that turn), which lets us look up the segment to scroll to._questionJumpButtonHtml()embeds theonclickcall; it needs to embed the assistant rawIdx as a second argument sojumpToTurnQuestion()can use it at click time without a DOM query.What Changed
static/ui.js: in_questionJumpButtonHtml(), acceptassistantRawIdxas a second parameter and embed it in theonclick; injumpToTurnQuestion(), resolve and prefer the assistant segment viaquerySelector('[data-msg-idx]')withblock:'start', falling back to the user row; after thequestionRawIdxByAssistantRawIdxbuild loop inrenderMessages(), add a reverse mapassistantRawIdxByQuestionRawIdx; pass the currentrawIdxas the second argument at the_questionJumpButtonHtml()call site.Why It Matters
Clicking "↑ to question" on an assistant response now positions the viewport at the start of that response rather than at the user message above it, making the navigation button actually useful for finding where a long response begins.
Verification
Manual: open a session with multiple long assistant responses, click "↑ to question" on one — the viewport should scroll to the top of that assistant response block, not to the user message.
Risks / Follow-ups
block:'start'(scroll to the beginning of the turn). If a question produces many assistant segments (long tool-chain), all buttons correctly point to the same first segment.assistantRawIdxis not available (e.g., a button rendered before this change, though that would not persist across page reload).Model Used
Claude Opus 4.6 via Claude Code CLI