//! User scenarios run headless: the real app (`SolveApp`) drawn frame by frame into an egui //! context without a window, driven by the same requests as the control channel (clicks, keys, //! drags, toolbar starts) the way a person would use it, with checks on the result. //! //! A scenario is a JSON list of steps: //! - `{"start": "solid.extrude"}`: a toolbar click; `{"key": "E", "shift"?, "cmd"?}`; `{"text": "20"}` //! - `{"widget": "Revolute", "near"?: "Type"}`: click a dialog field, choice or button by its //! text (with `near`: the one in that label's row) //! - `{"click": AT, "double"?, "shift"?, "ctrl"?, "button"?}`, `{"move": AT}`, //! `{"drag": [AT, AT], "shift"?, "ctrl"?, "steps"?}` where AT is `[x, y]` (screen), `{"world": [x,y,z]}` //! or `{"sketch": [x,y]}` (the sketch being edited) //! - `{"queued": "engine.execute", "params": {…}, "min_frames"?: n, "max_frames"?: n}`: a request sent the way an //! agent's arrive (through the app's request queue, answered between frames, so the agent //! cursor can act it out first); fails if the answer came in fewer than `min_frames` frames //! - `{"call": "ui.view", "params": {…}}`: any other control request (`fail: true` expects an //! error) //! - `{"make_image": "pic.png"}`: a picture in the scenario's folder //! - `{"choose_file": "part.step"}`: the next Open picker returns that file of the scenario's //! folder (Save pickers save there) //! - `{"shot": "name"}`: a screenshot when run in a window; headless, a render of the model //! into `$SOLVECRAFT_SCENARIO_SHOTS` when that is set; `{"debug": 1}` //! prints the UI state (`"widgets"`: also every widget's text; `"handles"`: every published //! handle, browser rows as `row: