# Planning UX Polish + Sequential Subtask Queue — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add sequential execution of planning subtasks (new `Waiting` status, context-menu trigger, worker-side chain advancement) plus three small UX changes (auto-collapse done planning parents in the task list, collapsible Description in the Details pane, narrower island GridSplitters).

**Architecture:** Foundation first — add the new `Waiting` enum value and its surface in the UI (chip, virtual-queued filter, row plumbing). Then ship the three UI polish items independently. Finally build the worker-side chain coordinator behind TDD and wire up the SignalR method + context-menu entry.

**Tech Stack:** .NET 8, Avalonia 12, CommunityToolkit.Mvvm, EF Core (Sqlite), SignalR, xUnit.

**Spec:** `docs/superpowers/specs/2026-04-24-planning-ux-and-sequential-subtasks-design.md`

---

## Task 1: Add `Waiting` status to the enum

**Files:**
- Modify: `src/ClaudeDo.Data/Models/TaskEntity.cs` (TaskStatus enum)
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs` (chip class switch)
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs` (virtual-queued match predicate)

- [ ] **Step 1: Add `Waiting` to the enum**

Append `Waiting` as the last value (keeps existing numeric slots stable for any int-serialized rows).

`src/ClaudeDo.Data/Models/TaskEntity.cs`:

```csharp
public enum TaskStatus
{
    Manual,
    Queued,
    Running,
    Done,
    Failed,
    Planning,
    Planned,
    Draft,
    Waiting,
}
```

- [ ] **Step 2: Extend `StatusChipClass` switch in TaskRowViewModel**

`src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs` — update the switch:

```csharp
public string StatusChipClass => Status switch
{
    TaskStatus.Running => "running",
    TaskStatus.Failed  => "error",
    TaskStatus.Done    => "review",
    TaskStatus.Queued  => "queued",
    TaskStatus.Waiting => "waiting",
    _                  => "idle",
};
```

- [ ] **Step 3: Add `IsWaiting` and include it in virtual-queued matching**

In the same `TaskRowViewModel.cs`, add alongside `IsQueued`:

```csharp
public bool IsWaiting => Status == TaskStatus.Waiting;
```

In `src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs`, find the `TaskMatchesList` static method and update the `virtual:queued` branch so tasks in `Waiting` also match. Locate the existing match for `ListKind.Virtual when list.Id == "virtual:queued"` and change it to match `t.Status == TaskStatus.Queued || t.Status == TaskStatus.Waiting`. If the existing line reads `t.Status == TaskStatus.Queued` exactly, replace it with `t.Status == TaskStatus.Queued || t.Status == TaskStatus.Waiting`.

- [ ] **Step 4: Build**

Run:

```bash
dotnet build src/ClaudeDo.Data/ClaudeDo.Data.csproj
dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
```

Expected: both build with 0 errors. Existing warnings OK.

- [ ] **Step 5: Commit**

```bash
git add src/ClaudeDo.Data/Models/TaskEntity.cs \
        src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs \
        src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs
git commit -m "feat(data): add Waiting task status and include it in virtual:queued"
```

---

## Task 2: Narrower island GridSplitters

**Files:**
- Modify: `src/ClaudeDo.Ui/Views/MainWindow.axaml` (lines 158 and 170)

- [ ] **Step 1: Halve the splitter width**

Both `GridSplitter` elements currently use `Width="5"`. Change both to `Width="3"`. Leave all other attributes untouched.

- [ ] **Step 2: Build**

```bash
dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
```

Expected: 0 errors.

- [ ] **Step 3: Commit**

```bash
git add src/ClaudeDo.Ui/Views/MainWindow.axaml
git commit -m "style(ui): narrow island GridSplitters from 5 to 3"
```

---

## Task 3: Collapsible Description section in Details pane

**Files:**
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs`
- Modify: `src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml`

- [ ] **Step 1: Add observable flag + toggle command**

In `DetailsIslandViewModel.cs`, add beside the existing editable fields:

```csharp
[ObservableProperty] private bool _isDescriptionExpanded = true;

