Skip to content

fix(diff): keep the collapsed section header in view - #24466

Open
innocarpe wants to merge 2 commits into
stablyai:mainfrom
innocarpe:fix-20665-diff-collapse-scroll
Open

innocarpe wants to merge 2 commits into
stablyai:mainfrom
innocarpe:fix-20665-diff-collapse-scroll

Conversation

@innocarpe

Copy link
Copy Markdown
Contributor

Description

In the View all diff, collapsing the section the viewport is inside left the scroll anchor pointing at an offset inside that section's body. The body is gone after the collapse, so restore kept the old offset and the viewport landed on later sections.

Focused fix

  • In: before a section collapses, if it is the anchored section, set that anchor's within-section offset to 0 so the existing structural restore pins the header. scrollTop on the anchor is left unchanged, because restore only runs when the viewport still matches the recorded scroll position.
  • Out: expanding a section, collapsing a different section, and the restore math itself.

Preserves

Collapse still toggles the same section and still loads a section when it expands. An anchor on a later section is not rewritten, so collapsing a section above the one being read still restores that later section.

Evidence

node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts --cache false src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-collapse-scroll-anchor.test.ts

4 passed. oxlint on the three changed files reported 0 warnings. The live Electron diff view was not rechecked.

User-regression-tradeoffs

A user who was scrolled into a long section and then collapses it sees that section's header at the top of the viewport, rather than the later files the old offset jumped to. A user reading a different section is unaffected.

Fixes #20665

@pullfrog pullfrog 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.

ℹ️ No critical issues — the fix is sound and correctly scoped. Two follow-ups worth a look before merge.

Reviewed changes

  • Pin the collapsed section's header — new pure helper pinScrollAnchorWhenCollapsingSection zeroes the scroll anchor's within-section offset when the anchored section is the one collapsing, and CombinedDiffViewer.toggleSection calls it before flipping collapsed.
  • Scope guards — expanding, and collapsing a section other than the anchor, leave the anchor untouched so a later section stays put.
  • Unit test — covers the helper's four branches (pin, expand, other section, missing inputs).

The identity test is sound: anchor keys are section.key for both virtual and DOM recording, and restore applies the offset verbatim for an exact key, so offset = 0 does pin the header. Calling it before setSections is also correct — the resulting structureRevision bump changes the restore signal, and the layout-phase restore runs before paint.

ℹ️ "Collapse All" still exhibits the original jump

The toolbar's Collapse All path sets every section's collapsed flag directly and never routes through toggleSection, so pinScrollAnchorWhenCollapsingSection is not called. The stale non-zero anchor offset survives, restore resolves it against the now header-only row, and the viewport lands past it — the same behavior #20665 reported, just via a different entry point. If collapsing one section at the current view should keep its header visible, Collapse All likely wants the same treatment.

Technical details
# Collapse All bypasses the new pin

## Affected sites
- `src/renderer/src/components/editor/combined-diff/review-controls/use-combined-diff-view-preferences.ts:67-82` — `setAllSectionsCollapsed` does `setSections((prev) => prev.map((section) => ({ ...section, collapsed })))` without pinning the anchor.
- Wired at `CombinedDiffViewer.tsx:343` (`setAllSectionsCollapsed={preferences.setAllSectionsCollapsed}`), invoked from `combined-diff-toolbar.tsx:168`.

## Required outcome
- Collapsing the anchored section via Collapse All keeps that section's header in view (same intent as the single-toggle fix).

## Suggested approach (optional)
- Thread `restore.scrollAnchorRef` (and the `sectionsRef`) into `useCombinedDiffViewPreferences`, or move the pin to a shared callback the toolbar handler calls before `setSections`. Only pin when `collapsed === true`.
- Note the guard must run before the state update, matching `toggleSection`.

## Open questions for the human
- Is preserving the anchor section's header the desired outcome for Collapse All, or should it stay wherever the browser reflows since all rows become headers?

ℹ️ Chosen behavior differs from the issue's requested fix

#20665 asks for GitHub "Viewed"-parity: on collapse, jump to the next section's header. This PR instead pins the collapsed section's own header at the top, which the author documents in the PR's tradeoffs. That is a defensible choice for a manual header click and definitely beats the arbitrary jump, but it does not deliver the behavior the issue requested — worth confirming with the reporter/product which is intended before closing the issue.

Technical details
# Pin-header vs jump-to-next-section

## Affected sites
- `src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-collapse-scroll-anchor.ts:16-24` — forces `offset = 0` on the collapsed (anchored) section.
- Issue #20665 "Expected behavior" requests scrolling so the *next* section's header lands at the top.

## Open questions for the human
- Should collapsing jump to the next section's header (issue's ask), or keep the collapsed header pinned (this PR)? The two produce different viewport positions.
- If jump-to-next is wanted, the anchor would need to move to the next section key rather than zeroing the current offset.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adjusts scroll position when collapsing diff sections.

The PR appears safe to merge, though the previously noted viewport-regression test gap remains.

Findings

  1. P2 Viewport behavior remains untested. ▶

Summary

The PR pins the scroll anchor to a section header before that section collapses, while leaving expansion and other-section anchors unchanged.

  • Moves the toggle decision into a helper and adds a test for the anchor update.

Reviews (2) · Last reviewed commit: "test(diff): pin the collapsed header bef..."

