Dashboard

A browser view of everything a generation produces: its sources and provenance, its runs as they execute, and its archives afterwards. One process, standard-library HTTP, no database — everything shown is read from the files the pipeline already writes.

$ motion-spec dashboard                  # serve on :8080, browse $MOTION_SPEC_GEN
$ motion-spec dashboard --port 8090 --logs path/to/generations
$ motion-spec dashboard -b               # detached; -k stops it, -r restarts
$ motion-spec dashboard --lan            # on the network: replay and simulated runs only

--logs names the generation root to browse (default $MOTION_SPEC_GEN); --sources the model source root (default: the logs root’s parent); --env the environment file health, builds and runs use (default: the nearest one above the logs root, --no-env for this shell). The Health page can switch between the workspace’s environment files itself; health rechecks under the one chosen, and every build and run started from the page after that uses it.

The pages

Generations. Every generation under the root: its model, whether the sources have drifted, whether it is built, its runs. A built generation can be run from here; a model can be edited in place and regenerated.

Run. One run, live or finished, fed by the shared memory block while the runtime writes and by the archived frame log afterwards. Constraint plots (Open in Jupyter seeds a notebook beside the run), SPARQL over the model graph, the run’s console, the files the run wrote (frame log, REC record, bag, videos — each opens read-only), its notes (dated, tagged, kept beside the archive), and the camera — a recorded mp4, or a ROS image topic on a real platform.

Health. motion-spec health on the page, with the installation’s versions and roots above it. Reached from the sidebar footer, alongside the repository and these docs. The checks are slow, so they are remembered — re-check reruns them. See Setup.

Organizing results

Generations and runs have editable labels, pins, tags, and dated notes. Labels leave the folder name and provenance unchanged. Pinned generations appear first; pinned runs lead their run list. Search includes model names, labels and tags. The generation browser remembers folded groups, filters and sorting; use newest, last run or largest to order the list.

Choose Use as model baseline on a run to mark it as the reference for later runs of the same model, including runs from other generations.

Protect on a generation or run refuses every deletion of it, and of anything holding it, until the protection is lifted; lifting it asks for the folder’s own name. It is deliberately two steps and independent of pinning, which one click undoes.

Selected generations and runs move to desktop Trash; space is freed only when Trash is emptied. Trash in the sidebar lists this root’s removed items by deletion time, lets you sort them, and restores them without overwriting existing paths.

Labels, pins, protection and explicit tags live in dashboard.json beside the generation or run; notes live in notes.json. A model’s dashboard-model.json records its baseline. These ordinary JSON annotation files are separate from the generated RDF and REC provenance.

Returning to an investigation

Archived replay views remember open plots, signals, zoom, panel, camera and cursor when leaving or reloading. Named plot presets reuse signal choices across runs of the same model; unavailable signals are reported and skipped, and a preset never reuses another run’s time window. These preferences are local to the browser and generation root.

Copy link to this moment includes the replay frame. Run notes can attach the current frame or an optional frame range; their timestamps and transport markers return to it. Generation notes remain general notes. Verdict times in the Reports panel open the relevant replay position and constraint plots.

Verification

Run the dashboard Python tests with the workspace environment and PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/test_dashboard_*.py. The optional browser integration test requires Playwright and MOTION_SPEC_BROWSER_EXECUTABLE pointing to a Chromium executable. Give pytest a fresh --basetemp directory of your own; the browser test moves and restores only its own fixtures.

Driving a simulated run

A simulated run creates a control block the loop polls once per tick; the dashboard writes it, the loop acks. Pause skips the whole tick — no FSM step, no frame, no control against a frozen plant. Step runs a counted number of ticks while paused. Speed sets the real-time factor, so it means nothing to an uncapped headless run. On a real platform the block does not exist and the controls are not offered.

The block is named /motion_spec_ctrl_<schema_hash[:16]>; motion_spec/dashboard/control.py is its authoritative layout.

Nothing else is a channel: live values ride the frame publisher the runtime already writes, history is the frame log, and cameras are mp4 recordings or ROS topics. A generation run without the dashboard behaves identically — a zeroed control block commands nothing.