docs(ui): add Mission Control multi-task monitoring spec + plan
This commit is contained in:
98
docs/superpowers/plans/2026-06-25-mission-control.md
Normal file
98
docs/superpowers/plans/2026-06-25-mission-control.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user