From 2dfc4559b1eaf26cbafcc34f145f01187d3cc277 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Fri, 5 Jun 2026 10:20:42 +0200 Subject: [PATCH] feat(ui): add conflict-resolution worker contract (foundation for merge rework) --- .../Services/Interfaces/IWorkerClient.cs | 7 +++++++ src/ClaudeDo.Ui/Services/WorkerClient.cs | 18 ++++++++++++++++++ tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs | 5 +++++ .../UiVm/TasksIslandViewModelPlanningTests.cs | 5 +++++ 4 files changed, 35 insertions(+) diff --git a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs index b68ed1d..ac31e35 100644 --- a/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/Interfaces/IWorkerClient.cs @@ -43,6 +43,13 @@ public interface IWorkerClient : INotifyPropertyChanged Task RejectReviewToQueueAsync(string taskId, string feedback); Task RejectReviewToIdleAsync(string taskId); Task CancelReviewAsync(string taskId); + + // ── 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); Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default); Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default); Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default); diff --git a/src/ClaudeDo.Ui/Services/WorkerClient.cs b/src/ClaudeDo.Ui/Services/WorkerClient.cs index cc366e5..3d21695 100644 --- a/src/ClaudeDo.Ui/Services/WorkerClient.cs +++ b/src/ClaudeDo.Ui/Services/WorkerClient.cs @@ -269,6 +269,21 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC "MergeTask", taskId, targetBranch, removeWorktree, commitMessage); } + 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); + public Task GetMergeTargetsAsync(string taskId) => TryInvokeAsync("GetMergeTargets", taskId); @@ -532,6 +547,9 @@ public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Block public record MergeResultDto(string Status, IReadOnlyList ConflictFiles, string? ErrorMessage); public record MergePreviewDto(string Status, IReadOnlyList ConflictFiles, int ChangedFileCount); public record MergeTargetsDto(string DefaultBranch, IReadOnlyList LocalBranches); +public record MergeConflictsDto(string TaskId, IReadOnlyList Files); +public record ConflictFileDto(string Path, IReadOnlyList Hunks); +public record ConflictHunkDto(string Ours, string Theirs, string? Base); public sealed record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType); public sealed record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null); public sealed record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null); diff --git a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs index c8919ad..f46979c 100644 --- a/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs +++ b/tests/ClaudeDo.Ui.Tests/StubWorkerClient.cs @@ -58,6 +58,11 @@ public abstract class StubWorkerClient : IWorkerClient public virtual Task RejectReviewToQueueAsync(string taskId, string feedback) => Task.CompletedTask; public virtual Task RejectReviewToIdleAsync(string taskId) => Task.CompletedTask; public virtual Task CancelReviewAsync(string taskId) => Task.CompletedTask; + 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; public virtual Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask; public virtual Task OpenInteractiveTerminalAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask; public virtual Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) => Task.CompletedTask; diff --git a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs index 334cf17..6e236ee 100644 --- a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs +++ b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs @@ -45,6 +45,11 @@ sealed class FakeWorkerClient : IWorkerClient public Task ApproveReviewAsync(string taskId, string targetBranch) => Task.FromResult(null); public Task PreviewMergeAsync(string taskId, string targetBranch) => Task.FromResult(null); public Task MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty(), null)); + 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; public Task RejectReviewToQueueAsync(string taskId, string feedback) => Task.CompletedTask; public Task RejectReviewToIdleAsync(string taskId) => Task.CompletedTask; public Task CancelReviewAsync(string taskId) => Task.CompletedTask;