Skip to content

fix: stop windowed diff/list scroll flashes and jumps - #34

Merged
erwin-wee merged 2 commits into
mainfrom
fix/windowed-scroll-jumps
Sep 25, 2026
Merged

erwin-wee merged 2 commits into
mainfrom
fix/windowed-scroll-jumps

Conversation

@erwin-wee

Copy link
Copy Markdown
Owner

Problem

Opening an AI review finding's inline card made the diff flash and jump. useWindowedRows stored measured row heights by index and discarded all of them whenever the row count changed (inserting the card row). TextDiff then scrolled in a post-paint useEffect using guessed offsets, painted a blank frame until the scroll event re-rendered, and never re-centred once real heights were measured.

The same mechanism affected blame cards, hunk context expansion, the History search highlight, Explain references, History pagination on phones, and the conflict view's low-confidence jump.

Changes

  • lib/windowing.ts: measured heights are keyed by row identity (keyOf), so inserting or removing rows keeps every other row's height. scrollTo renders the target window before paint and re-aligns after each measurement pass until the position settles (capped at 8 passes). The scroll/ResizeObserver effect no longer re-subscribes on every count change.
  • TextDiff: scroll-to effects are useLayoutEffects. They fire once the scroll container exists, which fixes cross-file finding focus never scrolling, and no longer re-centre when the same file reloads.
  • Explain references: they now use diff.revealLine and the windowed scrollTo instead of looking for the row in the page. Lines outside the rendered window were silently ignored before, and the 250 ms file-switch timeout is gone. The flash highlight is rendered by TextDiff.
  • Changes list: resetKey is now the repo path instead of the status object, so measured heights are not thrown away on every status refresh.
  • History list: rows are keyed by sha, so loading more commits keeps the measured heights.

Verification

  • Typecheck passes; 1121 tests pass (4 skipped).
  • Server mode, side by side against main: 2000-line file, wrap on, injected pre-commit findings, per-frame measurement after each click.
    • main: a blank first frame on far jumps; the target lands 90–150 px below centre.
    • This branch: no blank frames; the target is centred on the first frame and stays put.
  • Explain reference to an off-screen line: main does not scroll; this branch centres and flashes the line.
  • Blame card: a single scroll step, then stable.
  • Independent review subagent: no findings. Its two minor notes are addressed in 0b534ea.

Not exercised directly: History "load more" on a phone viewport and the conflict view's low-confidence jump. Both go through the shared windowing change.

- windowing: key measured heights by row identity so inserting a card, expanding context or loading more commits no longer discards them; scrollTo renders the target window before paint and re-aligns until measured heights settle
- TextDiff: scroll-to effects run as layout effects, fire once the container exists (cross-file focus), and no longer re-center when the same file reloads
- Explain references scroll through the windowed diff (revealLine in the diff store) instead of querying rendered rows, fixing far-away lines and the 250ms file-switch race
- Changes list no longer resets row heights on every status refresh
A first align that doesn't move no longer drops the target: the settle pass re-checks against measured heights and freshly rendered spacers, covering a 'nearest' row whose estimate looked in view and a 'center' clamped by a stale scroll height.
@erwin-wee
erwin-wee merged commit 61ebc08 into main Sep 25, 2026
2 checks passed
@erwin-wee
erwin-wee deleted the fix/windowed-scroll-jumps branch September 25, 2026 14:18
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