feat(ui): spinners for ConPTY session start and task refine

Both actions previously gave no feedback: opening a ConPTY session only created
the tile after the launch-spec roundtrip (which may build a worktree), and the
refine button just disappeared while the run was in flight.

Add a shared Ellipse.spinner style, and let ConPtyPaneViewModel resolve its own
launch spec so the tile shows up immediately with a starting overlay. A failed
launch now keeps the tile with its inline error banner instead of never
appearing — Start() is separated from the ctor so the host can subscribe to
ErrorReported before the launch begins.
This commit is contained in:
Mika Kuns
2026-07-27 15:02:51 +02:00
parent 6c8de4aef3
commit 1466d0fbab
9 changed files with 150 additions and 90 deletions
@@ -298,7 +298,7 @@ public class MissionControlViewModelTests : IDisposable
}
[Fact]
public async Task OpenConPtySessionAsync_WorkerThrows_RaisesErrorReported_NoPaneAdded()
public async Task OpenConPtySessionAsync_WorkerThrows_RaisesErrorReported_PaneShowsFailure()
{
var worker = new ThrowingLaunchSpecWorker();
using var vm = BuildVm(worker);
@@ -307,8 +307,10 @@ public class MissionControlViewModelTests : IDisposable
await vm.OpenConPtySessionAsync("t1");
Assert.Empty(vm.ConPtySessions);
Assert.Empty(vm.Panes);
// The tile is shown up-front (spinner) and keeps the failure inline rather than vanishing.
var pane = Assert.Single(vm.ConPtySessions);
Assert.NotNull(pane.Terminal.StartError);
Assert.False(pane.Terminal.IsStarting);
Assert.NotNull(error);
}
@@ -378,7 +380,7 @@ public class MissionControlViewModelTests : IDisposable
}
[Fact]
public async Task OpenAdHocConPtySessionAsync_WorkerThrows_RaisesErrorReported_NoPaneAdded()
public async Task OpenAdHocConPtySessionAsync_WorkerThrows_RaisesErrorReported_PaneShowsFailure()
{
var worker = new ThrowingAdHocLaunchSpecWorker();
using var vm = BuildVm(worker);
@@ -387,8 +389,9 @@ public class MissionControlViewModelTests : IDisposable
await vm.OpenAdHocConPtySessionAsync(Path.Combine("C:", "Some", "Path"));
Assert.Empty(vm.ConPtySessions);
Assert.Empty(vm.Panes);
var pane = Assert.Single(vm.ConPtySessions);
Assert.NotNull(pane.Terminal.StartError);
Assert.False(pane.Terminal.IsStarting);
Assert.NotNull(error);
}
@@ -451,7 +454,7 @@ public class MissionControlViewModelTests : IDisposable
}
[Fact]
public async Task OpenMergeHelperConPtySessionAsync_WorkerThrows_RaisesErrorReported_NoPaneAdded()
public async Task OpenMergeHelperConPtySessionAsync_WorkerThrows_RaisesErrorReported_PaneShowsFailure()
{
var worker = new ThrowingMergeHelperLaunchSpecWorker();
using var vm = BuildVm(worker);
@@ -460,8 +463,9 @@ public class MissionControlViewModelTests : IDisposable
await vm.OpenMergeHelperConPtySessionAsync("L1", new[] { "t1" });
Assert.Empty(vm.ConPtySessions);
Assert.Empty(vm.Panes);
var pane = Assert.Single(vm.ConPtySessions);
Assert.NotNull(pane.Terminal.StartError);
Assert.False(pane.Terminal.IsStarting);
Assert.NotNull(error);
}