@@ -2,7 +2,7 @@ import type { ScrollBoxRenderable } from "@opentui/core";
22import { useCallback , useEffect , useLayoutEffect , useMemo , useState , type RefObject } from "react" ;
33import type { AgentAnnotation , DiffFile , LayoutMode } from "../../../core/types" ;
44import type { VisibleAgentNote } from "../../lib/agentAnnotations" ;
5- import { estimateDiffBodyRows } from "../../lib/sectionHeights" ;
5+ import { estimateDiffBodyRows , estimateHunkAnchorRow } from "../../lib/sectionHeights" ;
66import { diffHunkId , diffSectionId } from "../../lib/ids" ;
77import type { AppTheme } from "../../themes" ;
88import { DiffSection } from "./DiffSection" ;
@@ -185,18 +185,50 @@ export function DiffPane({
185185 return next ;
186186 } , [ adjacentPrefetchFileIds , selectedFileId , visibleViewportFileIds , windowingEnabled ] ) ;
187187
188- const selectedFile = selectedFileId ? files . find ( ( file ) => file . id === selectedFileId ) : undefined ;
188+ const selectedFileIndex = selectedFileId ? files . findIndex ( ( file ) => file . id === selectedFileId ) : - 1 ;
189+ const selectedFile = selectedFileIndex >= 0 ? files [ selectedFileIndex ] : undefined ;
189190 const selectedAnchorId = selectedFile
190191 ? ( selectedFile . metadata . hunks [ selectedHunkIndex ] ? diffHunkId ( selectedFile . id , selectedHunkIndex ) : diffSectionId ( selectedFile . id ) )
191192 : null ;
193+ const selectedEstimatedScrollTop = useMemo ( ( ) => {
194+ if ( ! selectedFile || selectedFileIndex < 0 ) {
195+ return null ;
196+ }
197+
198+ let top = 0 ;
199+ for ( let index = 0 ; index < selectedFileIndex ; index += 1 ) {
200+ top += ( index > 0 ? 1 : 0 ) + 1 + ( estimatedBodyHeights [ index ] ?? 0 ) ;
201+ }
202+
203+ if ( selectedFileIndex > 0 ) {
204+ top += 1 ;
205+ }
206+
207+ top += 1 ;
208+ top += estimateHunkAnchorRow ( selectedFile , layout , showHunkHeaders , selectedHunkIndex ) ;
209+ return top ;
210+ } , [ estimatedBodyHeights , files , layout , selectedFile , selectedFileIndex , selectedHunkIndex , showHunkHeaders ] ) ;
192211
193212 useLayoutEffect ( ( ) => {
194213 if ( ! selectedAnchorId ) {
195214 return ;
196215 }
197216
198217 const scrollSelectionIntoView = ( ) => {
199- scrollRef . current ?. scrollChildIntoView ( selectedAnchorId ) ;
218+ const scrollBox = scrollRef . current ;
219+ if ( ! scrollBox ) {
220+ return ;
221+ }
222+
223+ // In the common no-wrap/no-note path we can estimate the selected hunk row and keep it
224+ // comfortably below the top edge instead of merely making it barely visible.
225+ if ( ! wrapLines && visibleAgentNotesByFile . size === 0 && selectedEstimatedScrollTop !== null ) {
226+ const topPaddingRows = Math . max ( 2 , Math . floor ( scrollViewport . height * 0.25 ) ) ;
227+ scrollBox . scrollTo ( Math . max ( 0 , selectedEstimatedScrollTop - topPaddingRows ) ) ;
228+ return ;
229+ }
230+
231+ scrollBox . scrollChildIntoView ( selectedAnchorId ) ;
200232 } ;
201233
202234 // Run after this pane renders the selected section/hunk, then retry briefly while layout settles.
@@ -206,7 +238,7 @@ export function DiffPane({
206238 return ( ) => {
207239 timeouts . forEach ( ( timeout ) => clearTimeout ( timeout ) ) ;
208240 } ;
209- } , [ selectedAnchorId , scrollRef ] ) ;
241+ } , [ scrollRef , scrollViewport . height , selectedAnchorId , selectedEstimatedScrollTop , visibleAgentNotesByFile . size , wrapLines ] ) ;
210242
211243 return (
212244 < box
0 commit comments