[RelayCommand]
private void ToggleDescriptionExpanded() => IsDescriptionExpanded = !IsDescriptionExpanded;
```

- [ ] **Step 2: Reset flag when a new task is loaded**

Find the method that handles a new `Task` being bound (the existing `OnTaskChanged` / `Bind` path — it's the spot that already sets `EditableTitle`, `EditableDescription`, etc.). At the start of the load path where fields get reset, add:

```csharp
IsDescriptionExpanded = true;
```

(If the reset is scattered, put it next to the `EditableDescription = ""` assignment.)

- [ ] **Step 3: Wrap the description TextBox in a collapsible section**

In `DetailsIslandView.axaml`, locate the description TextBox. Wrap it so it looks like:

```xml
<StackPanel Spacing="4">
  <Button Classes="flat"
          Command="{Binding ToggleDescriptionExpandedCommand}"
          HorizontalAlignment="Stretch"
          HorizontalContentAlignment="Left"
          Padding="0">
    <StackPanel Orientation="Horizontal" Spacing="6">
      <PathIcon Width="10" Height="10"
                Data="{StaticResource Icon.ChevronDown}"
                IsVisible="{Binding IsDescriptionExpanded}"/>
      <PathIcon Width="10" Height="10"
                Data="{StaticResource Icon.ChevronRight}"
                IsVisible="{Binding !IsDescriptionExpanded}"/>
      <TextBlock Classes="eyebrow" Text="DESCRIPTION"/>
    </StackPanel>
  </Button>

  <!-- existing description TextBox goes here unchanged, but add: -->
  <TextBox ...existing attributes...
           IsVisible="{Binding IsDescriptionExpanded}"/>
</StackPanel>
```

If the existing `Icon.ChevronDown` / `Icon.ChevronRight` static resources don't exist, inspect `App.axaml` (or wherever `StaticResource Icon.*` icons live) and pick the closest existing chevron pair. If only one direction exists, use a simple `▾` / `▸` TextBlock substitute:

```xml
<TextBlock Text="▾" IsVisible="{Binding IsDescriptionExpanded}"/>
<TextBlock Text="▸" IsVisible="{Binding !IsDescriptionExpanded}"/>
```

- [ ] **Step 4: Build**

```bash
dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
```

Expected: 0 errors.

- [ ] **Step 5: Manual verify**

Launch the app (`dotnet run --project src/ClaudeDo.App`), open a task with a description, click the chevron. Verify the body collapses/expands; verify opening a different task restores the expanded default.

- [ ] **Step 6: Commit**

```bash
git add src/ClaudeDo.Ui/ViewModels/Islands/DetailsIslandViewModel.cs \
        src/ClaudeDo.Ui/Views/Islands/DetailsIslandView.axaml
git commit -m "feat(ui): collapsible description section in details pane"
```

---

## Task 4: Auto-collapse done planning parents in task list

**Files:**
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs`
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs`
- Modify: `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml`

- [ ] **Step 1: Add expansion state + "all children done" flag to `TaskRowViewModel`**

In `TaskRowViewModel.cs`, add below the existing observable properties:

```csharp
[ObservableProperty] private bool _areChildrenExpanded = true;
[ObservableProperty] private bool _allChildrenDone;

partial void OnAllChildrenDoneChanged(bool value)
{
    // Default children to collapsed once the planning parent is fully done.
    if (value) AreChildrenExpanded = false;
}

[RelayCommand]
private void ToggleChildrenExpanded() => AreChildrenExpanded = !AreChildrenExpanded;
```

- [ ] **Step 2: Compute `AllChildrenDone` during Regroup in `TasksIslandViewModel`**

In `TasksIslandViewModel.cs`, locate the `Regroup()` method (the one that clears and repopulates `OverdueItems`/`OpenItems`/`CompletedItems`). Before it distributes rows, build a lookup of children by parent id:

```csharp
var childrenByParent = Items
    .Where(r => r.IsChild && r.ParentTaskId is not null)
    .GroupBy(r => r.ParentTaskId!)
    .ToDictionary(g => g.Key, g => g.ToList());

