From a908d5109768668f6dabd2289f3b9898be2cc201 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Thu, 19 Mar 2026 18:39:39 +0000 Subject: [PATCH] fix(site): prevent scroll overshoot in diff viewer end spacer (#23305) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace fixed `100vh` spacer at bottom of diff list with a dynamically sized one - Adds "X files changed" message to the bottom of the diff > This PR was created with the help of Coder Agents, and was reviewed by several humans and robots. 🧑‍💻🤝🤖 --- site/src/pages/AgentsPage/DiffViewer.tsx | 76 +++++++++++++++++------- 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/site/src/pages/AgentsPage/DiffViewer.tsx b/site/src/pages/AgentsPage/DiffViewer.tsx index 1c196dca6d..f56f7f17c3 100644 --- a/site/src/pages/AgentsPage/DiffViewer.tsx +++ b/site/src/pages/AgentsPage/DiffViewer.tsx @@ -768,6 +768,34 @@ export const DiffViewer: FC = ({ } }, [scrollToFile, onScrollToFileComplete]); + // --------------------------------------------------------------- + // Viewport height for the last-file min-height trick: setting + // min-height on the last file wrapper lets CSS handle the + // "be at least viewport-tall" logic, removing the need for a + // separate spacer div and a second ResizeObserver. Uses a ref + // callback (same pattern as containerRef) so the measurement + // lands during commit — before useEffect-based scroll logic. + // --------------------------------------------------------------- + const [viewportHeight, setViewportHeight] = useState(0); + const scrollAreaRef = useCallback((node: HTMLElement | null) => { + const vp = node?.querySelector( + "[data-radix-scroll-area-viewport]", + ); + diffViewportRef.current = vp ?? null; + + if (!vp) return; + + setViewportHeight(vp.clientHeight); + const ro = new ResizeObserver(([entry]) => { + setViewportHeight(entry.contentRect.height); + }); + ro.observe(vp); + return () => { + ro.disconnect(); + diffViewportRef.current = null; + }; + }, []); + // --------------------------------------------------------------- // Loading state // --------------------------------------------------------------- @@ -843,30 +871,34 @@ export const DiffViewer: FC = ({ )} scrollBarClassName="w-1.5" viewportClassName="[&>div]:!block" - ref={(node) => { - const vp = node?.querySelector( - "[data-radix-scroll-area-viewport]", - ); - diffViewportRef.current = vp ?? null; - }} + ref={scrollAreaRef} >
- {sortedFiles.map((fileDiff) => ( -
setFileRef(fileDiff.name, el)} - > - -
- ))} - {/* Spacer so the last file can scroll fully to the top. */} -
+ {sortedFiles.map((fileDiff, i) => { + const isLast = i === sortedFiles.length - 1; + return ( +
setFileRef(fileDiff.name, el)} + style={isLast ? { minHeight: viewportHeight } : undefined} + > + + {isLast && ( +
+ {`${sortedFiles.length} ${sortedFiles.length === 1 ? "file" : "files"} changed`} +
+ )} +
+ ); + })}