feat(ui): open merge-helper ConPTY tile from selection
This commit is contained in:
@@ -268,6 +268,7 @@
|
|||||||
"overviewMode": "Übersicht",
|
"overviewMode": "Übersicht",
|
||||||
"closeSession": "Sitzung schließen",
|
"closeSession": "Sitzung schließen",
|
||||||
"conptyLaunchFailed": "ConPTY-Sitzung konnte nicht geöffnet werden: {0}",
|
"conptyLaunchFailed": "ConPTY-Sitzung konnte nicht geöffnet werden: {0}",
|
||||||
|
"mergeHelperTitle": "Merge-Helfer",
|
||||||
"submitForReviewFailed": "Einreichen zum Review fehlgeschlagen: {0}",
|
"submitForReviewFailed": "Einreichen zum Review fehlgeschlagen: {0}",
|
||||||
"submitForReview": "Zum Review einreichen",
|
"submitForReview": "Zum Review einreichen",
|
||||||
"submitForReviewTip": "Diesen Worktree committen und den Task ins Review bringen, damit er gemergt werden kann",
|
"submitForReviewTip": "Diesen Worktree committen und den Task ins Review bringen, damit er gemergt werden kann",
|
||||||
|
|||||||
@@ -268,6 +268,7 @@
|
|||||||
"overviewMode": "Overview",
|
"overviewMode": "Overview",
|
||||||
"closeSession": "Close session",
|
"closeSession": "Close session",
|
||||||
"conptyLaunchFailed": "Couldn't open ConPTY session: {0}",
|
"conptyLaunchFailed": "Couldn't open ConPTY session: {0}",
|
||||||
|
"mergeHelperTitle": "Merge Helper",
|
||||||
"submitForReviewFailed": "Couldn't submit for review: {0}",
|
"submitForReviewFailed": "Couldn't submit for review: {0}",
|
||||||
"submitForReview": "Submit for review",
|
"submitForReview": "Submit for review",
|
||||||
"submitForReviewTip": "Commit this worktree and move the task to review so it can be merged",
|
"submitForReviewTip": "Commit this worktree and move the task to review so it can be merged",
|
||||||
|
|||||||
@@ -238,6 +238,11 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
|||||||
OpenMissionControl();
|
OpenMissionControl();
|
||||||
_ = MissionControl.OpenPlanningConPtySessionAsync(taskId, resume);
|
_ = MissionControl.OpenPlanningConPtySessionAsync(taskId, resume);
|
||||||
};
|
};
|
||||||
|
Lists.LetClaudeHandleRequested += req =>
|
||||||
|
{
|
||||||
|
OpenMissionControl();
|
||||||
|
_ = MissionControl.OpenMergeHelperConPtySessionAsync(req.ListId, req.TaskIds);
|
||||||
|
};
|
||||||
Tasks.TasksChanged += (_, _) => _ = Lists.RefreshCountsAsync();
|
Tasks.TasksChanged += (_, _) => _ = Lists.RefreshCountsAsync();
|
||||||
Tasks.OpenListSettingsRequested += (_, _) =>
|
Tasks.OpenListSettingsRequested += (_, _) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -322,6 +322,40 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge-helper session over a hand-picked set of tasks ("Let Claude handle it").
|
||||||
|
// Ad-hoc style: no owning task, never deduped — every run opens a fresh pane.
|
||||||
|
public async System.Threading.Tasks.Task OpenMergeHelperConPtySessionAsync(string? listId, IReadOnlyList<string> taskIds)
|
||||||
|
{
|
||||||
|
if (taskIds is not { Count: > 0 }) return;
|
||||||
|
|
||||||
|
var title = Loc.T("missionControl.mergeHelperTitle");
|
||||||
|
if (listId is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var ctx = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var list = await ctx.Lists.AsNoTracking().FirstOrDefaultAsync(l => l.Id == listId);
|
||||||
|
if (list?.Name is { Length: > 0 } name) title = $"{title} — {name}";
|
||||||
|
}
|
||||||
|
catch { /* best-effort title lookup */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var spec = await _worker.GetMergeHelperLaunchSpecAsync(taskIds, listId);
|
||||||
|
var descriptor = new TerminalLaunchDescriptor(spec.Cwd, spec.Exe, spec.Args, spec.Env);
|
||||||
|
var pane = ConPtyPaneViewModel.CreateAdHoc(title, descriptor);
|
||||||
|
pane.ErrorReported += OnConPtyPaneError;
|
||||||
|
pane.CloseRequested += CloseConPtySession;
|
||||||
|
pane.SubmitForReviewRequested += OnPaneSubmitForReview;
|
||||||
|
ConPtySessions.Add(pane);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorReported?.Invoke(Loc.T("missionControl.conptyLaunchFailed", ex.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnConPtyPaneError(string message) => ErrorReported?.Invoke(message);
|
private void OnConPtyPaneError(string message) => ErrorReported?.Invoke(message);
|
||||||
|
|
||||||
// Submit a task's hand-driven ConPTY work for review, then close the pane (the interactive
|
// Submit a task's hand-driven ConPTY work for review, then close the pane (the interactive
|
||||||
|
|||||||
@@ -406,6 +406,65 @@ public class MissionControlViewModelTests : IDisposable
|
|||||||
Assert.Empty(vm.Panes);
|
Assert.Empty(vm.Panes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class ThrowingMergeHelperLaunchSpecWorker : StubWorkerClient
|
||||||
|
{
|
||||||
|
public override Task<LaunchSpec> GetMergeHelperLaunchSpecAsync(IReadOnlyList<string> taskIds, string? listId, CancellationToken ct = default)
|
||||||
|
=> throw new InvalidOperationException("spec failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OpenMergeHelperConPtySessionAsync_AddsAdHocPane()
|
||||||
|
{
|
||||||
|
var worker = new FakeWorker();
|
||||||
|
using var vm = BuildVm(worker);
|
||||||
|
|
||||||
|
await vm.OpenMergeHelperConPtySessionAsync(null, new[] { "t1", "t2" });
|
||||||
|
|
||||||
|
Assert.Single(vm.ConPtySessions);
|
||||||
|
Assert.Null(vm.ConPtySessions[0].TaskId);
|
||||||
|
Assert.Single(vm.Panes);
|
||||||
|
Assert.Same(vm.ConPtySessions[0], vm.Panes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OpenMergeHelperConPtySessionAsync_TwoRuns_NeverDeduped()
|
||||||
|
{
|
||||||
|
var worker = new FakeWorker();
|
||||||
|
using var vm = BuildVm(worker);
|
||||||
|
|
||||||
|
await vm.OpenMergeHelperConPtySessionAsync(null, new[] { "t1" });
|
||||||
|
await vm.OpenMergeHelperConPtySessionAsync(null, new[] { "t1" });
|
||||||
|
|
||||||
|
Assert.Equal(2, vm.ConPtySessions.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OpenMergeHelperConPtySessionAsync_EmptySelection_NoPane()
|
||||||
|
{
|
||||||
|
var worker = new FakeWorker();
|
||||||
|
using var vm = BuildVm(worker);
|
||||||
|
|
||||||
|
await vm.OpenMergeHelperConPtySessionAsync(null, Array.Empty<string>());
|
||||||
|
|
||||||
|
Assert.Empty(vm.ConPtySessions);
|
||||||
|
Assert.Empty(vm.Panes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OpenMergeHelperConPtySessionAsync_WorkerThrows_RaisesErrorReported_NoPaneAdded()
|
||||||
|
{
|
||||||
|
var worker = new ThrowingMergeHelperLaunchSpecWorker();
|
||||||
|
using var vm = BuildVm(worker);
|
||||||
|
string? error = null;
|
||||||
|
vm.ErrorReported += msg => error = msg;
|
||||||
|
|
||||||
|
await vm.OpenMergeHelperConPtySessionAsync(null, new[] { "t1" });
|
||||||
|
|
||||||
|
Assert.Empty(vm.ConPtySessions);
|
||||||
|
Assert.Empty(vm.Panes);
|
||||||
|
Assert.NotNull(error);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ToggleLayoutCommand_FlipsIsFocusMode()
|
public void ToggleLayoutCommand_FlipsIsFocusMode()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user