feat(ui): wire merge-aware approve and preview into the worker client

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-04 23:32:12 +02:00
parent 43f8f7f7d8
commit 3202c76674
7 changed files with 19 additions and 11 deletions

View File

@@ -37,7 +37,9 @@ public interface IWorkerClient : INotifyPropertyChanged
Task<ListConfigDto?> GetListConfigAsync(string listId);
Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto);
Task SetTaskStatusAsync(string taskId, TaskStatus status);
Task ApproveReviewAsync(string taskId);
Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch);
Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch);
Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage);
Task RejectReviewToQueueAsync(string taskId, string feedback);
Task RejectReviewToIdleAsync(string taskId);
Task CancelReviewAsync(string taskId);

View File

@@ -396,10 +396,11 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
await _hub.InvokeAsync("SetTaskStatus", taskId, status.ToString());
}
public async Task ApproveReviewAsync(string taskId)
{
await _hub.InvokeAsync("ApproveReview", taskId);
}
public Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch)
=> TryInvokeAsync<MergeResultDto>("ApproveReview", taskId, targetBranch);
public Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch)
=> TryInvokeAsync<MergePreviewDto>("PreviewMerge", taskId, targetBranch);
public async Task RejectReviewToQueueAsync(string taskId, string feedback)
{
@@ -529,6 +530,7 @@ public sealed record AppSettingsDto(
public sealed record WorktreeCleanupDto(int Removed);
public sealed record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks);
public record MergeResultDto(string Status, IReadOnlyList<string> ConflictFiles, string? ErrorMessage);
public record MergePreviewDto(string Status, IReadOnlyList<string> ConflictFiles, int ChangedFileCount);
public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalBranches);
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);

View File

@@ -1365,7 +1365,7 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
// The hub rejects (HubException) if the task is no longer WaitingForReview
// — e.g. after "Merge all" folded the parent. Swallow it; the TaskUpdated
// broadcast reconciles the UI. An unhandled command exception would crash.
try { await _worker.ApproveReviewAsync(Task.Id); }
try { await _worker.ApproveReviewAsync(Task.Id, SelectedMergeTarget ?? ""); }
catch { /* stale review action; broadcast reconciles */ }
}

View File

@@ -647,7 +647,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
private async Task ApproveReviewAsync(TaskRowViewModel? row)
{
if (row is null || !row.IsWaitingForReview || _worker is null) return;
try { await _worker.ApproveReviewAsync(row.Id); }
try { await _worker.ApproveReviewAsync(row.Id, ""); }
catch { /* offline; broadcast reconciles on return */ }
}

View File

@@ -52,7 +52,9 @@ public abstract class StubWorkerClient : IWorkerClient
public virtual Task<ListConfigDto?> GetListConfigAsync(string listId) => Task.FromResult<ListConfigDto?>(null);
public virtual Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask;
public virtual Task SetTaskStatusAsync(string taskId, TaskStatus status) => Task.CompletedTask;
public virtual Task ApproveReviewAsync(string taskId) => Task.CompletedTask;
public virtual Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch) => Task.FromResult<MergeResultDto?>(null);
public virtual Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch) => Task.FromResult<MergePreviewDto?>(null);
public virtual Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
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;

View File

@@ -74,8 +74,8 @@ public class DetailsIslandPlanningTests : IDisposable
private sealed class ThrowingReviewWorkerClient : StubWorkerClient
{
public override bool IsConnected => true;
public override Task ApproveReviewAsync(string taskId) =>
Task.FromException(new InvalidOperationException("Task is not waiting for review; cannot approve."));
public override Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch) =>
Task.FromException<MergeResultDto?>(new InvalidOperationException("Task is not waiting for review; cannot approve."));
}
private static SubtaskRowViewModel MakeSubtask(TaskStatus status, WorktreeState wt = WorktreeState.Active) =>

View File

@@ -42,7 +42,9 @@ sealed class FakeWorkerClient : IWorkerClient
public Task<ListConfigDto?> GetListConfigAsync(string listId) => Task.FromResult<ListConfigDto?>(null);
public Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask;
public Task SetTaskStatusAsync(string taskId, TaskStatus status) => Task.CompletedTask;
public Task ApproveReviewAsync(string taskId) => Task.CompletedTask;
public Task<MergeResultDto?> ApproveReviewAsync(string taskId, string targetBranch) => Task.FromResult<MergeResultDto?>(null);
public Task<MergePreviewDto?> PreviewMergeAsync(string taskId, string targetBranch) => Task.FromResult<MergePreviewDto?>(null);
public Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage) => Task.FromResult(new MergeResultDto("merged", System.Array.Empty<string>(), null));
public Task RejectReviewToQueueAsync(string taskId, string feedback) => Task.CompletedTask;
public Task RejectReviewToIdleAsync(string taskId) => Task.CompletedTask;
public Task CancelReviewAsync(string taskId) => Task.CompletedTask;