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:
co-authored by
Claude Opus 4.8
parent
c300f8c313
commit
f21c65be18
@@ -19,17 +19,21 @@ public sealed class PlanningChainCoordinator
|
||||
_state = state;
|
||||
}
|
||||
|
||||
// Sets up a sequential queue chain over a planning parent's children.
|
||||
// - First non-terminal child gets Status=Queued, BlockedByTaskId=null.
|
||||
// - Each subsequent non-terminal child gets Status=Queued + BlockedByTaskId=<predecessor>,
|
||||
// Sets up a sequential chain over a planning parent's children.
|
||||
// - First non-terminal child gets BlockedByTaskId=null.
|
||||
// - Each subsequent non-terminal child gets BlockedByTaskId=<predecessor>,
|
||||
// so the picker skips them until the predecessor finishes.
|
||||
// - When enqueue is true, each non-terminal child is also set to Status=Queued
|
||||
// (the user-driven "Queue plan"). When false (finalize), children are left
|
||||
// Idle and only the blocked-by links are established, so nothing runs until
|
||||
// the user queues the plan.
|
||||
// - Terminal children (Done/Failed/Cancelled) are left untouched; they are
|
||||
// skipped when computing predecessors so a re-run on a partially executed
|
||||
// chain leaves history alone but still reshapes the tail.
|
||||
// - Running children abort the operation — the chain cannot be reshaped while
|
||||
// one of its members is mid-flight.
|
||||
// Returns the number of children placed in the chain.
|
||||
internal async Task<int> SetupChainAsync(string parentTaskId, CancellationToken ct = default)
|
||||
internal async Task<int> SetupChainAsync(string parentTaskId, bool enqueue, CancellationToken ct = default)
|
||||
{
|
||||
await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
|
||||
var parent = await ctx.Tasks.FirstOrDefaultAsync(t => t.Id == parentTaskId, ct)
|
||||
@@ -56,7 +60,8 @@ public sealed class PlanningChainCoordinator
|
||||
var state = _state();
|
||||
for (int i = 0; i < sequenceable.Count; i++)
|
||||
{
|
||||
await state.EnqueueAsync(sequenceable[i].Id, ct);
|
||||
if (enqueue)
|
||||
await state.EnqueueAsync(sequenceable[i].Id, ct);
|
||||
if (i == 0)
|
||||
await state.UnblockAsync(sequenceable[i].Id, ct);
|
||||
else
|
||||
@@ -81,7 +86,7 @@ public sealed class PlanningChainCoordinator
|
||||
if (phase != PlanningPhase.Finalized)
|
||||
throw new InvalidOperationException("Plan must be finalized before it can be queued.");
|
||||
|
||||
return await SetupChainAsync(parentTaskId, ct);
|
||||
return await SetupChainAsync(parentTaskId, enqueue: true, ct);
|
||||
}
|
||||
|
||||
public async Task<string?> OnChildFinishedAsync(
|
||||
|
||||
@@ -135,7 +135,7 @@ public sealed class PlanningMcpService
|
||||
await BroadcastTaskUpdatedAsync(ctx.ParentTaskId, cancellationToken);
|
||||
}
|
||||
|
||||
[McpServerTool, Description("Finalize the planning session, promoting all draft child tasks to queued or manual status.")]
|
||||
[McpServerTool, Description("Finalize the planning session. Child tasks are left idle and chain-linked (each blocked by its predecessor); they are NOT queued automatically — the user queues the plan from the app when ready. The queueAgentTasks argument is accepted for compatibility but ignored.")]
|
||||
public async Task<int> Finalize(
|
||||
bool queueAgentTasks,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -149,8 +149,10 @@ public sealed class PlanningMcpService
|
||||
|
||||
var children = await _tasks.GetChildrenAsync(ctx.ParentTaskId, cancellationToken);
|
||||
int count = children.Count;
|
||||
if (queueAgentTasks && children.Count > 0)
|
||||
count = await _chain.SetupChainAsync(ctx.ParentTaskId, cancellationToken);
|
||||
// Establish the blocked-by chain but leave children Idle; queueing is a
|
||||
// deliberate user action ("Queue plan"), never an automatic finalize step.
|
||||
if (children.Count > 0)
|
||||
count = await _chain.SetupChainAsync(ctx.ParentTaskId, enqueue: false, cancellationToken);
|
||||
|
||||
foreach (var c in children)
|
||||
await BroadcastTaskUpdatedAsync(c.Id, cancellationToken);
|
||||
|
||||
@@ -199,6 +199,10 @@ public sealed class PlanningMergeOrchestrator
|
||||
parent.FinishedAt = DateTime.UtcNow;
|
||||
await ctx.SaveChangesAsync(ct);
|
||||
|
||||
// Surface the Done transition to the UI. Without this the parent row stays
|
||||
// visibly stuck in WaitingForReview even though the unit merge completed.
|
||||
await _broadcaster.TaskUpdated(parentTaskId);
|
||||
|
||||
// Only planning builds an integration branch via the aggregator; skip cleanup otherwise.
|
||||
if (isPlanning)
|
||||
{
|
||||
|
||||
@@ -209,12 +209,13 @@ public sealed class PlanningSessionManager
|
||||
throw new InvalidOperationException(
|
||||
finalizeResult.Reason ?? $"Could not finalize planning for task {taskId}.");
|
||||
|
||||
int count = 0;
|
||||
// Establish the blocked-by chain but leave children Idle; queueing is a
|
||||
// deliberate user action ("Queue plan"), never an automatic finalize step.
|
||||
// queueAgentTasks is accepted for compatibility but no longer auto-queues.
|
||||
var children = await tasks.GetChildrenAsync(taskId, ct);
|
||||
if (queueAgentTasks && children.Count > 0)
|
||||
count = await _chain.SetupChainAsync(taskId, ct);
|
||||
else
|
||||
count = children.Count;
|
||||
int count = children.Count;
|
||||
if (children.Count > 0)
|
||||
count = await _chain.SetupChainAsync(taskId, enqueue: false, ct);
|
||||
|
||||
// Best-effort cleanup — don't block finalization on git state.
|
||||
await TryCleanupWorktreeAsync(taskId, lists, settings, ct);
|
||||
|
||||
Reference in New Issue
Block a user