# Git Merge/Review — Shared Foundation + Layer A Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Build the shared worker conflict contract (so parallel Layer B/C sessions branch from frozen interfaces) and rework the Git tab into a single Approve+merge cockpit. **Architecture:** Phase 0 adds the conflict-resolution contract to `IWorkerClient`/`WorkerClient` (real `_hub.InvokeAsync` bodies — the worker hub methods are implemented later by Layer C; calls simply fail at runtime until then) plus client-side DTOs and test-fake updates, then commits + pushes so B and C branch from it. Phase A reworks `WorkConsole.axaml`'s Git tab and routes single-task merge/approve conflicts into a `RequestConflictResolution` seam (wired to Layer C's resolver by the integrator at merge time). **Tech Stack:** .NET 8, Avalonia 12 (Fluent), CommunityToolkit.Mvvm, SignalR, xUnit. Build individual csproj with `-c Release` (`.slnx` needs .NET 9; a running Worker locks `Debug`). **Reference spec:** `docs/superpowers/specs/2026-06-05-git-merge-review-rework-design.md` **Note on the canonical diff renderer:** the unified diff model/control already exists — `DiffFileViewModel`/`DiffLineViewModel`/`UnifiedDiffParser` (in `src/ClaudeDo.Ui/ViewModels/Modals/`) rendered by `DiffLinesView` (`src/ClaudeDo.Ui/Views/Controls/DiffLinesView.axaml`). `DiffModalView` and `PlanningDiffView` already use it. So "consolidate diff renderers" for this scope is just verifying that (Task A.3); migrating `WorktreeModalView`'s bespoke diff onto `DiffLinesView` is Layer B's job. --- ## File Structure **Phase 0 (foundation — pushed before B/C branch):** - Modify `src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs` — 5 new method signatures. - Modify `src/ClaudeDo.Ui/Services/WorkerClient.cs` — 5 `InvokeAsync` bodies + 3 new DTO records. - Modify `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs` — 5 new `virtual` no-op methods. - Modify `tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs` — 5 new methods on `FakeWorkerClient`. **Phase A (Layer A — this session, after foundation commit):** - Modify `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` — `RequestConflictResolution` seam; route Approve/Merge conflicts into it. - Modify `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml` — fuse REVIEW + MERGE sections into one cockpit block. - Test: `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs` (or a sibling test file in the same folder). --- ## Phase 0 — Shared Foundation ### Task 0.1: Add the conflict contract (interface + client + DTOs) **Files:** - Modify: `src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs` - Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs` - [ ] **Step 1: Add the 5 method signatures to `IWorkerClient`** In `src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs`, after the existing `Task CancelReviewAsync(string taskId);` line (line 45), add: ```csharp // ── Conflict resolution (worker hub side implemented by Layer C) ── Task StartConflictMergeAsync(string taskId, string targetBranch); Task GetMergeConflictsAsync(string taskId); Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent); Task ContinueMergeAsync(string taskId); Task AbortMergeAsync(string taskId); ``` - [ ] **Step 2: Add the 3 DTO records to `WorkerClient.cs`** In `src/ClaudeDo.Ui/Services/WorkerClient.cs`, immediately after line 534 (`public record MergeTargetsDto(...)`), add: ```csharp public record MergeConflictsDto(string TaskId, IReadOnlyList Files); public record ConflictFileDto(string Path, IReadOnlyList Hunks); public record ConflictHunkDto(string Ours, string Theirs, string? Base); ``` - [ ] **Step 3: Add the 5 client method bodies to `WorkerClient.cs`** In `src/ClaudeDo.Ui/Services/WorkerClient.cs`, right after the `MergeTaskAsync` method (ends at line 270), add: ```csharp public Task StartConflictMergeAsync(string taskId, string targetBranch) => _hub.InvokeAsync("StartConflictMerge", taskId, targetBranch); public Task GetMergeConflictsAsync(string taskId) => _hub.InvokeAsync("GetMergeConflicts", taskId); public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => _hub.InvokeAsync("WriteConflictResolution", taskId, path, resolvedContent); public Task ContinueMergeAsync(string taskId) => _hub.InvokeAsync("ContinueMerge", taskId); public Task AbortMergeAsync(string taskId) => _hub.InvokeAsync("AbortMerge", taskId); ``` - [ ] **Step 4: Build the UI project** Run: `dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj -c Release` Expected: build FAILS — the two test projects won't compile yet, but the UI project itself should succeed. If the UI project reports "does not implement interface member" it means a body is missing; fix before continuing. (Test projects are fixed in 0.2.) ### Task 0.2: Update the hand-rolled test fakes **Files:** - Modify: `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs` - Modify: `tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs` - [ ] **Step 1: Add 5 virtual no-ops to `StubWorkerClient`** In `tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs`, after the `MergeTaskAsync` override (line 57), add: ```csharp public virtual Task StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty(), null)); public virtual Task GetMergeConflictsAsync(string taskId) => Task.FromResult(new MergeConflictsDto(taskId, System.Array.Empty())); public virtual Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask; public virtual Task ContinueMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty(), null)); public virtual Task AbortMergeAsync(string taskId) => Task.CompletedTask; ``` - [ ] **Step 2: Add 5 methods to `FakeWorkerClient`** In `tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs`, after the `MergeTaskAsync` method (line 47), add: ```csharp public Task StartConflictMergeAsync(string taskId, string targetBranch) => Task.FromResult(new MergeResultDto("conflict", System.Array.Empty(), null)); public Task GetMergeConflictsAsync(string taskId) => Task.FromResult(new MergeConflictsDto(taskId, System.Array.Empty())); public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent) => Task.CompletedTask; public Task ContinueMergeAsync(string taskId) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty(), null)); public Task AbortMergeAsync(string taskId) => Task.CompletedTask; ``` - [ ] **Step 3: Build both test projects** Run: `dotnet build tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release && dotnet build tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj -c Release` Expected: both BUILD succeed. - [ ] **Step 4: Run the UI test suite to confirm green baseline** Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release` Expected: PASS (no behavior changed yet). ### Task 0.3: Commit and push the foundation - [ ] **Step 1: Commit** ```bash git add src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs src/ClaudeDo.Ui/Services/WorkerClient.cs tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs git commit -m "feat(ui): add conflict-resolution worker contract (foundation for merge rework)" ``` - [ ] **Step 2: Push so Layer B/C can branch from this commit** Run: `git push` Expected: pushed to `main`. (First push to git.kuns.dev may fail auth — retry once.) **This commit is the branch point for the Layer B and Layer C kickoff prompts.** --- ## Phase A — Layer A Review/Merge Cockpit ### Task A.1: Conflict-resolution seam + route Approve/Merge conflicts into it (TDD) **Files:** - Modify: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs` - Test: `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandConflictSeamTests.cs` (new) - [ ] **Step 1: Write the failing test** Create `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandConflictSeamTests.cs`. Mirror the VM-construction harness used in `tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandPlanningTests.cs` (same folder) — construct `DetailsIslandViewModel` exactly as that file does, including its `StubWorkerClient` subclass pattern. The test: ```csharp [Fact] public async Task ApproveReview_OnConflict_InvokesConflictResolutionSeam() { string? resolvedTaskId = null; string? resolvedTarget = null; // Construct the VM as in DetailsIslandPlanningTests, with a worker stub whose // ApproveReviewAsync returns a conflict result: // public override Task ApproveReviewAsync(string id, string target) // => Task.FromResult(new MergeResultDto("conflict", new[]{"a.cs"}, null)); var vm = CreateVm(/* worker stub above */); vm.RequestConflictResolution = (taskId, target) => { resolvedTaskId = taskId; resolvedTarget = target; return System.Threading.Tasks.Task.CompletedTask; }; // assign a task in WaitingForReview + a SelectedMergeTarget = "main" via the same // helpers DetailsIslandPlanningTests uses. await vm.ApproveReviewCommand.ExecuteAsync(null); Assert.Equal(/* the seeded task id */, resolvedTaskId); Assert.Equal("main", resolvedTarget); } ``` - [ ] **Step 2: Run the test to verify it fails** Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter ApproveReview_OnConflict_InvokesConflictResolutionSeam` Expected: FAIL — `RequestConflictResolution` property does not exist (compile error). - [ ] **Step 3: Add the seam property** In `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs`, beside the other view-wired delegates (`ShowDiffModal`, `ShowMergeModal` around line 387–390), add: ```csharp // Invoked when a single-task merge/approve hits a conflict. Wired by the // integrator to Layer C's conflict resolver. Args: (taskId, targetBranch). public Func? RequestConflictResolution { get; set; } ``` - [ ] **Step 4: Route the Approve conflict branch into the seam** In `ApproveReviewAsync` (around line 1453), replace the conflict branch body so it prefers the seam, falling back to the current preview-text behavior: ```csharp var result = await _worker.ApproveReviewAsync(Task.Id, SelectedMergeTarget ?? ""); if (result?.Status == "conflict") { if (RequestConflictResolution is not null) { await RequestConflictResolution(Task.Id, SelectedMergeTarget ?? ""); } else { var (text, _, _) = MergePreviewPresenter.Describe( new MergePreviewDto("conflict", result.ConflictFiles, 0)); MergePreviewText = text; MergeIsClean = false; MergeIsConflict = true; } } ``` - [ ] **Step 5: Route the manual Merge conflict branch into the seam** In `MergeAsync` (around line 1170), apply the same pattern to its conflict branch: ```csharp var result = await _worker.MergeTaskAsync(Task.Id, SelectedMergeTarget ?? "", false, "Merge task"); if (result.Status == "conflict") { if (RequestConflictResolution is not null) { await RequestConflictResolution(Task.Id, SelectedMergeTarget ?? ""); } else { var (text, _, _) = MergePreviewPresenter.Describe( new MergePreviewDto("conflict", result.ConflictFiles, 0)); MergePreviewText = text; MergeIsClean = false; MergeIsConflict = true; } } else { await RefreshMergePreviewAsync(); } ``` - [ ] **Step 6: Run the test to verify it passes** Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release --filter ApproveReview_OnConflict_InvokesConflictResolutionSeam` Expected: PASS. - [ ] **Step 7: Run the full UI suite (no regressions)** Run: `dotnet test tests/ClaudeDo.Ui.Tests/ClaudeDo.Ui.Tests.csproj -c Release` Expected: PASS. - [ ] **Step 8: Commit** ```bash git add src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs tests/ClaudeDo.Ui.Tests/ViewModels/DetailsIslandConflictSeamTests.cs git commit -m "feat(ui): route single-task merge conflicts into a resolution seam" ``` ### Task A.2: Fuse the Git tab into one Approve+merge cockpit **Files:** - Modify: `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml` - [ ] **Step 1: Replace the two Git-tab sections with one cockpit block** In `src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml`, replace the entire Git `ScrollViewer` body (lines 255–313 — the `` block containing the separate `REVIEW` `StackPanel` and the `MERGE & WORKTREE` `StackPanel`) with a single cockpit where Approve sits with the merge target/preview/actions. Keep the existing control class names (`section-label`, `field-label`, `btn`, `btn accent`, `meta`) and the existing bindings (`SelectedMergeTarget`, `MergeTargetBranches`, `MergePreviewText`, `MergeIsClean`, `MergeIsConflict`, `ShowMergePreviewMuted`, `OpenDiffCommand`, `ApproveReviewCommand`, `MergeCommand`, `ShowSingleMerge`, `OpenWorktreeCommand`, `ReviewCombinedDiffCommand`, `MergeAllCommand`, `CanMergeAll`, `MergeAllDisabledReason`, `MergeAllError`): ```xml