Skip to content

Diff panel Improvements: file collapse, full-width toggle and better per thread state management#2444

Open
AfzalH wants to merge 1 commit intopingdotgg:mainfrom
AfzalH:t3code/diff-panel-state-and-toggles
Open

Diff panel Improvements: file collapse, full-width toggle and better per thread state management#2444
AfzalH wants to merge 1 commit intopingdotgg:mainfrom
AfzalH:t3code/diff-panel-state-and-toggles

Conversation

@AfzalH
Copy link
Copy Markdown

@AfzalH AfzalH commented May 1, 2026

Summary

  • Move diff-panel UI state out of URL search params / component-local state into the Zustand UI store, with per-thread scoping where it makes sense (panel open, full-width, per-file collapse) and global session scope where it doesn't (render mode, line wrap)
  • Add a full-width toggle to the diff toolbar that hides the chat column and lets the diff fill the content area; the toggle is per-thread, so one thread can be expanded while another is split
  • Add a per-file collapse chevron in each file's diff header that swaps the FileDiff out for a compact summary row showing the same change-type icon, file path, and -N +M counts

Screenshots

File Collapse/Expand in diff view

Screenshot 2026-05-01 at 15 07 55

Full Width Toggle Button

Screenshot 2026-05-01 at 15 07 16

Full Width Diff View

Screenshot 2026-05-01 at 15 07 33

Why per-file collapse swaps the component instead of hiding via CSS

The @pierre/diffs Virtualizer reserves vertical space for each file based on hunk metadata — <div data-virtualizer-buffer> siblings of the pre get explicit pixel heights and grid columns inside the pre get min-height: calc(N * 1lh). A CSS-only hide (e.g. [data-diff] > *:not([data-diffs-header]) { display: none }) leaves a tall empty box below collapsed files and causes ResizeObserver thrashing as the buffers and content fight to be measured (visible as scroll flicker). Rendering a small fixed-height stand-in lets the Virtualizer measure a stable height and lays the panel out cleanly.

The compact header inlines the same change-type SVG paths the library uses in its sprite (the sprite lives inside each FileDiff's shadow DOM, so it can't be <use>d from outside) so the icon next to the file path is pixel-identical to the expanded view.

State scoping

Stored in useUiStateStore:

Field Scope Persisted
threadDiffOpenById per-thread yes
threadDiffFullWidthById per-thread yes
threadDiffFileCollapsedById per-thread, per-file yes
diffRenderMode global session no
diffWordWrap global session, hydrated once from settings.diffWordWrap no

?diff=1 is removed from DiffRouteSearch. Legacy URLs that still carry it parse cleanly and are ignored. diffTurnId and diffFilePath remain in the URL since they identify a specific selection within the panel and are useful for sharing.

Test plan

  • Open the diff panel in thread A; confirm thread B opens with its own (closed) state
  • Toggle full-width in A; navigate to B; B is split-pane; navigate back to A; A is still full-width
  • Toggle stacked → split in A; expand/collapse the panel; selection survives the remount
  • Collapse a small file via the chevron; confirm only the header is visible, no empty space
  • Collapse package-lock.json (or any large file); confirm no empty space below the header and no flicker while scrolling
  • Confirm the change-type icon (new/modified/deleted/renamed) is visible and colored correctly in the collapsed row
  • Click the file path on a collapsed row; opens in editor (existing behavior)
  • Click the chevron; expands; click again; collapses
  • Reload the page; per-thread open/full-width/per-file-collapse preferences are restored from localStorage
  • vitest run (apps/web) and tsc --noEmit pass

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it rewires diff panel open/selection behavior from URL params to persisted Zustand state (per-thread) and changes routing/search parsing, which could cause regressions in navigation or state restoration across threads/sessions.

Overview
Moves diff panel open/closed, full-width, and per-file collapse state from URL/search params and component-local state into useUiStateStore, persisted in localStorage and scoped per thread.

Adds a diff toolbar toggle to expand the diff view to full width (hiding the chat pane) and implements per-file collapse/expand in DiffPanel, while keeping sharable selection in the URL via diffTurnId/diffFilePath.

Removes the legacy ?diff=1 flag from DiffRouteSearch parsing/validation (still stripped for backward compatibility) and updates ChatView and the chat route to toggle diff visibility via the UI store instead of navigation/search updates.

Reviewed by Cursor Bugbot for commit 3a11c8d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Move diff panel open/collapsed and full-width state to per-thread UI store

  • Diff panel open/closed state is now tracked per-thread in useUiStateStore instead of via the ?diff=1 URL query param, which is removed from routing and URL generation throughout.
  • File collapse state and full-width mode are also stored per-thread in the UI store and persisted to localStorage across sessions.
  • A new full-width toggle in the inline diff panel header hides the chat pane and expands the diff to the full viewport width.
  • Diff view settings (render mode, word wrap, ignore whitespace) are stored globally in the UI store and hydrated from user settings on first DiffPanel mount.
  • Behavioral Change: ?diff=1 is no longer written to or read from the URL; only diffTurnId and diffFilePath remain as URL state. Existing bookmarks or links containing ?diff=1 will silently ignore that parameter.

Macroscope summarized 3a11c8d.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7c577660-e55a-4704-b638-d6e9a0eda205

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ 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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels May 1, 2026
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented May 1, 2026

Approvability

Verdict: Needs human review

This PR introduces new user-facing features (file collapse, full-width toggle) and refactors diff panel state management from URL params to a per-thread store. While well-tested and frontend-only, the scope of new UI capabilities and ~750 lines of new logic warrants human review.

You can customize Macroscope's approvability policy. Learn more.

@AfzalH AfzalH changed the title Diff panel: per-thread state, full-width toggle, and per-file collapse Diff panel Improvement: file collapse, full-width toggle and better per thread state management May 1, 2026
@AfzalH AfzalH changed the title Diff panel Improvement: file collapse, full-width toggle and better per thread state management Diff panel Improvements: file collapse, full-width toggle and better per thread state management May 1, 2026
@AfzalH AfzalH force-pushed the t3code/diff-panel-state-and-toggles branch from 8cbe456 to 1dece16 Compare May 2, 2026 14:13
Move diff-panel UI state from URL search params and component-local
useState into the Zustand UI store. State that is meaningful per
thread is keyed by scopedThreadKey so two threads can have different
diff panels open / expanded / collapsed independently. Wider state
(render mode, line wrap) lives in the store globally so it survives
the remount that expand/collapse causes.

Per-thread (persisted to localStorage):
- diff panel open/closed (replaces ?diff=1 in URL)
- full-width vs split-pane (new toolbar toggle)
- per-file collapse (new chevron on each file header)

Global session (in store, not persisted to disk):
- render mode (split/stacked)
- line wrap, hydrated once from settings.diffWordWrap

The per-file collapse swaps the FileDiff out for a small
CollapsedFileHeader rather than hiding content via CSS — the
library's Virtualizer reserves vertical space from hunk metadata, so
a CSS hide leaves a tall empty box and triggers ResizeObserver
thrashing during scroll. The collapsed header inlines the same
change-type SVG paths the library renders inside its shadow DOM so
the new/modified/deleted/renamed icon is identical between collapsed
and expanded views.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
@AfzalH AfzalH force-pushed the t3code/diff-panel-state-and-toggles branch from 1dece16 to 3a11c8d Compare May 5, 2026 11:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant