Skip to content

Sync active hunks to mouse scrolling and prefetch diff highlighting#172

Merged
benvinegar merged 6 commits into
mainfrom
feat/mouse-scroll-active-hunk
Apr 7, 2026
Merged

Sync active hunks to mouse scrolling and prefetch diff highlighting#172
benvinegar merged 6 commits into
mainfrom
feat/mouse-scroll-active-hunk

Conversation

@benvinegar

Copy link
Copy Markdown
Member

Summary

  • update the active file and hunk as mouse-wheel scrolling changes what is centered in the review viewport
  • keep wheel-driven selection changes from snapping the viewport back while the mouse scroll interaction is still in progress
  • prefetch diff syntax highlighting for files approaching the viewport so code is usually highlighted before it becomes visible

Testing

  • bun run typecheck
  • bun test
  • bun run test:tty-smoke

This PR description was generated by Pi using GPT-5

@greptile-apps

greptile-apps Bot commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two related features: (1) syncing the active file/hunk selection as mouse-wheel scrolling changes the viewport center, and (2) prefetching syntax highlighting for files approaching the viewport so code is highlighted before it becomes visible. The viewport selection sync is gated by a ref-based 120 ms debounce (mouseScrollSelectionSyncActiveRef) that suppresses the usual "snap viewport back to selection" behaviour while the mouse is actively scrolling. The prefetch window covers 3× the viewport height in each direction plus immediately adjacent files. Both features integrate cleanly with the existing geometry/layout pipeline in DiffPane.

Key changes:

  • viewportSelection.ts — new pure helper that binary-searches FileSectionLayout[] for the section nearest the viewport center, then walks hunkBounds to pick the nearest hunk
  • useHighlightedDiff.ts — exports a new prefetchHighlightedDiff function backed by the same module-level LRU cache (capped at 150 entries); adds a content fingerprint so stale entries are never served after reload
  • DiffPane.tsx — arms the debounce on onMouseScroll, drives viewport-selection updates via useLayoutEffect, computes highlightPrefetchFileIds for the scroll window, and passes shouldLoadHighlight down to each DiffSection
  • Loop variable layout inside the highlightPrefetchFileIds memo shadows the outer layout prop; minor but worth renaming for clarity

Confidence Score: 4/5

Safe to merge; a single P2 naming issue is the only finding

Score of 4 reflects one P2 style issue (loop variable 'layout' shadowing the outer layout prop on DiffPane.tsx line 418) that does not affect runtime behaviour but should be cleaned up for readability. The mouse-scroll debounce logic, viewport-centred hunk selection, and prefetch cache design are all sound and covered by tests.

src/ui/components/panes/DiffPane.tsx — loop variable 'layout' shadows the outer layout prop on line 418

Important Files Changed

Filename Overview
src/ui/lib/viewportSelection.ts New pure helper module — binary-searches FileSectionLayout[] to find the viewport-centred section, then walks hunkBounds to pick the nearest hunk; well-structured and fully tested
src/ui/lib/viewportSelection.test.ts Adds two unit tests covering file-switch on center entry and nearest-hunk selection when the center lands in a collapsed gap
src/ui/diff/useHighlightedDiff.ts Adds prefetchHighlightedDiff export and module-level LRU cache (MAX_CACHE_ENTRIES=150) with content-fingerprint invalidation; promise deduplication and staleness guard in commitHighlightResult are sound
src/ui/diff/PierreDiffView.tsx Adds shouldLoadHighlight prop to gate per-file highlight loading; small, targeted change with correct default (true)
src/ui/components/panes/DiffSection.tsx Threads shouldLoadHighlight through to PierreDiffView; memoization comparator correctly updated to include the new prop
src/ui/components/panes/DiffPane.tsx Core of the PR: adds mouse-scroll debounce refs, viewport-centred hunk detection via useLayoutEffect, prefetch window logic, and shouldLoadHighlight forwarding; minor loop variable naming shadow on line 418
src/ui/App.tsx Wires the new onViewportCenteredHunkChange callback down to DiffPane; minimal, correct change
src/ui/AppHost.interactions.test.tsx Adds integration test verifying mouse-wheel scrolling updates selectedFilePath and selectedHunkIndex via the host snapshot
src/ui/components/ui-components.test.tsx Adds component-level test verifying DiffPane prefetches highlight data for off-screen files before they mount

Sequence Diagram

sequenceDiagram
    participant User
    participant DiffPane
    participant mouseRef as mouseScrollSelectionSyncActiveRef
    participant timer as 120ms Debounce Timer
    participant viewSel as findViewportCenteredHunkTarget
    participant App
    participant cache as SHARED_HIGHLIGHTED_DIFF_CACHE

    User->>DiffPane: onMouseScroll
    DiffPane->>mouseRef: set true
    DiffPane->>timer: reset debounce
    timer-->>mouseRef: set false (after 120 ms)

    Note over DiffPane: scrollViewport.top changes
    DiffPane->>viewSel: findViewportCenteredHunkTarget()
    viewSel-->>DiffPane: { fileId, hunkIndex }
    DiffPane->>DiffPane: suppressNextSelectionAutoScrollRef = true
    DiffPane->>App: onViewportCenteredHunkChange(fileId, hunkIndex)
    App-->>DiffPane: re-render with new selectedFileId / selectedHunkIndex

    Note over DiffPane: selection auto-scroll useLayoutEffect fires
    DiffPane->>mouseRef: check → true → skip auto-scroll

    Note over DiffPane: highlightPrefetchFileIds recomputed
    DiffPane->>cache: prefetchHighlightedDiff(file, appearance)
    cache-->>DiffPane: cache miss → loadHighlightedDiff() started
    Note over cache: result stored on resolve
    DiffPane->>DiffPane: shouldLoadHighlight passed to DiffSection
Loading

Comments Outside Diff (1)

  1. src/ui/components/panes/DiffPane.tsx, line 418-422 (link)

    P2 Loop variable layout shadows the outer layout prop

    The loop variable declared here has the same name as the component's layout prop (Exclude<LayoutMode, "auto">). It doesn't cause a runtime bug since the outer prop is not referenced inside this useMemo, but it can mislead a reader who searches for all usages of the layout prop, or who later adds a reference to layout (the mode) inside this block and accidentally reads the FileSectionLayout instead.

    Consider renaming the loop variable to section or sectionLayout:

Reviews (1): Last reviewed commit: "refactor: document viewport prefetch and..." | Re-trigger Greptile

@benvinegar benvinegar merged commit 6d431a5 into main Apr 7, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant