diff --git a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs index db9fb90..db74847 100644 --- a/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs +++ b/src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs @@ -68,6 +68,15 @@ public sealed partial class TasksIslandViewModel : ViewModelBase var list = _currentList; if (list is null) return; + // virtual:queued / virtual:running include Planning parents whose children match, + // which can't be decided from a single entity. Always full-reload in those cases. + if (list.Kind == ListKind.Virtual && + (list.Id == "virtual:queued" || list.Id == "virtual:running")) + { + LoadForList(list); + return; + } + try { await using var db = await _dbFactory.CreateDbContextAsync(); @@ -97,14 +106,15 @@ public sealed partial class TasksIslandViewModel : ViewModelBase catch { } } + // NOTE: virtual:queued/virtual:running cannot be decided by a single entity — a Planning + // parent matches iff any child has the matching status. OnWorkerTaskUpdated handles those + // lists via a full reload rather than the delta path. private static bool TaskMatchesList(TaskEntity t, ListNavItemViewModel list) => list.Kind switch { ListKind.Smart when list.Id == "smart:my-day" => t.IsMyDay, ListKind.Smart when list.Id == "smart:important" => t.IsStarred, ListKind.Smart when list.Id == "smart:planned" => t.ScheduledFor != null, - ListKind.Virtual when list.Id == "virtual:queued" => t.Status == TaskStatus.Queued, - ListKind.Virtual when list.Id == "virtual:running" => t.Status == TaskStatus.Running, - ListKind.Virtual when list.Id == "virtual:review" => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active, + ListKind.Virtual when list.Id == "virtual:review" => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active && t.ParentTaskId == null, ListKind.User => $"user:{t.ListId}" == list.Id, _ => false, }; @@ -170,7 +180,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase ListKind.Virtual when list.Id == "virtual:running" => all.Where(t => (t.Status == TaskStatus.Running && t.ParentTaskId == null) || (IsPlanningStatus(t.Status) && all.Any(c => c.ParentTaskId == t.Id && c.Status == TaskStatus.Running))), - ListKind.Virtual when list.Id == "virtual:review" => all.Where(t => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active), + ListKind.Virtual when list.Id == "virtual:review" => all.Where(t => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active && t.ParentTaskId == null), ListKind.User => all.Where(t => $"user:{t.ListId}" == list.Id), _ => Enumerable.Empty(), }; @@ -213,6 +223,8 @@ public sealed partial class TasksIslandViewModel : ViewModelBase foreach (var parent in topLevel) { flat.Add(parent); + // Also expand for Done parents so their (Done) children reach the classification + // loop and land in CompletedItems alongside the parent. if ((parent.IsPlanningParent || parent.Done) && parent.IsExpanded) { var children = Items.Where(r => r.ParentTaskId == parent.Id); diff --git a/tests/ClaudeDo.Ui.Tests/ViewModels/TasksIslandRegroupTests.cs b/tests/ClaudeDo.Ui.Tests/ViewModels/TasksIslandRegroupTests.cs index 08ec6bf..eb436a9 100644 --- a/tests/ClaudeDo.Ui.Tests/ViewModels/TasksIslandRegroupTests.cs +++ b/tests/ClaudeDo.Ui.Tests/ViewModels/TasksIslandRegroupTests.cs @@ -122,6 +122,22 @@ public class TasksIslandRegroupTests : IDisposable Assert.Contains(vm.Items, r => r.Id == "p1" && !r.IsChild); } + [Fact] + public async Task VirtualQueued_PlannedParentWithQueuedChild_ParentIsStandaloneRow_ChildIsNot() + { + await SeedPlanningWithChildAsync( + parentStatus: TaskStatus.Planned, + childStatus: TaskStatus.Queued, + parentId: "p1", + childId: "c1"); + + var vm = BuildViewModel(); + await LoadAndWaitAsync(vm, VirtualList("virtual:queued", "Queued")); + + Assert.Contains(vm.Items, r => r.Id == "p1" && !r.IsChild); + Assert.DoesNotContain(vm.Items, r => r.Id == "c1" && !r.IsChild); + } + [Fact] public async Task VirtualRunning_RunningChildOfPlanningParent_IsNotStandaloneRow() {