feat(ui): richer diff viewer + surface child roadblocks on parents
All checks were successful
Changelog / changelog (push) Successful in 1s
Release / release (push) Successful in 38s

- UnifiedDiffParser detects added/deleted/renamed/binary files; diff
  modal shows a file list, binary/empty placeholders, and can diff a
  merged task by commit range after its worktree is gone
- DetailsIslandViewModel flags children needing attention (failed,
  cancelled, awaiting review, or with roadblocks) on the parent
- GitService gains worktree head-commit/range support; planning chain,
  merge orchestration, and session manager tweaks with updated tests
- refresh app/installer/worker icons

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-06-09 16:40:59 +02:00
parent c300f8c313
commit f21c65be18
28 changed files with 509 additions and 119 deletions

View File

@@ -121,19 +121,20 @@ public sealed class PlanningEndToEndTests : IDisposable
Assert.Equal(PlanningPhase.Finalized, reload!.PlanningPhase);
var kids = await _tasks.GetChildrenAsync(parent.Id);
// SetupChainAsync auto-attaches agent tag and queues all children;
// the first one is unblocked, the rest are BlockedBy their predecessor.
Assert.Equal(TaskStatus.Queued, kids[0].Status);
// Finalize no longer auto-queues. Children stay Idle and chain-linked
// (head unblocked, rest BlockedBy their predecessor) until the user
// explicitly queues the plan.
Assert.Equal(TaskStatus.Idle, kids[0].Status);
Assert.Null(kids[0].BlockedByTaskId);
Assert.Equal(TaskStatus.Queued, kids[1].Status);
Assert.Equal(TaskStatus.Idle, kids[1].Status);
Assert.Equal(kids[0].Id, kids[1].BlockedByTaskId);
}
// Regression: original bug was "queue never picks up planning tasks". After Finalize
// with queueAgentTasks=true, the first child must be claimable by the queue picker
// automatically — without anyone calling WakeQueue() manually.
// Regression: original bug was "queue never picks up planning tasks". Finalize
// leaves children Idle; queueing the plan (the explicit user gate) must make the
// first child claimable by the picker automatically — without a manual WakeQueue().
[Fact]
public async Task FinalizeAsync_FirstChildIsClaimedByPicker_WithinDeadline()
public async Task QueuePlanAfterFinalize_FirstChildIsClaimedByPicker_WithinDeadline()
{
var listId = Guid.NewGuid().ToString();
var wd = Path.Combine(Path.GetTempPath(), $"cd_e2e_wd_{Guid.NewGuid():N}");
@@ -160,12 +161,18 @@ public sealed class PlanningEndToEndTests : IDisposable
var kidsBefore = await _tasks.GetChildrenAsync(parent.Id);
var firstChildId = kidsBefore[0].Id;
var wakesBefore = _built.WakeCount();
await _manager.FinalizeAsync(parent.Id, queueAgentTasks: true, CancellationToken.None);
// The picker should pick the first child immediately. Auto-wake fires inside
// _state.EnqueueAsync; we don't need a manual WakeQueue() for the bug to be fixed.
// Finalize leaves every child Idle — nothing is claimable yet.
var afterFinalize = await _tasks.GetChildrenAsync(parent.Id);
Assert.All(afterFinalize, k => Assert.Equal(TaskStatus.Idle, k.Status));
// Queueing the plan is the gate that enqueues + auto-wakes. The picker should
// then pick the first child immediately, without a manual WakeQueue().
var wakesBefore = _built.WakeCount();
await _built.Chain.QueuePlanAsync(parent.Id, CancellationToken.None);
var picker = new QueuePicker(_db.CreateFactory());
TaskEntity? claimed = null;
@@ -181,6 +188,6 @@ public sealed class PlanningEndToEndTests : IDisposable
Assert.Equal(firstChildId, claimed!.Id);
Assert.Equal(TaskStatus.Running, claimed.Status);
Assert.True(_built.WakeCount() > wakesBefore,
"TaskStateService.EnqueueAsync should auto-wake the queue.");
"QueuePlanAsync → EnqueueAsync should auto-wake the queue.");
}
}