Skip to content

Review sidebar: two-column layout, resizable width, multi-repo support - #191

Merged
stippi merged 11 commits into
mainfrom
feat/review-sidebar
Sep 7, 2026
Merged

stippi merged 11 commits into
mainfrom
feat/review-sidebar

Conversation

@daniel-kurzynski

Copy link
Copy Markdown
Collaborator

Summary

Adds a Review right-sidebar for inspecting changed files and their diffs, with a two-column layout, a resizable width, and support for projects that contain multiple git repos.

What's included

UI (ui_gpui)

  • Two-column layout — unified diff on the left, per-repo changed-file trees on the right, separated by a draggable divider (h_resizable).
  • Multi-repo support — when the project root isn't itself a git repo but contains several repos, immediate-subfolder repos are discovered and each is shown as its own collapsible section with its own base-branch selector.
  • Resizable sidebar width — drag the sidebar's left edge to widen it.
  • Diff rendering — reuses the chat's unified-diff renderer (monospace body, red/green add/delete rows) and scrolls via a native ScrollHandle.
  • Persistence — sidebar width, tree-column width, and the default base branch persist globally across sessions.

Backend (code_assistant_core, git)

  • git changed-file and per-file diff support for both working tree and branch-vs-base modes.
  • repo discovery (root-is-repo, else immediate subfolders that are repo roots).
  • per-repo review listing in SessionService with per-repo base overrides.

Base-branch resolution

