* Every new command comes with input and output schemas A test fails for a command without a contract or an output type, unless it is a planned! placeholder or listed in schema/unconverted.txt, which holds the 400 commands that predate the rule and only shrinks: the test also fails when a listed command has its schemas, when the list names a command that doesn't exist, or when it isn't sorted. `checked!` now takes only the command's id and checks the contract that id publishes (`run_checked_as`), so the contract enforced is always the one described. Another test runs every command that has a contract in strict with an unknown key, so a contract is never published without `checked!`, under the command's own id, enforcing it. A new book page, Command schemas, explains how to write a contract and a reply type, the tests to add, the shared field types and how to add one, with a checklist; the failure messages point to it. "Adding a command", CONTRIBUTING.md and AGENTS.md now include the schemas, and the MCP tool description and docs/mcp.md no longer list the converted commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * List the commands added on main since: view.layout, view.history_layout, view.normal_to Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Command schemas guide: what participants and targets do, checking the evaluation, warning-free replies - `participants` changes the result for some features and nothing for others; where it and another key override each other, or where it has no effect, say so in that key's description or in the summary. - The example primitive's `targets` default to every body in the feature's component, not the bodies it meets; they're ignored when `participants` lists bodies. - Check what each value does where the feature is evaluated, not only how the handler reads it. - A reply test runs every form in Warn and checks for no `param_warnings` (Strict for a reply that isn't an object). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * List the 43 commands added on main since: frame, component, render and others Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * List the 11 commands added on main since: browser folding, standard views, parameter sweep, path flange browser.collapse_all, browser.expand_all, inspect.parameter_sweep, sheet.path_flange and the seven view.* standard views (back, bottom, front, isometric, left, right, top) landed on main without schemas; the gate flags them, as intended. 457 commands listed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Schema rule warns instead of failing for now A command without input and output schemas prints a warning listing it (test output and a CI annotation). SCHEMA_RULE_STRICT in coverage_tests.rs (or SOLIDCRAFT_SCHEMA_RULE_STRICT=1) makes it fail again. Docs say "should, and will become required". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
3.2 KiB
The command engine
Every user-visible action is a command: a CommandSpec registered in
crates/engine/src/cmd/, with a dotted, lower-case id (solid.extrude, sketch.create,
timeline.rollback). The toolbar, dialogs, marking menu, S box, scripts, the CLI, the control
channel and MCP all run commands through Session::execute(id, params). Commands take JSON
parameters and never open dialogs; a dialog is a front-end form that builds the parameters.
CommandSpec::new("solid.fillet", "Fillet", fillet)
.at("SOLID", "MODIFY") // toolbar tab and panel ("" = not on the toolbar)
.icon("fillet") // drawn in code, assets/icons/ui/source/icons.rs
.key("F") // default shortcut
.params("edges: [[x,y,z] | {body, index}]; radius: expr; …"),
run is fn(&mut Session, &Value) -> Result<Value>, and enabled is
fn(&Session) -> Result<(), String>; the error string is the disabled-button tooltip. The
params string is the one-line documentation shown by solidcraft-cli commands, MCP's
list_commands and the command reference.
A command that changes the design is undoable by default: the session snapshots the document
before it runs, and a failing command leaves the document unchanged. Feature commands add a
feature to the timeline and let the document evaluate it; they validate their inputs first
(units, ranges, references) and return BadParams instead of building a broken feature.
Only the registry's own dotted ids name commands (matched ignoring case); any other id fails
with unknown command, which names up to three close ids: a typo's (solid.extrud), or one
ending in the same word (file.open: did you mean doc.open?).
Adding a command
- Add a
CommandSpecto the right file incrates/engine/src/cmd/and implementrun. - Give it input and output schemas: a parameters type (its contract) and a reply type in the
module for its command file under
crates/engine/src/schema/(schema/features.rsforcmd/features.rs, and so on; create it for the file's first contract), built from the shared field types inschema/types.rs. Register them withchecked!(…),.contract(…)and.output(…), assolid.extrudedoes, and add the command to that module'sschemas_and_the_key_check_agreetest, with error-message cases and a reply test. The parameters type documents each parameter (keep the.params(…)prose too, for now), anddescribe_commandserves both schemas to agents and scripts. A test (every_command_has_input_and_output_schemas) warns for a command without them, and will fail it once the rule becomes required; the commands that predate the rule are listed inschema/unconverted.txt, which only shrinks. Command schemas walks through it, with the shared types and a checklist. - Write engine tests that measure the result: volume, area, and face/edge/vertex counts against values you can derive by hand.
- The hostile-input test (
hostile_params_never_panicincrates/engine/src/tests.rs) runs your command automatically with every documented parameter set to junk. It must return errors, never panic. - For UI work, add a scenario (see Test harnesses).