Command Architecture Roadmap
Progress Summary
Last updated: 2026-03-12
| Phase | Status | Notes |
|---|---|---|
| Phase 1: Command Foundation | Complete | 6 commands, factory, validation, variable substitution, recorder, mutation guard, golden traces |
| Phase 2: Triage Session | Nearly Complete | Core, widget, guided editor all done. Keyboard shortcuts (2.4) and tracked-regions overlay (2.5) remain. |
| Phase 3: Persistence & Save Registry | Complete | SaverRegistry, 8 savers (CSV ×6, CapnProto, OpenCV), SaveData + LoadData commands, round-trip fuzz tests |
| Phase 3.5: Command Recording & Test Generation | Not Started | Wire CommandRecorder into GUI, route widgets through executeSequence(), minimal Command Log UI |
| Phase 4: Action Journal Integration | Not Started | Generalizes CommandRecorder into persistent, timestamped journal |
| Phase 5: Undo/Redo | Not Started | CommandStack, memento-based EditorState undo, inverse commands |
| Phase 6: CLI Batch & Macro Recording | Not Started | Headless command execution; generalizes golden trace tests |
| Widget Command-ification | Ongoing | 9 new commands identified via audit; none yet implemented. See GUI Verb Audit. |
For the full architectural design, types, and code examples, see the Command Architecture Design Document.
What Exists Today
Registered Commands (6)
| Command | Category | Undoable | Description |
|---|---|---|---|
MoveByTimeRange |
data_mutation | Yes | Move entities in a frame range between same-type data objects |
CopyByTimeRange |
data_mutation | No | Copy entities (source unmodified) |
AddInterval |
data_mutation | No | Append interval to DigitalIntervalSeries; creates series if missing |
ForEachKey |
meta | No | Iterate a list of values, execute sub-commands per item |
SaveData |
persistence | No | Save a data object via LoaderRegistry::trySave() |
LoadData |
persistence | Yes | Load a data object via LoaderRegistry::tryLoad() |
Infrastructure
| Component | Location | Purpose |
|---|---|---|
CommandFactory |
src/Commands/Core/ |
createCommand(), createCommandFromJson(), getAvailableCommands(), getCommandInfo() |
CommandRecorder |
src/Commands/Core/ |
Records executed commands for replay; hook in executeSequence() |
MutationGuard |
src/DataManager/Commands/ |
Debug-only: warns when DataManager is mutated outside a command |
DataManagerSnapshot |
src/DataManager/ |
Lightweight state snapshot (type, entity count, content hash) for golden trace tests |
ParameterSchema |
src/ParameterSchema/ |
Compile-time parameter schema extraction from reflect-cpp structs |
AutoParamWidget |
src/WhiskerToolbox/AutoParamWidget/ |
Dynamic Qt form builder from ParameterSchema |
TriageSession |
src/TriageSession/ |
Mark/Commit/Recall state machine using command sequences |
SaverRegistry |
src/DataManager/IO/core/ |
SaverInfo, AtomicWrite, getSupportedSaveFormats() |
Validation |
src/Commands/Core/ |
validateSequence() — structural, type, and variable checks |
VariableSubstitution |
src/Commands/Core/ |
Two-pass ${variable} substitution + normalizeJsonNumbers() |
Test Coverage
- Command unit tests: 25+ test cases (factory, move, copy, interval, forEach, error handling)
- CommandRecorder tests: 19 test cases (record/clear, JSON serialization, executeSequence integration, MutationGuard)
- Validation tests: 10+ test cases (structural, deserialization, variable resolution, type compatibility)
- Variable substitution tests: 10+ test cases (static/runtime variables, integer normalization)
- SaveData/LoadData tests: 22 test cases (factory, execution, undo, serialization, round-trip)
- Saver fuzz tests: 9 fuzz test files (CSV round-trip for all 6 types, CapnProto lines, OpenCV masks)
- TriageSession tests: State machine transitions, runtime variable population, integration
- Golden trace tests: CopyByTimeRange, MoveByTimeRange, AddInterval, multi-command sequences, replay determinism
Dependency Graph (Remaining Work)
Completed Foundation (Phases 1, 3)
│
├──► Phase 2 Remaining: Keyboard shortcuts (2.4), tracked-regions viz (2.5)
│
├──► Phase 3.5: Command Recording & Test Generation
│ Wires CommandRecorder into GUI, routes widgets through executeSequence()
│ Enables: "do it in GUI → export JSON → write golden trace test"
│
├──► Widget Command-ification (ongoing, on-demand)
│ Uses: MutationGuard for gap detection, golden traces for verification
│ Blocked commands: CreateData, DeleteData, AddEvent, RemoveEvent, etc.
│
├──► Phase 4: Action Journal
│ Depends on: Phase 3.5 (recorder wired into app)
│ Generalizes recorder into persistent timestamped log
│
├──► Phase 5: Undo/Redo
│ Independent of Phases 2, 4, 6
│ MoveByTimeRange already supports undo; needs CommandStack
│
└──► Phase 6: CLI Batch & Macro Recording
Depends on: Phases 3 (done), 4
Generalizes golden trace tests into CLI tool
Parallelizable: Phases 3.5, 5, and widget command-ification can proceed independently. Phase 4 depends on Phase 3.5. Phase 6 requires Phase 4.
Completed Phases (Summary)
Phase 1 — Command Foundation (Complete)
All 14 steps complete. Delivered:
- Core types (Steps 1.1–1.3):
CommandResult,CommandContext,CommandDescriptor,CommandSequenceDescriptor,ICommandbase class,CommandFactorywithcreateCommand()/createCommandFromJson(). - Execution engine (Steps 1.4–1.5): Two-pass
${variable}substitution,executeSequence()with stop-on-error,SequenceResultfor undo. - Query helpers (Step 1.6):
getEntityIdsInRange()forRaggedTimeSeries<T>,DigitalEventSeries, andDigitalIntervalSeries. - First commands (Step 1.7):
MoveByTimeRange,CopyByTimeRange,AddInterval,ForEachKey. - Validation (Step 1.9):
validateSequence()with structural, deserialization, variable, key existence, and type compatibility checks. - GUI verb audit (Step 1.10): On-demand hybrid strategy. DataManager_Widget and DataInspector_Widget audited. See GUI Verb Audit.
- ParameterSchema (Step 1.11): Extracted to
src/ParameterSchema/as standalone library. - CommandInfo (Step 1.12):
getAvailableCommands(),getCommandInfo(), per-commandParameterUIHints. - CommandRecorder + MutationGuard (Step 1.13): Trace recording with
executeSequence()hook; debug-only strangulation pattern for runtime gap detection. - DataManagerSnapshot + Golden Traces (Step 1.14): Lightweight snapshot comparison; golden trace test pattern for widget verification.
Known pitfall (resolved): rfl::Generic stores integers as double, causing round-trip failures. Fixed via normalizeJsonNumbers() in VariableSubstitution and createCommandFromJson() in CommandFactory.
See the design document for architecture details.
Phase 3 — Persistence Commands & Save Registry (Complete)
All steps complete. Delivered:
SaverRegistry infrastructure (Step 3.1.1):
SaverInfometadata,AtomicWriteutility,getSupportedSaveFormats()onLoaderRegistry. See SaverInfo & AtomicWrite.8 format savers (Steps 3.1.2–3.1.9):
Format Data Type Fuzz Tested CSV PointData Yes (round-trip + delimiter) CSV LineData (single-file) Yes (round-trip + delimiter) CSV LineData (multi-file) Yes (round-trip) CSV AnalogTimeSeries Yes (round-trip + delimiter) CSV DigitalEventSeries Yes (round-trip + delimiter) CSV DigitalIntervalSeries Yes (round-trip + delimiter) CSV MaskData (RLE) Yes (round-trip + delimiter) CapnProto LineData Yes (exact binary round-trip) OpenCV (Image) MaskData Yes (robustness) SaveData command (Step 3.2): Dispatches via
LoaderRegistry::trySave(). Not undoable. See SaveData Command.LoadData command (Step 3.3): Dispatches via
LoaderRegistry::tryLoad(). Undoable (undo callsdeleteData()). See LoadData Command.
Phase 2 — Triage Session (Nearly Complete)
Goal: Mark/Commit/Recall workflow using command sequences.
Completed steps:
- TriageSession core (Step 2.1):
Idle ↔︎ Markingstate machine insrc/TriageSession/.commit()populates runtime variables (mark_frame,current_frame) and delegates toexecuteSequence(). Retains last commit commands for undo support. - TriageSessionState (Step 2.2): EditorState subclass with pipeline/tracked-regions serialization.
- TriageSessionWidget (Step 2.3): Mark/Commit/Recall buttons, pipeline display, tracked-regions summary.
- Unit tests (Step 2.6): State machine transitions, runtime variable population, full triage round-trip integration test.
- AutoParamWidget extraction (Step 2.7): Extracted to
src/WhiskerToolbox/AutoParamWidget/withQt6::Widgets+ParameterSchemadependencies only. - Guided Pipeline Editor (Step 2.8): Command picker dialog, editable command list with
AutoParamWidgetrows, bidirectional sync with raw JSON editor.
Remaining Steps
Step 2.4 — Keyboard Shortcuts
Step 2.5 — Tracked-Regions Visualization
Widget Command-ification (Ongoing)
Goal: Route all DataManager mutations through commands. Audited on-demand per widget.
Strategy: See GUI Verb Audit for the full audit process, widget tables, and command designs.
Definition of Done
A widget is fully command-ified when:
- Zero direct
DataManagermutations — allsetData(),deleteData(), etc. go throughcreateCommand()+execute(). - Zero MutationGuard warnings — exercising the widget in a debug build produces no console warnings.
- Golden trace test exists — at least one Catch2 test replays a recorded command sequence and asserts the final
DataManagerSnapshot.
Audited Widgets
| Widget | Verbs | Covered | New Cmds Needed | Golden Trace |
|---|---|---|---|---|
| DataManager_Widget | 3 | 0 | 2 (CreateData, DeleteData) |
Not yet |
| DataInspector_Widget | ~30 | ~10 | ~14 | Not yet |
Commands Still Needed
9 commands identified by audit but not yet implemented. See New Commands Identified for full designs.
Wave 1 — Core CRUD: CreateData, DeleteData, AddEvent, RemoveEvent
Wave 2 — Entity Operations: DeleteByEntityIds, RemoveInterval, ModifyInterval
Wave 3 — Group Operations: AssignToGroup, RemoveFromGroup
Remaining widgets (DataImport, DataExport, Media, etc.) are audited on-demand as they are touched. MutationGuard provides automated runtime gap detection.
Phase 3.5 — Command Recording & Test Generation
Goal: Wire CommandRecorder into the running application so GUI actions produce exportable CommandSequenceDescriptor JSON, enabling semi-automated golden trace test generation.
Depends on: Phase 1 (done)
Motivation: All the infrastructure exists (CommandRecorder, executeSequence() hook, DataManagerSnapshot, golden trace tests) but nothing is connected in the running app. Widgets call cmd.execute(ctx) directly, bypassing executeSequence() and its recording hook. This phase wires everything together so a user can: do actions in GUI → see recorded commands → copy JSON → paste into a Catch2 golden trace test.
Current Gap
| Component | Status |
|---|---|
CommandRecorder class |
✅ Implemented, tested (19 test cases) |
executeSequence() recording hook |
✅ Implemented (accepts CommandRecorder* param) |
MutationGuard |
✅ Active in debug builds |
| Shared recorder instance in app | ✅ MainWindow owns CommandRecorder; commandRecorder() getter |
ctx.recorder fallback |
✅ executeSequence() falls back to ctx.recorder when no explicit recorder passed |
| DataSynthesizer “Generate” | ✅ Routes through executeSequence() |
| SaveData call sites | ✅ Routes through executeSequence() |
TriageSession commit() |
✅ Routes through executeSequence() with recorder |
| UI to view/export recorded commands | ✅ CommandLog_Widget (Step 3.5.3) |
Step 3.5.1 — Centralized Recorder (Complete)
Step 3.5.2 — Route Widgets Through executeSequence()
Step 3.5.3 — Command Log UI (Complete)
Note: This is intentionally minimal — NOT the full Phase 4 Action Journal. No persistence, timestamps, filtering, or provenance. Just enough to see what happened and export it.
Step 3.5.4 — End-to-End Verification
Exit Criteria
- All GUI command execution paths go through
executeSequence()with a recorder. - Command Log widget displays recorded commands in real-time.
- “Copy as JSON” produces a valid
CommandSequenceDescriptorthat, when pasted into a Catch2 test and executed viaexecuteSequence(), reproduces the sameDataManagerSnapshot.
Phase 4 — Action Journal Integration
Goal: Commands auto-record to journal; journal can replay command entries.
Depends on: Phase 3.5 (recorder wired into app)
Generalizes the CommandRecorder into a persistent, timestamped, filterable log with workspace integration. The executeSequence() hook from Step 1.13 is reused — the change is swapping CommandRecorder* for ActionJournal* with persistence and metadata.
Step 4.1 — Journal Hook in executeSequence
Step 4.2 — Journal Replay
Step 4.3 — WorkspaceManager Integration
Phase 5 — Undo/Redo
Goal: CommandStack, EditorState memento undo, inverse-command undo for data mutations.
Depends on: Phase 1 (done)
Step 5.1 — CommandStack
See CommandStack in the design document.
Step 5.2 — WidgetStateChangeCommand
See WidgetStateChangeCommand in the design document.
Step 5.3 — CommandSequenceCommand
See CommandSequenceCommand in the design document.
Step 5.4 — MoveByTimeRange Undo Verification
Step 5.5 — UI Integration
Phase 6 — CLI Batch Executor & Macro Recording
Goal: Execute command sequences headlessly; record GUI actions as commands.
Depends on: Phases 3 (done), 4
Generalizes the golden trace test pattern (load JSON → create DataManager → executeSequence() → check results) into a CLI tool. The macro recorder generalizes CommandRecorder from a test utility into a user-facing feature.
Step 6.1 — CLI Executor
Step 6.2 — Macro Recorder
Step 6.3 — ConfigExportToolbar
Step 6.4 — CommandSequenceWidget
Relationship to State Management Roadmap
This roadmap replaces Phases 4–6 of the State Management Roadmap with a more comprehensive command-first architecture. The key differences:
| State Mgmt Roadmap (old) | Command Architecture (this doc) |
|---|---|
| Phase 4: Action Journal records ad-hoc action types | Phase 4 here: records CommandDescriptors — standardized, replayable |
Phase 5: Command interface with execute()/undo() |
Phases 1+5 here: ICommand base with factory, CommandStack layered on top |
Phase 6: SessionReplayer replays workspace provenance |
Phase 6 here: CLI executor replays CommandSequenceDescriptor |
| No concept of triage or batch operations | Triage Session (Phase 2) demonstrates the architecture |
Ideas carried forward from the State Management Roadmap:
- Action Journal (
ActionEntry, append-only log, JSONL format, query/filter) — Phase 4 - CommandStack (undo/redo, clean state, merge) — Phase 5
- Memento-based EditorState undo (
WidgetStateChangeCommand) — Phase 5 - Command merging for sequential similar operations — Phase 5 (
ICommand::mergeWith()) - ConfigExportToolbar (“Copy as JSON”, “Save Configuration…”) — Phase 6
- Session replay / batch processing — Phase 6
- Incremental adoption (not every operation needs to be a command immediately) — design principle
See Also
- Command Architecture Design Document — full architecture, types, and code examples
- GUI Verb Audit — widget audit tables and new command designs
- Data Manager — core data management documentation