From 3d0cc4ffed2b01696eb416b86d7b101ebe3c1686 Mon Sep 17 00:00:00 2001 From: Mika Kuns Date: Wed, 22 Apr 2026 09:46:20 +0200 Subject: [PATCH] feat(ui): add MergeModalViewModel --- .../ViewModels/Modals/MergeModalViewModel.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/ClaudeDo.Ui/ViewModels/Modals/MergeModalViewModel.cs diff --git a/src/ClaudeDo.Ui/ViewModels/Modals/MergeModalViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Modals/MergeModalViewModel.cs new file mode 100644 index 0000000..2768170 --- /dev/null +++ b/src/ClaudeDo.Ui/ViewModels/Modals/MergeModalViewModel.cs @@ -0,0 +1,117 @@ +using System.Collections.ObjectModel; +using ClaudeDo.Ui.Services; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace ClaudeDo.Ui.ViewModels.Modals; + +public sealed partial class MergeModalViewModel : ViewModelBase +{ + private readonly WorkerClient _worker; + + public string TaskId { get; set; } = ""; + public string TaskTitle { get; set; } = ""; + + public ObservableCollection Branches { get; } = new(); + + [ObservableProperty] private string? _selectedBranch; + [ObservableProperty] private bool _removeWorktree = true; + [ObservableProperty] private string _commitMessage = ""; + + [ObservableProperty] private bool _isBusy; + [ObservableProperty] private string? _errorMessage; + [ObservableProperty] private string? _warningMessage; + [ObservableProperty] private string? _successMessage; + [ObservableProperty] private bool _hasConflict; + [ObservableProperty] private IReadOnlyList _conflictFiles = Array.Empty(); + + public Action? CloseAction { get; set; } + + public MergeModalViewModel(WorkerClient worker) + { + _worker = worker; + } + + public async Task InitializeAsync(string taskId, string taskTitle) + { + TaskId = taskId; + TaskTitle = taskTitle; + CommitMessage = $"Merge task: {taskTitle}"; + + IsBusy = true; + try + { + var targets = await _worker.GetMergeTargetsAsync(taskId); + Branches.Clear(); + if (targets is null) + { + ErrorMessage = "Worker offline — cannot list branches."; + return; + } + foreach (var b in targets.LocalBranches) Branches.Add(b); + SelectedBranch = Branches.Contains(targets.DefaultBranch) + ? targets.DefaultBranch + : Branches.FirstOrDefault(); + } + catch (Exception ex) + { + ErrorMessage = $"Failed to load branches: {ex.Message}"; + } + finally { IsBusy = false; } + } + + private bool CanSubmit() => + !IsBusy && !HasConflict && !string.IsNullOrWhiteSpace(SelectedBranch); + + [RelayCommand(CanExecute = nameof(CanSubmit))] + private async Task SubmitAsync() + { + if (string.IsNullOrWhiteSpace(SelectedBranch)) return; + IsBusy = true; + ErrorMessage = null; + WarningMessage = null; + SuccessMessage = null; + try + { + var result = await _worker.MergeTaskAsync( + TaskId, SelectedBranch!, RemoveWorktree, CommitMessage); + + switch (result.Status) + { + case "merged": + SuccessMessage = result.ErrorMessage is not null + ? $"Merged with warning: {result.ErrorMessage}" + : "Merged."; + // Auto-close after a short delay. + _ = Task.Run(async () => + { + await Task.Delay(1200); + Avalonia.Threading.Dispatcher.UIThread.Post(() => CloseAction?.Invoke()); + }); + break; + case "conflict": + HasConflict = true; + ConflictFiles = result.ConflictFiles; + ErrorMessage = "Merge conflict — target branch restored. Resolve manually or via Continue, then retry."; + break; + case "blocked": + ErrorMessage = $"Blocked: {result.ErrorMessage}"; + break; + default: + ErrorMessage = $"Unknown status: {result.Status}"; + break; + } + } + catch (Exception ex) + { + ErrorMessage = $"Merge failed: {ex.Message}"; + } + finally + { + IsBusy = false; + } + } + + [RelayCommand] + private void Cancel() => CloseAction?.Invoke(); +}