foreach (var parent in Items.Where(r => r.IsPlanningParent && !r.IsChild))
{
    if (childrenByParent.TryGetValue(parent.Id, out var kids) && kids.Count > 0)
        parent.AllChildrenDone = kids.All(c => c.Status == TaskStatus.Done);
    else
        parent.AllChildrenDone = false;
}
```

Then inside the existing distribution loop, skip child rows whose parent row has `AreChildrenExpanded == false`:

```csharp
foreach (var row in Items)
{
    if (row.IsChild && row.ParentTaskId is not null)
    {
        var parentRow = Items.FirstOrDefault(p => p.Id == row.ParentTaskId);
        if (parentRow is not null && !parentRow.AreChildrenExpanded) continue;
    }
    // ... existing distribution into Overdue/Open/Completed ...
}
```

If `Regroup()` currently uses LINQ expressions instead of a loop, split them out into explicit foreach so the skip is clear. Keep the overdue/completed logic intact — children of a collapsed parent are excluded from every bucket.

- [ ] **Step 3: Re-run Regroup when a row's expansion flag toggles**

In `TasksIslandViewModel.cs`, in the constructor (after `Items` is created), subscribe to changes so toggling one row triggers a regroup:

```csharp
Items.CollectionChanged += (_, e) =>
{
    if (e.NewItems is not null)
        foreach (TaskRowViewModel r in e.NewItems)
            r.PropertyChanged += OnItemPropertyChanged;
    if (e.OldItems is not null)
        foreach (TaskRowViewModel r in e.OldItems)
            r.PropertyChanged -= OnItemPropertyChanged;
};
```

Add the handler:

```csharp
private void OnItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    if (e.PropertyName == nameof(TaskRowViewModel.AreChildrenExpanded))
        Regroup();
}
```

- [ ] **Step 4: Add chevron toggle button to the planning-parent row**

In `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml`, inside the main task card where the title/eyebrow row lives (co-located with `PlanningBadge`), add a chevron button visible only when `IsPlanningParent && HasPlanningChildren`:

```xml
<Button Classes="flat"
        Command="{Binding ToggleChildrenExpandedCommand}"
        IsVisible="{Binding HasPlanningChildren}"
        Padding="0" Margin="0,0,6,0"
        VerticalAlignment="Center">
  <TextBlock FontSize="10"
             Text="▾"
             IsVisible="{Binding AreChildrenExpanded}"/>
  <TextBlock FontSize="10"
             Text="▸"
             IsVisible="{Binding !AreChildrenExpanded}"/>
</Button>
```

Place it immediately before the title TextBlock in the parent-row layout. Leave child rows untouched.

- [ ] **Step 5: Build**

```bash
dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
```

Expected: 0 errors.

- [ ] **Step 6: Manual verify**

Create a planning parent with ≥2 children. Mark both children `Done` (manually via DB if needed, or via a full planning run). Reload the list — the children should be hidden by default. Click the chevron on the parent — children appear. Click again — collapse.

- [ ] **Step 7: Commit**

```bash
git add src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs \
        src/ClaudeDo.Ui/ViewModels/Islands/TasksIslandViewModel.cs \
        src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
git commit -m "feat(ui): auto-collapse done planning parents in task list"
```

---

## Task 5: PlanningChainCoordinator — worker-side chain advancement (TDD)

**Files:**
- Create: `src/ClaudeDo.Worker/Planning/PlanningChainCoordinator.cs`
- Create: `tests/ClaudeDo.Worker.Tests/Planning/PlanningChainCoordinatorTests.cs`

- [ ] **Step 1: Write the first failing test — queueing sets first child Queued, rest Waiting**

Create `tests/ClaudeDo.Worker.Tests/Planning/PlanningChainCoordinatorTests.cs`:

```csharp
using System.Threading.Tasks;
using ClaudeDo.Data;
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Planning;
using Microsoft.EntityFrameworkCore;
using Xunit;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;

namespace ClaudeDo.Worker.Tests.Planning;

public class PlanningChainCoordinatorTests
{
    private static DbContextOptions<ClaudeDoDbContext> InMemoryOptions() =>
        new DbContextOptionsBuilder<ClaudeDoDbContext>()
            .UseSqlite("DataSource=:memory:;Cache=Shared")
            .Options;

    private static async Task<(ClaudeDoDbContext ctx, TaskRepository repo)> NewDbAsync()
    {
        var ctx = new ClaudeDoDbContext(InMemoryOptions());
        await ctx.Database.OpenConnectionAsync();
        await ctx.Database.EnsureCreatedAsync();
        return (ctx, new TaskRepository(ctx));
    }

    private static async Task SeedPlanningFamily(TaskRepository repo, string parentId, int childCount)
    {
        await repo.AddAsync(new TaskEntity
        {
            Id = parentId, ListId = "L1", Title = "Parent",
            CreatedAt = System.DateTime.UtcNow, Status = TaskStatus.Planned,
        });
        for (int i = 0; i < childCount; i++)
        {
            await repo.AddAsync(new TaskEntity
            {
                Id = $"{parentId}-c{i}", ListId = "L1", Title = $"Child {i}",
                CreatedAt = System.DateTime.UtcNow, Status = TaskStatus.Manual,
                ParentTaskId = parentId, SortOrder = i,
            });
        }
    }