For "Branch vs base" mode, each repo's base is resolved as: an explicit selection → the current branch's upstream → the first origin/* candidate → any other branch. A user's chosen base is remembered globally and preselected for other repos when valid.

Testing

  • cargo build/cargo check --workspace clean; cargo clippy clean on touched crates.
  • cargo test green: git (24), ui_gpui (109), plus new discover_review_repos unit tests (root-is-repo, two child repos, nested-non-root ignored).
  • Built and ran the macOS bundle locally.

daniel-kurzynski and others added 10 commits August 31, 2026 16:15
…upport

Add a Review right-sidebar that shows changed files and their diffs:

- Two-column layout: unified diff on the left, per-repo changed-file trees
  on the right, separated by a draggable divider (h_resizable).
- Multi-repo support: when the project root isn't itself a git repo but
  contains multiple repos, discover immediate-subfolder repos and show each
  with its own base-branch selector.
- Resizable sidebar width via a left-edge drag handle.
- Diff pane reuses the chat's unified-diff rendering (monospace body,
  red/green rows) and scrolls via a native ScrollHandle.
- Persist sidebar width, tree-column width, and default base globally.

Backend: git changed-file/diff support (working tree and branch-vs-base),
repo discovery, and per-repo review listing in SessionService.
…n quit

update_ui_settings previously cloned the settings at call time and spawned
a detached background write per call. Two in-flight writes to
ui-settings.json could complete out of order, letting an older snapshot
clobber a newer one (hot path: per-event window-bounds saves during a
window drag). The write is now debounced like the UiStateStore flush, and
the snapshot is taken when the timer fires, so the last write always
carries the latest state.

An on_app_quit hook flushes both the pending settings write and any dirty
per-session UI state, closing the debounce window on exit (previously the
UiStateStore could silently drop up to 500ms of changes on quit).

Also drop UiSessionState::review_base_branch, which was only ever written
as None and never read — the base preference is persisted globally via
review_default_base — and narrow the accessors to the compare mode.
ReviewView renders on every MainScreen notify — ~120fps during sidebar
animations and resize drags, plus every cx.refresh() from the event loop.
Each render deep-cloned the changed-files listing out of its mutex global
and deep-compared it for change detection, cloned the selected file's
FileDiffContent (up to ~3MB of text, twice), and re-ran the full Myers
line diff via render_unified_diff. That per-frame cost scales with diff
size and threatens the frame budget.

- The review globals on Gpui now carry a generation counter bumped on
  every write; the view's per-frame unchanged path is a mutex lock plus
  an integer compare (review_listing_if_newer / review_diff_if_newer).
- The line diff is computed once when UpdateReviewDiff arrives
  (compute_diff_lines) and cached as PreparedDiff; render only builds
  elements from the cached lines (render_diff_lines, SharedString rows).
  render_unified_diff keeps its signature for the chat diff cards.
… disk cache

Opening the Review panel on a project with many root-level repos scanned
everything in one blocking service call — the panel stayed empty until
the last repo finished, with no indication anything was happening.

- Discovery and scanning are now separate service calls: list_review_repos
  returns the repo stubs immediately, scan_review_repo handles one repo.
  The Gpui command layer streams a listing update after every state
  change, so the panel shows all repos at once — one Scanning (spinner),
  the rest Pending — and results fill in repo by repo. An epoch counter
  supersedes in-flight scans when a newer request starts.
- Scan results are cached per (repo, mode) under
  <config_dir>/review-cache/; on open the last known files and stats show
  instantly (marked with a trailing wait indicator) while the background
  rescan refreshes them.
- Each repo header shows a +adds/−dels summary from git diff --numstat
  (untracked files are listed but not line-counted).
- Section separators now sit above each section instead of between a
  header and its content, the tree no longer claims 'No changes' before
  a repo has ever been scanned, and the panel shows a spinner before the
  first discovery response instead of rendering empty.
…review panel

The per-repo activity indicator now uses the same rotating double-arrow
(icons/arrow_circle.svg) as active sessions, in muted grey — including
the pre-discovery placeholder. Repos without changes render only their
header: the tree is omitted entirely and the 'No changes' placeholder is
gone; the absent +/− badge already communicates a clean repo.
… panel

Queued repos now show the same arrow_circle icon as the running scan,
static and at 40% of the muted color, replacing the '⋯' text marker —
both standalone and trailing a cached stats badge.
… and off-thread

Replaces the two-column tree/diff layout with a single scrollable stack,
the way Zed's Project Diff (and most coding-agent UIs) present changes:
each file is a collapsible header (icon, path, status letter, per-file
+/− counts) with its diff directly below, then the next file. The file
tree, the resizable split, and the review_tree_width setting are gone.

Rendering and loading follow what makes Zed's diff view fast:

- Diffs show only hunks — changed lines plus 3 context lines
  (similar::grouped_ops) with a '⋯' separator between hunks — instead of
  the whole file, so element counts scale with changed lines, not file
  sizes. Line numbers stay real per hunk, with a shared gutter width.
- Hunk computation runs in the command layer on the background executor;
  the UI thread only builds elements from prepared hunks and never runs
  a Myers diff. Pure adds/deletes skip diffing entirely (this also fixes
  the phantom blank 'deleted' line on added files).
- Diffs load lazily, one file at a time: the view keeps a single request
  in flight and asks for the next missing visible diff after each
  arrival; collapsed repos and files are skipped until expanded.
- UpdateReviewFiles/UpdateReviewDiff are now payload-free notifications;
  the command layer mirrors listing and prepared diffs straight into the
  generation-counted Gpui globals.
…llapse

- Diff rows in the chat tool cards and the review sidebar now highlight
  the words that changed within a line: collect_change_lines uses
  similar's iter_inline_changes (unicode word segmentation enabled) and
  records emphasis byte ranges per DiffLine; rendering layers a stronger
  add/delete tint over those ranges via StyledText highlights, so the
  emphasis wraps with the text. Replace blocks over 16 lines skip the
  word diff — pairing lines across big rewrites is noise — and similar
  itself falls back to plain lines when the block similarity is too low.
- Review repo sections now default to collapsed; expanding one is
  persisted in ui-settings.json as review_expanded_repos, keyed by
  absolute repo root (naturally per project). Combined with the lazy
  pipeline, a freshly opened panel loads no diffs until a section is
  expanded.
@stippi
stippi merged commit c7a4d01 into main Sep 7, 2026
5 checks passed
@stippi
stippi deleted the feat/review-sidebar branch September 7, 2026 05:59
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.

2 participants