Skip to content

fix(diff-view): restore scroll position and fix tab handling on save/deny#589

Open
awschmeder wants to merge 2 commits into
Zoo-Code-Org:mainfrom
awschmeder:fix/diff-scroll-position
Open

fix(diff-view): restore scroll position and fix tab handling on save/deny#589
awschmeder wants to merge 2 commits into
Zoo-Code-Org:mainfrom
awschmeder:fix/diff-scroll-position

Conversation

@awschmeder

@awschmeder awschmeder commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Closes: #586

Description

Fixes the diff-view scroll and tab-handling problems described in #586.
All changes are confined to DiffViewProvider.ts;
no tool files needed to change because the default diff path already calls
open()/update()/scrollToFirstDiff()/saveChanges() in the right order.

The "how", broken down by the three symptoms in the issue:

  1. Diff opens at the wrong scroll position.

    • Removed the unconditional scrollEditorToLine(0) in open(). It fired after the
      diff rendered and overrode whatever position should have been shown, leaving
      off-screen edits out of view.
    • scrollToFirstDiff() is refactored into three private helpers:
      • findFirstDiffLine() walks diff.diffLines() to compute the first-change line and
        clamps it to a valid index so pure end-of-file removals (where the computed index
        lands past document.lineCount - 1) are still brought into view.
      • resolveLiveEditor() re-resolves the modified-side editor from
        vscode.window.visibleTextEditors at scroll time, replacing any stale/detached
        activeDiffEditor reference whose visibleRanges no longer reflect the on-screen
        diff widget. A revealRange on a stale reference is a silent no-op; this was the
        root cause of "scrolls to top" when the file was already open.
      • revealDiffLine() anchors the editor selection on the target line before calling
        revealRange. The selection nudge is necessary when the target is already inside
        the current viewport -- a bare revealRange on a visible line is a no-op.
    • A deferred re-reveal fires 100 ms after the synchronous one. The vscode.diff
      command re-lays-out the diff widget after a short delay; the deferred reveal ensures
      the scroll sticks after that layout pass. It is gated on activeDiffEditor === editor
      so a stale timer from a previous edit never acts on a later edit's diff widget.
  2. Viewport jumped to the top after Save/Deny.

    • open() now captures preEditScrollLine (the first visible line) BEFORE the
      tab-closing loop -- after that loop the editor is no longer in visibleTextEditors
      and the capture would always be undefined.
    • Both accept and deny paths route through showEditedFileWithoutDisruptingFocus(),
      which calls revealRange(..., AtTop) to restore the pre-edit scroll position.
    • cancelDeferredScroll() is called at the start of both saveChanges() and
      revertChanges() before any programmatic editor activation. With auto-approve writes,
      saveChanges() runs in the tight window right after scrollToFirstDiff()'s deferred
      timer is set; the cancellation prevents the late reveal from firing after the viewport
      is restored and yanking the user back to the diff target.
  3. Tabs left open for files that were not previously open.

    • saveChanges() previously re-showed the file unconditionally; closeAllDiffViews()
      only closes diff-scheme tabs, so the plain file tab leaked. The re-show is now gated.
    • Added closeFileTab() to close the transiently opened plain file tab when the file
      was not open before the edit AND the user did not interact with it.
    • Added a userTouchedDocument signal: while the diff is open for a
      not-previously-open file, an onDidChangeActiveTextEditor listener marks the file as
      "touched" if the user activates its own (non-diff) editor tab. Touched files are kept
      open; untouched ones are closed. The listener is disposed before any programmatic
      showTextDocument so our own re-show never counts as a touch, and is cleared in
      reset().