    [Fact]
    public async Task QueueSubtasksSequentially_SetsFirstQueued_RestWaiting()
    {
        var (ctx, repo) = await NewDbAsync();
        await using var _ = ctx;
        await SeedPlanningFamily(repo, "P", 3);

        var coord = new PlanningChainCoordinator(repo);
        await coord.QueueSubtasksSequentiallyAsync("P", default);

        var kids = await ctx.Tasks.Where(t => t.ParentTaskId == "P").OrderBy(t => t.SortOrder).ToListAsync();
        Assert.Equal(TaskStatus.Queued, kids[0].Status);
        Assert.Equal(TaskStatus.Waiting, kids[1].Status);
        Assert.Equal(TaskStatus.Waiting, kids[2].Status);
    }
}
```

- [ ] **Step 2: Run the test — expect failure (class doesn't exist)**

```bash
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter FullyQualifiedName~PlanningChainCoordinatorTests
```

Expected: compile error "PlanningChainCoordinator not found".

- [ ] **Step 3: Create the coordinator with the minimum to pass**

`src/ClaudeDo.Worker/Planning/PlanningChainCoordinator.cs`:

```csharp
using ClaudeDo.Data.Models;
using ClaudeDo.Data.Repositories;

namespace ClaudeDo.Worker.Planning;

public sealed class PlanningChainCoordinator
{
    private readonly TaskRepository _tasks;

    public PlanningChainCoordinator(TaskRepository tasks) => _tasks = tasks;

    public async Task QueueSubtasksSequentiallyAsync(string parentTaskId, CancellationToken ct)
    {
        var parent = await _tasks.GetByIdAsync(parentTaskId, ct)
            ?? throw new InvalidOperationException($"Task {parentTaskId} not found.");

        var children = (await _tasks.GetChildrenAsync(parentTaskId, ct))
            .OrderBy(t => t.SortOrder)
            .ToList();
        if (children.Count == 0)
            throw new InvalidOperationException("Parent has no subtasks.");

        var bad = children.FirstOrDefault(c => c.Status is not (TaskStatus.Manual or TaskStatus.Planned));
        if (bad is not null)
            throw new InvalidOperationException($"Child {bad.Id} is in status {bad.Status}; expected Manual or Planned.");

        for (int i = 0; i < children.Count; i++)
        {
            children[i].Status = i == 0 ? TaskStatus.Queued : TaskStatus.Waiting;
            await _tasks.UpdateAsync(children[i], ct);
        }
    }
}
```

If `TaskRepository.GetChildrenAsync` does not yet exist, add it:

```csharp
// in src/ClaudeDo.Data/Repositories/TaskRepository.cs
public Task<List<TaskEntity>> GetChildrenAsync(string parentTaskId, CancellationToken ct = default) =>
    _ctx.Tasks.Where(t => t.ParentTaskId == parentTaskId).ToListAsync(ct);
