6.1 KiB
6.1 KiB
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
LogKindenum andLogLineViewModelfromDetailsIslandViewModel.csintoViewModels/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 fromDetailsIslandViewModel:Log,_subscribedTaskId,_formatter,_claudeBuf,OnTaskMessage,AppendStdoutLine,FlushClaudeBuffer,ReplayLogFileAsync,ExpandUserPath;AgentState+ allIs*flags +OnAgentStateChanged;StatusToStateKey/FinishedStatusToStateKey;SessionOutcome/Roadblocks+ApplyOutcome+RoadblockMarker; the workerTaskMessage/Started/Finished/Updatedsubscriptions for the streaming concern;Title/TaskIdBadge/Model/TurnsText/TokensFormatted/ diff text/elapsed;BlockingReason(+visible flag) fromBlockedByTaskId/review/children/roadblocks. - Ctor takes
IDbContextFactory<ClaudeDoDbContext>,IWorkerClient.Attach(taskId)/AttachAsync(entity)to (re)bind + replay;IDisposableunsubscribes (mirror existing Dispose). - Unit test (Ui.Tests): feed
[stdout]/[claude]/[tool]lines via the worker fake →Logaccumulates correctly;TaskFinishedflipsAgentState;ApplyOutcomesplits the roadblock marker. Reuse the existing IWorkerClient fake (seeiworkerclient_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; routeBind/BindAsynctoMonitor.Attach. Remove the moved members; keep subtasks/attachments/editing/merge/review/child outcomes/notes/prep intact. DisposeMonitor. - Repoint
WorkConsole.axamlOutput-tab bindings (Log,IsRunning/IsDone/IsFailed,SessionOutcome,TurnsText,DiffAddText/DiffDelText,Model) toMonitor.*. 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> Monitorskeyed by id; seed fromGetActive(); add onTaskStarted, flip-state-and-keep onTaskFinished;ClearFinishedcommand;ColumnCount/layout signal fromMonitors.Count; least-active collapse.IDisposabledisposes all monitors. InjectIDbContextFactory,IWorkerClient,IServiceProvider. - Register
AddSingleton<MissionControlViewModel>inApp/Program.cs. - Unit test: simulate two
TaskStarted→ two monitors;TaskFinishedkeeps the pane;ColumnCountmatches 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.OpenInAppto it (via anAction<string>?set by the shell, like the existingCloseDetail/DeleteFromListhooks — 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. AddmissionControl.*en+de keys. - Build + Localization.Tests. Commit:
feat(ui): add MonitorPaneView.
Task 2.4 — MissionControlView grid + MissionControlWindow
MissionControlView.axaml:ItemsControl/UniformGridofMonitorPaneViewdriven byColumnCount, horizontal scroll fallback, header withClearFinished(+ 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 = OnMainWindowCloseinApp.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)hostingMonitorPaneView;Detachremoves 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 Releaseif Worker is running) before marking a task done. - Never push without asking.