Additional fixes discovered during implementation:

  1. Focus disruption on keep-open. After Save/Deny, showing the edited file previously
    stole the foreground even when the user had navigated to a different file.
    showEditedFileWithoutDisruptingFocus() captures the user's active editor before the
    re-show, uses preserveFocus: true, and re-activates the user's prior editor afterward
    when they were viewing something else.

  2. Pin state lost. Closing the target file's tab to open the diff dropped VS Code's pin
    state. open() now records documentWasPinned; after the diff closes the pin is
    restored via workbench.action.pinEditor.

  3. Preview-tab eviction. Opening the diff reuses the editor group's single preview slot
    and permanently evicts unrelated preview tabs. open() now snapshots all unrelated
    preview tabs (with their scroll positions) before the diff opens; restorePreviewTabs()
    re-opens any that were evicted after the diff closes. Files that no longer exist on disk
    are skipped gracefully.

Reviewers should pay attention to: the ordering of disposeActiveEditorListener() ->
cancelDeferredScroll() -> closeAllDiffViews() -> keepOrCloseEditedFile() ->
restorePreviewTabs() in both saveChanges() and revertChanges(), the clamp logic in
findFirstDiffLine() and the second clamp in revealDiffLine(), and the stale-editor
guard in resolveLiveEditor().

Incidental change (discovered during this work): the diff tab label constant
DIFF_VIEW_LABEL_CHANGES still read "Original <-> Roo's Changes". Since this is a
user-visible label in the rebranded fork, it was corrected to "Original <-> Zoo's
Changes". It is a one-line string change referenced only via the constant (tests use the
constant, not the literal), so it is behavior-neutral for tab matching. Called out here for
transparency since it is outside the strict scope of the scroll fix.

Test Procedure

Automated:

cd src && npx vitest run integrations/editor/__tests__/DiffViewProvider.spec.ts

44 tests pass, covering:

  • scrollToFirstDiff(): first-change line for addition-only, deletion-only, mixed, and
    end-of-file removal (clamped); selection anchor before reveal (in-viewport fix); deferred
    re-reveal fires at 100 ms; live editor resolved at reveal time (stale reference fix);
    stale timer from a previous edit does not act on a new diff editor; no-op when no editor.
  • preEditScrollLine: captured from visibleRanges[0].start.line in open(); undefined
    when no visibleRanges; saveChanges() and revertChanges() call revealRange(AtTop)
    when set; no revealRange when undefined; deferred scroll cancelled so it cannot fight
    the scroll restore (auto-approve race).
  • userTouchedDocument: saveChanges() and revertChanges() close the tab when untouched;
    keep it open when touched; activation listener fires correctly; listener ignored for diff
    tab activations.
  • showEditedFileWithoutDisruptingFocus(): re-activates user's prior editor when they
    navigated away; does not re-activate when already on the edited file; re-pins and focuses
    correctly; does not pin when not originally pinned.
  • Preview tab snapshot/restore: captures preview tabs with scroll positions, excludes the
    diff target; restores evicted tabs in preview mode with scroll; skips still-open tabs;
    skips files that no longer exist.

Manual (Extension Development Host, F5): unit tests cannot confirm the composite diff
widget visibly scrolls or that focus/tabs behave correctly across real VS Code sessions.
Full manual matrix is documented in a comment attached to this PR.

Summary: for a 100+ line file, verify the diff opens on the first changed line
and the viewport is restored / the tab is closed appropriately after Save and after Deny,
for both already-open and not-open files, across pure-add, pure-delete, and mixed edits,
including diffs at the top, midpoint, and end of the file. Confirm no focus steal, no
preview-tab loss, and no stuck-scroll across sequential edits.

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Screenshots / Videos

Attached in a follow-up comments.

Documentation Updates

  • No documentation updates are required.

Additional Notes

The fix targets the DEFAULT (diff-visible) path only. When the
EXPERIMENT_IDS.PREVENT_FOCUS_DISRUPTION experiment is enabled, tools call
saveDirectly() instead, which intentionally avoids any diff view, scroll-to-change, or
viewport restoration -- none of the code in this PR runs on that path.

Commentary / Opinion

Automatically closing an un-touched tab after applying an edit or creating a new file could be somewhat confusing to novice coders.

On the other hand, not closing un-touched files after edits creates significant tab pollution in long sessions, leading to dozens of tabs left open.

The unclosed-tab pollution problem also burdens the context window in an uncontrolled manner and drives up inference cost, as Zoo's default includes 20 open tabs (probably set too high, by the way... 1 is a better default).

On balance, I think the best solution is to automatically close files that the user did not either explicitly open or have some affirmative interaction with by viewing / focusing on or editing.

@awschmeder

Copy link
Copy Markdown
Contributor Author

Manual Test Plan: Diff View Scroll Position + Tab/Focus Handling

Covers the fixes in DiffViewProvider.ts
for issue #586 and the follow-on bugs found during review. Unit tests verify which line is
targeted and which branch runs, but they cannot confirm that the composite VS Code diff
widget visibly scrolls, or that tab/focus behavior is correct in a live session.

Run this matrix manually in the Extension Development Host.

Bugs this plan must catch

These are the concrete defects found and fixed. Each has dedicated coverage below.

  1. In-viewport no-scroll -- file already open and scrolled to the middle; a diff
    targeting a line already inside the viewport did not scroll (the captured editor
    reported a stale viewport, so revealRange was a silent no-op).
  2. Scroll-to-top on open -- file already open and scrolled to the middle; the diff
    jumped to the top and never showed the change (stale/detached editor reference).
  3. End-of-file no-scroll -- a diff targeting the last line failed to scroll.
  4. Stuck at line 0 -- once the scroll broke, every subsequent edit also scrolled to
    the top until the file was closed and reopened (leaking deferred-scroll timers acting
    on a later edit's diff editor).
  5. Auto-approve late-timer race -- with auto-approve write enabled, a deferred scroll
    could fire after the post-save viewport restore and yank the file back to the change.
  6. Preview-tab eviction -- an unrelated file open as a preview tab (italic,
    not-yet-edited) was permanently lost when the diff reused the editor group's single
    preview slot.
  7. Focus disruption on keep-open -- after Save/Deny, the edited file was force-shown
    and stole the foreground even when the user had navigated to a different file.

Prerequisites

  1. Launch the Extension Development Host: open the repo in VS Code and press F5
    (Run Extension). A second VS Code window opens with the dev build of Zoo Code.
  2. In the dev window, open a workspace containing a long file -- several hundred lines,
    well beyond one screen height. A file of ~400-600 lines is ideal so that "top",
    "midpoint", and "end" are all clearly off-screen from one another. Have at least two
    such files available (referred to below as file-1 and file-2).
  3. Confirm the preventFocusDisruption experiment is OFF (Settings ->
    Experimental). This fix only applies to the default diff-visible path; with the
    experiment on, saveDirectly() runs and none of this behavior applies.
  4. When prompting Zoo, instruct it to use the diff/apply_diff tool so it does not apply
    edits via shell commands. Example: "Using the apply_diff tool, remove the last line of
    long.ts." This keeps the edit on the path under test.
  5. Know how to toggle auto-approve writes -- Matrix 4 requires it ON, all other
    matrices assume it OFF (so you can observe the diff and click Save/Deny).

What "correct" looks like

  • On diff open: the diff editor is scrolled so the first changed line is visible
    (centered), regardless of whether the change is a pure addition, pure deletion, or mixed,
    regardless of where in the file the change is, and regardless of where the file was
    already scrolled
    (including when the change is already inside the current viewport).
  • After Save:
    • File was already open before the edit -> the file is shown and the viewport is
      restored to the pre-edit scroll position (the saved line at the top of the viewport).
    • File was NOT open before, and the user did not interact with it -> the transient tab
      is closed (no tab pollution).
    • File was NOT open before, but the user clicked into its tab during the diff -> the tab
      stays open.
  • After Deny: same tab keep/close and scroll-restore behavior as Save. A
    newly-created (non-existent) file is removed and its tab closed.
  • Focus: focus is never stolen to the edited file. If you navigate to a different file
    while the diff is up, you remain on that file after Save/Deny -- you are neither pulled
    back to the edited file nor left on a stale diff tab.
  • Preview tabs: an unrelated file open as a preview tab is not permanently lost; after
    the diff session ends it is restored as a preview tab at its prior scroll position.
  • Sequential edits: scrolling stays correct edit after edit; a broken scroll never
    "sticks" across edits.

Matrix 1 -- Diff opens on the first change (scroll-to-first-diff)

For each combination below, trigger the edit and observe ONLY the diff-open scroll
position (do not Save/Deny yet -- that is Matrix 2/3).

Edit positions to test: line 0 / top, midpoint of the file, end of the file.
Edit shapes to test: pure addition, pure deletion, mixed (remove + add).

For the "Already open" rows, first scroll the file to the middle before prompting, so
the in-viewport and stale-viewport cases are exercised (bugs 1-3).

# File state Position Shape Expected on open
1.1 Already open (scrolled to top) Top Add Diff shows the added line near the top
1.2 Already open (scrolled to MIDDLE) Midpoint Add Diff scrolls to the midpoint change (NOT the top)
1.3 Already open (scrolled to MIDDLE) End Add Diff scrolls to the end change
1.4 Already open (scrolled to MIDDLE) Midpoint Delete Diff scrolls to the deletion
1.5 Already open (scrolled to MIDDLE) End Delete Diff scrolls to the deletion (clamped to last line, widget moves)
1.6 Already open (scrolled to MIDDLE) Midpoint Mixed Diff scrolls to the first changed line
1.7 Already open (scrolled to MIDDLE) End Mixed Diff scrolls to the first changed line
1.8 NOT open End Delete File opens; diff scrolls to the deletion
1.9 NOT open End Add File opens; diff scrolls to the added region
1.10 NOT open Midpoint Mixed File opens; diff scrolls to the first changed line
1.11 Already open (scrolled to MIDDLE) Change INSIDE the current viewport Mixed Diff scrolls/recenters on the change; viewport does NOT jump to the top (bug 1)
1.12 Already open (scrolled to MIDDLE) End (last line) Mixed Diff scrolls to the last line; the widget actually moves (bug 3)

Pay special attention to 1.2-1.7, 1.11, 1.12 -- these are the regressions where an
already-open, mid-scrolled file either jumped to the top or refused to scroll.

1.S -- "Stuck scroll" sequential check (bug 4)

  1. Open file-1, scroll to the middle.
  2. Ask Zoo to make an end-of-file edit to file-1 via apply_diff. Observe the
    diff-open scroll (it should reach the end, not the top).
  3. WITHOUT closing the file, ask Zoo to make a midpoint edit to file-1.
  4. Expected: the second edit scrolls to the midpoint change. It must NOT be "stuck"
    scrolling to line 0. Repeat for several edits in a row.

Matrix 2 -- Save (accept) behavior

Set up a known pre-edit scroll position before each run by manually scrolling the file, so
restoration is observable.

# File state before edit User action during diff After clicking Save
2.1 Open, scrolled to midpoint none File shown; viewport restored to the midpoint scroll position
2.2 Open, scrolled to end none File shown; viewport restored to the end scroll position
2.3 NOT open none Transient file tab is CLOSED; no leftover tab
2.4 NOT open click into the file's editor tab during the diff File tab STAYS open after Save
2.5 Open; then navigate to a different file during the diff viewing another file Focus stays on the other file; you are not pulled back
2.6 New file (did not exist) none File is created and saved; no stray diff tab; viewport sane

Matrix 3 -- Deny (reject) behavior

# File state before edit User action during diff After clicking Deny
3.1 Open, scrolled to midpoint none Edit reverted; viewport restored to the midpoint scroll position
3.2 NOT open none Edit reverted; transient file tab is CLOSED
3.3 NOT open click into the file's editor tab during the diff Edit reverted; file tab STAYS open
3.4 New file (did not exist) none File is deleted; its tab is closed; created dirs cleaned up
3.5 Open; navigate elsewhere during the diff viewing another file Focus stays on the other file

Matrix 4 -- Auto-approve late-timer race (bug 5)

Enable auto-approve writes for these rows so save runs immediately after the diff
opens (the tight window where a deferred scroll could fire after the viewport is restored).

# File state before edit Position Expected after the auto-approved save settles
4.1 Open, scrolled to midpoint Midpoint File ends restored to the pre-edit midpoint scroll; it does NOT settle on the change line a beat later
4.2 Open, scrolled to midpoint End File ends restored to the pre-edit scroll; no late jump to the end
4.3 Open, scrolled to top Midpoint File ends at the pre-edit top; no delayed scroll-to-change
4.4 Sequence: auto-approve several edits in a row mixed positions No accumulating drift; each settles at the restored position; no tab accumulation

Watch for ~100ms-late movement: the buggy behavior was a brief correct restore followed
by the viewport snapping to the diff target.


Matrix 5 -- Preview-tab eviction (bug 6)

A preview tab is the italicized, single reusable tab VS Code shows when you open a file
but have not edited it.

# Setup Action Expected
5.1 Open file-1 as a preview tab (single click in Explorer, do not edit). file-2 is closed. Ask Zoo to apply_diff file-2, then Save. file-1 is NOT permanently lost: after the diff session it is restored as a preview tab at its prior scroll position. file-2 handled per Matrix 2.
5.2 Same as 5.1 Click Deny instead of Save Same restore of file-1's preview tab.
5.3 Open file-1 as a preview tab, scrolled to the middle. apply_diff file-2 + Save file-1 restored AND scrolled back to its prior (middle) position.
5.4 Open file-1 as a permanent (non-preview, e.g. double-click or edited) tab. apply_diff file-2 + Save file-1 is untouched throughout (permanent tabs are never evicted); no duplicate file-1 tab appears.
5.5 Open file-1 as a preview tab. During the diff, the preview file is deleted/renamed on disk, then Save Restore is skipped gracefully -- no error dialog, no broken tab.

Matrix 6 -- Focus preservation on keep-open (bug 7)

# Setup Action sequence Expected after Save/Deny
6.1 file-1 open (non-preview). file-2 closed. apply_diff file-2; click file-2's plain tab; then click file-1; then Save You remain on file-1 (where you were actually looking). Focus is NOT yanked to file-2. file-2 stays open in the background.
6.2 Same as 6.1 Same but click Deny Same: you remain on file-1; edit reverted; file-2 stays open in the background.
6.3 file-2 already open and active. apply_diff file-2; stay on file-2; Save You remain on file-2 at its restored scroll position (no redundant focus round-trip).
6.4 Two editor groups (split). file-1 active in group 1. apply_diff file-2 (opens in a group); navigate around; Save You end on whatever editor you were last viewing; no surprise foreground change.

Regression smoke checks

  • Pinned tab: pin the file's tab, run an edit + Save. Confirm the tab is not unpinned
    or duplicated.
  • Dirty unrelated tab: have another file with unsaved changes open; run an edit + Save.
    Confirm the unrelated dirty tab is untouched (closeFileTab filters on !isDirty and
    matches only the target path).
  • Multiple sequential edits: ask Zoo to make several edits in a row to the same long
    file; confirm the viewport does not drift to the top between edits, the scroll never gets
    "stuck", and tabs do not accumulate.
  • Diff tab label: confirm the diff tab reads "Original <-> Zoo's Changes".
  • Interrupted session: start an edit, then reload the dev window (Cmd+Shift+P ->
    "Developer: Reload Window") before accepting. Confirm no preview file is left
    permanently promoted and no stray diff tab remains.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

DiffViewProvider refactored to preserve VS Code editor context across diff sessions: captures pre-edit scroll position and pin state on open, snapshots unrelated preview tabs, detects user activation, restores scroll/pin state on save/revert, scrolls to first changed line with clamping and guarded deferred reveals, and manages transient tabs without disrupting focus.

Changes

Diff View Scroll Position and Tab State Preservation

Layer / File(s) Summary
State shape and UI label
src/integrations/editor/DiffViewProvider.ts
DiffViewProvider private state expanded with documentWasPinned, preEditScrollLine, snapshotPreviewTabs, deferredScrollTimer, and activeEditorChangeListener fields; diff view label constant updated from "Original ↔ Roo's Changes" to "Original ↔ Zoo's Changes".
Scroll and pin state capture on diff open
src/integrations/editor/DiffViewProvider.ts
When opening a diff, provider captures the currently visible editor's first visible line (pre-edit scroll) for scroll restoration, initializes session pin-state tracking, and records whether the target file tab was already pinned so pin state can be re-applied.
Preview tab snapshot and user activation tracking
src/integrations/editor/DiffViewProvider.ts
During diff open, provider snapshots unrelated preview tabs (excluding diff target) to preserve their scroll positions, and when the target file wasn't previously open, installs an active-editor listener to track whether the user manually activated the target file during the diff session.
Save and revert cleanup orchestration
src/integrations/editor/DiffViewProvider.ts
saveChanges() and revertChanges() dispose the activation listener, cancel pending deferred scroll timers, close diff views, decide whether to keep or close the edited file based on user touch tracking, and restore evicted preview tabs with scroll positions; revertChanges() additionally closes transient file tabs.
Tab and focus restoration helpers
src/integrations/editor/DiffViewProvider.ts
New internal helpers: dispose/cancel listener and timers, decide tab close/keep based on user interaction, show edited file without disrupting focus (with optional scroll restore and re-pinning), snapshot unrelated preview tabs, restore evicted preview tabs with scroll, and close transient file tabs.
Scroll to first diff with clamping and deferred reveal
src/integrations/editor/DiffViewProvider.ts
scrollToFirstDiff() refactored to find first changed line, resolve live modified-side editor at reveal time, clamp reveal targets to valid line bounds (including end-of-file removals), anchor selection before reveal, and use guarded deferred timer (100ms) to handle layout reflow while preventing stale timers from affecting later edits.
Session state cleanup
src/integrations/editor/DiffViewProvider.ts
reset() expands to clear all tracked state (pin state, deferred scroll timer, pre-edit scroll line, preview tab snapshots), dispose the activation listener, and prepare provider for next session.
Test infrastructure and mock enhancements
src/integrations/editor/__tests__/DiffViewProvider.spec.ts
Expanded mocks to include fs/promises access stub, vscode.tabGroups activeTab structure, and rich editor primitives (Range, Position, Selection, TextEditorRevealType, TabInputText) needed to emulate VS Code runtime objects.
Test coverage: open() pinned tracking and scrollToFirstDiff() scenarios
src/integrations/editor/__tests__/DiffViewProvider.spec.ts
Tests verify open() records documentWasPinned when target file is open in pinned tab; comprehensive scrollToFirstDiff() coverage for addition-only, deletion-only, mixed diffs, clamping at file boundaries, selection anchoring, deferred re-reveal via fake timers, using live editor instead of stale captured editor, and preventing stale deferred reveals after editor swaps.
Test coverage: preview tabs, focus restoration, and document touch semantics
src/integrations/editor/__tests__/DiffViewProvider.spec.ts
Tests validate preview tab snapshot/restore (scroll positions, skip when still open, skip on access failure), showEditedFileWithoutDisruptingFocus() (preserves user context, avoids re-activation when already on edited file, conditionally re-pins), preEditScrollLine capture/restore with timer cancellation semantics, and userTouchedDocument close/keep logic with activation tracking.
Changeset documentation
.changeset/fix-diff-scroll-position.md
Changeset entry documents the behavioral fix: diff now opens scrolled to first changed line with clamping for end-of-file removals, pre-edit scroll positions restored after accept/deny, transient tabs closed appropriately, preview tabs preserved with scroll restoration, and pin state re-applied.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A rabbit hops through scroll positions,
Capturing pin states and preview visions,
Previews return with scroll intact,
Focus stays true, no jumps off track,
Zoo's diffs now dance with gentle grace! 🎯✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing scroll position restoration and tab handling in the diff-view during save/deny operations.
Linked Issues check ✅ Passed The PR implementation directly addresses all stated objectives from issue #586: consistent diff scroll positioning, viewport restoration after Save/Deny, tab cleanup, focus preservation, pin state restoration, and preview-tab eviction prevention.
Out of Scope Changes check ✅ Passed All changes are confined to DiffViewProvider.ts and its test file; one incidental label change (DIFF_VIEW_LABEL_CHANGES) is transparently documented. No unrelated code changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description fully adheres to the repository template with all required sections completed: linked issue (#586), detailed implementation explanation covering the 'how' with technical design choices, comprehensive test procedure (automated and manual), completed pre-submission checklist, and documentation impact assessment.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@awschmeder

awschmeder commented Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Videos from Manual Testing:

Target file open - scrolled to top - diff scrolls to edit at top, middle, bottom:
https://github.com/user-attachments/assets/b290570c-373b-4cb7-b640-966a038ce0aa

Target file open - scrolled to middle - diff scrolls to edit and restores viewport after accept:
https://github.com/user-attachments/assets/f2e8a29f-0faf-4d9c-b12a-115b2c1af94d

Target file not open - diff scrolls to edit - tab is cleaned up after accept or reject:
https://github.com/user-attachments/assets/db8eee31-64f5-4c91-a352-59b3d9d81a16

Stress test multiple sequential writes & scrolling with apply_diff - no race conditions or errors:
https://github.com/user-attachments/assets/02d95019-9826-4f7e-8fa3-178a95ce02dc

Preview tab open with file 1, diff targets unopened file 2, after accept, preview tab is restored:
https://github.com/user-attachments/assets/a89d501b-b456-4694-8446-c1321abb71a9

Permanent tab open with file 1, diff targets unopened file 2, user changes tab then accepts - no jump back:
https://github.com/user-attachments/assets/197ddd37-2f38-4811-b97f-44058d6a72b6

Pinned file targeted with diff - stays pinned correctly:
https://github.com/user-attachments/assets/a016eb27-6f54-4759-b3f5-d2dec3041bb6

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.01887% with 36 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/integrations/editor/DiffViewProvider.ts 83.01% 36 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/integrations/editor/DiffViewProvider.ts`:
- Around line 615-633: captureUnrelatedPreviewTabs currently returns only { uri,
scrollLine } so restorePreviewTabs reopens previews in the active group; include
the originating group/viewColumn in the snapshot (e.g., add tab.group.viewColumn
or tab.group.id to the captured object inside captureUnrelatedPreviewTabs) and
update restorePreviewTabs to call vscode.window.showTextDocument(snapshot.uri, {
preview: true, preserveFocus: true, viewColumn: snapshot.viewColumn }) (or open
in the original group via its id) and honor the saved scrollLine after opening;
update types/usages of the snapshot array and any related helper functions to
handle the new viewColumn field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 82610a67-0e7a-4e94-8022-c666f20722a9

📥 Commits

Reviewing files that changed from the base of the PR and between 47f9470 and c51054c.

📒 Files selected for processing (3)
  • .changeset/fix-diff-scroll-position.md
  • src/integrations/editor/DiffViewProvider.ts
  • src/integrations/editor/__tests__/DiffViewProvider.spec.ts

Comment thread src/integrations/editor/DiffViewProvider.ts
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.

[BUG] Diff view scroll position not preserved -- viewport jumps on open and after Save/Deny

1 participant