```

(If the repo uses `AsNoTracking()` elsewhere for reads, match that pattern. For this method we want tracked entities so `UpdateAsync` works without extra attach.)

- [ ] **Step 4: Run the test — expect pass**

```bash
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter FullyQualifiedName~PlanningChainCoordinatorTests
```

Expected: 1 passed.

- [ ] **Step 5: Add failing test — on child Done, next Waiting sibling flips to Queued**

Append to `PlanningChainCoordinatorTests.cs`:

```csharp
[Fact]
public async Task OnChildDone_FlipsNextWaitingToQueued()
{
    var (ctx, repo) = await NewDbAsync();
    await using var _ = ctx;
    await SeedPlanningFamily(repo, "P", 3);

    var coord = new PlanningChainCoordinator(repo);
    await coord.QueueSubtasksSequentiallyAsync("P", default);

    // Simulate first child finishing Done.
    var first = await ctx.Tasks.FirstAsync(t => t.Id == "P-c0");
    first.Status = TaskStatus.Done;
    await ctx.SaveChangesAsync();

    var advanced = await coord.OnChildFinishedAsync("P-c0", TaskStatus.Done, default);

    Assert.Equal("P-c1", advanced);
    var kids = await ctx.Tasks.Where(t => t.ParentTaskId == "P").OrderBy(t => t.SortOrder).ToListAsync();
    Assert.Equal(TaskStatus.Done,    kids[0].Status);
    Assert.Equal(TaskStatus.Queued,  kids[1].Status);
    Assert.Equal(TaskStatus.Waiting, kids[2].Status);
}
```

- [ ] **Step 6: Run — expect failure**

Expected: compile error "OnChildFinishedAsync does not exist".

- [ ] **Step 7: Implement `OnChildFinishedAsync`**

In `PlanningChainCoordinator.cs`:

```csharp
/// <summary>
/// Call after a child task transitions to a terminal status.
/// Returns the id of the newly-queued sibling (if any), else null.
/// </summary>
public async Task<string?> OnChildFinishedAsync(string childTaskId, TaskStatus finalStatus, CancellationToken ct)
{
    if (finalStatus != TaskStatus.Done) return null;

    var child = await _tasks.GetByIdAsync(childTaskId, ct);
    if (child?.ParentTaskId is null) return null;

    var siblings = (await _tasks.GetChildrenAsync(child.ParentTaskId, ct))
        .OrderBy(t => t.SortOrder)
        .ToList();

    var next = siblings
        .Where(s => s.SortOrder > child.SortOrder && s.Status == TaskStatus.Waiting)
        .FirstOrDefault();
    if (next is null) return null;

    next.Status = TaskStatus.Queued;
    await _tasks.UpdateAsync(next, ct);
    return next.Id;
}
```

- [ ] **Step 8: Run — expect pass**

```bash
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter FullyQualifiedName~PlanningChainCoordinatorTests
```

Expected: 2 passed.

- [ ] **Step 9: Add failing test — on Failed, chain stops**

```csharp
[Fact]
public async Task OnChildFailed_DoesNotAdvanceChain()
{
    var (ctx, repo) = await NewDbAsync();
    await using var _ = ctx;
    await SeedPlanningFamily(repo, "P", 3);

    var coord = new PlanningChainCoordinator(repo);
    await coord.QueueSubtasksSequentiallyAsync("P", default);

    var first = await ctx.Tasks.FirstAsync(t => t.Id == "P-c0");
    first.Status = TaskStatus.Failed;
    await ctx.SaveChangesAsync();

    var advanced = await coord.OnChildFinishedAsync("P-c0", TaskStatus.Failed, default);

    Assert.Null(advanced);
    var kids = await ctx.Tasks.Where(t => t.ParentTaskId == "P").OrderBy(t => t.SortOrder).ToListAsync();
    Assert.Equal(TaskStatus.Failed,  kids[0].Status);
    Assert.Equal(TaskStatus.Waiting, kids[1].Status);
    Assert.Equal(TaskStatus.Waiting, kids[2].Status);
}
```

- [ ] **Step 10: Run — expect pass (existing guard handles it)**

```bash
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj --filter FullyQualifiedName~PlanningChainCoordinatorTests
```

Expected: 3 passed.

- [ ] **Step 11: Commit**

```bash
git add src/ClaudeDo.Worker/Planning/PlanningChainCoordinator.cs \
        src/ClaudeDo.Data/Repositories/TaskRepository.cs \
        tests/ClaudeDo.Worker.Tests/Planning/PlanningChainCoordinatorTests.cs
