diff --git a/src/ClaudeDo.Localization/locales/de.json b/src/ClaudeDo.Localization/locales/de.json index 76d879b..dc1c290 100644 --- a/src/ClaudeDo.Localization/locales/de.json +++ b/src/ClaudeDo.Localization/locales/de.json @@ -237,7 +237,8 @@ "send": "Senden", "stop": "Sitzung beenden", "interrupt": "Aktuellen Zug unterbrechen", - "queued": "Wartet — wird nach dem aktuellen Zug gesendet" + "queued": "Wartet — wird nach dem aktuellen Zug gesendet", + "unqueue": "Aus Warteschlange entfernen" } }, "missionControl": { diff --git a/src/ClaudeDo.Localization/locales/en.json b/src/ClaudeDo.Localization/locales/en.json index 05156e6..679d641 100644 --- a/src/ClaudeDo.Localization/locales/en.json +++ b/src/ClaudeDo.Localization/locales/en.json @@ -237,7 +237,8 @@ "send": "Send", "stop": "Stop session", "interrupt": "Interrupt current turn", - "queued": "Queued — sends after the current turn" + "queued": "Queued — sends after the current turn", + "unqueue": "Remove from queue" } }, "missionControl": { diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs index 1762275..1baa1a9 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TaskMonitorViewModel.cs @@ -70,7 +70,7 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable private const string RoadblockMarker = "Roadblocks reported during the run:"; - public ObservableCollection QueuedMessages { get; } = new(); + public ObservableCollection QueuedMessages { get; } = new(); public bool HasQueuedMessages => QueuedMessages.Count > 0; // Captured handler delegates for disposal @@ -181,7 +181,15 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable { if (taskId != _subscribedTaskId) return; QueuedMessages.Clear(); - foreach (var m in pending) QueuedMessages.Add(m); + foreach (var m in pending) + { + var text = m; + QueuedMessages.Add(new QueuedMessageViewModel + { + Text = text, + RemoveCommand = new CommunityToolkit.Mvvm.Input.RelayCommand(() => _ = RemoveQueuedAsync(text)), + }); + } OnPropertyChanged(nameof(HasQueuedMessages)); }; _worker.InteractiveQueueChangedEvent += _onInteractiveQueueChanged; @@ -234,6 +242,12 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable await _worker.InterruptInteractiveSessionAsync(_subscribedTaskId); } + private async System.Threading.Tasks.Task RemoveQueuedAsync(string text) + { + if (!string.IsNullOrEmpty(_subscribedTaskId)) + await _worker.RemoveQueuedInteractiveMessageAsync(_subscribedTaskId, text); + } + private void ClearPendingQuestion() { PendingQuestionId = null; @@ -515,3 +529,9 @@ public sealed partial class TaskMonitorViewModel : ViewModelBase, IDisposable _worker.InteractiveMessageSentEvent -= _onInteractiveMessageSent; } } + +public sealed class QueuedMessageViewModel +{ + public required string Text { get; init; } + public required System.Windows.Input.ICommand RemoveCommand { get; init; } +} diff --git a/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml index 59cd30e..24bb82a 100644 --- a/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/Detail/WorkConsole.axaml @@ -276,19 +276,30 @@ Foreground="{DynamicResource TextMuteBrush}" /> - - - + + - + - + TextTrimming="CharacterEllipsis" + VerticalAlignment="Center" /> + + diff --git a/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml b/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml index 7877c53..6190830 100644 --- a/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml +++ b/src/ClaudeDo.Ui/Views/Islands/SessionTerminalView.axaml @@ -64,19 +64,30 @@ Foreground="{DynamicResource TextMuteBrush}" /> - - - + + - + - + TextTrimming="CharacterEllipsis" + VerticalAlignment="Center" /> + + diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/TaskMonitorViewModelTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/TaskMonitorViewModelTests.cs index b16ebfd..7e5c9d2 100644 --- a/tests/ClaudeDo.Ui.Tests/ViewModels/TaskMonitorViewModelTests.cs +++ b/tests/ClaudeDo.Ui.Tests/ViewModels/TaskMonitorViewModelTests.cs @@ -333,6 +333,8 @@ public class TaskMonitorViewModelTests : IDisposable worker.RaiseInteractiveQueueChanged("t1", new[] { "msg1", "msg2" }); Assert.Equal(2, vm.QueuedMessages.Count); + Assert.Equal("msg1", vm.QueuedMessages[0].Text); + Assert.Equal("msg2", vm.QueuedMessages[1].Text); Assert.True(vm.HasQueuedMessages); } @@ -378,6 +380,22 @@ public class TaskMonitorViewModelTests : IDisposable Assert.False(vm.HasQueuedMessages); } + [Fact] + public async Task QueuedMessageViewModel_RemoveCommand_RecordsRemoveCall() + { + var worker = new FakeWorker(); + using var vm = Build(worker); + vm.SetTaskId("t1"); + worker.RaiseInteractiveQueueChanged("t1", new[] { "a", "b" }); + + vm.QueuedMessages[0].RemoveCommand.Execute(null); + // RemoveQueuedAsync is fire-and-forget; yield to let the async continuation run + await System.Threading.Tasks.Task.Yield(); + + Assert.Single(worker.RemovedQueued); + Assert.Equal(("t1", "a"), worker.RemovedQueued[0]); + } + [Fact] public async Task InterruptInteractiveCommand_WhenLive_RecordsOneCall() {