feat(ui): add conflict resolution dialog for planning merge-all

Opens a modal when PlanningMergeConflict fires, listing conflicted files
with options to open in VS Code, continue, or abort the merge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-04-24 18:08:45 +02:00
parent a6ebff3f34
commit bc788e1e0f
7 changed files with 382 additions and 1 deletions

View File

@@ -0,0 +1,83 @@
using System.Diagnostics;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ClaudeDo.Ui.Services;
namespace ClaudeDo.Ui.ViewModels.Planning;
public sealed partial class ConflictResolutionViewModel : ObservableObject
{
private readonly IWorkerClient _worker;
private readonly string _planningTaskId;
private readonly string _worktreePath;
public string SubtaskTitle { get; }
public string TargetBranch { get; }
public IReadOnlyList<string> ConflictedFiles { get; }
[ObservableProperty] private string? _vsCodeError;
[ObservableProperty] private string? _actionError;
public Action? CloseRequested { get; set; }
public ConflictResolutionViewModel(
IWorkerClient worker,
string planningTaskId,
string subtaskTitle,
string targetBranch,
IReadOnlyList<string> conflictedFiles,
string worktreePath)
{
_worker = worker;
_planningTaskId = planningTaskId;
_worktreePath = worktreePath;
SubtaskTitle = subtaskTitle;
TargetBranch = targetBranch;
ConflictedFiles = conflictedFiles;
}
[RelayCommand]
private void OpenInVsCode()
{
try
{
var args = string.Join(" ", ConflictedFiles.Select(f => $"\"{f}\""));
Process.Start(new ProcessStartInfo
{
FileName = "code",
Arguments = args,
WorkingDirectory = _worktreePath,
UseShellExecute = true,
});
VsCodeError = null;
}
catch (Exception ex)
{
VsCodeError = $"Could not launch VS Code: {ex.Message}. Paths are listed above — copy them manually.";
}
}
[RelayCommand]
private async Task ContinueAsync()
{
ActionError = null;
try
{
await _worker.ContinuePlanningMergeAsync(_planningTaskId);
CloseRequested?.Invoke();
}
catch (Exception ex) { ActionError = ex.Message; }
}
[RelayCommand]
private async Task AbortAsync()
{
ActionError = null;
try
{
await _worker.AbortPlanningMergeAsync(_planningTaskId);
CloseRequested?.Invoke();
}
catch (Exception ex) { ActionError = ex.Message; }
}
}