git commit -m "feat(worker): add PlanningChainCoordinator with sequential subtask advancement"
```

---

## Task 6: Hook chain advancement into TaskRunner finish path

**Files:**
- Modify: `src/ClaudeDo.Worker/Runner/TaskRunner.cs`
- Modify: `src/ClaudeDo.Worker/Program.cs` (DI registration)

- [ ] **Step 1: Register `PlanningChainCoordinator` in DI**

Locate `src/ClaudeDo.Worker/Program.cs` where other services are registered (look for `services.AddSingleton<PlanningSessionManager>` or similar). Add:

```csharp
services.AddScoped<PlanningChainCoordinator>();
```

Use `AddScoped` if `TaskRepository` is scoped (check how it's registered — match its lifetime). If `TaskRepository` is constructed ad-hoc inside the worker, add a constructor overload on `PlanningChainCoordinator` that takes `IDbContextFactory<ClaudeDoDbContext>` and builds its own `TaskRepository` per call, then register as Singleton. Mirror the pattern used by `PlanningSessionManager`.

- [ ] **Step 2: Inject coordinator into `TaskRunner`**

In `src/ClaudeDo.Worker/Runner/TaskRunner.cs`, add `PlanningChainCoordinator` to the constructor parameter list and store it in a readonly field (match the style used for `_broadcaster`).

If `TaskRunner` is not a good fit for direct injection (e.g., it's used in contexts without DI), instead inject `IServiceProvider` / `IDbContextFactory<ClaudeDoDbContext>` and new-up a coordinator inside the finish handler. Pick whichever matches existing `TaskRunner` patterns.

- [ ] **Step 3: Call coordinator after Done/Failed emission**

Immediately after each `await _broadcaster.TaskFinished(slot, task.Id, "done", finishedAt);` on line ~338 and the two failed emissions on lines ~355 and ~372, add:

```csharp
if (task.ParentTaskId is not null)
{
    var advancedId = await _chainCoordinator.OnChildFinishedAsync(
        task.Id,
        /* Done or Failed based on path */,
        CancellationToken.None);
    if (advancedId is not null)
        await _broadcaster.TaskUpdated(advancedId);
}
```

Use `TaskStatus.Done` in the done-path call site and `TaskStatus.Failed` in the failed-path call sites. For the failed paths that use `justFailed` rather than `task`, read `justFailed?.ParentTaskId` and `justFailed?.Id` to stay consistent with the surrounding code.

After this call the existing queue-pickup loop will see the newly-Queued sibling and dispatch it on its next tick.

- [ ] **Step 4: Build**

```bash
dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
```

Expected: 0 errors.

- [ ] **Step 5: Run full test suite**

```bash
dotnet test tests/ClaudeDo.Worker.Tests/ClaudeDo.Worker.Tests.csproj
```

Expected: all pre-existing tests + 3 new ones pass.

- [ ] **Step 6: Commit**

```bash
git add src/ClaudeDo.Worker/Runner/TaskRunner.cs \
        src/ClaudeDo.Worker/Program.cs
git commit -m "feat(worker): advance planning subtask chain on child finish"
```

---

## Task 7: Hub method + client + context menu entry

**Files:**
- Modify: `src/ClaudeDo.Worker/Hub/WorkerHub.cs`
- Modify: `src/ClaudeDo.Ui/Services/IWorkerClient.cs`
- Modify: `src/ClaudeDo.Ui/Services/WorkerClient.cs`
- Modify: `src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs`
- Modify: `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml`
- Modify: `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs`

- [ ] **Step 1: Add hub method**

In `src/ClaudeDo.Worker/Hub/WorkerHub.cs`, add (match the style of other planning methods):

```csharp
public async Task QueuePlanningSubtasks(string parentTaskId)
{
    await using var ctx = await _dbFactory.CreateDbContextAsync();
    var repo = new TaskRepository(ctx);
    var coord = new PlanningChainCoordinator(repo);
    await coord.QueueSubtasksSequentiallyAsync(parentTaskId, CancellationToken.None);

    // Broadcast updates for the parent and all its children so the UI refreshes.
    var children = await ctx.Tasks
        .Where(t => t.ParentTaskId == parentTaskId)
        .Select(t => t.Id)
        .ToListAsync();
    await _broadcaster.TaskUpdated(parentTaskId);
    foreach (var id in children)
        await _broadcaster.TaskUpdated(id);

    // Make sure the queue picks up the now-Queued first child immediately.
    _queueSignal.Wake();
}
```

If the existing hub constructs `PlanningSessionManager` via DI directly, inject `PlanningChainCoordinator` the same way and call `_chainCoordinator.QueueSubtasksSequentiallyAsync(...)` instead of newing one up. If the hub exposes a queue-wakeup via a different name than `_queueSignal`, use that (search the file for `WakeQueue` or `.Wake()`).

- [ ] **Step 2: Add method to `IWorkerClient`**

In `src/ClaudeDo.Ui/Services/IWorkerClient.cs`, add next to the other planning methods:

```csharp
Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default);
```

- [ ] **Step 3: Implement in `WorkerClient`**

In `src/ClaudeDo.Ui/Services/WorkerClient.cs`, add (match the pattern of `StartPlanningSessionAsync` etc.):

```csharp
public Task QueuePlanningSubtasksAsync(string parentTaskId, CancellationToken ct = default) =>
    _connection.InvokeAsync("QueuePlanningSubtasks", parentTaskId, ct);
