Merge claudedo/1891227787ae4085a52c112bd1dcd75a

This commit is contained in:
mika kuns
2026-08-10 15:05:53 +02:00
13 changed files with 259 additions and 100 deletions
@@ -45,6 +45,10 @@ public sealed partial class TaskRowViewModel : ViewModelBase
// Set by the custom drag while this row is being dragged — drives the "grabbed" row style.
[ObservableProperty] private bool _isDragging;
// True while a drag is hovering this row (i.e. it would show a drop-hint gap). Used to
// suppress the ordinary hover highlight/transitions so they don't fight the hint.
public bool IsDropTarget => DropHintAbove || DropHintBelow;
public bool CanRefine => Status == TaskStatus.Idle && PlanningPhase == PlanningPhase.None
&& !IsRefining && !IsManual;
@@ -289,6 +293,8 @@ public sealed partial class TaskRowViewModel : ViewModelBase
partial void OnDiffAdditionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffAdditionsText)); }
partial void OnDiffDeletionsChanged(int value) { OnPropertyChanged(nameof(HasDiff)); OnPropertyChanged(nameof(DiffDeletionsText)); }
partial void OnRoadblockCountChanged(int value) { OnPropertyChanged(nameof(HasRoadblock)); OnPropertyChanged(nameof(RoadblockTooltip)); }
partial void OnDropHintAboveChanged(bool value) => OnPropertyChanged(nameof(IsDropTarget));
partial void OnDropHintBelowChanged(bool value) => OnPropertyChanged(nameof(IsDropTarget));
public void RefreshLocalized()
{
@@ -336,26 +336,53 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
try
{
await using var db = await _dbFactory.CreateDbContextAsync(ct);
var all = await db.Tasks
.Include(t => t.List)
.Include(t => t.Worktree)
.OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt)
.ToListAsync(ct);
var filter = _filters.Resolve(list.Id);
var baseQuery = db.Tasks.Include(t => t.List).Include(t => t.Worktree);
var filteredList = filter is null
? new List<TaskEntity>()
: await baseQuery.Where(filter.MatchExpression).ToListAsync(ct);
ct.ThrowIfCancellationRequested();
var filter = _filters.Resolve(list.Id);
var filteredList = filter is null
? new List<TaskEntity>()
: all.Where(t => filter.Matches(t) || filter.MatchesAsContext(t, all)).ToList();
var topIds = filteredList.Where(t => t.ParentTaskId == null).Select(t => t.Id).ToHashSet();
var existingIds = filteredList.Select(t => t.Id).ToHashSet();
foreach (var c in all.Where(t => t.ParentTaskId != null && topIds.Contains(t.ParentTaskId!)))
if (filter is not null)
{
if (existingIds.Add(c.Id))
filteredList.Add(c);
// Contextual parent rows (e.g. a planning parent hosting an already-matched queued/running
// child): only fetch candidates that could plausibly qualify — parents of tasks we already
// matched — instead of scanning the whole table.
var candidateParentIds = filteredList
.Where(t => t.ParentTaskId != null)
.Select(t => t.ParentTaskId!)
.Distinct()
.ToList();
if (candidateParentIds.Count > 0)
{
var existingParentIds = filteredList.Select(t => t.Id).ToHashSet();
var parentCandidates = await baseQuery
.Where(t => candidateParentIds.Contains(t.Id) && !existingParentIds.Contains(t.Id))
.ToListAsync(ct);
foreach (var p in parentCandidates)
if (filter.MatchesAsContext(p, filteredList))
filteredList.Add(p);
}
}
ct.ThrowIfCancellationRequested();
// Pull in every child of an already-matched top-level row, regardless of whether the child
// itself matches the filter, so subtasks render nested under their parent.
var topIds = filteredList.Where(t => t.ParentTaskId == null).Select(t => t.Id).ToHashSet();
if (topIds.Count > 0)
{
var existingIds = filteredList.Select(t => t.Id).ToHashSet();
var extraChildren = await baseQuery
.Where(t => t.ParentTaskId != null && topIds.Contains(t.ParentTaskId!) && !existingIds.Contains(t.Id))
.ToListAsync(ct);
filteredList.AddRange(extraChildren);
}
filteredList = filteredList.OrderBy(t => t.SortOrder).ThenBy(t => t.CreatedAt).ToList();
var showListChip = list.Kind == ListKind.Virtual;
foreach (var t in filteredList)
{