Skip to content

Commit 4a2e6dd

Browse files
fix(web): fix inaccurate scroll position when selecting chat references (SOU-724)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b6ae24d commit 4a2e6dd

File tree

2 files changed

+34
-21
lines changed

2 files changed

+34
-21
lines changed

packages/web/src/features/chat/components/chatThread/referencedFileSourceListItem.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,18 @@ const ReferencedFileSourceListItemComponent = ({
214214
return isExpanded ? ChevronDown : ChevronRight;
215215
}, [isExpanded]);
216216

217+
const isSelectedWithoutRange = useMemo(() => {
218+
return references.some(r => r.id === selectedReference?.id && !selectedReference?.range);
219+
}, [references, selectedReference?.id, selectedReference?.range]);
220+
217221
return (
218222
<div className="relative" id={id}>
219223
{/* Sentinel element to scroll to when collapsing a file */}
220224
<div id={`${id}-start`} />
221225
{/* Sticky header outside the bordered container */}
222226
<div className={cn("sticky top-0 z-10 flex flex-row items-center bg-accent py-1 px-3 gap-1.5 border-l border-r border-t rounded-t-md", {
223227
'rounded-b-md border-b': !isExpanded,
228+
'border-chat-reference-selected-border border-b': isSelectedWithoutRange,
224229
})}>
225230
<ExpandCollapseIcon className={`h-3 w-3 cursor-pointer mt-0.5`} onClick={() => onExpandedChanged(!isExpanded)} />
226231
<PathHeader

packages/web/src/features/chat/components/chatThread/referencedSourcesListView.tsx

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -117,34 +117,42 @@ const ReferencedSourcesListViewComponent = ({
117117
const view = editorRef.view;
118118
const lineNumber = selectedReference.range.startLine;
119119

120-
// Get the line's position within the CodeMirror document
121120
const pos = view.state.doc.line(lineNumber).from;
122-
const blockInfo = view.lineBlockAt(pos);
123-
const lineTopInCodeMirror = blockInfo.top;
124-
125-
// Get the bounds of both elements
126-
const viewportRect = scrollAreaViewport.getBoundingClientRect();
127-
const codeMirrorRect = view.dom.getBoundingClientRect();
128-
129-
// Calculate the line's position relative to the ScrollArea content
130-
const lineTopRelativeToScrollArea = lineTopInCodeMirror + (codeMirrorRect.top - viewportRect.top) + scrollAreaViewport.scrollTop;
131-
132-
// Get the height of the visible ScrollArea
133-
const scrollAreaHeight = scrollAreaViewport.clientHeight;
134-
135-
// Calculate the target scroll position to center the line
136-
const targetScrollTop = lineTopRelativeToScrollArea - (scrollAreaHeight / 3);
137121

138122
// Expand the file if it's collapsed.
139123
setCollapsedFileIds((collapsedFileIds) => collapsedFileIds.filter((id) => id !== fileId));
140124

141-
// Scroll to the calculated position
142-
// @NOTE: Using requestAnimationFrame is a bit of a hack to ensure
143-
// that the collapsed file ids state has updated before scrolling.
125+
// @hack: CodeMirror 6 virtualizes line rendering — it only renders lines near the
126+
// browser viewport and uses estimated heights for everything else. This means
127+
// coordsAtPos() returns inaccurate positions for lines that are off-screen,
128+
// causing the scroll to land at the wrong position on the first click.
129+
//
130+
// To work around this, we use a two-step scroll:
131+
// Step 1: Instantly bring the file element into the browser viewport. This
132+
// forces CodeMirror to render and measure the target lines.
133+
// Step 2: In the next frame (after CodeMirror has measured), coordsAtPos()
134+
// returns accurate screen coordinates which we use to scroll precisely
135+
// to the target line.
136+
scrollIntoView(fileSourceElement, {
137+
scrollMode: 'if-needed',
138+
block: 'start',
139+
behavior: 'instant',
140+
});
141+
144142
requestAnimationFrame(() => {
143+
const coords = view.coordsAtPos(pos);
144+
if (!coords) {
145+
return;
146+
}
147+
148+
const viewportRect = scrollAreaViewport.getBoundingClientRect();
149+
const lineTopRelativeToScrollArea = coords.top - viewportRect.top + scrollAreaViewport.scrollTop;
150+
const scrollAreaHeight = scrollAreaViewport.clientHeight;
151+
const targetScrollTop = lineTopRelativeToScrollArea - (scrollAreaHeight / 3);
152+
145153
scrollAreaViewport.scrollTo({
146154
top: Math.max(0, targetScrollTop),
147-
behavior: 'smooth',
155+
behavior: 'instant',
148156
});
149157
});
150158
}
@@ -154,7 +162,7 @@ const ReferencedSourcesListViewComponent = ({
154162
scrollIntoView(fileSourceElement, {
155163
scrollMode: 'if-needed',
156164
block: 'start',
157-
behavior: 'smooth',
165+
behavior: 'instant',
158166
});
159167
}
160168
}, [getFileId, sources, selectedReference]);

0 commit comments

Comments
 (0)