feat(workspace): add merge-conflict resolution UI#166
Open
liam-russell wants to merge 1 commit into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an in-app merge-conflict resolution flow in the Workspace UI, backed by a new IPC endpoint that reads base/ours/theirs content from git index stages, and extends worktree status parsing to flag conflicted paths (while also addressing a porcelain parsing edge-case).
Changes:
- Introduces a new Conflicts tab (banner + sidebar badges) and a three-pane Monaco-based conflict resolution panel with per-block accept actions and “Mark Resolved”.
- Adds
git:conflictContentIPC + git library support to read conflict stage blobs from the index (stages 1/2/3). - Updates status parsing/types to expose
StatusFileEntry.conflictedand fixes a porcelain parsing misclassification for unstaged-only entries.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/types/src/ipc.ts | Adds IPC constant and IpcMap entry for git:conflictContent. |
| packages/types/src/git.ts | Extends status entries with conflicted and defines conflict stage/content result types. |
| packages/git/src/staging.ts | Adjusts porcelain status retrieval/parsing and flags conflicted XY codes. |
| packages/git/src/index.ts | Re-exports the new conflicts helper from the git package entrypoint. |
| packages/git/src/conflicts.ts | Implements index-stage reads for base/ours/theirs conflict content. |
| packages/git/src/tests/conflicts.test.ts | Adds unit tests for conflict detection and stage-blob reads. |
| packages/git/package.json | Exports the new ./conflicts module entry. |
| e2e/specs/merge-conflict-workflow.spec.ts | Adds E2E coverage for banner → Conflicts tab → resolve → mark resolved. |
| app/src/renderer/workspace/WorktreeSidebar.tsx | Shows conflict icon/badge per worktree and updates tooltip text. |
| app/src/renderer/workspace/ConflictResolutionPanel.tsx | Adds the conflict resolution UI (three panes + per-block actions + mark resolved). |
| app/src/renderer/workspace/ConflictedFilesPanel.tsx | Adds conflicted-file list panel for the Conflicts tab. |
| app/src/renderer/workspace/conflict-blocks.ts | Adds parsing/resolution logic for conflict markers (incl. diff3 tolerance). |
| app/src/renderer/workspace/tests/conflict-blocks.test.ts | Adds unit tests for conflict parsing and per-block resolution. |
| app/src/renderer/stores/workspace-store.ts | Adds conflicts to persisted workspace tab state. |
| app/src/renderer/routes/workspace.tsx | Wires up the banner/tab/panels and hooks for conflict counts/status. |
| app/src/renderer/queries.ts | Adds worktree conflict counts and a per-worktree status query hook. |
| app/src/preload/index.ts | Exposes getConflictFileContent to the renderer via preload API. |
| app/src/main/ipc/git.ts | Registers IPC handler for git:conflictContent with path assertion. |
Comments suppressed due to low confidence (1)
packages/git/src/staging.ts:121
parsePorcelainStatustrims the extracted file path (line.slice(3).trim()), which can silently corrupt valid filenames that begin/end with whitespace. Since porcelain v1 already has a fixedXY␠prefix, it’s safer to take the substring as-is and only strip a trailing\r(for CRLF) if present.
const indexStatus = line[0] ?? ' ';
const worktreeStatus = line[1] ?? ' ';
const filePath = line.slice(3).trim();
081260f to
0eef5af
Compare
Detects unmerged paths via git status porcelain codes and surfaces them as a sidebar badge, workspace banner, and dedicated Conflicts tab. Conflicted files get a three-pane ours/theirs/result view (Monaco-based) with per-block accept-ours/accept-theirs/accept-both actions and a resolve action that stages the file once no markers remain. Also fixes a pre-existing bug in getWorktreeStatus: gitForPath's `trimmed: true` config trimmed the whole porcelain status string, silently eating the leading space of a lone unstaged-only entry and misreading it as staged. Fixed by forcing a `##` branch header as the guaranteed first line. Closes #91
0eef5af to
a8e932f
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.
Type
Summary
Why
Closes #91 — today, resolving a merge conflict means dropping to a terminal or an external merge tool. This adds a minimal but real in-app resolution flow on top of the existing Monaco-based file editor (#57).
Screenshots / recordings
Captured via a WebdriverIO screenshot against the real Electron app (same mechanism as
hero-screenshots.spec.ts), but this session's browser tooling for uploading it inline to the PR isn't available right now. Description of the captured view instead:I'll attach the actual screenshot as a follow-up comment once browser tooling is available, or on request.
Breaking changes
None — adds a new IPC channel (
git:conflictContent) and aconflictedfield onStatusFileEntry; both are additive.Test plan
packages/git/src/__tests__/conflicts.test.ts— real merge conflicts (UU, both-added/AA) verifyingconflicteddetection and base/ours/theirs blob reads via index stages.app/src/renderer/workspace/__tests__/conflict-blocks.test.ts— conflict-marker parsing/resolution, including diff3-style markers and multi-block files.e2e/specs/merge-conflict-workflow.spec.ts— scripts a real merge conflict in a managed worktree, then drives the full UI flow (banner → Conflicts tab → accept-ours → mark resolved → banner disappears), asserting the on-disk result and that git no longer reports the path as unmerged.pnpm lint && pnpm typecheck && pnpm testall pass (362 unit tests + 15/15 E2E specs green, run 3x for flake-checking).