# Plan — Mission Control (multi-task live monitoring)

Spec: `docs/superpowers/specs/2026-06-25-mission-control-design.md`

Execution: subagent-driven, **sonnet** model, TDD where a test is meaningful, build + test before
each commit, one Conventional Commit per task. Stage files explicitly by path (never `git add -A`).
**No duplication** — every task reuses the assets named in the spec's reuse map.

---

## Phase 1 — Extract the reusable monitor core (no behavior change)

### Task 1.1 — Move `LogLineViewModel` + `LogKind` to their own file
- Cut `LogKind` enum and `LogLineViewModel` from `DetailsIslandViewModel.cs` into
  `ViewModels/Islands/LogLineViewModel.cs` (same namespace). No logic change.
- Build `ClaudeDo.App`; run Ui.Tests. Commit: `refactor(ui): split LogLineViewModel into own file`.

### Task 1.2 — Create `TaskMonitorViewModel` owning the streaming/status/outcome core
- New `ViewModels/Islands/TaskMonitorViewModel.cs`. Move from `DetailsIslandViewModel`:
  `Log`, `_subscribedTaskId`, `_formatter`, `_claudeBuf`, `OnTaskMessage`, `AppendStdoutLine`,
  `FlushClaudeBuffer`, `ReplayLogFileAsync`, `ExpandUserPath`; `AgentState` + all `Is*` flags +
  `OnAgentStateChanged`; `StatusToStateKey` / `FinishedStatusToStateKey`; `SessionOutcome` /
  `Roadblocks` + `ApplyOutcome` + `RoadblockMarker`; the worker `TaskMessage/Started/Finished/Updated`
  subscriptions for the streaming concern; `Title`/`TaskIdBadge`/`Model`/`TurnsText`/`TokensFormatted`/
  diff text/elapsed; `BlockingReason` (+visible flag) from `BlockedByTaskId`/review/children/roadblocks.
- Ctor takes `IDbContextFactory<ClaudeDoDbContext>`, `IWorkerClient`. `Attach(taskId)` /
  `AttachAsync(entity)` to (re)bind + replay; `IDisposable` unsubscribes (mirror existing Dispose).
- Unit test (Ui.Tests): feed `[stdout]`/`[claude]`/`[tool]` lines via the worker fake → `Log`
  accumulates correctly; `TaskFinished` flips `AgentState`; `ApplyOutcome` splits the roadblock marker.
  Reuse the existing IWorkerClient fake (see `iworkerclient_fakes_sync`).
- Build + test. Commit: `feat(ui): extract TaskMonitorViewModel streaming core`.

### Task 1.3 — `DetailsIslandViewModel` delegates to `Monitor`
- Add `public TaskMonitorViewModel Monitor { get; }`; construct it; route `Bind`/`BindAsync` to
  `Monitor.Attach`. Remove the moved members; keep subtasks/attachments/editing/merge/review/child
  outcomes/notes/prep intact. Dispose `Monitor`.
- Repoint `WorkConsole.axaml` Output-tab bindings (`Log`, `IsRunning/IsDone/IsFailed`,
  `SessionOutcome`, `TurnsText`, `DiffAddText`/`DiffDelText`, `Model`) to `Monitor.*`. Leave
  review/merge/session bindings unchanged.
- Build + test. **Manual visual pass: Details pane behaves exactly as before** (flag for Mika).
  Commit: `refactor(ui): route DetailsIsland streaming through Monitor`.

---

## Phase 2 — Mission Control window

### Task 2.1 — `MissionControlViewModel`
- New `ViewModels/MissionControlViewModel.cs`: `ObservableCollection<TaskMonitorViewModel> Monitors`
  keyed by id; seed from `GetActive()`; add on `TaskStarted`, flip-state-and-keep on `TaskFinished`;
  `ClearFinished` command; `ColumnCount`/layout signal from `Monitors.Count`; least-active collapse.
  `IDisposable` disposes all monitors. Inject `IDbContextFactory`, `IWorkerClient`, `IServiceProvider`.
- Register `AddSingleton<MissionControlViewModel>` in `App/Program.cs`.
- Unit test: simulate two `TaskStarted` → two monitors; `TaskFinished` keeps the pane; `ColumnCount`
  matches count. Commit: `feat(ui): add MissionControlViewModel`.

### Task 2.2 — `RevealTaskAsync` navigation on the shell
- Add `IslandsShellViewModel.RevealTaskAsync(taskId)` (resolve list → select → await load → select row).
- Wire `TaskMonitorViewModel.OpenInApp` to it (via an `Action<string>?` set by the shell, like the
  existing `CloseDetail`/`DeleteFromList` hooks — no new DI cycle).
- Unit test for the select-by-id path. Commit: `feat(ui): reveal a task by id from anywhere`.

### Task 2.3 — `MonitorPaneView` (reuses `SessionTerminalView`)
- New `Views/MissionControl/MonitorPaneView.axaml(.cs)`: header (title/chip/tok/turn/elapsed),
  blocking banner (`live-chip`/`terminal`/error-tint classes from IslandStyles — reuse), body =
  `<SessionTerminalView Entries="{Binding Log}" ... />`, footer (Open in app / Detach / Cancel).
  `x:DataType=TaskMonitorViewModel`. No new console control. Add `missionControl.*` en+de keys.
- Build + Localization.Tests. Commit: `feat(ui): add MonitorPaneView`.

### Task 2.4 — `MissionControlView` grid + `MissionControlWindow`
- `MissionControlView.axaml`: `ItemsControl`/`UniformGrid` of `MonitorPaneView` driven by `ColumnCount`,
  horizontal scroll fallback, header with `ClearFinished` (+ optional QuickAdd, deferrable).
- `MissionControlWindow.axaml(.cs)`: hosts the view; lazy-create + hide-on-close.
- Build. Commit: `feat(ui): add MissionControl window + grid`.

### Task 2.5 — Launch button + lifetime
- Title-bar toggle button in `MainWindow.axaml` → shell command that shows/focuses the window
  (created lazily, owns the singleton VM).
- Set `desktop.ShutdownMode = OnMainWindowClose` in `App.OnFrameworkInitializationCompleted`.
- Build. **Manual visual pass** (flag for Mika): open with 2+ running tasks; main window still adds
  tasks; blocking banner; Open-in-app. Commit: `feat(ui): open Mission Control from the title bar`.

---

## Phase 3 — Per-pane detach (lowest priority)

### Task 3.1 — `TaskMonitorWindow` + detach/re-dock
- `Views/MissionControl/TaskMonitorWindow.axaml(.cs)` hosting `MonitorPaneView`; `Detach` removes the
  monitor from the grid and shows it in the window (optional always-on-top); close re-docks.
- Build. Manual visual pass. Commit: `feat(ui): detach a monitor into its own window`.

---

## Cross-cutting checklist (every task)
- Stage by explicit path; sonnet subagents; reuse per the spec's map — no new console/streaming/insert path.
- en.json + de.json parity for any new string (Localization.Tests).
- If `IWorkerClient`/ctor signatures change, update the hand-rolled fakes in **both** test projects.
- Build `ClaudeDo.App` (`-c Release` if Worker is running) before marking a task done.
- Never push without asking.
