fix(ui): stop the reconcile tick on native close, index rows in the tick

Two follow-ups to the Phase 3 reconcile tick:

- LogVisualizer and WorktreesOverview only stopped their timer in the Close
  command, but ShowLogVisualizerAsync/ShowWorktreesOverviewAsync had no
  dlg.Closed fallback (unlike the MergeHelper picker). An Alt+F4 or owner
  close left a 4s timer polling the worker / re-running LoadAsync on an
  orphaned VM, accumulating one per open. Extracted StopReconcileTick on both
  VMs and wired it from Closed.
- The tick scanned Items linearly per id; at the 500-row cap that is ~250k
  string comparisons every few seconds. Index the rows once instead.
This commit is contained in:
mika kuns
2026-08-10 16:05:37 +02:00
parent 1b7b18fdfc
commit 82881acc70
4 changed files with 25 additions and 4 deletions
@@ -1298,6 +1298,10 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
if (ReconcileTickTestBarrier is { } barrier) await barrier();
var byId = entities.ToDictionary(e => e.Id);
// Index the rows once instead of scanning Items per id — at the 500-row cap a linear
// scan per id is 250k comparisons every few seconds, for nothing.
var rowById = new Dictionary<string, TaskRowViewModel>(Items.Count);
foreach (var r in Items) rowById[r.Id] = r;
foreach (var id in ids)
{
// Superseded by a fresher delta refresh or a later tick that landed while this one
@@ -1305,8 +1309,7 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
if (!_deltaSeq.TryGetValue(id, out var current) || current != seqByTaskId[id]) continue;
if (!byId.TryGetValue(id, out var entity)) continue; // deleted; the delta path removes rows, not the tick
var row = Items.FirstOrDefault(r => r.Id == id);
row?.UpdateFromEntity(entity);
if (rowById.TryGetValue(id, out var row)) row.UpdateFromEntity(entity);
}
}
}