Comment on lines +10 to +18
it('pins the header when the anchored section is the one being collapsed', () => {
const current = anchor(1_800)
pinScrollAnchorWhenCollapsingSection(current, { collapsed: false, key: 'src/app.ts' })
expect(current.offset).toBe(0)
expect(current.scrollTop).toBe(2_000)
expect(current.key).toBe('src/app.ts')
})

it('leaves the anchor when that section is expanding', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Viewport behavior remains untested. This test checks that the helper changes the anchor, but not that collapsing a section keeps its header in view. The scroll restore can still be skipped after the anchor changes, so that viewport regression could return without this test failing. Please test a collapse from a nonzero position inside the section and assert where the viewport lands.

Knowledge Base Used: Desktop renderer experience

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The viewer calls planCombinedDiffSectionToggle before setSections. That plan pins a mid-body anchor to offset 0 before it returns the updater that flips collapsed, and the new test fails if that pin has not happened yet. Five anchor tests passed. I did not render CombinedDiffViewer, so this does not assert the viewport pixel position.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That addresses the ordering invariant: the anchor is pinned before the updater publishes the collapsed section, while the updater still preserves the section data and only toggles collapsed.

You’re also right that this does not assert the rendered pixel position; it verifies the helper’s input to the existing structural restore rather than exercising the Electron viewport. Given the focused scope and the five anchor tests, I consider the unit coverage sufficient and withdraw the request for an additional viewer-render test.

@coderabbitai

coderabbitai Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 4e9fc9c7-2099-40d1-b003-6d4876dccfdb

📥 Commits

Reviewing files that changed from the base of the PR and between 930a554 and c20289d.

📒 Files selected for processing (3)
  • src/renderer/src/components/editor/combined-diff/CombinedDiffViewer.tsx
  • src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-collapse-scroll-anchor.test.ts
  • src/renderer/src/components/editor/combined-diff/scroll-viewport/combined-diff-collapse-scroll-anchor.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

CombinedDiffViewer now uses planCombinedDiffSectionToggle to plan section updates. Before a matching expanded section collapses, the planner sets its scroll anchor offset to zero. The viewer applies the returned section state and requests a load when the plan indicates expansion. Tests cover anchor behavior, section data, and missing inputs.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to c2028

The change preserves the collapsed section’s own header rather than navigating to the next header. Source inspection supports the intended behavior; no merge-blocking issue was identified. Live viewport behavior remains untested.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issue #20665 requires View all collapse to place the next section header at the viewport top, regardless of the collapsed section height. The PR pins the collapsed section's anchor offset to zero when… Capture the next section or equivalent scroll anchor before collapse. Restore the viewport to that section's header after reflow. Add automated tests for a collapsed section above or at the viewed area, including a long section, and verify …
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately identifies the primary change: keeping the collapsed section header visible in the View all diff.
Description check ✅ Passed The description explains the user-visible problem, the fix, preserved behavior, testing, and linked issue. It omits the repository template headings and the required Visual Proof section or an explici…
Out of Scope Changes check ✅ Passed The changes modify combined-diff collapse scroll-anchor handling and add focused tests for that behavior. These changes support the linked issue's diff-collapse viewport scope. No unrelated change is …
Full details: Linked Issues check

Explanation

Issue #20665 requires View all collapse to place the next section header at the viewport top, regardless of the collapsed section height. The PR pins the collapsed section's anchor offset to zero when that section is the anchor. It leaves an anchor on a later section unchanged. The added tests verify this different behavior and do not verify next-header placement after reflow.

Resolution

Capture the next section or equivalent scroll anchor before collapse. Restore the viewport to that section's header after reflow. Add automated tests for a collapsed section above or at the viewed area, including a long section, and verify the next header position.

  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

innocarpe and others added 2 commits October 2, 2026 07:51
Collapsing the section the viewport is inside left the scroll anchor's
within-section offset pointing at a body that no longer exists, so restore
landed on later sections. Pin that anchor to the header before the collapse
flips. Collapsing or expanding any other section keeps its anchor.

Fixes stablyai#20665
The viewer asks for that plan before it publishes the collapsed flag, and the regression fails if the header pin happens later.

Co-authored-by: Cursor <cursoragent@cursor.com>
@innocarpe
innocarpe force-pushed the fix-20665-diff-collapse-scroll branch from 930a554 to c20289d Compare October 1, 2026 22:52
@innocarpe

Copy link
Copy Markdown
Contributor Author

Sync update (c20289d7b2)

The viewer now builds the toggle plan before setSections. That plan pins a mid-section anchor to offset 0 before the collapsed flag is published, and the regression checks that order. Five anchor tests passed. This does not render the diff viewer.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Toggle extracted into a testable plan — planCombinedDiffSectionToggle now computes the anchor pin and the shouldLoadAfterExpand read and returns the state updater in one call, so toggleSection builds the plan before setSections. The pin-before-publish ordering is now a tested contract of the plan rather than an incidental line ordering.
  • Ordering regression test added — asserts the anchor reaches offset 0 at plan-construction time, before plan.next publishes the collapsed flag, and that unrelated row fields survive the update. This can fail if the pin is deferred or dropped, addressing the prior review's test-coverage thread.

No inline comments: the incremental delta is a behavior-preserving refactor plus the ordering test, and the earlier open question about the Collapse All path (a separate entry point that does not route through the toggle) remains a product-scoping call rather than a defect in this change.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

This branch has not been deployed

No deployments
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.

Collapsing a diff section doesn't preserve scroll position — view jumps arbitrarily instead of tracking the reviewed section

1 participant