diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
index d45a04f..d2e59ef 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs
@@ -853,7 +853,8 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase
}
private bool CanEnqueue() =>
- Task != null && _worker.IsConnected && IsIdle;
+ Task != null && _worker.IsConnected && IsIdle
+ && (!Task.IsChild || Task.ParentFinalized);
[RelayCommand(CanExecute = nameof(CanDequeue))]
private async System.Threading.Tasks.Task DequeueAsync()
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs
index a4b8ce0..c4e9bb2 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs
@@ -29,6 +29,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
[ObservableProperty] private bool _hasPlanningChildren;
[ObservableProperty] private bool _hasQueuedSubtasks;
[ObservableProperty] private bool _showListChip = true;
+ [ObservableProperty] private bool _parentFinalized;
public DateTime CreatedAt { get; init; }
public string CreatedAtFormatted => CreatedAt == default ? "—" : $"Created {CreatedAt:MMM d}";
@@ -39,7 +40,9 @@ public sealed partial class TaskRowViewModel : ViewModelBase
public bool IsChild => !string.IsNullOrEmpty(ParentTaskId);
public bool IsPlanningParent => PlanningPhase != PlanningPhase.None
|| HasPlanningChildren;
- public bool IsDraft => IsChild && Status == TaskStatus.Idle;
+ // A subtask is Draft until its planning parent is finalized, then Planned (queueable).
+ public bool IsDraft => IsChild && Status == TaskStatus.Idle && !ParentFinalized;
+ public bool IsPlanned => IsChild && Status == TaskStatus.Idle && ParentFinalized;
public bool CanOpenPlanningSession => Status == TaskStatus.Idle
&& PlanningPhase == PlanningPhase.None
@@ -61,7 +64,12 @@ public sealed partial class TaskRowViewModel : ViewModelBase
public bool IsQueued => Status == TaskStatus.Queued && string.IsNullOrEmpty(BlockedByTaskId);
public bool IsWaiting => Status == TaskStatus.Queued && !string.IsNullOrEmpty(BlockedByTaskId);
public bool CanRemoveFromQueue => IsQueued || HasQueuedSubtasks;
- public bool CanSendToQueue => !IsRunning && !IsQueued && !HasQueuedSubtasks;
+ public bool CanSendToQueue => !IsRunning && !IsQueued && !HasQueuedSubtasks
+ && (!IsChild || ParentFinalized);
+ // Parent-level "send plan to queue" — only once the plan is finalized (children Planned).
+ public bool CanQueuePlan => !IsChild && HasPlanningChildren
+ && PlanningPhase == PlanningPhase.Finalized
+ && !HasQueuedSubtasks;
public bool HasSchedule => ScheduledFor.HasValue;
public bool HasLiveTail => IsRunning && !string.IsNullOrEmpty(LiveTail);
@@ -87,6 +95,7 @@ public sealed partial class TaskRowViewModel : ViewModelBase
OnPropertyChanged(nameof(IsWaiting));
OnPropertyChanged(nameof(HasLiveTail));
OnPropertyChanged(nameof(IsDraft));
+ OnPropertyChanged(nameof(IsPlanned));
OnPropertyChanged(nameof(CanOpenPlanningSession));
OnPropertyChanged(nameof(CanRemoveFromQueue));
OnPropertyChanged(nameof(CanSendToQueue));
@@ -96,21 +105,32 @@ public sealed partial class TaskRowViewModel : ViewModelBase
{
OnPropertyChanged(nameof(IsChild));
OnPropertyChanged(nameof(IsDraft));
+ OnPropertyChanged(nameof(IsPlanned));
+ OnPropertyChanged(nameof(CanSendToQueue));
OnPropertyChanged(nameof(CanOpenPlanningSession));
}
+ partial void OnParentFinalizedChanged(bool value)
+ {
+ OnPropertyChanged(nameof(IsDraft));
+ OnPropertyChanged(nameof(IsPlanned));
+ OnPropertyChanged(nameof(CanSendToQueue));
+ }
+
partial void OnPlanningPhaseChanged(PlanningPhase value)
{
OnPropertyChanged(nameof(IsPlanningParent));
OnPropertyChanged(nameof(PlanningBadge));
OnPropertyChanged(nameof(CanOpenPlanningSession));
OnPropertyChanged(nameof(CanResumeOrDiscardPlanning));
+ OnPropertyChanged(nameof(CanQueuePlan));
}
partial void OnHasQueuedSubtasksChanged(bool value)
{
OnPropertyChanged(nameof(CanRemoveFromQueue));
OnPropertyChanged(nameof(CanSendToQueue));
+ OnPropertyChanged(nameof(CanQueuePlan));
}
partial void OnBlockedByTaskIdChanged(string? value)
@@ -121,7 +141,10 @@ public sealed partial class TaskRowViewModel : ViewModelBase
}
partial void OnHasPlanningChildrenChanged(bool value)
- => OnPropertyChanged(nameof(IsPlanningParent));
+ {
+ OnPropertyChanged(nameof(IsPlanningParent));
+ OnPropertyChanged(nameof(CanQueuePlan));
+ }
partial void OnBranchChanged(string? value) => OnPropertyChanged(nameof(HasBranch));
partial void OnLiveTailChanged(string? value) => OnPropertyChanged(nameof(HasLiveTail));
diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs
index 0108fae..3f7cde2 100644
--- a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs
+++ b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs
@@ -244,6 +244,16 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
foreach (var r in Items)
r.HasQueuedSubtasks = parentsWithQueuedKids.Contains(r.Id);
+ // A subtask is "Planned" (queueable) once its planning parent is finalized;
+ // until then it is a "Draft".
+ var finalizedParents = Items
+ .Where(r => r.PlanningPhase == PlanningPhase.Finalized)
+ .Select(r => r.Id)
+ .ToHashSet();
+ foreach (var r in Items)
+ r.ParentFinalized = !string.IsNullOrEmpty(r.ParentTaskId)
+ && finalizedParents.Contains(r.ParentTaskId!);
+
Regroup();
UpdateSubtitle();
}
@@ -645,7 +655,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
await _worker.ResumePlanningSessionAsync(row.Id);
break;
case UnfinishedPlanningModalResult.FinalizeNow:
- await _worker.FinalizePlanningSessionAsync(row.Id);
+ await _worker.FinalizePlanningSessionAsync(row.Id, queueAgentTasks: false);
break;
case UnfinishedPlanningModalResult.Discard:
await TryDiscardPlanningWithRetryAsync(row.Id);
@@ -713,7 +723,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
private async Task FinalizePlanningSessionAsync(TaskRowViewModel? row)
{
if (row is null) return;
- try { await _worker!.FinalizePlanningSessionAsync(row.Id, queueAgentTasks: true); }
+ try { await _worker!.FinalizePlanningSessionAsync(row.Id, queueAgentTasks: false); }
catch { }
}
diff --git a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
index 3a07fdb..53a3119 100644
--- a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
+++ b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
@@ -60,7 +60,7 @@
IsVisible="{Binding CanResumeOrDiscardPlanning}"/>
+ IsVisible="{Binding CanQueuePlan}"/>