Files
linuxcncWebWASM/docs/linuxcnc-axis-web-control-panel-source-logic.md

605 lines
60 KiB
Markdown

# LinuxCNC AXIS Web Control Panel Source Logic
Status: source-locked implementation contract
Scope: the selected `XYZAC TRT` Web/WASM panel and its LinuxCNC-compatible
Worker command path
Source baseline: LinuxCNC submodule `239dcf4284762ad7e9c65d390bc5524d855c2f8e`
Relationship map: [`linuxcnc-axis-web-control-panel-source-logic.png`](linuxcnc-axis-web-control-panel-source-logic.png)
Reproducible graph source: [`linuxcnc-axis-web-control-panel-source-logic.dot`](linuxcnc-axis-web-control-panel-source-logic.dot)
This document is the control-flow authority for the Web panel. A button is
implemented only when its complete path is defined here:
```text
browser gesture
-> React handler
-> AxisOperationController operation
-> SimSession command gate
-> LinuxCNC/WASM task, interpreter, TP or IO authority
-> canonical snapshot
-> React projection
```
The React state is never an authority. A rejected Worker command must remain
rejected in the UI, with the source gate reason visible as the disabled-button
title and in Diagnostics.
"Source parity" has a strict meaning in this document:
| Mark | Meaning |
| --- | --- |
| `SOURCE_EXACT` | Same user action, source handler, UI enable formula, command ordering and observable result as AXIS. |
| `SOURCE_ADAPTER` | Browser/WASM transport differs, but the accepted action and observable machine semantics are the same. Any stronger Web safety gate is stated explicitly. |
| `SOURCE_BLOCKED` | AXIS has the action, but the selected WASM or browser boundary cannot represent its canonical state. It stays disabled or absent and is never emulated with local state. |
| `WEB_EXTENSION` | No AXIS control exists. The control must be visibly and textually separated from source parity and must not alter the meaning or gate of an AXIS action. |
Only `SOURCE_EXACT` and `SOURCE_ADAPTER` count as implemented AXIS parity.
`SOURCE_BLOCKED` is an honest capability boundary. `WEB_EXTENSION` is never
evidence of parity. If this document and the pinned source disagree, the pinned
source wins and this document, its graph and the Web implementation must be
updated together.
The panel does not add a startup/run alias. The source-defined machine sequence
is explicit: release E-stop when needed, power on, home, then press the single
toolbar Run control (or the source `R` shortcut).
Native AXIS selects its startup program in this exact priority order:
positional command-line argument, `AXIS_OPEN_FILE`, `[DISPLAY] OPEN_FILE`, then
the built-in lathe/mill sample. The browser has no AXIS process argument or
process environment override, so the selected INI's `[DISPLAY] OPEN_FILE` is
the first applicable source authority. For `xyzac-trt.ini` it selects
`./demos/xyzac_switchkins.ngc`. Browser UI persistence must not insert a new
higher-priority executable source and must never schedule a hidden second
`PROGRAM_LOAD` after the configured file appears.
AXIS has both Machine/View menu bindings and toolbar/notebook bindings. The Web
surface keeps the toolbar as the single machine-command entry point and the
notebook tabs as the single view entry point. This removes duplicate buttons;
the command, gate and source symbol for each retained entry are unchanged.
## Source References
The primary GUI source is
`linuxcnc/src/emc/usr_intf/axis/scripts/axis.py`. The accepted command and
post-completion/abort behavior are also checked against
`linuxcnc/src/emc/task/emctaskmain.cc`; the Web panel must not stop at the
Python button handler when the LinuxCNC task layer defines an additional
transition.
### Source-lock manifest
The source baseline is the submodule commit above, not a LinuxCNC version name
or an approximate UI description. The two primary GUI files are additionally
fingerprinted so the audit fails on an unreviewed source drift:
| Source | Baseline SHA-256 | Normative responsibility |
| --- | --- | --- |
| `src/emc/usr_intf/axis/scripts/axis.py` | `328c038a1e8fefd952e53c8e398e3fa98b04602351c44318edd1b044e8f63ffa` | Handlers, mode transitions, keyboard bindings, preview and live-plot ownership |
| `share/axis/tcl/axis.tcl` | `c9f0b4dc686717b37565f85fb0ab8cc8ded9cfa0028cc0aaaccdbcf808c4c524` | Widgets, menu/toolbar wiring and exact dynamic enable formulas |
| `src/emc/task/emctask.cc` | pinned by submodule commit | Machine state edges and observed `ESTOP`/`ESTOP_RESET`/`ON` state |
| `src/emc/task/emctaskmain.cc` | pinned by submodule commit | Task command acceptance, homing enforcement, run/pause/resume/abort and task-plan reopen/close |
| `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini` plus `remap_subs/{428,429,430}remap.ngc` | pinned by submodule commit | Selected axes, limits, speeds, startup file and switchkins remaps |
The current local LinuxCNC tree contains unrelated heartbeat instrumentation
and an `INTRO_GRAPHIC` substitution. Neither changes the AXIS control flow
specified here; they are not used to redefine parity.
| AXIS source symbol | Role |
| --- | --- |
| `manual_ok`, `ensure_mode` | Polling, task-state and mode preconditions |
| `estop_clicked`, `onoff_clicked` | E-stop and machine power edges |
| `open_file_guts` | File replacement transaction and mode restoration |
| `task_run`, `task_run_line`, `task_step`, `task_pause`, `task_resume`, `task_pauseresume`, `task_stop` | Program execution lifecycle |
| `send_mdi`, `send_mdi_command` | MDI history, mode and command submission |
| `home_all_joints`, `home_joint` | Homing and joint/kinematics restrictions |
| `touch_off_system`, `touch_off_tool` | Coordinate/tool offset dialogs and `G10`/`G43` sequence |
| `spindle`, `spindle_forward_toggle`, `spindle_backward_toggle`, `spindle_increase`, `spindle_decrease`, `spindle_constant` | Spindle direction, speed and brake controls |
| `mist_toggle`, `flood_toggle`, `brake_on`, `brake_off` | Auxiliary IO controls |
| `toggle_override_limits`, `cycle_view` | Limit override and preview view selection |
| `update_state`, `set_mode_from_tab` in `axis.tcl` | Exact widget enable/disable rules and idle mode restoration |
The widget wiring is in `linuxcnc/share/axis/tcl/axis.tcl`: File/Machine/View/
Help menus are at lines 55-464, toolbar bindings at 490-669, manual and MDI
controls at 839-1288, sliders at 1396-1538, control groups at 1715-1726 and the
normative `update_state` formulas at 1767-1888.
## Source Surface Inventory
This inventory separates source controls from the smaller Web surface. A
source-only or host-bound entry is documented explicitly rather than silently
replaced by a different action.
| Source surface | Source entries | Web treatment |
| --- | --- | --- |
| File menu | Open, Recent Files, Edit, Reload, Save As, Properties, Edit Tool Data, Reload Tool Data, Ladder Editor, Quit | Open and Reload are `SOURCE_ADAPTER` toolbar entries. Edit Tool Data and Quit are visible `SOURCE_BLOCKED` entries. The remaining host/file actions are recorded below and are not silently aliased. |
| Machine menu | E-stop, Power, Run, Run from selected line, Step, Pause, Resume, Stop, Optional Stop, Block Delete, MDI history actions, six desktop tools, Homing, Unhoming, ten zero-coordinate actions and two tool-touch modes | E-stop/Power/Run/Step/Pause/Resume/Stop/Home/Touch Off are represented; every omitted or ABI-blocked source action is recorded below. |
| View menu | Five preset views, inches/mm, program/rapid/alpha/live/tool/extents/offset/limit/velocity/DTG layers, grid, large DRO font, PyVCP, commanded/actual, machine/relative, joint/world and Clear Live Plot | The menu has five views; the toolbar additionally has inverted-front `Y2`, for six toolbar presets. Supported projections are `SOURCE_ADAPTER`; every omitted view action is recorded below. |
| Toolbar | E-stop, Power, Open, Reload, Run, Step, Pause/Resume, Stop, Block Delete, Optional Stop, zoom, six views, rotate, Clear Plot | Same source order and icon assets are retained for supported controls; unsupported controls are disabled with a source/ABI reason. |
| Manual notebook | Axis/joint selector, continuous/increment jog, Home Axis, Touch Off, Tool Touch Off, Override Limits, spindle direction/stop/speed/brake and coolant | XYZAC axis selector, jog, Home Axis, Touch Off, spindle and coolant are represented. Joint selection, Tool Touch Off and Override Limits are explicitly bounded by the selected configuration/ABI. Tool and probe state is read-only; Prepare/Change/Arm/Disarm are IO/HAL operations, not AXIS main-panel controls. |
| Web case strip | no AXIS equivalent | `Load exact XYZAC TRT case` is a Web workspace/bootstrap control, not an AXIS source button. It may only call the same documented `LOAD_WORKSPACE` + `PROGRAM_LOAD` transaction and must not introduce a second Run/Power/Home flow. |
| MDI notebook | MDI entry, Go, history selection/double-click, Up/Down history, Ctrl-M/H/Shift-H clipboard operations | Entry, Go and local history are represented; clipboard/file history operations are host/UI-only and are not sent as machine commands. |
| Help menu/dialogs | About AXIS, Quick Reference, dialog close buttons and website link | About is `SOURCE_ADAPTER`. Quick Reference and the external website link are not rendered; neither is mapped to a machine action. |
| Bottom controls | Linear jog speed, angular jog speed, max velocity, spindle override, feed override and rapid override | Linear jog, max velocity, spindle and feed are adapters. Angular jog and rapid override are `SOURCE_BLOCKED`; one must never stand in for the other. |
| Browser UI persistence | AXIS preferences do not override `[DISPLAY] OPEN_FILE` with executable source text | OPFS may persist presentation preferences such as view, zoom, tabs, units and selected axis. It must not persist or automatically load G-code source/path. |
The Web surface may remove duplicate menu entries, but it may not change the
meaning of a retained command. This is why Machine/View menus are not rendered
when the same source action already has a toolbar or notebook entry.
### Exhaustive native-action ledger
This ledger accounts for every interactive family constructed by `axis.tcl` or
bound by `axis.py`. Separators, labels, scrollbars and read-only status text are
not actions. Dynamically generated joint and grid entries are counted as
families because their cardinality comes from the selected INI.
| Native action family | Source symbols | Parity disposition |
| --- | --- | --- |
| File Open/Recent/Edit/Reload/Save/Properties | `open_file`, `open_file_name`, `edit_program`, `reload_file`, `save_gcode`, `gcode_properties` | Open/Reload: `SOURCE_ADAPTER`; Recent/Edit/Save/Properties: browser file/host boundary, not rendered |
| Tool table edit/reload, Ladder Editor, Quit | `edit_tooltable`, `reload_tool_table`, `classicladder`, `destroy` | Tool editor/Quit: visible `SOURCE_BLOCKED`; reload/Ladder: host boundary, not rendered |
| E-stop and Power | `estop_clicked`, `onoff_clicked` | `SOURCE_ADAPTER`; exact edge selection and source widget gates apply |
| Run, Run from line, Step, Pause, Resume, Stop | `task_run`, `task_run_line`, `task_step`, `task_pause`, `task_resume`, `task_pauseresume`, `task_stop` | Run/Step/toggle Pause-Resume/Stop: `SOURCE_ADAPTER`; Run from line is `SOURCE_BLOCKED` until source-line selection is canonical |
| Optional Stop and Block Delete | `toggle_optional_stop`, `toggle_block_delete` | Visible `SOURCE_BLOCKED`; block delete's source wait, MANUAL switch and preview reload must not be approximated by a local checkbox |
| MDI history clear/copy/paste/select/double-click/Up/Down | `clear_mdi_history`, `mdi_history_hist2clip`, `mdi_history_clip2hist`, `mdi_history_butt_1`, `mdi_history_double_butt_1`, `mdi_up_cmd`, `mdi_down_cmd` | Select/Up/Down/local history: `SOURCE_ADAPTER`; clipboard/file persistence variants are browser/host boundaries |
| Calibration, HAL Configuration, HAL Meter, HAL Scope, LinuxCNC Status, Debug Level | external `exec` commands in Machine menu | Desktop-process boundary; not rendered and never mapped to Diagnostics as though it were the same action |
| Home all/joint and Unhome all/joint | `home_all_joints`, `home_joint_number`, `unhome_all_joints`, `unhome_joint_number` | Home all/selected axis: `SOURCE_ADAPTER`; Unhome and joint-mode selector: `SOURCE_BLOCKED` |
| Zero P1..P9 and G92 coordinate systems | `clear_offset` | `SOURCE_BLOCKED`; Touch Off is not an alias for clearing a coordinate system |
| Tool touch-off target workpiece/fixture | `toggle_tto_g11` selecting `G10 L10`/`G10 L11` | `SOURCE_BLOCKED` with Tool Touch Off; no local target state is exposed |
| Top/Z2/Side/Front/Y2/Perspective, zoom and rotate | `set_view_*`, `zoomin`, `zoomout`, rotate Tcl command | `SOURCE_ADAPTER`; Y2 is toolbar-only in the native layout |
| Inches/MM, commanded/actual, machine/relative | `metric`, `display_type`, `coord_type` | `SOURCE_ADAPTER`; display projection only |
| Show program, rapids, alpha, live plot, tool, extents, offsets, limits, velocity, DTG | `toggle_show_*`, `toggle_program_alpha` | Program/live/extents/offsets/limits/velocity have adapters; rapids, alpha, tool toggle and DTG are not rendered |
| Grid Off/custom/INI increments, large DRO font, PyVCP, Joint/World | `set_grid_size`, `set_grid_size_custom`, `toggle_dro_large_font`, `toggle_show_pyvcppanel`, `set_teleop_mode` | Not rendered; Joint/World is `SOURCE_BLOCKED`, presentation-only settings are not aliased |
| Clear Live Plot | `clear_live_plot` -> `LivePlotter.clear` -> `positionlogger.clear` | `SOURCE_ADAPTER`; this is the only native live-trace deletion action |
| Axis/joint selection and jog increment | `ja_rbutton`, `jogincrement`, `activate_ja_widget` | XYZAC axis and increment selection: `SOURCE_ADAPTER`; native joint selection is `SOURCE_BLOCKED` |
| Jog negative/positive and key jog | `jog_minus`, `jog_plus`, `jog_on`, `jog_off` | `SOURCE_ADAPTER`; press/release semantics and Manual-tab guard apply |
| Home Axis, Touch Off, Tool Touch Off, Override Limits | corresponding `zerohome`/`override` widgets | Home/Touch Off: `SOURCE_ADAPTER`; Tool Touch Off/Override Limits: visible `SOURCE_BLOCKED` |
| Spindle CCW/Stop/CW, slower/faster, brake | `spindle`, `spindle_decrease`, `spindle_increase`, `spindle_constant`, `brake` | `SOURCE_ADAPTER`; button-state formulas and press/release ordering apply |
| Mist and Flood | `mist`, `flood`, `mist_toggle`, `flood_toggle` | `SOURCE_ADAPTER`; independent canonical IO bits |
| MDI entry, Go, Enter, history activation | `send_mdi`, `send_mdi_command` | `SOURCE_ADAPTER`; source trim, mode and adjacent-history de-duplication apply |
| Linear/angular jog speed, max velocity, spindle/feed/rapid override | `update_*slider_vel`, `set_maxvel`, `set_spindlerate`, `set_feedrate`, `set_rapidrate` | Linear jog/max velocity/spindle/feed: `SOURCE_ADAPTER`; angular jog/rapid: `SOURCE_BLOCKED` |
| About, Quick Reference, clear notifications | About/keys dialogs and `Ctrl-Space` binding | About: local adapter; Quick Reference and notification-clear are not rendered |
| Case selector/load, packaged 3D Chips, Diagnostics, pan/reset buttons, source editor | no AXIS widget equivalent | `WEB_EXTENSION`; may not be counted as AXIS parity |
## Source Keyboard Contract
The following bindings are copied from `axis.py` lines 3109-3186 and
`3194-3327`. The source binds lowercase Tk event sequences even though Quick
Reference displays several letters in uppercase. Keys requiring an unavailable
command are blocked instead of being silently mapped to another operation.
| Key | Source handler | Web treatment |
| --- | --- | --- |
| `F1`, `F2`, lowercase `o`, `r`, `t`, `p`, `s`, `Escape`, `Ctrl-r`, `F7`-`F12` | E-stop, power, open, run, step, pause, resume, stop, reload, coolant and spindle handlers | Worker-backed commands where the corresponding control is supported. Display labels remain `O/R/T/P/S`. |
| uppercase `R`, `F` | `task_reverse`, `task_forward` | Source controls are recorded as blocked because the selected WASM program ABI has no reverse/forward command; they are never mapped to Run. |
| `Home`, `Ctrl-Home`, `Shift-Home`, `End`, `Ctrl-End` | selected joint home, home all, zero selected G54 axis, system touch off, tool touch off | Home and system Touch Off have adapters; zero-G54 and Tool Touch Off are blocked. |
| lowercase `x`, `y`, `z`, `a` | Select the first four source axes | Axis selection adapter. Native AXIS does **not** bind lowercase `c` to C-axis selection; `c` selects continuous jog. C remains selectable only through its radio control. |
| `-`, `=`, arrows/Page keys/`[`/`]` and their Shift variants | `jog_on_map`/`jog_off_map` or selected-axis `jog_minus`/`jog_plus` | Jog key semantics require keydown start and keyup stop. Pointer controls are supported; unavailable key bindings are not reassigned. |
| lowercase `c`, lowercase `i`, uppercase `I` | Continuous, next incremental and previous incremental jog modes | Represented by the jog increment selector; no second machine-mode state is created. |
| `` ` ``, digits `0`-`9` | Select joints in joint mode; otherwise set feed override from 0% to 100% | Joint-mode selection is blocked. Feed changes, if bound, must use the same mode-dependent meaning. |
| `,`/`.`; `<`/`>`; `;`/`'` | Linear jog speed; angular jog speed; maximum velocity | Linear jog/max velocity have adapters; angular jog speed is blocked and must not alias linear speed. |
| `v`, `d`, `!`, `@`, `#`, `$` | Cycle view, rotate, units, display type, coordinate type, teleop mode | View/rotate/units/display/coordinate actions are projected; teleop mode is explicitly unavailable. |
| uppercase `B`, lowercase `b` | `brake_on`, `brake_off` | Must preserve the distinct source edges; Quick Reference describes them as Shift-B/B. |
| `Ctrl-K` | `clear_live_plot` | Worker visual-only `VISUAL_CLEAR`. |
| `Ctrl-S`, `Ctrl-M/H/Shift-H`, `F3`, `F4`, `F5`, `Ctrl-Space` | Save G-code, MDI history, notebook selection and notification clear | Save/clipboard actions are host boundaries; tabs are local navigation plus their documented mode callback; notification clear has no machine effect. |
## State Gates
AXIS does not have one combined `RUNNING/STOPPED/READY` enum. Source parity is
derived from independent LinuxCNC status dimensions; Web-friendly labels may be
displayed, but they may not replace these dimensions as gate authority.
| Source dimension | Exact values used by AXIS | Meaning |
| --- | --- | --- |
| `task_state` | `STATE_ESTOP`, `STATE_ESTOP_RESET`, `STATE_OFF`, `STATE_ON` | Machine E-stop/power state. `emctask.cc::determineState()` publishes only ESTOP, ESTOP_RESET or ON; `STATE_OFF` is an accepted command value and is documented by `emcmodule.cc` as not used as a stable status value. |
| `task_mode` | `MODE_MANUAL`, `MODE_AUTO`, `MODE_MDI` | Current task command domain. |
| `interp_state` | `INTERP_IDLE`, `INTERP_READING`, `INTERP_PAUSED`, `INTERP_WAITING` | Interpreter lifecycle used by `axis.tcl::update_state`. |
| `task_paused` / `paused` | Boolean pause projection | Used by `task_resume` and toolbar relief/icon projection. |
| `exec_state` | `DONE`, `ERROR`, motion/IO/delay/system-command wait states | Task executor progress; it is not interchangeable with `interp_state`. |
| `motion_mode` | `TRAJ_MODE_FREE`, `TRAJ_MODE_COORD`, `TRAJ_MODE_TELEOP` | Joint/world motion selection and touch-off availability. |
| `taskfile`, `homed[]`, `on_any_limit`, `queued_mdi_commands`, `spindle[].direction` | Independent status fields | Additional source gates; none may be inferred from a React label. |
The source helper definitions are exact:
```text
running = task_mode in {AUTO, MDI} and interp_state != IDLE
manual_ok = task_state == ON
and not running
and (interp_state == IDLE
or (task_mode == MDI
and queued_mdi_commands < max_queued_mdi_commands))
```
`ensure_mode(m, *p)` polls, returns if the current task mode is an accepted
mode, otherwise sends `c.mode(m)`, waits for completion and polls again. It is a
command transaction, not a React state assignment and not a success check that
may be skipped.
### Source-exact widget enable formulas
These expressions are copied from `axis.tcl::update_state`; handler and task
checks still apply after a widget is enabled.
| Native widget/family | Exact AXIS UI enable condition |
| --- | --- |
| E-stop | Always enabled |
| Power | `task_state != STATE_ESTOP` |
| Open | `interp_state == INTERP_IDLE` |
| Reload | `interp_state == INTERP_IDLE && taskfile != ""` |
| Run | `task_state == STATE_ON && interp_state == INTERP_IDLE` |
| Step | `task_state == STATE_ON && taskfile != ""` |
| Pause menu | `task_state == STATE_ON && interp_state in {INTERP_READING, INTERP_WAITING}` |
| Resume menu | `task_state == STATE_ON && interp_state == INTERP_PAUSED` |
| Pause/Resume toolbar | `task_state == STATE_ON && interp_state != INTERP_IDLE` |
| Stop | `task_state == STATE_ON && interp_state != INTERP_IDLE` |
| Run from selected line | `task_state == STATE_ON && interp_state == INTERP_IDLE && highlight_line != -1` |
| Homing/Unhoming/Zero-coordinate menus | `task_state == STATE_ON && interp_state == INTERP_IDLE` |
| Manual group | `task_state == STATE_ON && interp_state == INTERP_IDLE` |
| MDI group | `task_state == STATE_ON && queued_mdi_commands < max_queued_mdi_commands` |
| Touch Off | `task_state == STATE_ON && interp_state == INTERP_IDLE && (motion_mode != TRAJ_MODE_FREE || kinematics_type == KINEMATICS_IDENTITY)` |
| Tool Touch Off | Touch Off condition plus a loaded tool |
| Override Limits | `on_any_limit` |
| Spindle slower/faster | `task_state == STATE_ON && interp_state == INTERP_IDLE && spindle.direction != 0` |
| Feed/spindle/rapid override and max-velocity sliders | No `update_state` disable formula; setters send their command directly. INI and command-layer range/enable rules still apply. |
| View controls and Clear Live Plot | No machine-state gate |
The Web may disable an action while a browser-only asynchronous load is in
flight. Any other gate stronger than this table must be identified as a Web
safety adaptation, not attributed to AXIS.
## Control Matrix
| Panel control | React command | Source flow and required result |
| --- | --- | --- |
| Emergency Stop / Cancel | `ESTOP {active}` | `SOURCE_ADAPTER`: `estop_clicked` sends `STATE_ESTOP_RESET` only when status is `STATE_ESTOP`; every other status sends `STATE_ESTOP`. `emcTaskSetState(ESTOP)` aborts motion, spindle, task and IO, disables trajectory, applies volatile unhome and synchronizes the task plan. |
| Power on / off | `POWER {active}` | `SOURCE_ADAPTER`: `onoff_clicked` sends `STATE_ON` only from `STATE_ESTOP_RESET`; otherwise it sends `STATE_OFF`. Power off aborts motion/task/IO and volatile homes; the subsequently observed source state is normally `STATE_ESTOP_RESET`, not stable `STATE_OFF`. The selected taskfile path remains status data. |
| Open G-code file | browser file + `PROGRAM_LOAD` | `SOURCE_ADAPTER`: native UI gate is interpreter IDLE. `open_file_guts` polls, saves mode, ensures MANUAL, processes an optional filter, restores saved mode, resets start line/highlight, `task_plan_synch` + wait, `program_open`, replaces the text/canon, constructs status-derived preview init codes, parses, redraws, then calls `set_mode_from_tab`. A parse error is reported against the newly selected file; AXIS does not roll back to the previous canon. |
| Load case / packaged program / edited source | `LOAD_WORKSPACE` and/or `PROGRAM_LOAD` | `WEB_EXTENSION` entry points. Once a file is selected they must use the Open/Reload adapter and may add a browser-load/queue exclusion while asynchronous parsing is active. Edited text is committed only through Reload; these controls are not native AXIS buttons. |
| Switchkins Identity / TCP: XYZAC / User K | `KINEMATICS_SELECT {type}` | `SOURCE_ADAPTER` for the selected INI's HALUI MDI commands: `M429 -> type 0`, `M428 -> type 1`, `M430 -> type 2`; each remap executes `M68 E3 Q<type>` then `M66 E0 L0` and verifies `motion.switchkins-type` in task context. The Web's homed/idle/empty-TP gate is a stated safety strengthening around the source MDI/task gate. |
| Run | `PROGRAM_RUN` | `run_warn`, `ensure_mode(AUTO)`, `AUTO_RUN` from the selected start line, then periodic task/TP ticks. Normal AXIS Run uses line 0. In `emctaskmain.cc`, an ended or aborted task plan is closed; the next `EMC_TASK_PLAN_RUN` reopens the current taskfile and runs from line 0. Therefore Run after `completed` or `aborted` must work directly and must not require a separate Reload/Restart click. |
| Run from selected line | not exposed | AXIS `task_run_line` requires an editor highlight and source `AUTO_RUN` start line. The Web editor has no source-line selection command yet, so no partial/fake button is rendered. |
| Reverse / Forward | not exposed | AXIS `task_reverse`/`task_forward` are keyboard-only source actions; the selected WASM program ABI has no corresponding command and Web preserves the distinction from Run. |
| Step | `PROGRAM_STEP` | `SOURCE_ADAPTER`: source toolbar gate is ON plus taskfile, not interpreter IDLE. `task_step` clears highlight/runs warning unless already AUTO+IDLE, ensures AUTO and sends `AUTO_STEP`. Task-layer homing remains authoritative. |
| Pause / Resume | `PROGRAM_PAUSE` / `PROGRAM_RESUME` | `SOURCE_ADAPTER`: `task_pause` accepts AUTO plus READING/WAITING. `task_resume` requires `paused` plus mode AUTO or MDI. The combined toolbar calls `task_pauseresume`, accepts AUTO or MDI, blocks resume when `resume_inhibit`, and otherwise pauses any non-IDLE interpreter. Web scheduling must serialize the edge but cannot change these state conditions. |
| Stop | `PROGRAM_ABORT` | AXIS enables Stop only while the interpreter is non-idle; then it highlights the active line, issues `c.abort()`, waits for completion, and clears the abort edge. The Worker may accept an internal abort edge in more states, but the Web button follows the AXIS UI gate. |
| Block delete / optional pause | disabled | LinuxCNC source has these controls, but the selected WASM ABI does not expose their canonical state. They are visible as disabled, with `blocked_by_abi`, and never emulate a local toggle. |
| Preview view, zoom, rotate, clear plot | local view or `VISUAL_CLEAR` | `SOURCE_ADAPTER`: view operations do not change task/interpreter/TP/IO. `Ctrl-K` maps `clear_live_plot()` -> `live_plotter.clear()` -> `positionlogger.clear()` and clears only Live Plot; it never deletes the loaded `AxisCanon` preview. Native Run, Stop, completion and Open/Reload do not call this clear path. |
| DRO coordinate/display/units | local projection | Maps AXIS `coord_type`, `display_type` and `metric` variables; values remain Worker snapshot projections. |
| Preview layers | local projection | `SOURCE_ADAPTER` only where the source variable exists: path=`show_program`, live=`show_live_plot`, limits=`show_machine_limits`, velocity=`show_machine_speed`, offsets=`show_offsets`, extents=`show_extents`. AXIS also has rapids, alpha, tool and DTG controls that the Web does not expose. Toggling visibility never deletes layer data. |
| Manual / MDI tabs | `SET_MODE` | `SOURCE_ADAPTER`: native tab callbacks are `ensure_manual` and `ensure_mdi`, including `set_motion_teleop` transitions. `axis.tcl::update_state` calls `set_mode_from_tab` only when returning to ON+IDLE from a non-idle/task-state transition while task mode is AUTO. No unrelated React effect may force a mode. |
| Joint/World (teleop) mode | not exposed | AXIS View menu calls `set_teleop_mode`/`c.teleop_enable`; the selected WASM motion ABI has no canonical teleop setter, so Web does not pretend its coordinate mode is a joint/world toggle. |
| Jog `-` / `+` | `JOG_START`, `SIM_STEP`, `JOG_STOP` | `SOURCE_ADAPTER`: source requires `manual_ok` and the visible Manual tab. Incremental sends one `JOG_INCREMENT`; Continuous sends one `JOG_CONTINUOUS` and motion continues until the deferred release issues `JOG_STOP`. Web servo sampling is transport/runtime work, not repeated user jog commands. |
| Home Axis | `HOME {axis}` | `manual_ok`, `ensure_mode(MANUAL)`, then source joint-home command. Individual axis home is rejected outside identity kinematics, matching `home_joint`. |
| Home All / Re-home All | `HOME {all:true}` | `SOURCE_ADAPTER`: `manual_ok`, ensure MANUAL, if `all_homed()` ask the native re-home confirmation, then `set_motion_teleop(0)`, `c.home(-1)`, wait. Native text remains Home All; the Web `RE-HOME ALL` label is presentation-only and cannot trigger a hidden home during load. |
| Touch Off | `TOUCH_OFF {axis,value,system,units}` | `SOURCE_ADAPTER`: source UI condition is the Touch Off formula above; task-layer MDI additionally enforces homing unless `NO_FORCE_HOMING`. Prompt for `P0 Current` through `P9 G59.3`, save mode, ensure MDI, scale linear units, issue `G10 L20 <P-token> <axis>[value*scale]`, wait, redraw/reload, restore saved mode and TELEOP. |
| Tool Touch Off | `TOOL_TOUCH_OFF {axis,value,system,units}` | LinuxCNC source requires `G10 L10/L11` plus `G43`, but the selected WASM interpreter does not expose canonical tool-offset storage. The control is therefore disabled with `blocked_by_abi_tool_offsets`; it never sends a fake fixed `G43`. |
| Override limits | disabled | `SOURCE_BLOCKED`: widget enable requires `on_any_limit`; handler separately requires interpreter IDLE. If already overridden it ensures AUTO; otherwise it ensures MANUAL and calls `c.override_limits()`. The selected WASM ABI cannot represent this canonical path. |
| Spindle CCW / stop / CW | `SPINDLE_COMMAND` | `SOURCE_ADAPTER`: `manual_ok`, ensure MANUAL, then `c.spindle(0)` or `c.spindle(direction, default_spindle_speed)`. Pressed state comes from canonical `spindle[0].direction`, not an RPM-sign inference. |
| Spindle - / + | `SPINDLE_COMMAND {delta}` → `SPINDLE_CONSTANT` on release | AXIS binds Button-1 to `spindle_decrease`/`spindle_increase` and ButtonRelease to `spindle_constant`; Web preserves this press/release sequence. |
| Brake | `SPINDLE_BRAKE` | Source brake command, gated by `manual_ok`. |
| Mist / Flood | `COOLANT_COMMAND` | `SOURCE_ADAPTER`: manual checkboxes belong to the ON+IDLE manual group and call `c.mist(value)`/`c.flood(value)` directly. F7/F8 poll status and send the inverse canonical bit. |
| Tool / probe status | read-only | AXIS does not put Prepare/Change/Arm/Disarm buttons in this manual notebook. Tool changes remain interpreter/MDI and IO/HAL operations. Web displays canonical tool/probe state but exposes no extra main-panel command buttons. |
| Jog Speed | `SET_JOG_SCALE` | `SOURCE_ADAPTER`: the slider changes AXIS's linear jog-speed value used by subsequent jog commands; it is not itself a motion command and has no `update_state` power gate. |
| Angular Jog Speed | not exposed | AXIS has a separate `ajogspeed` control for rotary joints; the selected WASM TP exposes one shared jog scale only, so the Web panel does not alias linear Jog Speed to this control. |
| Feed Override | `SET_OVERRIDE` | `SOURCE_ADAPTER`: `set_feedrate` sends `c.feedrate(percent/100)` without a machine-state UI gate. Selected INI `MAX_FEED_OVERRIDE=2` makes the range 0-200%. Web debouncing may coalesce pointer events but must preserve the last ordered value. |
| Spindle Override | `SET_SPINDLE_OVERRIDE` | `SOURCE_ADAPTER`: `set_spindlerate` sends `c.spindleoverride(percent/100)` without an `update_state` power gate. Its maximum defaults to MAX_FEED_OVERRIDE when `MAX_SPINDLE_OVERRIDE` is absent. |
| Max Velocity | `SET_MAX_VELOCITY` | `SOURCE_ADAPTER`: `set_maxvel` converts display units and sends `c.maxvel(value/60)` without an `update_state` power gate. The selected WASM TP exposes this as its velocity limit/scale. |
| Rapid Override | disabled | AXIS `set_rapidrate` calls `c.rapidrate` and is distinct from Max Velocity. The selected WASM TP has no rapid-scale setter, so the Web control is disabled with `blocked_by_abi_rapidrate`. |
| MDI command/history | `MDI_EXECUTE` | `SOURCE_ADAPTER`: `send_mdi` validates `manual_ok`; nonempty text is trimmed, the entry cleared, MDI mode ensured, and the command is inserted unless it equals the immediately preceding history entry, then `c.mdi(command)` is sent. A single click selects history; native double-click executes it. |
| File tool-data editor / Quit | disabled | AXIS launches a host editor or terminates the desktop process. Browser controls remain visibly disabled with `host_boundary_tool_editor` / `host_boundary_process_quit`; clicking them cannot report a false success. |
| About AXIS | local dialog | `SOURCE_ADAPTER`: mirrors the source `.about` open/OK-close action and changes no Worker/task/TP/IO state. The native website link is a separate external-host action. |
## Complete Web Control Inventory
Every rendered actionable element has a stable `data-axis-control` value. This
table is exhaustive for the selected XYZAC panel. A control may not be added to
React until its ID, source mapping, gate and result are added here. Repeated MDI
history rows intentionally share the family ID `mdi-history-select`.
| Control ID(s) | Source mapping | Operation and gate | Accepted result / disabled behavior |
| --- | --- | --- | --- |
| `menu-file`, `menu-help` | `axis.tcl` File/Help cascades | Local menu disclosure; always available | Opens/closes menu only; no machine state change |
| `menu-file-edit-tool-data`, `menu-file-quit` | `edit_tooltable`, `quit` host process operations | Permanently disabled in a browser | `host_boundary_tool_editor` / `host_boundary_process_quit`; no fake status success |
| `menu-help-about`, `about-close` | `.menu.help` About entry and `.about.ok` | Local modal open/close | About visibility changes only |
| `toolbar-estop` | `.toolbar.machine_estop` -> `estop_clicked` | `ESTOP {active}`; accepted from every task state | Toggle source E-stop edge; active edge aborts interpreter/TP |
| `toolbar-power` | `.toolbar.machine_power` -> `onoff_clicked` | `POWER {active}`; source widget enabled whenever status is not ESTOP | Send ON only from ESTOP_RESET, otherwise OFF; stable post-off status normally projects ESTOP_RESET |
| `toolbar-open`, `program-file-input` | `.toolbar.file_open` -> `open_file`/`open_file_guts` | File picker then `PROGRAM_LOAD`; interpreter must be IDLE | One replacement transaction; input itself is hidden and only the toolbar button opens it |
| `toolbar-load-case` | `WEB_EXTENSION`; uses INI `DISPLAY`, `HAL`, `KINS` and `OPEN_FILE` inputs | `LOAD_WORKSPACE` then Open adapter; IDLE plus browser async/queue exclusion | Reloads the selected exact case; never counts as an AXIS control or adds Power/Home/Run aliases |
| `toolbar-reload` | `.toolbar.reload` -> `reload_file` | `PROGRAM_LOAD` of current editor source/path; IDLE taskfile required | Replaces preview/interpreter at line 0; this is the only visible commit action for edited source |
| `toolbar-run` | `.toolbar.program_run` -> `task_run` | `PROGRAM_RUN`; native widget ON+IDLE; task layer additionally requires a taskfile and homing unless `NO_FORCE_HOMING=1` | AUTO/READING from line 0; completed/aborted taskfile reopens directly |
| `toolbar-step` | `.toolbar.program_step` -> `task_step` | `PROGRAM_STEP`; native widget ON+taskfile, then handler/task gates | AUTO_STEP at the next source boundary; do not add an IDLE-only UI gate |
| `toolbar-pause-resume` | `.toolbar.program_pause` -> `task_pauseresume` | Native widget ON+non-IDLE; handler distinguishes `paused` and accepts AUTO/MDI | One source toggle whose icon/label and command follow canonical pause state and resume-inhibit |
| `toolbar-stop` | `.toolbar.program_stop` -> `task_stop` | `PROGRAM_ABORT`; native widget ON+non-IDLE | Highlight AUTO running line when nonzero, abort, wait, retain selected taskfile |
| `toolbar-block-delete`, `toolbar-optional-pause` | Source toolbar toggles | Permanently disabled for selected ABI | `blocked_by_abi`; no React-only toggle |
| `toolbar-zoom-in`, `toolbar-zoom-out` | `zoomin`, `zoomout` | Local preview camera only | Bounded camera zoom; task/TP unchanged |
| `toolbar-view-z`, `toolbar-view-z2`, `toolbar-view-x`, `toolbar-view-y`, `toolbar-view-y2`, `toolbar-view-p` | `set_view_z/z2/x/y/y2/p` | Local source view selection | Exactly one pressed view; opens Preview tab; machine state unchanged |
| `toolbar-rotate` | `.toolbar.rotate` | Local drag/rotate projection | Toggles pressed state and viewer interaction only |
| `toolbar-clear-live-plot` | `clear_live_plot` -> `positionlogger.clear()` | Worker `VISUAL_CLEAR`; always visual-only | Advances `live_path_epoch`, clears Live Plot only and retains static preview |
| `workspace-case-select` | Web selection of the INI/configuration input | Local selection; selected catalog is restricted to supported XYZAC case | Does not load or change machine state until `toolbar-load-case` |
| `workspace-load-3d-chips` | Web packaged-file equivalent of `open_file_guts` | Fetch published `3D_Chips.ngc`, then `PROGRAM_LOAD`; IDLE only | Same canonical file replacement path as Open; no automatic Run/Home |
| `manual-home-all` | Machine Homing menu -> `home_all_joints` | `HOME {all:true}`; `manual_ok`, then MANUAL | Repeat asks source confirmation; Web Re-home label is non-authoritative presentation only |
| `switchkins-0`, `switchkins-1`, `switchkins-2` | `M429`, `M428`, `M430` remaps and `motion.switchkins-type` | `KINEMATICS_SELECT`; ON, homed, IDLE, empty TP | Identity/XYZAC/User K canonical type, or stable gate reason |
| `tab-manual`, `tab-mdi` | AXIS notebook F3/F5 `ensure_manual`/`ensure_mdi` | `SET_MODE` adapter; source `manual_ok` plus joint/teleop transitions apply | Tab selection plus acknowledged source-equivalent MANUAL/MDI and motion-mode transition |
| `tab-diagnostics` | Web diagnostics boundary | Local tab only | Reads Worker state/gates; sends no command |
| `tab-preview`, `tab-dro` | AXIS preview/DRO presentation | Local tab only | Changes visible projection; machine state unchanged |
| `manual-axis-x`, `manual-axis-y`, `manual-axis-z`, `manual-axis-a`, `manual-axis-c` | AXIS axis radio group / `ja_rbutton` | Local axis selection | Supplies the selected axis to Jog/Home/Touch Off; sends no motion by itself |
| `manual-jog-increment` | `jogincrement` combobox | Local choice: Continuous or configured increment | Determines `JOG_CONTINUOUS` versus `JOG_INCREMENT` payload only |
| `manual-jog-negative`, `manual-jog-positive` | `jogminus`, `jogplus` -> `jog_on` | `JOG_START`; visible Manual tab and `manual_ok`; source chooses joint/teleop mode from kinematics+homing | Incremental submits once; Continuous remains active until one release/cancel `JOG_STOP` |
| `manual-home-axis` | `home_joint` | `HOME {axis}`; `manual_ok`, MANUAL, identity kinematics for coordinate-axis home | Canonical joint home; repeated home asks confirmation |
| `manual-touch-off` | `touch_off_system` | `TOUCH_OFF`; exact Touch Off UI formula, `manual_ok`, valid axis/value/system, plus task-layer homing policy | MDI `G10 L20`, wait, reload and restore saved task/motion mode |
| `manual-tool-touch-off` | `touch_off_tool` | Permanently disabled for selected ABI | `blocked_by_abi_tool_offsets`; never approximated with `G92`/fixed `G43` |
| `manual-override-limits` | `toggle_override_limits` | Permanently disabled for selected ABI | No state mutation; explanatory ABI title |
| `manual-spindle-ccw`, `manual-spindle-stop`, `manual-spindle-cw` | spindle radio group -> `spindle` | `SPINDLE_COMMAND`; `manual_ok`, MANUAL | Canonical signed RPM/direction determines pressed state |
| `manual-spindle-decrease`, `manual-spindle-increase` | `spindle_decrease/increase`; ButtonRelease -> `spindle_constant` | Enabled only while spindle direction is nonzero, matching `axis.tcl update_state` | Press sends speed edge without crossing through zero into the opposite direction; release/cancel sends `SPINDLE_CONSTANT` |
| `manual-spindle-brake` | `brake` | `SPINDLE_BRAKE`; same manual source gate | Canonical brake state controls checkbox |
| `manual-coolant-mist`, `manual-coolant-flood` | manual-group `mist`/`flood` controls | `COOLANT_COMMAND`; checkboxes use the source ON+IDLE manual-group gate | Independent canonical coolant bits; F7/F8 inverse the polled source value |
| `mdi-command`, `mdi-submit` | MDI entry and `send_mdi` | Local text; submit/Enter sends `MDI_EXECUTE` under `manual_ok`/queue gate | Trimmed nonempty block, MDI mode, canonical interpreter result |
| `mdi-history-select` | AXIS MDI history row selection | Local history selection | Copies selected prior command into entry; does not execute until Submit/Enter |
| `preview-panLeft`, `preview-panRight`, `preview-panUp`, `preview-panDown`, `preview-reset` | Web accessible equivalent of Gremlin mouse pan/reset | Local camera only | Moves/resets camera; no Worker command |
| `dro-coordinate`, `dro-display`, `dro-units` | `coord_type`, `display_type`, `metric` | Local snapshot projection | Machine/Relative, Actual/Commanded and mm/inch display only |
| `override-jog-speed` | `update_jog_slider_vel` | `SET_JOG_SCALE`; no native machine-state UI gate | Canonical linear jog value used by later jogs |
| `override-feed` | `set_feedrate` | Debounced `SET_OVERRIDE`; no native `update_state` power gate; selected INI range 0-200% | Canonical feed override, including while AUTO where command layer permits |
| `override-spindle` | `set_spindlerate` | `SET_SPINDLE_OVERRIDE`; no native `update_state` power gate | Canonical spindle override |
| `override-max-velocity` | `set_maxvel` | `SET_MAX_VELOCITY`; no native `update_state` power gate | Canonical TP velocity limit/scale |
| `override-rapid` | `set_rapidrate` | Permanently disabled for selected ABI | `blocked_by_abi_rapidrate`; never aliases Max Velocity |
| `preview-layer-path`, `preview-layer-live`, `preview-layer-limits`, `preview-layer-velocity`, `preview-layer-offsets`, `preview-layer-extents` | AXIS View visibility variables | Local visibility only; path maps `show_program` | Shows/hides existing snapshot data without deleting or mutating it |
| `program-editor` | `WEB_EXTENSION`, not AXIS external Edit | Edits pending source text locally | Does not change loaded interpreter until the Reload adapter is accepted |
Tool/probe IO operations (`TOOL_PREPARE`, `TOOL_PREPARED`, `TOOL_CHANGE`,
`TOOL_CHANGED`, `PROBE_CONFIG`) remain Worker capabilities for interpreter,
MDI, HAL and focused IO tests. They are deliberately absent from the Web main
panel because no corresponding AXIS main-panel buttons exist.
## Button Dependency Matrix
The following matrix describes the side effects that determine the next set of
enabled controls. The right-hand column is an observable Worker state change;
React must derive its button gates from that state on the next snapshot.
| Action | Must be true before the click | State change after acceptance | Controls affected next |
| --- | --- | --- | --- |
| `ESTOP(active=true)` | Always accepted by the task state machine | IO `estop=1`, `ready=0`; interpreter and TP aborted; task `ESTOP`; loaded source retained | Disable power-on, home, jog, MDI, spindle, tool, probe and all program controls except E-stop reset/recovery-safe actions |
| `ESTOP(active=false)` | Source toggle requires current status ESTOP | IO E-stop clears; observed source task state becomes `ESTOP_RESET`; Web may display `STOPPED` only as an adapter label | Power is enabled; motion/manual/MDI remain disabled until ON |
| `POWER(active=true)` | Source ON edge is valid only from `ESTOP_RESET`; it does not require interpreter IDLE | Trajectory enables and observed task state becomes ON | Manual group requires ON+IDLE; program Run still meets UI/taskfile adapter and task-layer homing rules |
| `POWER(active=false)` | Source toolbar allows the toggle whenever status is not ESTOP, including active execution | Motion/task/IO abort, trajectory disables, volatile-home joints unhome, task plan synchronizes; taskfile retained | Observed state returns to ESTOP_RESET; nonvolatile homing is not falsely cleared by the UI |
| `HOME(all/axis)` | AXIS homing UI: ON+IDLE; handler `manual_ok`, then MANUAL | Home authority updates `homed[]`; individual coordinate-axis home is rejected for non-identity/duplicate coordinates | AUTO's task layer may run only when all required joints are homed unless `NO_FORCE_HOMING=1` |
| `PROGRAM_LOAD` | Native Open/Reload requires interpreter IDLE; Web additionally excludes active TP/MDI queue and concurrent parse | Save mode, ensure MANUAL, restore mode, set line 0, synchronize, open/replace taskfile and canon, parse status-derived preview, then apply tab mode | New taskfile/preview replace the old selection. Static preview replacement is independent of Live Plot data. |
| `KINEMATICS_SELECT` | Machine on, homed, idle, TP queue empty, valid type 0/1/2 | M429/M428/M430 selection updates canonical kinematics and the HAL switchkins type | Identity is required for individual coordinate-axis homing; a non-empty queue blocks selection |
| `SET_MODE(MANUAL)` | Source task rejects leaving active AUTO; tab handler also requires `manual_ok` | Task mode MANUAL plus source joint/teleop selection | Manual widgets are still controlled by ON+IDLE, not mode alone |
| `SET_MODE(MDI)` | Tab handler requires `manual_ok`; task mode transition follows source motion-mode calls | Task mode MDI | MDI widgets use ON+queue-capacity; submission still calls `manual_ok` |
| `PROGRAM_RUN` | Native toolbar formula is ON+IDLE and does not test taskfile. Web adds current-taskfile validation; task acceptance adds all-homed unless `NO_FORCE_HOMING=1`. Completed/aborted are valid idle starts. | `task_run`: warning, ensure AUTO, `AUTO_RUN(start_line)`, reset next start line to 0. Task layer reopens the retained file when plan is closed. | During non-IDLE, native Run relief is pressed and Open/Reload/Run/manual controls disable; on IDLE, direct Run is available again. |
| `PROGRAM_PAUSE` | Source handler: AUTO plus READING/WAITING; combined toolbar: AUTO/MDI plus non-IDLE and not currently paused | Trajectory pauses, previous interpreter state is saved, interpreter becomes PAUSED | Combined toolbar remains enabled and becomes Resume; Stop, E-stop and Power remain enabled by their own formulas |
| `PROGRAM_RESUME` | `paused` true, mode AUTO or MDI, and for combined toolbar `resume_inhibit` false | Trajectory resumes and interpreter restores `interpResumeState` | Combined toolbar returns to Pause; manual controls remain disabled until interpreter IDLE |
| `PROGRAM_ABORT` | AXIS exposes Stop while machine ON and interpreter non-IDLE; the task abort command itself is immediate | Interpreter aborts, TP queue is cleared, current taskfile/path remains selected, and the open task plan is closed | Stop periodic run polling; direct Run is enabled again when the task is idle and reopens the same taskfile from line 0. Reload is not a prerequisite. |
| `JOG_START` | `manual_ok`, Manual tab visible and a valid selected joint/axis. Homing selects coordinate TELEOP versus joint FREE behavior; it is not an unconditional GUI prerequisite. | One incremental command or one continuous command is sent | Continuous stays active until release/cancel sends `JOG_STOP`; incremental has no stop edge |
| `JOG_STOP` | An active jog gesture exists | Active jog is aborted/stopped; TP remains runnable | Allows a subsequent jog, MDI or source-gated manual command |
| `TOUCH_OFF` | Source widget/handler formula plus valid value; task MDI homing enforcement applies unless configured off | MDI `G10 L20` updates the selected coordinate system, reloads preview, then restores saved task/motion mode | Position/DRO/offsets refresh; program execution state remains idle |
| `SET_MAX_VELOCITY` | No native machine-state UI gate; numeric value follows source unit conversion/range | Source TP maximum velocity changes | Motion duration/velocity changes; jog/feed/rapid controls remain independent |
| Rapid Override control | Not available in the selected WASM TP | No state change | Rapid Override remains disabled; Max Velocity must not be used as a substitute |
| Spindle/Coolant | Native manual widgets require ON+IDLE; spindle direction/brake handlers additionally use `manual_ok` and ensure MANUAL where source does; coolant checkbox handlers do not switch mode | Canonical device IO changes only after Worker verification | Direction/checked states update from canonical status; spindle speed +/- also requires nonzero source direction |
| Feed/Spindle override | No native machine-state UI gate; values follow INI ranges | Send `c.feedrate`/`c.spindleoverride` equivalents | Canonical override changes without inventing an ON-only restriction |
### Dependency Flow
```text
ESTOP ──release──> ESTOP_RESET ──POWER ON──> ON
│
├──HOME ALL──> homed ON
│ │
│ ├──RUN──> AUTO/RUNNING
│ │ ├──PAUSE──> PAUSED
│ │ │ └──RESUME──> RUNNING
│ │ └──STOP/ESTOP/POWER OFF──> idle loaded taskfile
│ └──MANUAL/MDI──> jog/home/offset/device gates
└──PROGRAM LOAD──> loaded taskfile (idle only)
```
Any E-stop or power-off edge invalidates active motion immediately. It does not
delete the selected taskfile, static preview or Live Plot, and only joints
configured with volatile home are unhomed by the native source. The UI must not
leave stale enabled controls while waiting for the next canonical snapshot;
the Worker gate remains final authority if a click races a status transition.
## Required Transactions
### Startup program selection
```text
native priority: argv[0] -> AXIS_OPEN_FILE -> [DISPLAY] OPEN_FILE
browser applicable priority: selected INI [DISPLAY] OPEN_FILE
-> keep preview/program controls in loading state
-> LOAD_WORKSPACE
-> exactly one PROGRAM_LOAD for the configured OPEN_FILE
-> publish the configured program, source hash and preview atomically
-> enable source-gated controls
```
The UI cache is read only for presentation preferences. A stale cached program
from an earlier Web version is ignored and replaced on the next UI preference
save. No default-preview-then-cached-preview transition is permitted.
### File replacement
```text
poll
-> native Open/Reload gate: interpreter IDLE
-> Web safety adapter also requires TP/MDI queue empty and no active parse
-> save task mode
-> ensure MANUAL
-> optional source FILTER; on failure report and stop
-> restore saved task mode before PLAN_OPEN-equivalent parsing
-> set first line 0 and clear previous highlight
-> task_plan_synch + wait_complete
-> program_open / PROGRAM_LOAD
-> replace editor text and AxisCanon/static preview owner
-> construct preview init codes from canonical status and parse
-> reapply selected tab mode after the UI refresh
-> publish one canonical replacement snapshot
```
The replacement is rejected while the TP or an MDI move still has a queued or
active segment by the Web safety adapter. Native `open_file_guts` builds
preview initialization from polled machine position, units, tool, modal G/M
codes and tool offsets. Therefore loading B after A moved the machine must not
silently initialize relative preview at the origin. A failed filter does not
open a new taskfile. A G-code preview parse error is reported for the newly
selected file; native AXIS does not promise rollback to the old static preview.
### E-stop recovery and startup
```text
ESTOP(active=true)
-> emcMotionAbort + spindle/task/IO abort + trajectory disable
-> volatile-home joints may unhome; task-plan synch
-> observed task_state ESTOP
ESTOP(active=false)
-> send STATE_ESTOP_RESET; abort/synch; observed task_state ESTOP_RESET
POWER(active=true)
-> valid from ESTOP_RESET; trajectory enable; observed task_state ON
HOME(all=true)
-> manual_ok -> MANUAL -> joint mode -> c.home(-1) -> wait
PROGRAM_RUN
-> if the previous plan is completed/aborted: reopen current taskfile at line 0
-> AUTO mode, interpreter READING; browser scheduler drives WASM samples
```
### Run after completion or Stop
```text
RUNNING -> M2/end-of-file -> WAITING -> queue/IO drained -> IDLE/completed
-> press Run
-> reopen current taskfile
-> AUTO_RUN line 0
RUNNING -> Stop/abort -> abort interpreter + TP -> close task plan -> IDLE/aborted
-> press Run
-> reopen current taskfile
-> AUTO_RUN line 0
```
This is the source behavior in `emctaskmain.cc`: closing the task plan avoids
resuming at an aborted line, and `EMC_TASK_PLAN_RUN_TYPE` opens the selected
file again when no plan is open. The Web runtime may expose `PROGRAM_RESTART`
internally for recovery/testing, but it is not a required user step and is not
a second visible Run control. After the reopen/synchronization, a positioning
block whose target already equals the current canonical pose is a zero-motion
block: it advances the interpreter line but must not enqueue an invalid
zero-length TP segment.
### Touch-off command encoding
AXIS's `all_systems` list is `P0 Current`, `P1 G54` through `P9 G59.3`.
`touch_off_system` passes the first token from the selected entry to
`G10 L20`, so the source command is `G10 L20 P1 X...` for G54 (and `P0` for
the current system). The Worker accepts the same P-token or a G54..G59.3
display token, records the normalized `source_system`, and executes the same
canonical P form. This keeps the source command and the interpreter encoding
auditable without changing the UI behavior.
### Continuous jog
AXIS binds Button-1 to continuous jog and ButtonRelease to `jog_stop`.
The source sends one `JOG_CONTINUOUS` command and motion continues until a
deferred `JOG_STOP`; it does not submit a fresh user command every GUI sample.
The Web equivalent keeps one active `{axis,direction}` gesture, sends one
`JOG_START`, advances its WASM servo simulation internally and sends
`JOG_STOP` exactly once on release, cancel, leave, focus loss or E-stop.
### Toolpath display lifecycle
The panel renders two different path layers. They must never share an implicit
clear condition:
| Layer | Source owner | Visible/retained | Cleared or replaced |
| --- | --- | --- | --- |
| Static program preview | `open_file_guts` assigns a new `AxisCanon`; Gremlin renders that canon | Remains visible during Run, Pause, Resume, Stop and normal completion | Open/Reload replaces the previous canon with the newly selected program's canon and parses into it. A preview error is reported for the new file; native AXIS does not roll back to the old canon. Clear Live Plot does not affect it. |
| Machining Live Plot | `LivePlotter` owns `linuxcnc.positionlogger`; `show_live_plot` controls visibility | Accumulates independently of the selected static program. Pause freezes and retains it; Resume continues; Stop and normal completion retain it. Opening, reloading, running or rerunning a program also retains the existing trace. Hiding changes visibility only. | Only `clear_live_plot()` calls `LivePlotter.clear()`, whose `clear()` calls `positionlogger.clear()`. No call exists in `open_file_guts`, `reload_file`, `task_run`, `task_step`, `task_pause`, `task_resume` or `task_stop`. |
The Worker publishes a monotonic `visual.live_path_epoch`. React may clear the
machining Live Plot only when this value changes. Array identity, a full versus
delta snapshot, Pause, Resume, Stop, task mode, queue depth and completion are
not clear signals. To match the source, the epoch changes only for the explicit
`VISUAL_CLEAR` adapter (or restoration of a previously persisted epoch); it
must not change on a program lifecycle command.
```text
PROGRAM_LOAD accepted -> live epoch unchanged -> replace static preview only
PROGRAM_RUN accepted -> live epoch unchanged -> continue accumulating live trace
PROGRAM_PAUSE -> epoch unchanged -> retain/freeze live trace
PROGRAM_RESUME -> epoch unchanged -> continue live trace
PROGRAM_ABORT/STOP -> epoch unchanged -> retain live trace
program completed -> epoch unchanged -> retain live trace
VISUAL_CLEAR -> increment live_path_epoch -> clear live trace only
PROGRAM_RESTART -> epoch unchanged -> source has no visible Restart action
```
If Clear Plot is used while motion is still active, history before the clear
edge is removed and `positionlogger` semantics immediately begin accumulating
the subsequent motion in the new epoch. At IDLE, the cleared trace stays empty.
This source-exact rule supersedes the earlier Web extension that cleared the
trace on Run, Restart or Load. That extension contradicted native AXIS and is no
longer part of this contract. A product variant that wants per-job traces must
provide a separately named export/archive action; it may not change the AXIS
Clear Live Plot lifecycle while claiming source parity.
## Implementation Invariants
1. `web/worker/sim-session.mjs` is the command gate and transaction boundary.
2. `web/worker/wasm-linuxcnc-tp.mjs`, `wasm-program-runtime.mjs`,
`wasm-xyzac-io.mjs` and `wasm-xyzac-device-io.mjs` are the only authorities
for selected-case motion, interpreter and device state.
3. `web/react/src/App.tsx` may render a button disabled from a gate snapshot,
but a Worker gate is still mandatory because UI state can be stale.
4. Every accepted operation produces a ledger entry and canonical snapshot;
every rejected operation produces a stable source/gate reason.
5. A feature that cannot be represented by the selected ABI is disabled with
an explicit reason. It must not be approximated with `G92`, a fixed `G43`,
local booleans or a second JavaScript state machine.
6. New controls must be added to this matrix, the source symbol inventory and
a focused Worker/browser regression test before deployment.
7. Executable program source/path is never part of UI preference persistence;
refresh performs one INI-authorized startup load.
8. Live Plot clearing is controlled only by the Worker `live_path_epoch`;
React object/array replacement is never a lifecycle signal, and only the
explicit `VISUAL_CLEAR` source adapter advances the epoch.
9. Every contract row carries or inherits one of `SOURCE_EXACT`,
`SOURCE_ADAPTER`, `SOURCE_BLOCKED` or `WEB_EXTENSION`; extensions and blocked
capabilities are never counted as source parity.
10. Exact `axis.tcl::update_state` formulas remain the reference UI gates.
A browser asynchronous-load exclusion must be documented separately; no
other stronger React gate may be attributed to AXIS.
## Change Procedure
Focused evidence for this contract is kept in
`web/worker/test_xyzac_touch_off_source_flow.mjs`,
`web/worker/test_xyzac_panel_buttons.mjs` and
`scripts/test_xyzac_panel_buttons_browser.mjs`. The completed/aborted direct
rerun contract is covered by `web/worker/test_xyzac_program_rerun.mjs` and the
published-site execution sequence is covered by
`scripts/test_xyzac_program_execution_browser_strict.mjs`.
The source-surface audit is enforced by `scripts/test_axis_control_contract.mjs`.
The exhaustive control-ID audit is enforced by
`scripts/test_axis_control_inventory.mjs` and
`scripts/test_axis_control_inventory_browser.mjs`; the latter rejects any
rendered actionable element without a documented ID or with a duplicate ID.
When LinuxCNC source behavior or the selected machine configuration changes:
1. Update the source baseline and the affected matrix row/transaction above.
2. Recompute the source-lock hashes and review every changed source symbol.
3. Update the Worker gate/authority path.
4. Update the React handler only after the Worker contract is verified.
5. Add normal, blocked and recovery assertions to the focused test.
6. Regenerate the relationship graph from its DOT source.
7. Run the panel browser test, build the React bundle, and publish a new
release directory without replacing the previous rollback target.
The document deliberately records ABI blockers and source boundaries. A
`verified` Web slice means the implemented path matches this contract; it does
not claim that an unavailable LinuxCNC host or official native oracle has been
silently reproduced in the browser.