Fix diff pane header popping while scrolling#159
Conversation
Greptile SummaryThis PR stabilizes the pinned file-header behavior in the diff pane by replacing the old "pop-in sticky header" pattern with an always-pinned top row and removing the in-stream header only for the first file. Scroll math throughout (
Confidence Score: 5/5Safe to merge — all remaining findings are P2 style suggestions with no current bugs. The implementation is logically correct: the effectiveScrollTop - 1 handoff boundary, the bodyTop-based scroll alignment, the event-based viewport tracking, and the settle logic all behave as intended and are backed by thorough unit and PTY integration tests. The only finding is a P2 inconsistency in the buildFileSectionLayouts default fallback that has no impact on current callers. src/ui/lib/fileSectionLayout.ts — minor default fallback inconsistency at line 40. Important Files Changed
Sequence DiagramsequenceDiagram
participant User as User scroll/key
participant ScrollBox as ScrollBox
participant DiffPane as DiffPane
participant PinnedHeader as Pinned Header Row
participant Stream as In-Stream Content
User->>ScrollBox: scroll or scrollTo(bodyTop)
ScrollBox->>DiffPane: verticalScrollBar change event
DiffPane->>DiffPane: updateViewport sets scrollViewport state
DiffPane->>DiffPane: pinnedHeaderFile = findHeaderOwningFileSection(scrollTop - 1)
DiffPane->>DiffPane: useLayoutEffect calls renderer.intermediateRender()
DiffPane->>PinnedHeader: render current file header
DiffPane->>Stream: render file bodies, first file has no in-stream header
Note over PinnedHeader,Stream: In-stream header visible while scrollTop equals headerTop. Pinned header hands off one row later via the scrollTop minus one policy.
Reviews (1): Last reviewed commit: "Remove unused DiffSection selected prop" | Re-trigger Greptile |
src/ui/lib/fileSectionLayout.ts
Outdated
|
|
||
| files.forEach((file, index) => { | ||
| const separatorHeight = index > 0 ? 1 : 0; | ||
| const headerHeight = Math.max(0, headerHeights?.[index] ?? 1); |
There was a problem hiding this comment.
Inconsistent default for
headerHeights fallback
The fallback ?? 1 applies to all indices, including index === 0, but getInStreamFileHeaderHeight(0) returns 0. Any caller that omits headerHeights (e.g. a future test or a new call site) will compute an off-by-one bodyTop for the first file and all subsequent positions.
All current callers pass buildInStreamFileHeaderHeights(files) explicitly, so there is no current bug — but the inconsistency is a latent footgun. Consider changing the fallback to use getInStreamFileHeaderHeight:
| const headerHeight = Math.max(0, headerHeights?.[index] ?? 1); | |
| const headerHeight = Math.max(0, headerHeights?.[index] ?? getInStreamFileHeaderHeight(index)); |
This keeps the function self-consistent and makes the default match the actual policy.
There was a problem hiding this comment.
Good catch — I changed the fallback to use getInStreamFileHeaderHeight(index) so buildFileSectionLayouts() stays consistent even if a caller omits headerHeights.
Responded by pi using unknown-model.
Summary
Testing