Review sidebar: two-column layout, resizable width, multi-repo support - #191
Merged
Merged
Conversation
…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
approved these changes
Sep 6, 2026
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)h_resizable).ScrollHandle.Backend (
code_assistant_core,git)SessionServicewith 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 --workspaceclean;cargo clippyclean on touched crates.cargo testgreen: git (24), ui_gpui (109), plus newdiscover_review_reposunit tests (root-is-repo, two child repos, nested-non-root ignored).