Skip to content

feat(diff-view): add Branch section for full-branch diff vs default#2707

Open
SpielerNogard wants to merge 8 commits into
generalaction:mainfrom
SpielerNogard:emdash/diffview-z3mme
Open

feat(diff-view): add Branch section for full-branch diff vs default#2707
SpielerNogard wants to merge 8 commits into
generalaction:mainfrom
SpielerNogard:emdash/diffview-z3mme

Conversation

@SpielerNogard

@SpielerNogard SpielerNogard commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

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 | All toggle. Committed mode shows defaultBranch...HEAD (three-dot via the existing mergeBaseRange); All mode shows git diff defaultBranch (working tree vs default, includes committed + staged + unstaged). Clicking a file opens a diff tab with the new diffGroup: 'branch'. The section is hidden when no default branch is configured.

A separate getMergeBase RPC 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-existing no-explicit-any warnings in tab-provider-registry.ts / task-tab-registry.tsx).
  • pnpm run typecheck — clean.
  • pnpm run test — 65/65 in the diff-view suite (incl. new tests in branch-diff-store.test.ts, diff-tab-manager.test.ts, and changes-view-store.test.ts); 1982/1982 in the wider non-browser suite.
  • Manual: against a workspace snapshot — Branch section appears between Staged and PR, toggling Committed/All re-fetches files, clicking a file opens a (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
Bildschirmfoto 2026-06-29 um 11 02 24

After the PR was merged, you no longer can access changed files
Bildschirmfoto 2026-06-29 um 11 02 42

Now there is a branch view, where you can see everything changed on this branch
Bildschirmfoto 2026-06-29 um 11 43 14

Bildschirmfoto 2026-06-29 um 11 37 59
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

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.
@SpielerNogard SpielerNogard force-pushed the emdash/diffview-z3mme branch from dd47b24 to 2d0ade0 Compare June 29, 2026 08:59
…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).
@SpielerNogard SpielerNogard force-pushed the emdash/diffview-z3mme branch 2 times, most recently from 1d2b0fd to 8d8541d Compare June 29, 2026 09:42
@SpielerNogard SpielerNogard marked this pull request as ready for review June 29, 2026 09:47
@greptile-apps

greptile-apps Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a Branch diff section to the Changes panel. The main changes are:

  • New merge-base RPC support for worktree comparisons.
  • A BranchDiffStore with Committed and All compare modes.
  • Branch diff tabs, labels, model prefetching, and renderer support.
  • Persisted branch compare mode and updated diff-view tests.

Confidence Score: 4/5

The Branch diff path can open or display comparisons against the wrong base ref.

  • Remote default branches are not treated as the current local default branch.
  • Stale merge-base requests can overwrite the active store state.
  • Tabs opened before merge-base resolution can stay pinned to the live default branch.

branch-diff-store.ts and branch-section.tsx

Important Files Changed

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
Loading
%%{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
Loading
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.
@SpielerNogard SpielerNogard force-pushed the emdash/diffview-z3mme branch from 8d8541d to 289a409 Compare June 29, 2026 10:03
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.

1 participant