fix(ui): align virtual list semantics and complete planning roll-up coverage
This commit is contained in:
@@ -68,6 +68,15 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
var list = _currentList;
|
var list = _currentList;
|
||||||
if (list is null) return;
|
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
|
try
|
||||||
{
|
{
|
||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
@@ -97,14 +106,15 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
catch { }
|
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
|
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:my-day" => t.IsMyDay,
|
||||||
ListKind.Smart when list.Id == "smart:important" => t.IsStarred,
|
ListKind.Smart when list.Id == "smart:important" => t.IsStarred,
|
||||||
ListKind.Smart when list.Id == "smart:planned" => t.ScheduledFor != null,
|
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:review" => t.Status == TaskStatus.Done && t.Worktree?.State == WorktreeState.Active && t.ParentTaskId == null,
|
||||||
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.User => $"user:{t.ListId}" == list.Id,
|
ListKind.User => $"user:{t.ListId}" == list.Id,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
@@ -170,7 +180,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
ListKind.Virtual when list.Id == "virtual:running" => all.Where(t =>
|
ListKind.Virtual when list.Id == "virtual:running" => all.Where(t =>
|
||||||
(t.Status == TaskStatus.Running && t.ParentTaskId == null) ||
|
(t.Status == TaskStatus.Running && t.ParentTaskId == null) ||
|
||||||
(IsPlanningStatus(t.Status) && all.Any(c => c.ParentTaskId == t.Id && c.Status == TaskStatus.Running))),
|
(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),
|
ListKind.User => all.Where(t => $"user:{t.ListId}" == list.Id),
|
||||||
_ => Enumerable.Empty<TaskEntity>(),
|
_ => Enumerable.Empty<TaskEntity>(),
|
||||||
};
|
};
|
||||||
@@ -213,6 +223,8 @@ public sealed partial class TasksIslandViewModel : ViewModelBase
|
|||||||
foreach (var parent in topLevel)
|
foreach (var parent in topLevel)
|
||||||
{
|
{
|
||||||
flat.Add(parent);
|
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)
|
if ((parent.IsPlanningParent || parent.Done) && parent.IsExpanded)
|
||||||
{
|
{
|
||||||
var children = Items.Where(r => r.ParentTaskId == parent.Id);
|
var children = Items.Where(r => r.ParentTaskId == parent.Id);
|
||||||
|
|||||||
@@ -122,6 +122,22 @@ public class TasksIslandRegroupTests : IDisposable
|
|||||||
Assert.Contains(vm.Items, r => r.Id == "p1" && !r.IsChild);
|
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]
|
[Fact]
|
||||||
public async Task VirtualRunning_RunningChildOfPlanningParent_IsNotStandaloneRow()
|
public async Task VirtualRunning_RunningChildOfPlanningParent_IsNotStandaloneRow()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user