feat(ui): separate OperationStatus per worktree action (refresh/cleanup/reset/force-remove/batch-merge)

Replaces the shared IsBusy/IsMerging flags in WorktreesOverviewModalViewModel and the
reset flow in WorktreesSettingsTabViewModel with dedicated OperationStatus instances
shown via OperationIndicator, so a running Refresh no longer blocks the Cleanup
indicator. ForceRemove gains a CanExecute guard against re-entrancy while it's running,
and the reconcile tick's busy guard now checks all four action statuses instead of the
old IsBusy||IsMerging pair.
This commit is contained in:
Mika Kuns
2026-08-12 09:39:10 +02:00
parent d3abd4b88b
commit a0d5db0db0
8 changed files with 189 additions and 100 deletions
@@ -20,6 +20,8 @@ public class WorktreesOverviewModalErrorTests
private sealed class ThrowingWorker : StubWorkerClient
{
public string ExceptionMessage { get; init; } = "worktree is locked by another process";
public override Task<List<WorktreeOverviewDto>> GetWorktreesOverviewAsync(string? listId) =>
throw new Exception(ExceptionMessage);
public override Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null) =>
throw new Exception(ExceptionMessage);
public override Task<ForceRemoveResultDto?> ForceRemoveWorktreeAsync(string taskId) =>
@@ -39,7 +41,7 @@ public class WorktreesOverviewModalErrorTests
Assert.NotNull(vm.StatusMessage);
Assert.Contains(worker.ExceptionMessage, vm.StatusMessage);
Assert.False(vm.IsBusy);
Assert.False(vm.CleanupStatus.IsRunning);
}
[Fact]
@@ -61,5 +63,17 @@ public class WorktreesOverviewModalErrorTests
Assert.NotNull(vm.StatusMessage);
Assert.Contains(worker.ExceptionMessage, vm.StatusMessage);
Assert.Contains(row, vm.Rows);
Assert.False(vm.ForceRemoveStatus.IsRunning);
}
[Fact]
public async Task Refresh_WhenWorkerThrows_ResetsRefreshStatus()
{
var worker = new ThrowingWorker();
var vm = NewVm(worker);
await Assert.ThrowsAsync<Exception>(() => vm.RefreshCommand.ExecuteAsync(null));
Assert.False(vm.RefreshStatus.IsRunning);
}
}