fix(ui): stop app crash when approving review after Merge all

The Details island review commands (Approve/Reject/Park/Cancel) invoked the
hub without catching exceptions. After "Merge all" folds the parent out of
WaitingForReview, pressing Approve made the hub throw a HubException, which
escaped the generated AsyncRelayCommand as an unobserved async-void exception
and crashed the app. Wrap the calls in try/catch like the Tasks island does;
the TaskUpdated broadcast reconciles the UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-04 18:04:37 +02:00
parent 22a1ba7f30
commit cc7355eaa4
2 changed files with 36 additions and 5 deletions

View File

@@ -63,12 +63,21 @@ public class DetailsIslandPlanningTests : IDisposable
public Task DeleteAsync(string id) => Task.CompletedTask;
}
private DetailsIslandViewModel BuildVm(FakeWorkerClient worker)
private DetailsIslandViewModel BuildVm(StubWorkerClient worker)
{
var factory = new TestDbFactory(NewContext);
return new DetailsIslandViewModel(factory, worker, new NullServiceProvider(), new StubNotesApi());
}
// Connected worker whose review calls fail the way the hub does when the task
// is no longer WaitingForReview (e.g. after "Merge all" folded the parent).
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."));
}
private static SubtaskRowViewModel MakeSubtask(TaskStatus status, WorktreeState wt = WorktreeState.Active) =>
new() { Id = Guid.NewGuid().ToString(), Title = "t", Status = status, WorktreeState = wt };
@@ -135,6 +144,21 @@ public class DetailsIslandPlanningTests : IDisposable
vm.MergeAllDisabledReason.Contains("discarded", StringComparison.OrdinalIgnoreCase));
}
// ── Review-action resilience: a failing hub call must not crash the app ───
[Fact]
public async Task ApproveReview_WhenWorkerThrows_DoesNotPropagate()
{
var vm = BuildVm(new ThrowingReviewWorkerClient());
vm.Bind(new TaskRowViewModel { Id = "p", Status = TaskStatus.WaitingForReview });
// Before the fix this surfaced the HubException as an unobserved
// async-void exception from the command, crashing the process.
var ex = await Record.ExceptionAsync(() => vm.ApproveReviewCommand.ExecuteAsync(null));
Assert.Null(ex);
}
// ── Branch-load test exercising the VM via Bind ──────────────────────────
[Fact]