feat(diff-view): add Branch section for full-branch diff vs default#2707
Open
SpielerNogard wants to merge 8 commits into
Open
feat(diff-view): add Branch section for full-branch diff vs default#2707SpielerNogard wants to merge 8 commits into
SpielerNogard wants to merge 8 commits into
Conversation
Adds BranchSection between Staged and Pull Requests panels, wired to
BranchDiffStore. Extends changesViewModeSchema and settings defaults
with a 'branch' key so useChangesViewMode('branch') resolves correctly.
dd47b24 to
2d0ade0
Compare
…ile corruption Exclude 'branch' tabs from the stale-tab sweep in DiffTabLifecycleStore so they are never auto-closed when unstaged/staged/PR file lists change. Also extend the trust-the-override fast path in DiffViewStore.activeFile to cover 'branch', matching the existing guard for 'git' and 'pr'. Adds a regression test that confirms the lifecycle-store bug (RED without fix, GREEN after).
1d2b0fd to
8d8541d
Compare
Contributor
Greptile SummaryThis PR adds a Branch diff section to the Changes panel. The main changes are:
Confidence Score: 4/5The Branch diff path can open or display comparisons against the wrong base ref.
branch-diff-store.ts and branch-section.tsx
|
| Filename | Overview |
|---|---|
| apps/emdash-desktop/src/renderer/features/tasks/diff-view/stores/branch-diff-store.ts | Adds Branch diff state, compare modes, merge-base resolution, and file loading; remote/local default-branch equality and stale async merge-base writes need fixes. |
| apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/branch-section.tsx | Adds the Branch section UI and opens Branch diff tabs; unresolved merge-base fallback can open tabs against the live default branch. |
| apps/emdash-desktop/src/main/core/git/worktree/controller.ts | Adds the getMergeBase RPC handler and returns the expected { sha } payload. |
| packages/core/src/git/git-worktree.ts | Adds core merge-base support through git merge-base. |
| apps/emdash-desktop/src/renderer/features/tasks/diff-view/main-panel/diff-file-renderer.tsx | Extends diff model registration and URI selection for Branch tabs. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant UI as BranchSection
participant Store as BranchDiffStore
participant RPC as gitWorktree RPC
participant Git as Git service
Store->>RPC: getMergeBase(defaultBranch, currentBranch)
RPC->>Git: git merge-base base head
Git-->>RPC: sha or null
RPC-->>Store: merge-base result
Store->>RPC: getChangedFiles(mergeBase...HEAD or mergeBase)
RPC-->>Store: branch files
UI->>Store: read files and mergeBaseRef
UI->>UI: open branch diff tab
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant UI as BranchSection
participant Store as BranchDiffStore
participant RPC as gitWorktree RPC
participant Git as Git service
Store->>RPC: getMergeBase(defaultBranch, currentBranch)
RPC->>Git: git merge-base base head
Git-->>RPC: sha or null
RPC-->>Store: merge-base result
Store->>RPC: getChangedFiles(mergeBase...HEAD or mergeBase)
RPC-->>Store: branch files
UI->>Store: read files and mergeBaseRef
UI->>UI: open branch diff tab
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
apps/emdash-desktop/src/renderer/features/tasks/diff-view/stores/branch-diff-store.ts:106
**Remote Default Branch Mismatch**
When the configured default branch resolves to `origin/main`, `defaultBranchRef` is a remote branch but `currentBranchRef` is always a local ref for `main`. `refsEqual` returns false for that type mismatch, so a worktree on the default branch skips the `on-default-branch` state and loads a Branch comparison against itself or its remote instead of showing the intended default-branch empty state.
### Issue 2 of 3
apps/emdash-desktop/src/renderer/features/tasks/diff-view/stores/branch-diff-store.ts:158-160
**Stale Merge Base Wins**
Disposing the old `Resource` does not cancel its in-flight `getMergeBase` call, but the callback still writes `_mergeBaseSha` on the shared store. If a default-branch, head, or compare-mode change starts a newer load and the older request finishes later, the file list can come from the new base while `mergeBaseRef` points at the old base, so clicking a Branch file opens a diff against the wrong branch point.
### Issue 3 of 3
apps/emdash-desktop/src/renderer/features/tasks/diff-view/changes-panel/branch-section.tsx:38
**Unresolved Base Opens Live Default**
Before `getMergeBase` resolves, this falls back to `defaultBranchRef`, and `openDiff` stores that live branch ref in the tab. A user who clicks a Branch file during that load window gets a tab pinned to the default branch tip instead of the branch point, and the tab keeps that wrong `originalRef` even after the store later resolves the merge base.
Reviews (1): Last reviewed commit: "fix(diff-view): pin branch diff to merge..." | Re-trigger Greptile
The Branch section's file list uses three-dot semantics (merge-base...HEAD) but clicking a file used to open a diff tab against the live default-branch tip. Two consequences: 1. After a squash-merge, the default and feature tips become content-equal (the squash holds the same final state the feature already had). Monaco loaded two equal blobs and rendered an empty diff while the file list still showed +N/-N. 2. All mode compared the working tree against the live default tip, so any file where main independently caught up to the same content (a parallel PR, cherry-pick, or the same squash-merge) silently dropped out of All while still showing in Committed — making All look like a subset of Committed instead of a superset. Add a `getMergeBase` RPC and have BranchDiffStore resolve the SHA before each file-list fetch. Both modes now compare against `commitRef(mergeBase)`: Committed uses `mergeBase...HEAD`, All uses `working_tree vs mergeBase`. BranchSection uses `mergeBaseRef` as the originalRef in both modes too, with defaultBranchRef as the fallback while the SHA is still loading or when merge-base resolution fails.
8d8541d to
289a409
Compare
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.
Description
Adds a Branch section to the Changes Panel that shows files differing between the project's default branch and the current task branch, with a
Committed | Alltoggle. Committed mode showsdefaultBranch...HEAD(three-dot via the existingmergeBaseRange); All mode showsgit diff defaultBranch(working tree vs default, includes committed + staged + unstaged). Clicking a file opens a diff tab with the newdiffGroup: 'branch'. The section is hidden when no default branch is configured.A separate
getMergeBaseRPC resolves the merge-base SHA so Committed-mode diff tabs are pinned to the branch point. Without that pin, after a squash-merge the default and feature tips become content-equal and Monaco rendered an empty diff even though the file list (which uses three-dot semantics) still showed +N/-N. Branch tabs are read-only — no stage / discard / commit affordances — and the lifecycle store excludes them from the stale-tab sweep so they survive working-tree updates.Related issues
None linked.
Testing
pnpm run format— clean.pnpm run lint— clean (only pre-existingno-explicit-anywarnings intab-provider-registry.ts/task-tab-registry.tsx).pnpm run typecheck— clean.pnpm run test— 65/65 in the diff-view suite (incl. new tests inbranch-diff-store.test.ts,diff-tab-manager.test.ts, andchanges-view-store.test.ts); 1982/1982 in the wider non-browser suite.(Branch)diff tab, default-branch task shows the On default branch empty state, branch tabs stay open during working-tree mutations, and merged-PR branches now render the expected red/green diff instead of an empty editor.Screenshot/Recording (if applicable)
Before: When PR is open you can see the changed files

After the PR was merged, you no longer can access changed files

Now there is a branch view, where you can see everything changed on this branch

Checklist
I kept this PR small and focused
I ran a self-review before opening this PR
I ran the relevant local checks or explained why not
I updated docs when behavior or setup changed
I added or updated tests when behavior changed, or explained why not
I only added comments where the logic is not obvious
I used Conventional Commits for commit messages and, when possible, the PR title