feat(ui): richer diff viewer + surface child roadblocks on parents
- 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:
@@ -75,7 +75,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
{
|
||||
await SeedPlanningFamilyAsync("P", 3);
|
||||
|
||||
var count = await _sut.SetupChainAsync("P", default);
|
||||
var count = await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
Assert.Equal(3, count);
|
||||
var kids = await GetChildrenAsync("P");
|
||||
@@ -92,7 +92,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
{
|
||||
await SeedPlanningFamilyAsync("P", 2, childStatus: TaskStatus.Idle);
|
||||
|
||||
var count = await _sut.SetupChainAsync("P", default);
|
||||
var count = await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
Assert.Equal(2, count);
|
||||
}
|
||||
@@ -101,7 +101,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
public async Task OnChildDone_UnblocksTheSuccessor()
|
||||
{
|
||||
await SeedPlanningFamilyAsync("P", 3);
|
||||
await _sut.SetupChainAsync("P", default);
|
||||
await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
// Mark the head child Done before announcing.
|
||||
await using (var ctx = _factory.CreateDbContext())
|
||||
@@ -128,7 +128,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
public async Task OnChildFailed_CancelsPendingSuccessors_ChainIsNotWedged()
|
||||
{
|
||||
await SeedPlanningFamilyAsync("P", 3);
|
||||
await _sut.SetupChainAsync("P", default);
|
||||
await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
await using (var ctx = _factory.CreateDbContext())
|
||||
{
|
||||
@@ -153,7 +153,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
{
|
||||
// Chain: c0 → c1 → c2 → c3. c1 fails mid-chain; c2 and c3 must be cancelled.
|
||||
await SeedPlanningFamilyAsync("P", 4);
|
||||
await _sut.SetupChainAsync("P", default);
|
||||
await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
// Mark c0 Done so c1 was unblocked; c1 ran and failed.
|
||||
await using (var ctx = _factory.CreateDbContext())
|
||||
@@ -182,7 +182,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
public async Task OnChildDone_LastChild_ReturnsNull()
|
||||
{
|
||||
await SeedPlanningFamilyAsync("P", 2);
|
||||
await _sut.SetupChainAsync("P", default);
|
||||
await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
await using (var ctx = _factory.CreateDbContext())
|
||||
{
|
||||
@@ -208,7 +208,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
}
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => _sut.SetupChainAsync("P", default));
|
||||
() => _sut.SetupChainAsync("P", enqueue: true, default));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -230,7 +230,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var count = await _sut.SetupChainAsync("P", default);
|
||||
var count = await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
Assert.Equal(4, count);
|
||||
var kids = await GetChildrenAsync("P");
|
||||
@@ -257,7 +257,7 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var count = await _sut.SetupChainAsync("P", default);
|
||||
var count = await _sut.SetupChainAsync("P", enqueue: true, default);
|
||||
|
||||
// Only the two non-terminal tail children get chained.
|
||||
Assert.Equal(2, count);
|
||||
@@ -303,4 +303,21 @@ public sealed class PlanningChainCoordinatorTests : IDisposable
|
||||
var kids = await GetChildrenAsync("P");
|
||||
Assert.All(kids, k => Assert.Equal(TaskStatus.Idle, k.Status));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetupChain_LinkOnly_LeavesChildrenIdle_ButEstablishesChain()
|
||||
{
|
||||
// Finalize path: children must stay Idle (nothing auto-queues) but still
|
||||
// get the blocked-by chain so a later "Queue plan" runs them in order.
|
||||
await SeedPlanningFamilyAsync("P", 3);
|
||||
|
||||
var count = await _sut.SetupChainAsync("P", enqueue: false, default);
|
||||
|
||||
Assert.Equal(3, count);
|
||||
var kids = await GetChildrenAsync("P");
|
||||
Assert.All(kids, k => Assert.Equal(TaskStatus.Idle, k.Status));
|
||||
Assert.Null(kids[0].BlockedByTaskId);
|
||||
Assert.Equal(kids[0].Id, kids[1].BlockedByTaskId);
|
||||
Assert.Equal(kids[1].Id, kids[2].BlockedByTaskId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user