fix(ui): block delete on interactive-session tasks, clear ghost selection

Delete (row context menu + detail header) now gates on HasInteractiveSession
the same way SendToQueue already does, so an open ConPTY session's claude
process can't be orphaned by deleting its task out from under it.

Row removal (in-UI delete and the delta-refresh path for externally deleted
tasks) now clears SelectedTask via SelectFrom(null, "row-removed") when the
removed row was selected, so the detail pane no longer keeps showing a
deleted task with live buttons.
This commit is contained in:
Mika Kuns
2026-08-26 15:46:40 +02:00
parent bce0c9d897
commit c1ab042d0a
6 changed files with 159 additions and 5 deletions
@@ -219,6 +219,13 @@ public sealed partial class TaskRowViewModel : ViewModelBase
}
public string? PlanningDisabledReason => PlanningGate().Reason;
// Deleting out from under a live ConPTY session would orphan the claude process in Mission
// Control (it never gets a task-deleted signal) — same rationale as SendToQueueGate.
private (bool Can, string? Reason) DeleteGate() =>
HasInteractiveSession ? (false, Loc.T("tasks.reasonInteractiveSession")) : (true, null);
public bool CanDeleteTask => DeleteGate().Can;
public string? DeleteDisabledReason => DeleteGate().Reason;
public bool HasSchedule => ScheduledFor.HasValue;
// "Add to My Day" — shown on any task not already in My Day; a Done task has no place in
// today's focus list. The mirror of "Remove from My Day" (gated on IsMyDay).
@@ -402,6 +409,8 @@ public sealed partial class TaskRowViewModel : ViewModelBase
OnPropertyChanged(nameof(StatusChipTooltip));
OnPropertyChanged(nameof(CanSendToQueue));
OnPropertyChanged(nameof(SendToQueueDisabledReason));
OnPropertyChanged(nameof(CanDeleteTask));
OnPropertyChanged(nameof(DeleteDisabledReason));
}
partial void OnFailureReasonChanged(string? value)