feat(interactive): New session button for ad-hoc ConPTY sessions

Adds a 'New session' header button in Mission Control that opens a folder picker
and starts a task-less embedded ConPTY session in the chosen directory. ConPtyPaneViewModel
TaskId is now nullable (ad-hoc panes have no task and are never deduped) with a
CreateAdHoc factory; the view does the picking, the VM stays picker-agnostic.
This commit is contained in:
mika kuns
2026-07-23 16:47:15 +02:00
parent 9ab48d7094
commit 3feb08d9d9
7 changed files with 127 additions and 4 deletions
@@ -341,6 +341,71 @@ public class MissionControlViewModelTests : IDisposable
Assert.Empty(vm.Panes);
}
private sealed class ThrowingAdHocLaunchSpecWorker : StubWorkerClient
{
public override Task<LaunchSpec> GetAdHocLaunchSpecAsync(string directory, CancellationToken ct = default)
=> throw new InvalidOperationException("bad directory");
}
[Fact]
public async Task OpenAdHocConPtySessionAsync_AddsPane_TitleFromDirectoryLeaf()
{
var worker = new FakeWorker();
using var vm = BuildVm(worker);
await vm.OpenAdHocConPtySessionAsync(Path.Combine("C:", "Some", "Path", "MyProject"));
Assert.Single(vm.ConPtySessions);
Assert.Null(vm.ConPtySessions[0].TaskId);
Assert.Equal("MyProject", vm.ConPtySessions[0].DisplayTitle);
Assert.Single(vm.Panes);
Assert.Same(vm.ConPtySessions[0], vm.Panes[0]);
}
[Fact]
public async Task OpenAdHocConPtySessionAsync_TwoOpens_NeverDeduped_YieldsTwoPanes()
{
var worker = new FakeWorker();
using var vm = BuildVm(worker);
var dir = Path.Combine("C:", "Some", "Path");
await vm.OpenAdHocConPtySessionAsync(dir);
await vm.OpenAdHocConPtySessionAsync(dir);
Assert.Equal(2, vm.ConPtySessions.Count);
Assert.Equal(2, vm.Panes.Count);
Assert.All(vm.ConPtySessions, s => Assert.Null(s.TaskId));
}
[Fact]
public async Task OpenAdHocConPtySessionAsync_WorkerThrows_RaisesErrorReported_NoPaneAdded()
{
var worker = new ThrowingAdHocLaunchSpecWorker();
using var vm = BuildVm(worker);
string? error = null;
vm.ErrorReported += msg => error = msg;
await vm.OpenAdHocConPtySessionAsync(Path.Combine("C:", "Some", "Path"));
Assert.Empty(vm.ConPtySessions);
Assert.Empty(vm.Panes);
Assert.NotNull(error);
}
[Fact]
public async Task CloseConPtySession_RemovesAdHocPane_FromConPtySessionsAndPanes()
{
var worker = new FakeWorker();
using var vm = BuildVm(worker);
await vm.OpenAdHocConPtySessionAsync(Path.Combine("C:", "Some", "Path"));
var pane = vm.ConPtySessions[0];
pane.CloseCommand.Execute(null);
Assert.Empty(vm.ConPtySessions);
Assert.Empty(vm.Panes);
}
[Fact]
public void ToggleLayoutCommand_FlipsIsFocusMode()
{