```

- [ ] **Step 4: Add `CanQueueSubtasksSequentially` + `HasPlanningChildren` observable to `TaskRowViewModel`**

Confirm `HasPlanningChildren` exists (it's referenced in the spec). If not, add it as `[ObservableProperty] bool _hasPlanningChildren;` and ensure `TasksIslandViewModel.Regroup()` already sets it (there should be a parent-side "has children" pass similar to the `AllChildrenDone` one added in Task 4 — if not, set it there).

Then add:

```csharp
public bool CanQueueSubtasksSequentially =>
    IsPlanningParent && HasPlanningChildren && !IsChild;
```

Add `OnPropertyChanged(nameof(CanQueueSubtasksSequentially))` inside `OnStatusChanged` and `OnHasPlanningChildrenChanged` so the flag refreshes when status or children change.

- [ ] **Step 5: Add context-menu entry**

In `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml`, inside the existing `<ContextMenu>`, directly after the "Discard planning session" item:

```xml
<Separator IsVisible="{Binding CanQueueSubtasksSequentially}"/>
<MenuItem Header="Queue subtasks sequentially"
          IsVisible="{Binding CanQueueSubtasksSequentially}"
          Click="OnQueueSubtasksSequentiallyClick"/>
```

- [ ] **Step 6: Add click handler in code-behind**

In `src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs`, add (match the other `On*Click` handlers — they pull the `TaskRowViewModel` from `DataContext` and call the shell / worker):

```csharp
private async void OnQueueSubtasksSequentiallyClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
    if (DataContext is not TaskRowViewModel row) return;
    var worker = App.Services.GetRequiredService<IWorkerClient>();
    try
    {
        await worker.QueuePlanningSubtasksAsync(row.Id);
    }
    catch (Exception ex)
    {
        // Match the toast/log pattern used by OnSendToQueueClick et al.
        System.Diagnostics.Debug.WriteLine($"QueuePlanningSubtasks failed: {ex}");
    }
}
```

Use the same `App.Services` / `IWorkerClient` lookup pattern as `OnSendToQueueClick` — do not introduce a new DI pattern. If the existing handlers use a shell/mediator indirection, use that instead.

- [ ] **Step 7: Build**

```bash
dotnet build src/ClaudeDo.Worker/ClaudeDo.Worker.csproj
dotnet build src/ClaudeDo.Ui/ClaudeDo.Ui.csproj
```

Expected: 0 errors.

- [ ] **Step 8: Manual verify end-to-end**

1. Launch app: `dotnet run --project src/ClaudeDo.App`.
2. Open a planning task with ≥2 subtasks (all in `Manual`/`Planned`).
3. Right-click parent → **Queue subtasks sequentially**.
4. Confirm in the task list: first child shows `Queued` chip, others show `Waiting` chip.
5. Let the first run to completion (or, for a quick smoke test, edit the DB to mark it `Done` and emit `TaskUpdated` via a restart).
6. Confirm the next child's status flips `Waiting → Queued` without user interaction.
7. Force-fail a child (cancel it mid-run) — confirm remaining `Waiting` children stay `Waiting`.

- [ ] **Step 9: Commit**

```bash
git add src/ClaudeDo.Worker/Hub/WorkerHub.cs \
        src/ClaudeDo.Ui/Services/IWorkerClient.cs \
        src/ClaudeDo.Ui/Services/WorkerClient.cs \
        src/ClaudeDo.Ui/ViewModels/Islands/TaskRowViewModel.cs \
        src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml \
        src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
git commit -m "feat(ui+worker): context menu to queue planning subtasks sequentially"
```

---

## Self-review checklist (for the plan author before handing off)

- All four spec items mapped: auto-collapse (Task 4), collapsible description (Task 3), narrower splitters (Task 2), sequential subtask queue (Tasks 1, 5, 6, 7).
- `Waiting` enum touches: enum, chip class, virtual:queued filter — covered in Task 1.
- TDD applied where it pays off (the coordinator); UI tasks rely on manual verification (correct for this codebase).
- No placeholders. Every code step shows the code to paste.
- Type names consistent: `PlanningChainCoordinator`, `QueueSubtasksSequentiallyAsync`, `OnChildFinishedAsync`, `QueuePlanningSubtasksAsync`, `AreChildrenExpanded`, `AllChildrenDone`, `IsDescriptionExpanded` — used the same across tasks.
- Commits are small and conventional.
