refactor(ui): flatten TasksIsland's task list into one virtualized ListBox
Replace the three ItemsControl sections (Overdue/Open/Completed) with a single flat Rows collection (HeaderRow | TaskRowViewModel) bound to a ListBox + VirtualizingStackPanel, so the realized container count stays bounded instead of growing with the list (measured: ~7-8 realized containers at any scroll position with 1000 source rows). Group headers become regular row entries, each section is simply omitted from Rows when empty, and the Completed section's Clear-completed action moves onto the header row itself. SectionFor/ReorderAsync/FindNextInSameSection now read a row's section off the nearest preceding HeaderRow in Rows instead of three separate bound collections, and ScrollSelectedIntoView goes through ListBox.ScrollIntoView so it still works for rows outside the realized window. Drag/reorder behavior, grouping/counts, and the Clear-completed button are unchanged. Auto-scroll-while-dragging and the cross-section reorder bug are explicitly out of scope (follow-up tasks).
This commit is contained in:
@@ -211,6 +211,19 @@ public class TasksIslandViewModelPlanningTests
|
||||
PlanningPhase phase = PlanningPhase.None)
|
||||
=> new TaskRowViewModel { Id = id, Status = status, ParentTaskId = parentId, PlanningPhase = phase };
|
||||
|
||||
// vm.Rows is the flat HeaderRow|TaskRowViewModel union — a row is "in the Open section" when
|
||||
// it's present in Rows and the nearest preceding HeaderRow (if any) isn't Overdue/Completed.
|
||||
// HasAction uniquely identifies the Completed header (its Clear-completed button).
|
||||
private static bool IsVisibleInOpen(TasksIslandViewModel vm, TaskRowViewModel row)
|
||||
{
|
||||
var idx = vm.Rows.IndexOf(row);
|
||||
if (idx < 0) return false;
|
||||
for (int i = idx - 1; i >= 0; i--)
|
||||
if (vm.Rows[i] is HeaderRow h)
|
||||
return !h.IsOverdue && !h.HasAction;
|
||||
return true;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanningParentWithChildren_CollapsedByDefault_ToggleExpands()
|
||||
{
|
||||
@@ -221,16 +234,16 @@ public class TasksIslandViewModelPlanningTests
|
||||
var (vm, _) = VmFactory.Create([parent, child1, child2]);
|
||||
|
||||
// Collapsed by default — children hidden, parent still present
|
||||
Assert.DoesNotContain(child1, vm.OpenItems);
|
||||
Assert.DoesNotContain(child2, vm.OpenItems);
|
||||
Assert.Contains(parent, vm.OpenItems);
|
||||
Assert.False(IsVisibleInOpen(vm, child1));
|
||||
Assert.False(IsVisibleInOpen(vm, child2));
|
||||
Assert.True(IsVisibleInOpen(vm, parent));
|
||||
|
||||
// Expand the parent
|
||||
vm.ToggleExpandCommand.Execute(parent);
|
||||
|
||||
// Children now visible
|
||||
Assert.Contains(child1, vm.OpenItems);
|
||||
Assert.Contains(child2, vm.OpenItems);
|
||||
Assert.True(IsVisibleInOpen(vm, child1));
|
||||
Assert.True(IsVisibleInOpen(vm, child2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -268,15 +281,15 @@ public class TasksIslandViewModelPlanningTests
|
||||
var (vm, _) = VmFactory.Create([parent, child]);
|
||||
|
||||
// Collapsed by default
|
||||
Assert.DoesNotContain(child, vm.OpenItems);
|
||||
Assert.False(IsVisibleInOpen(vm, child));
|
||||
|
||||
// Expand
|
||||
vm.ToggleExpandCommand.Execute(parent);
|
||||
Assert.Contains(child, vm.OpenItems);
|
||||
Assert.True(IsVisibleInOpen(vm, child));
|
||||
|
||||
// Collapse again
|
||||
vm.ToggleExpandCommand.Execute(parent);
|
||||
Assert.DoesNotContain(child, vm.OpenItems);
|
||||
Assert.False(IsVisibleInOpen(vm, child));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -291,7 +304,7 @@ public class TasksIslandViewModelPlanningTests
|
||||
Assert.False(orphan.ParentInView);
|
||||
Assert.False(orphan.ShowAsChild);
|
||||
Assert.False(orphan.IsDraft);
|
||||
Assert.Contains(orphan, vm.OpenItems);
|
||||
Assert.True(IsVisibleInOpen(vm, orphan));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user