fix(ui): gate Mission Control submit-for-review, add retry, fix event leak

Submit for Review is now disabled while a ConPTY pane is starting, has
failed to launch, or has already exited, and MissionControlViewModel
guards against a rapid double-click racing two SubmitTaskForReviewAsync
calls. A failed launch no longer permanently occupies its TaskId dedupe
slot -- a Retry button re-fetches the launch spec and restarts the pane
in place. CloseConPtySession/Dispose now also unsubscribe
SubmitForReviewRequested, matching the other pane event handlers. Also
fixes the pre-existing nullable-dereference warning in
IslandsShellViewModel.SyncInteractiveSessionChips.
This commit is contained in:
mika kuns
2026-08-06 13:42:06 +02:00
parent 0d1e3b9a6f
commit 57c61a2043
9 changed files with 299 additions and 8 deletions
@@ -20,7 +20,11 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl
[ObservableProperty] private string _displayTitle;
public InteractiveTerminalViewModel Terminal { get; } = new();
[ObservableProperty] private InteractiveTerminalViewModel _terminal = new();
/// <summary>Set by the host (Mission Control) while a submit-for-review round trip is in
/// flight, so a rapid double-click can't race two submissions for the same task.</summary>
[ObservableProperty] private bool _isSubmitPending;
/// <summary>Raised when the terminal failed to start — the host surfaces this via the footer error strip.</summary>
public event Action<string>? ErrorReported;
@@ -76,12 +80,21 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl
{
if (e.PropertyName == nameof(InteractiveTerminalViewModel.StartError) && Terminal.StartError is { Length: > 0 } error)
ErrorReported?.Invoke(error);
if (e.PropertyName is nameof(InteractiveTerminalViewModel.IsStarting)
or nameof(InteractiveTerminalViewModel.StartError)
or nameof(InteractiveTerminalViewModel.HasExited))
{
SubmitForReviewCommand.NotifyCanExecuteChanged();
RetryCommand.NotifyCanExecuteChanged();
}
}
[RelayCommand]
private void Close() => CloseRequested?.Invoke(this);
private bool CanSubmitForReview() => IsTaskBased;
private bool CanSubmitForReview() =>
IsTaskBased && !IsSubmitPending && !Terminal.IsStarting && Terminal.StartError is null && !Terminal.HasExited;
[RelayCommand(CanExecute = nameof(CanSubmitForReview))]
private void SubmitForReview()
@@ -89,6 +102,22 @@ public sealed partial class ConPtyPaneViewModel : ViewModelBase, IMissionControl
if (TaskId is { } id) SubmitForReviewRequested?.Invoke(id);
}
partial void OnIsSubmitPendingChanged(bool value) => SubmitForReviewCommand.NotifyCanExecuteChanged();
// A launch failure permanently occupies the TaskId dedupe slot unless the user can retry —
// re-opening the same task would otherwise just re-focus a dead tile.
private bool CanRetry() => Terminal.HasExited && Terminal.StartError is not null;
[RelayCommand(CanExecute = nameof(CanRetry))]
private void Retry()
{
Terminal.PropertyChanged -= OnTerminalPropertyChanged;
Terminal.Dispose();
Terminal = new InteractiveTerminalViewModel();
Terminal.PropertyChanged += OnTerminalPropertyChanged;
Start();
}
public void Dispose()
{
Terminal.PropertyChanged -= OnTerminalPropertyChanged;