This commit is contained in:
2026-09-15 01:12:50 +08:00
parent 2db889ad89
commit 1a75503105
99 changed files with 4168 additions and 726 deletions
@@ -87,7 +87,7 @@
<article data-uid="Anatomy-ExecutionToolBar">
<h1 id="execution-tool-bar">Execution Tool Bar</h1>
<p>The transport controls for the run. In the web application they sit atop the <a href="index.html">Execution Page</a>'s primary editor panel, shown for the Execution root and every node under it, and this sole instance registers the F5F8 shortcuts.</p>
<p>The transport controls for the run. In the web application they sit in the run header of the <a href="index.html">Execution Page</a>'s Control Tree pane — pinned above the tree, beside the run-state badge, on the Execution page only — and this sole instance registers the F5F8 shortcuts.</p>
<h2 id="layout">Layout</h2>
<ul>
<li><code>Execution Tool Bar</code>
@@ -102,17 +102,19 @@
</ul>
</li>
</ul>
<p>In the web application the status text moved out of the tool bar and onto the Execution tree item, which carries the run-state badge. The two single-advance buttons there share one <code>skip_next</code> icon and are separated by a letter drawn into the button's corner — <code>L</code> for the line button, <code>S</code> for the step button — rather than by colour; the tool-tips carry the key names.</p>
<p>In the web application the status text is a run-state badge to the right of the button strip; the two share the Control Tree pane's run header, and when the tree column is narrowed the button strip scrolls while the badge keeps its width, so the run state is the last thing a resize hides. The two single-advance buttons share one <code>skip_next</code> icon and are separated by a letter drawn into the button's corner — <code>L</code> for the line button, <code>S</code> for the step button — rather than by colour; the tool-tips carry the key names.</p>
<h2 id="behavior">Behavior</h2>
<ul>
<li>The webservice watches <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html">LocalProjectService</a> events to track <a class="xref" href="../../api/Hi.MachiningProcs.LocalProjectService.html#Hi_MachiningProcs_LocalProjectService_PacePlayer">PacePlayer</a> status changes.</li>
<li>In the webservice, <code>ExecutionStatusService</code> subscribes to those events and broadcasts status changes over SignalR through <code>ExecutionStatusHub</code>.</li>
<li>The frequently used buttons carry hotkeys: Start / Resume, Pause, Run One Line and Run One Step. The app's tool-tips are the only place those key bindings are written down.</li>
<li>The background color of the <code>Status Text Field</code> follows the status:
<li>The badge reads the status in lower case (<code>ready</code>, <code>running</code>, <code>paused</code>, <code>finished</code>, <code>no project</code>, <code>error</code>), and the background color of the <code>Status Text Field</code> follows it:
<ul>
<li>Warning style — Running</li>
<li>Secondary style — Paused, No Project</li>
<li>Success style — Finished, Ready</li>
<li>Warning (amber) — Running</li>
<li>Orange — Paused</li>
<li>Positive (green) — Finished, Ready</li>
<li>Negative (red) — Error</li>
<li>Grey — No Project, and any status the client has no label for (its raw name is shown)</li>
</ul>
</li>
</ul>
@@ -150,7 +152,7 @@
</tbody>
</table>
<p><strong>Neither single-advance button is enabled while the run is <code>Running</code></strong> — stepping is a <code>Ready</code>-or-<code>Paused</code> operation, so a moving run has to be paused before it can be stepped.</p>
<p>The F5F8 handler applies the same predicates before acting, and declines in two more cases: when the event target is an <code>input</code>, a <code>textarea</code> or a contenteditable element, and when the hosting page is deactivated. The second matters because the shell keep-alives the routed pages, so leaving the Execution route deactivates rather than unmounts this component; without the detach, F5 elsewhere in the app would drive the transport instead of reloading the browser. Every declined key falls through to the browser.</p>
<p>The F5F8 handler applies the same predicates before acting, and declines in two more cases: when the event target is an <code>input</code>, a <code>textarea</code> or a contenteditable element, and when the hosting page is deactivated. The second matters because the shell keep-alives the routed pages, so leaving the Execution route deactivates rather than unmounts this component; without the detach, F5 elsewhere in the app would drive the transport instead of reloading the browser. Collapsing the Control Tree row does not detach it: the dock keeps the tree pane mounted through a collapse. Every declined key falls through to the browser.</p>
<div class="TIP">
<h5>Tip</h5>
<p>Use icons rather than text on the tool-bar buttons. Run One Line and Run One Step share an icon, so they need a second mark to tell them apart — this bar draws an <code>L</code> and an <code>S</code> in the button corner. The default colour is enough for the rest.</p>
@@ -160,6 +162,9 @@
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> — the buttons. All state and handlers come from the shared <code>useExecutionTransport</code> composable, so the component is pure markup.</li>
<li><code>wwwroot-src/src/composables/useExecutionTransport.ts</code> — the enable rules, the API-calling handlers and the F5F8 listener; the shared reset-in-flight flag lives here.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the Control Tree pane; its run header mounts the bar beside the run-state badge on the Execution page only, with the badge unshrinkable and the button strip the part that scrolls.</li>
<li><code>wwwroot-src/src/utils/executionStatusPresent.ts</code> — the badge's label and color per status.</li>
<li><code>Execution/ExecutionController.cs</code><code>POST /api/Execution/start | pause | resume | run-line | run-step | stop | reset</code>, and <code>GET /api/Execution/status</code>.</li>
<li><code>Execution/ExecutionStatusHub.cs</code> + <code>Execution/ExecutionStatusService.cs</code> — the status broadcast.</li>
</ul>
@@ -87,7 +87,7 @@
<article data-uid="Anatomy-Execution">
<h1 id="execution-page">Execution Page</h1>
<p>The Execution page is the run cockpit: the simulation plays here, and every panel on it reads the run. It is the app's landing route — <code>/</code> redirects to <code>/execution</code> — and its Control Tree, the <strong>Execution</strong> root with <strong>Mission</strong> and <strong>Program</strong> beneath it, rides the <code>?tree=</code> query, so <code>/execution?tree=execution/mission</code> deep-links the Mission editor.</p>
<p>The Execution page is the run cockpit: the simulation plays here, and every panel on it reads the run. It is the app's landing route — <code>/</code> redirects to <code>/execution</code> — and its Control Tree — two roots, <strong>Mission</strong> above <strong>Program</strong> rides the <code>?tree=</code> query, so <code>/execution?tree=execution/mission</code> deep-links the Mission editor.</p>
<div class="NOTE">
<h5>Note</h5>
<p>The app says <strong>Execution</strong> throughout: the route <code>/execution</code>, the REST endpoints (<code>/api/Execution/*</code>), the status hub (<code>/executionStatusHub</code>) and the stored layout (<code>UserConfig.ExecutionDivConfig</code>). The HiAPI engine keeps its own library vocabulary — <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> <small>(API)</small> and <a class="xref" href="../../api/Hi.Common.PacePlayer.html">PacePlayer</a> <small>(API)</small> — which is a different codebase's naming, not an inconsistency here.</p>
@@ -101,16 +101,16 @@
<h2 id="layout">Layout</h2>
<p>Four columns, left to right. The two dock dividers are pixel-sized, so dragging one moves only that dock's edge while the flexible middle absorbs the change.</p>
<ul>
<li><strong>Left dock</strong> — the Control Tree over the primary editor panel for the selected tree node. The <a href="execution-tool-bar.html">Execution Tool Bar</a> — the transport buttons — sits atop that panel and is shown for the Execution root and every node under it.</li>
<li><strong>Left dock</strong> — the Control Tree over the primary editor panel for the selected tree node. The tree pane opens with a run header pinned above its rows: the <a href="execution-tool-bar.html">Execution Tool Bar</a> — the transport buttons — with the live status badge to its right. The editor panel below holds the editor alone.</li>
<li><strong>Main</strong> — the <a class="xref" href="../../technique/rendering/rendering-canvas.html">RenderingCanvas</a> over the <a class="xref" href="../shell/session-message-panel.html">Session Message Panel</a>, stacked as collapsible panels with a draggable divider between them. The canvas panel's header adopts the <a class="xref" href="../widget/renderingcanvas-tool-bar.html">RenderingCanvas Tool Bar</a> and the <a href="execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a>.</li>
<li><strong>Strip Charts</strong> — the strip-chart group bar over the three <a href="strip-charts.html">Strip Charts</a>: Availability Chart, Surface Roughness Chart and Color Index Time Chart.</li>
<li><strong>Step Info</strong><em>Sentence Syntax</em> as its own region (an NC sentence need not map to any machining step, so it is not a member of the step-based panels), then the Step Info group bar, then <a href="selected-step-info-panel.html">Step Properties</a>, <em>CWE</em> (cutterworkpiece engagement) and the four <a href="cycle-line-charts.html">Cycle-Line Charts</a>.</li>
</ul>
<p>The four columns are toggled from the app menu bar; the panels inside them collapse in place from their own expansion headers. The canvas stays mounted while collapsed so its rendering connection survives, and its engine is paused rather than torn down. The page itself also stays mounted while the user is on another route: the shell wraps the router view in a <code>keep-alive</code> keyed on the project epoch, so navigating away pauses the canvas but keeps the page — and its connection — alive until the loaded project changes.</p>
<h2 id="control-tree">Control Tree</h2>
<p>The page hosts an <code>execution</code>-scoped Control Tree: an <strong>Execution</strong> root with the <strong>Mission</strong> branch above the <strong>Program</strong> branch. Selecting a node fills the primary editor pane below the tree, and the <a href="execution-tool-bar.html">Execution Tool Bar</a> is mounted on top of that pane whenever the selected id is <code>execution</code> or starts with <code>execution/</code> — so the transport stays reachable from every node of this tree.</p>
<p><strong>Execution root</strong> is the cockpit's home: a short orientation panel, because the controls it would otherwise hold live where they act — the transport on the primary pane's header, the panel switches on each panel's own expansion header, the column switches on the nav bar. The live run state rides the tree itself: the Execution tree item carries a status badge fed by <code>/executionStatusHub</code>.</p>
<p><strong>Mission</strong> (<code>execution/mission</code>) is the editable command list a run executes — the project's <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> <small>(API)</small>, always a list, with nested lists read as sub-trees. Its ItemType registry supplies the tree checkbox that decides whether a command runs. The branch root is the entry-list editor — Add Command over a drag-reorderable row list with up / down / duplicate / delete — each command entry gets a control bar over that kind's own editor, and each of the kind's remaining setting groups becomes a section child of the command node. Every command type has its own page under <a href="mission/index.html">Mission</a>.</p>
<p>The page hosts an <code>execution</code>-scoped Control Tree with two roots and no wrapper node over them: the <strong>Mission</strong> branch above the <strong>Program</strong> branch. Node ids keep the <code>execution/</code> prefix (<code>execution/mission</code>, <code>execution/program/…</code>) because that first segment is the page-scope key — <code>?tree=</code> routing and the remembered last selection hang off it — not a parent row; a bare <code>?tree=execution</code> resolves to <code>execution/mission</code>, and a URL with no <code>?tree=</code> lands on the remembered selection or, failing that, on Mission. Selecting a node fills the primary editor pane below the tree; that pane holds nothing but the selected node's editor.</p>
<p><strong>Run header.</strong> The tree pane opens, on this page only, with a row pinned above the tree scroller: the <a href="execution-tool-bar.html">Execution Tool Bar</a> — the page's sole transport, which therefore owns the F5F8 shortcuts — and, to its right, the status badge fed by <code>/executionStatusHub</code>. It sits inside the pane rather than on the dock's expansion header because that header is shared with General Setup, which renders neither piece. The button strip is the only shrinkable child, so narrowing the tree column scrolls the buttons away while the badge stays in view; the dock keeps the tree row mounted while collapsed, so the transport and its shortcuts survive a collapsed row. The other controls the cockpit needs live where they act — the panel switches on each panel's own expansion header, the column switches on the app menu bar.</p>
<p><strong>Mission</strong> (<code>execution/mission</code>) is the editable command list a run executes — the project's <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> <small>(API)</small>, always a list, with nested lists read as sub-trees. Its ItemType registry supplies the tree checkbox that decides whether a command runs. Every row-level structural op lives on the tree rows, not in a panel: each command row carries a drag handle and — revealed on hover and on the selected row — move up / move down / duplicate / delete; every list row, the Mission root included, carries an always-visible <strong>+</strong> (Add Command) that opens the command catalog and inserts the pick right after that list's focused command (the last selected or last added), else at the list's tail. The one panel-side insert is the Program File editor's <a href="mission/NcFileCommand-panel.html#multi-pick-fan-out">multi-pick fan-out</a>: each extra pick becomes a new Program File command right after that command in its own list, and the panel reports the insert range to the host, which treats it as a structural op like any row op — the same busy gate, the same carry-over of index-based ids onto the new siblings, the same rebuild scoped to the owning list. Dropping a dragged row on another command row's edge reorders it within its list or relocates it into the target row's list; dropping on a List row's middle band or on the Mission root row appends it into that list. The host runs all five ops through one path — a busy gate that holds tree selection, the checkboxes and further drags read-only while an op is in flight; the open editor's dirty-switch gate, which can refuse the op: a Script Command with unsaved edits asks <strong>Save &amp; switch</strong> / <strong>Discard</strong> / <strong>Cancel</strong>, while an NC Code editor or a List title sends its pending save without asking, and either one answers no when the user cancels or the save does not land — a refused gate drops the op and toasts that the open editor keeps its unsaved edits, which is why a delete that was already confirmed can still leave its row in place; then the API call, a rebuild scoped to the list it rewrote (the nearest list above both, for a move across lists), then the selection. The selection moves only when it already rode on the acted-on row — that row, or a section or nested entry under it: it follows a moved command, lands on the clone of a duplicated one, and after a delete lands on the nearest surviving entry of that list or, once the list is empty, on the owning list row; a selection riding elsewhere stays on its own re-indexed row, and Add Command never moves it at all — only the Add focus moves, onto the new row, so a run of adds chains in order. The root's own panel is informational — what the tree cannot say: a no-project note while no project is loaded, otherwise the command count and, while it is zero, a prompt to click Add Command; the run-order hint closes it either way. A command node's panel is that kind's own editor (a List shows only its optional Title; its entries are its tree children), and each of the kind's remaining setting groups becomes a section child of the command node. Every command type has its own page under <a href="mission/index.html">Mission</a>.</p>
<p><strong>Program</strong> (<code>execution/program</code>) is the read-only inspection twin of Mission: one node per NC source file the session read, its passes and execution marks filled in from run data rather than typed in. Its nodes, its per-pass line and mark views, and the status changes that rebuild it are on <a href="program.html">Program Branch</a>.</p>
<p>Selection and URL are synced two ways through the <code>tree</code> query argument: the URL's <code>?tree=</code> is adopted once the tree is built, and every selection change replaces it. A <code>?tree=</code> id belonging to another page's tree redirects to that page (<code>routeForTreeId</code>), and <code>/mission</code> lands on <code>/execution?tree=execution/mission</code>.</p>
<h2 id="renderingcanvas-behavior">RenderingCanvas Behavior</h2>
@@ -129,18 +129,19 @@
<ul>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — the routed page at <code>/execution</code>: the four columns, their pixel / ratio splitters, the panel-expansion stacks, and the <code>execution</code>-scoped Control Tree host it provides.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>/</code><code>execution</code> redirect, the <code>execution</code> route, and the <code>/mission</code> redirect to <code>/execution?tree=execution/mission</code>.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code><code>routeForTreeId</code>, which lands a <code>?tree=</code> id on the page that owns that branch.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the Control Tree state machine: <code>buildExecutionRoot()</code> builds the <code>execution</code> root holding <code>execution/mission</code> above <code>execution/program</code>, syncs <code>?tree=</code>, and re-reads the Program branch whenever the broadcast execution status changes.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the left dock: the Control Tree row over the primary editor row.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree pane; carries the live execution-status badge on the Execution tree item.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the primary editor pane for the selected node; mounts the Execution Tool Bar on top whenever the selection is <code>execution</code> or a descendant.</li>
<li><code>wwwroot-src/src/components/controlTree/ExecutionRootPanel.vue</code> — the editor panel of the Execution root node.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code><code>migrateLegacyTreeId</code>, which folds legacy ids onto the current ones (a bare <code>execution</code> becomes <code>execution/mission</code>), and <code>routeForTreeId</code>, which runs that migration first and then lands a <code>?tree=</code> id on the page that owns its first segment.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the Control Tree state machine: <code>buildExecutionRoots()</code> builds the two roots, <code>execution/mission</code> above <code>execution/program</code>; <code>landingSelection()</code> picks the remembered selection or the first root; the <code>?tree=</code> sync; the Program branch re-read on every broadcast execution-status change; and the five mission row ops — add, drag move, step, duplicate, delete — through one <code>missionRowOp</code> helper that owns the busy gate, the dirty-switch gate (a refusal drops the op and toasts it), the scoped rebuild and the post-op selection.</li>
<li><code>wwwroot-src/src/components/controlTree/useMissionTreeDrag.ts</code> — the HTML5 drag-and-drop bookkeeping between Mission rows: the edge / into drop modes and the row indicator classes; the move itself is the host's.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the left dock: the Control Tree row over the primary editor row, each kept mounted while collapsed; the tree row's expansion header is the same on both tree pages, so nothing run-specific rides it.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree pane; on the Execution page it pins the run header — the Execution Tool Bar and the live status badge — above the tree scroller, and renders the Mission rows' drag handle, the hover-revealed row ops and the Add Command <strong>+</strong> on every list row.</li>
<li><code>wwwroot-src/src/components/controlTree/AddCommandDialog.vue</code> — the search-first command catalog the <strong>+</strong> opens, fed by <code>GET /api/Mission/command-catalog</code>; one pick per opening.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the primary editor pane shared by both tree pages: it mounts the selected node's registry panel, or one of the three inline root panels it carries for General Setup's equipment roots (Machine Tool, Fixture, Workpiece — object management plus a summary), which no Execution node ever selects; here it shows the registry panel alone, and hands that panel's dirty-switch gate to the host.</li>
</ul>
<p>Mission branch:</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the branch's ItemType registry and the per-command enable ticks.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the entry-list editor of a list command, serving the branch root and every nested list.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the per-command editor host.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the Mission root's informational panel: a no-project note while no project is loaded, otherwise the command count and a &ldquo;No commands yet&rdquo; prompt while it is zero, and the run-order hint in every state.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the per-command editor host; a List command shows only its optional Title.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionSectionPanel.vue</code> — one setting section of a command.</li>
</ul>
<p>Program branch (see the dedicated anatomy page for details):</p>
@@ -150,7 +151,7 @@
<p>Panels and canvas:</p>
<ul>
<li><code>wwwroot-src/src/components/RenderingCanvas.vue</code> — the browser canvas and its <code>/renderingHub</code> connection.</li>
<li><code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> — the transport controls.</li>
<li><code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> — the transport controls; the page's one instance, mounted in the tree pane's run header, registers F5F8.</li>
<li><code>wwwroot-src/src/components/execution/ExecutionExtendedToolBar.vue</code> — the view and rendering-flag controls adopted onto the canvas panel's header.</li>
<li><code>wwwroot-src/src/components/execution/SelectedStepInfoPanel.vue</code> — Step Properties.</li>
<li><code>wwwroot-src/src/components/execution/StepVolumePanel.vue</code> — the CWE panel.</li>
@@ -173,7 +174,7 @@
<p>Backends:</p>
<ul>
<li><code>Execution/ExecutionController.cs</code><code>POST /api/Execution/start | pause | resume | run-line | run-step | stop | reset</code>; <code>GET status</code>, <code>status/{connectionId}</code>, <code>project-status</code>, <code>selected-step-info</code> and <code>cl-strip-dots</code>; the canvas actions <code>initialize/{connectionId}</code> (binds the displayee and calls <code>SetViewToHomeView</code>) and <code>fit-view/{connectionId}</code>; and the <code>step-volume/{connectionId}</code> family behind the CWE panel.</li>
<li><code>Execution/ExecutionStatusHub.cs</code> + <code>Execution/ExecutionStatusService.cs</code><code>/executionStatusHub</code>, the run-state broadcast that drives the transport and the tree badge.</li>
<li><code>Execution/ExecutionStatusHub.cs</code> + <code>Execution/ExecutionStatusService.cs</code><code>/executionStatusHub</code>, the run-state broadcast that drives the transport and the run header's badge.</li>
<li><code>Execution/SessionSinkHub.cs</code> + <code>Execution/SessionSinkBroadcastService.cs</code> — one hub per message sink (<code>/shellMessageHub</code>, <code>/ncDiagnosticHub</code>, <code>/stepDiagnosticHub</code>, <code>/ncManipulationDiagnosticHub</code>), so a client subscribes to exactly the sink it wants.</li>
<li><code>Execution/SelectedStepInfoService.cs</code> — the selected-step payload. It has no hub of its own: the &ldquo;selection changed&rdquo; push rides <code>/clStripHub</code>.</li>
<li><code>Execution/ClStripController.cs</code> + <code>Execution/ClStripHub.cs</code> + <code>Execution/ClStripBroadcastService.cs</code> — CL-strip range state and the zoom / pan / select / enter broadcasts.</li>
@@ -197,10 +198,10 @@
</ol>
</div>
<h2 id="pages">Pages</h2>
<p>Ordered as the cockpit reads: the two tool bars across the top, the step column and the charts down
the side, the menus and dialogs they open, then the Control-Tree branch the page hosts.</p>
<p>Ordered as the cockpit reads: the two tool bars — the transport pinned in the Control Tree pane's run header, the extended controls on the canvas panel's header — then the step column and the charts down
the side, the menus and dialogs they open, then the two Control-Tree branches the page hosts.</p>
<ul>
<li><a href="execution-tool-bar.html">Execution Tool Bar</a> — The transport controls on the primary panel header, and the status they read</li>
<li><a href="execution-tool-bar.html">Execution Tool Bar</a> — The transport controls in the Control Tree pane's run header, and the status they read</li>
<li><a href="execution-extended-renderingcanvas-tool-bar.html">Execution Extended RenderingCanvas Tool Bar</a> — The run-specific canvas controls: the CL strip, fit view and the Scene menu</li>
<li><a href="selected-step-info-panel.html">Selected-Step Info Panel</a> — The Step Properties panel in the Step Info column</li>
<li><a href="strip-charts.html">Strip Charts</a> — The whole-program charts in the Strip Charts column</li>
@@ -94,11 +94,12 @@ with everything nested under it — the wrapper yields nothing while its flag is
own command is always a list, so the <a class="xref" href="mission-root-panel.html">Mission branch root</a> <em>is</em> a
list, and every nested one is the same shape one level down.</p>
<p><code>List</code> is an ordinary catalog kind — category Flow — so a list is added the way any command is, and
adding one grows a sub-tree: the node's children are its own entries, and its panel embeds the same
entry-list editor the branch root uses, scoped to the nested list.</p>
adding one grows a sub-tree: the node's children are its own entries, edited on their own Control
Tree rows, and the list's own panel holds only its optional title, beneath the Disabled Caption
while the command is unticked.</p>
<p>This page covers what a list is, how its title reads, and what moving commands in and out of one
costs. The entry-list editor itself — Add Command, the row actions, the three drag landings — is
documented once, on <a class="xref" href="mission-root-panel.html">Mission Root Panel</a>.</p>
costs. The Control Tree row affordances every list shares — Add Command, the row buttons, the three
drag landings — are documented once, on <a class="xref" href="mission-root-panel.html">Mission Root Panel</a>.</p>
<h2 id="key-models">Key Models</h2>
<ul>
<li><strong>Primary</strong>: <a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html">ListCommand</a><a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html#Hi_SessionCommands_ListCommand_Title">Title</a>
@@ -121,72 +122,92 @@ and <a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html#Hi_Se
<ul>
<li>List Entry Panel
<ul>
<li>Control Bar
<li>Disabled Caption
<ul>
<li>Up / Down / Duplicate / Delete — labelled buttons for the operations that rewrite the
<strong>parent</strong> list. Delete asks for confirmation in a dialog naming the command.</li>
<li>At the bar's left, a caption while the command is disabled: skipped during play, still
editable.</li>
<li>Present only while the command is disabled: skipped during play, still editable.</li>
</ul>
</li>
<li>Title (optional) Input</li>
<li>Embedded Entry-List Editor
<ul>
<li>This list's own entries, scoped to the node's path.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>The Title input holds the <strong>raw</strong> title — empty when unset — while the label the row and the tree
show is composed by the engine: <code>List</code> when the title is blank, <code>List [title]</code> when it is not. A
<p>Nothing else is on the panel. The list's entries are the node's children in the tree, and the
operations that rewrite the <strong>parent</strong> list — Move up, Move down, Duplicate, Delete — are the
buttons on the list's own tree row, revealed on hover and on the selected row, and always shown on
a touch or pen device, where there is no hover; Delete asks for confirmation in a dialog naming the
command. The <code>+</code> on that row adds a command into this list.</p>
<p>The Title input holds the <strong>raw</strong> title — empty when unset — while the label the tree row shows is
composed by the engine: <code>List</code> when the title is blank, <code>List [title]</code> when it is not. A
titled list still says what it is. A whitespace-only title counts as unset and is never written into
the project file, so it cannot come back as a changed title on the next load.</p>
<p>Keystrokes debounce into one title save. The pending save is flushed before anything that shifts the
entry paths — a structural operation, a duplicate, a selection change — and cancelled on delete and
on unmount, so a late write cannot land on whichever command slid into the old path. Once it is
saved the tree label refreshes in place rather than by rebuilding the branch, which would remount
the open editor mid-edit.</p>
the project file: it reloads as the empty title, and the label reads <code>List</code> both before and after,
so a save cannot come back as a different label on the next load.</p>
<p>Keystrokes debounce into one title save. The same dirty gate flushes the pending save — and awaits a
save already on the wire — on two occasions: before every Control Tree row op, whose rebuild
remounts the panel and can shift the entry paths, and before a selection change, which switches
the panel away; unmount cancels it, so a late write cannot land on whichever command slides into
the old path. A flush whose save fails refuses the op or the switch, and a refused op says so in a
notice; a save that was already on the wire when the gate ran is only waited for — its failure
raises a notice of its own, and the op then proceeds regardless, leaving the title unsaved. Once
it is saved the tree label refreshes in place rather than by rebuilding the branch, which would
remount the open editor mid-edit.</p>
<p>The root list is the one list without a title input: the branch root reads <code>Mission</code>.</p>
<h2 id="enabling-and-what-a-disabled-list-skips">Enabling, and What a Disabled List Skips</h2>
<p>The enable switch is the checkbox on the command's own Control Tree item, not a control in the entry
row. It decides only whether the command <strong>runs</strong>: while it is clear the row and the tree node dim,
and the editor stays open and fully editable. Unticking a list dims its whole sub-tree — the dim
rides ancestor propagation in the tree while each nested entry keeps its own flag, which is exactly
what the run does: a disabled entry is skipped with everything under it.</p>
<p>The enable switch is the checkbox on the command's own Control Tree row, not a control on its panel
and not one of the row's buttons. It decides only whether the command <strong>runs</strong>: while it is clear
the tree row dims, and the editor stays open and fully editable. The checkboxes are read-only while
a mission row op, or a tick's own writes, are in flight. Unticking a list dims its whole sub-tree —
the dim rides ancestor propagation in the tree while each nested entry keeps its own flag, which is
exactly what the run does: a disabled entry is skipped with everything under it.</p>
<h2 id="paths">Paths</h2>
<p>A command node's key is its Mission API path — <code>&quot;0&quot;</code>, <code>&quot;1&quot;</code>, nested <code>&quot;0.2&quot;</code> for entry 2 inside the
list at index 0. Every structural change re-mints them: deleting entry 2 slides the next command
into path <code>&quot;2&quot;</code>. Each branch build therefore stamps its nodes, so the editor panel remounts onto the
new path instead of going on showing the command that used to live there.</p>
new path instead of going on showing a command the path no longer names. The host carries its own
index-based ids — the expansion set, the Add focus and the selection — through each row op, so they
follow the rows rather than the slots: a selection riding on the acted-on row follows it — the
stepped or dropped row at its new index, or the clone a Duplicate makes — except on Delete, where
no row remains to follow: it lands on the nearest remaining entry of that list, the index clamped
to the new count, or on the owning list node once the list is empty, where Add Command waits. Any
other selection keeps its own re-indexed row.</p>
<h2 id="moving-commands-in-and-out">Moving Commands In and Out</h2>
<ul>
<li><strong>Into a list</strong> — drag a row onto the middle band of a list row; that row's outer quarters still
reorder around it.</li>
<li><strong>Out of a list</strong> — drag onto the drop-out zone of a nested list's editor, which is visible only
while a row inside that editor is being dragged. The entry lands right after the list command
itself in the owning list.</li>
<li><strong>Into a list</strong> — drag a command's tree row — the whole row drags; the handle icon at its left
only marks it as draggable — onto the middle band of a list row; that row's outer quarters still
reorder around it. The Mission root row is all middle band: a drop anywhere on it lands in the
root list. Either way the entry lands at that list's tail.</li>
<li><strong>Out of a list</strong> — drop the row on the edge of a command row in another list, before or after it;
the lower quarter of the list command's own row lands the entry right after the list in the
owning list.</li>
</ul>
<p>Both are the same reparent call. The server resolves the source list and the target list to object
references before it mutates either, so the index shift the removal causes cannot misroute the
insert, and it refuses to move a list into itself or into one of its own descendants. That guard
compares identity rather than path-string prefixes, because <code>&quot;02&quot;</code>, <code>&quot;+2&quot;</code> and <code>&quot; 2&quot;</code> all parse to
index 2.</p>
<p>Which call a landing makes depends on whether the entry changes list, not on where the pointer
sat. A landing that changes the list — the middle band of a list the entry is not in, or an edge in
another list — is the reparent call; a landing that keeps the list — an edge in the entry's own
list, or the middle band of the list row (the Mission root row included) that already holds it,
which sends it to the tail — is the reorder call, sent as the list's whole new order. The server
resolves the source list and the target list to object references before it mutates either, so the
index shift the removal causes cannot misroute the insert, and it refuses to move a list into itself
or into one of its own descendants. That guard compares identity rather than path-string prefixes,
because <code>&quot;02&quot;</code>, <code>&quot;+2&quot;</code> and <code>&quot; 2&quot;</code> all parse to index 2.</p>
<p>Two more properties of a list follow from the same API:</p>
<ul>
<li><strong>A new command lands at the end</strong> of the list it was added to, root or nested. Nothing is pinned;
the order is entirely the user's, changed with the row's up / down buttons or by dragging.</li>
<li><strong>A new command lands right after the focused command</strong> when that command is a direct entry of
the list the <code>+</code> belongs to, root or nested, and at that list's tail otherwise. The focus is one
for the whole Mission tree — the last selected or last added command, whichever list it sits in
— so each list row's <code>+</code>, scoped to its own list, ignores a focus that lies in another list.
Nothing is pinned; the order is entirely the user's, changed with the row's Move up / Move down
buttons or by dragging.</li>
<li><strong>Duplicate is a deep copy.</strong> The entry is cloned through the same XML round-trip the project file
uses, so a nested list copies with its whole sub-tree, and the clone lands right after the source.</li>
</ul>
<p>Dragging serves rearrangement only: a drag carrying text or files is ignored, because the row's drag
handling returns as soon as the drag did not start on a row. To bring several program files in at
once, open a <a class="xref" href="NcFileCommand-panel.html">Program File</a> command's picker and pick them
<p>Dragging serves rearrangement only: a row accepts a drop only while a mission row is being dragged,
so a drag carrying text or files keeps the browser's no-drop cursor. To bring several program files
in at once, open a <a class="xref" href="NcFileCommand-panel.html">Program File</a> command's picker and pick them
together — the first pick lands on that command and each further pick becomes another Program File
command right after it, in pick order, inside the same list at any depth.</p>
<h2 id="which-commands-a-list-can-hold">Which Commands a List Can Hold</h2>
<p>The addable set is the server's command catalog: every engine command carrying <code>[CommandCatalog]</code>,
reflected into the picker Add Command opens, which searches by display label, kind key and alias.
reflected into the picker Add Command opens, which searches by display label, kind key and alias
and takes one pick per opening.
<code>List</code> is one of those commands, which is what makes nesting an ordinary act rather than a special
case. The frontend contributes an icon per kind, and its own label where it has one; the server
supplies the localized label otherwise, so a newly attributed engine command becomes addable with no
@@ -196,50 +217,74 @@ creation. General Config is the one such command with an editor of its own: it i
settings bundle, so a project file storing one loads as the individual setting commands it stands
for and saving never writes the bundle back — editable when a project still constructs one through
the API, and absent from the catalog.</p>
<p>Selecting an entry row selects that command's tree node, and the command's editor opens on that
node — the kind's own panel when one exists, otherwise a generic editor built from the scalar fields
the command declares. Selection is one row at a time: there is no multi-selection here, and every
action acts on the row that carries it. Because the server composes every entry label, a command
carrying no title of its own still reads as its localized display name.</p>
<p>There is no second column inside this editor: the entry list is a single column in the tree's editor
pane. The draggable vertical divider on screen belongs to the Execution page, between the left dock
— the Control Tree over the editor panel — and the central area; a second divider inside that dock
drags the height between the tree and the editor.</p>
<p>Selecting a command's tree row opens that command's editor on its node — the kind's own panel when
one exists, otherwise a generic editor built from the scalar fields the command declares. Selection
is one row at a time: there is no multi-selection here, and every row button acts on the row that
carries it, whether or not that row is the selected one. Because the server composes every entry
label, a command carrying no title of its own still reads as its localized display name.</p>
<p>A list's entries never appear in the editor pane: they are the node's children in the Control Tree,
and the pane below the tree shows only the selected command's panel. The left-most of the Execution
page's three draggable vertical dividers sits between the left dock — the Control Tree over the
editor panel — and the central area; the divider inside that dock drags the height between the tree
and the editor.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<ul>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — a list entry's panel: the
control bar, the optional Title input with its debounce / flush / cancel discipline, the embedded
entry-list editor of a list entry, and the bespoke-or-generic editor choice for every other kind.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the entry-list editor embedded
here and serving the branch root: the rows and their actions, the three drag landings, and the
drop-out zone shown only during a drag inside a nested editor.</li>
disabled caption, the optional Title input with its debounce / flush / cancel discipline and the
dirty gate it answers to the host, and the bespoke-or-generic editor choice for every other kind.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree rows: the draggable row
with its handle icon, the Move up / Move down / Duplicate / Delete buttons on every command row
(revealed on hover and on the selected row, always shown on a touch or pen device), the <code>+</code>
on every list row (the Mission root included) and the Add Command dialog it opens, and the
drop-indicator styles.</li>
<li><code>wwwroot-src/src/components/controlTree/useMissionTreeDrag.ts</code> — the drag bookkeeping between
mission rows: which rows drag, which receive, and the before / after / into landing read from the
pointer's position on the row.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the branch root's panel, the one
without a title input: the command count and the run-order hint.</li>
<li><code>wwwroot-src/src/components/controlTree/AddCommandDialog.vue</code> — the search-first catalog picker;
one pick per opening.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the child builders that recurse
into a list's own entries, the per-kind editor override map, the kind and category icons, and the
per-build stamp that forces the panel remount.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — which nodes show a checkbox, the
dim that flows down a disabled command's sub-tree, the enable write, and the in-place label
refresh after a title edit or a language switch.</li>
into a list's own entries, the per-kind editor override map, and the per-build stamp that forces
the panel remount.</li>
<li><code>wwwroot-src/src/components/controlTree/missionKindIcons.ts</code> — the kind and category icons the
picker shows.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the five row ops — add, drag
move, step, duplicate, delete — through one <code>missionRowOp</code> helper (the busy gate, the open panel's
dirty flush, the API call, the scoped rebuild, the post-op selection), the insert-after-focus rule
scoped to the <code>+</code>'s own list, and the index translation that carries the expansion set, the Add
focus and the selection through each op; which nodes show a checkbox, the dim that flows down a
disabled command's sub-tree, the enable write, and the in-place label refresh after a title edit
or a language switch.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — the typed wrapper over the Mission API and the path convention
(<code>root</code> or empty = the root list, <code>0.2</code> = a nested entry).</li>
(<code>root</code> or empty = the root list, <code>0.2</code> = a nested entry); <code>addEntry</code> takes the optional insert
index and resolves to the new entry's path.</li>
<li><code>wwwroot-src/src/components/mission/GenericCommandPanel.vue</code> — the editor a catalog kind gets when
it ships no bespoke panel, built from the command's declared scalar fields.</li>
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code> — the multi-pick file dialog: the
first pick lands on the command, each further pick becomes another Program File command right
after it.</li>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — the page-level splitter between the left dock and the
central area.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the <code>tree.mission.*</code> strings: the kind names, the drop-out
zone, the run-order hint, the disabled hint and the operation labels.</li>
<li><code>wwwroot-src/src/pages/ExecutionPage.vue</code> — the page-level splitters; the left-most parts the left
dock from the central area.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the left dock's two rows and the
height divider between the tree and the editor.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the <code>tree.mission.*</code> strings: the kind names, the run-order
hint that names the row affordances, the disabled hint, the cancelled-op notice and the operation
labels.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the row buttons' titles and the Title (optional) label.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the Delete Command confirmation.</li>
<li><code>Missions/MissionController.cs</code> — the entry lifecycle: <code>GET list-command/entries</code> (recursive),
<code>POST list-command/entries</code> and <code>POST list-command/entries/{path}</code> (append at the tail of the root
or of a nested list), <code>DELETE list-command/entries/{path}</code>,
<code>POST list-command/entries/{path}/move</code>, <code>PUT list-command/reorder</code>,
<code>POST list-command/entries/{path}/duplicate</code> (the XML-round-trip deep clone),
<code>POST list-command/entries/{path}/reparent</code> (drag-into-list and drop-out, with the self/descendant
guard), <code>PUT commands/{path}/listcommand</code> for the title, and <code>PUT commands/{path}/enabled</code> for an
entry's enable flag. <code>GetCommandTitle</code> composes every entry label the tree shows.</li>
<code>POST list-command/entries</code> and <code>POST list-command/entries/{path}</code> (into the root or a nested
list, at an optional <code>insertIndex</code> clamped to the list and otherwise at its tail, answering the
new entry's index and path), <code>DELETE list-command/entries/{path}</code>,
<code>POST list-command/entries/{path}/move</code> (the row's Move up / Move down), <code>PUT list-command/reorder</code>
(a drop that keeps the owning list), <code>POST list-command/entries/{path}/duplicate</code> (the
XML-round-trip deep clone), <code>POST list-command/entries/{path}/reparent</code> (a drop that changes the
owning list, with the self/descendant guard), <code>PUT commands/{path}/listcommand</code> for the title, and
<code>PUT commands/{path}/enabled</code> for an entry's enable flag. <code>GetCommandTitle</code> composes every entry
label the tree shows.</li>
<li><code>Missions/MissionCommandCatalog.cs</code> — reflects the <code>[CommandCatalog]</code> commands into the addable
set and constructs the picked kind.</li>
</ul>
@@ -260,7 +305,7 @@ a loaded project expands a stored one into the split setting commands.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="mission-root-panel.html">Mission Root Panel</a> — the entry-list editor this command embeds, documented once</li>
<li><a class="xref" href="mission-root-panel.html">Mission Root Panel</a> — the branch root, and the Control Tree row affordances every list shares, documented once</li>
<li><a href="../../../manual/run/building-a-mission.html">Building a Mission</a> — the task this command serves: grouping a mission and moving entries between groups</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
@@ -100,9 +100,13 @@ program name the run log shows, which is why the model defaults it to the comman
<code>NC Code</code>, rather than to an empty string. It is also the detail the mission row brackets: the row
reads <code>NC Code</code> while the title is empty or still that default, and <code>NC Code [title]</code> once it says
something else.</p>
<p>The panel renders on the command's own node in the Mission branch of the Execution page's Control
Tree, below the move / duplicate / delete control bar. A single-purpose kind like this one embeds
its whole editor on that node rather than growing section child nodes.</p>
<p>The panel renders on the command's own node under the Mission root of the Execution page's Control
Tree. The editor pane holds nothing but the editor: the command's move up / move down / duplicate /
delete buttons and its drag handle sit on its tree row, and the Add Command <code>+</code> sits on the row of
the list that holds it. A single-purpose kind like this one embeds its whole editor on that node
rather than growing section child nodes. Its tree checkbox only decides whether the run plays the
command; a disabled command is skipped during play — a caption above the title says so — and its
editor stays usable.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>NcCodeCommand Panel
@@ -124,7 +128,7 @@ folder is the <a class="xref" href="script-command-panel.html">Script command</a
CodeMirror with a C# grammar, this one ships a text area.</li>
</ul>
</li>
<li>Stats and Actions Row (web)
<li>Stats and Actions Row
<ul>
<li>Line count and character count, both recomputed as the text changes.</li>
<li>Trim Blank Lines Button — trims every line and drops the ones left empty, then saves at once.</li>
@@ -137,11 +141,32 @@ CodeMirror with a C# grammar, this one ships a text area.</li>
</ul>
<h2 id="saving">Saving</h2>
<p>Typing in either field schedules one save 400 ms after the last keystroke, so a burst of typing
costs one request instead of one per character. Trim Blank Lines and Clear save immediately rather
than through that timer.</p>
costs one request instead of one per character. The title and the NC text share that timer, and the
save carries every field edited since the last one. Trim Blank Lines and Clear save immediately
rather than through that timer.</p>
<p>The write is a single patch endpoint carrying the NC text and the title. Each field is applied only
when the body actually carries it, which is why the client sends the field it changed and a null for
the one it did not.</p>
when the body actually carries it, which is why the client sends the fields it edited and a null
for the rest. The endpoint carries no content hash, so nothing on the server refuses a save that
arrives late. After each save the tree relabels the command's node in place rather than rebuilding
the branch, which would remount the editor mid-edit.</p>
<p>The panel snapshots its command path at mount, and every load and save targets that snapshot
rather than the live selection. The path is an index-based entry path which every structural op
re-points, so the panel hands the Control Tree host a switch gate that runs before the selection
moves off the command and before every mission row op — move, duplicate, delete, drag, Add Command
— wherever in the tree that op acts: the gate first lets a save already on the wire land, then
sends the edit still waiting in the timer while the path is still the command's own. The gate is
there because an op rebuilds the list it rewrites — a drag between two lists rebuilds the nearest
list above both — and that rebuild re-mints the build stamp of every node under the rebuilt list.
Only one command editor is ever mounted, the selected node's, and its mount key carries that stamp,
so the editor remounts when its command sits inside the rebuilt list; the host does not ask whether
it does before running the gate — an op in a list that does not hold this command leaves the
editor mounted, and its staged edit is flushed all the same. A save that fails refuses the op, and
the host drops it with a notice.
Unmount cancels the timer instead of flushing it, so no save from a retired instance can land on
whichever command has slid into its path. Selection and the tick checkboxes are read-only while a
row op is in flight, and equally while a tick's own PUTs are on the wire: the host holds one busy
gate for both, so an enable toggle refuses a selection change and ignores further ticks until its
writes have landed, exactly as a row op does.</p>
<p>NC text is not validated while it is edited: there is no parse at edit time and no error surface on
the panel — the server stores whatever string arrives. Errors surface when the mission plays the
text.</p>
@@ -152,15 +177,35 @@ text.</p>
<ul>
<li><code>wwwroot-src/src/components/mission/NcCodeCommandPanel.vue</code> — this panel: the Title input, the
18-row monospace text area, the 400 ms debounced saves, the live line and character stats, Trim
Blank Lines, and Clear behind a confirm dialog.</li>
Blank Lines, and Clear behind a confirm dialog; the path snapshot taken at mount, the switch gate
that waits for a save on the wire and then drains the timer, and the unmount that cancels rather
than flushes.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the command node's
slave panel: mounts this editor on the entry path, forwards its switch gate to the host, and
shows the disabled caption above the title.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — mounts the one editor pane, the
selected node's, under a key that carries the node's build stamp; a rebuild that re-mints the
stamp remounts it.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree rows: the drag handle
and the move / duplicate / delete buttons on the command's row, and the Add Command <code>+</code> on the
row of its list.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — funnels every selection change
and every mission row op through the active panel's switch gate; holds the one busy gate that
keeps selection and the tick checkboxes read-only while a row op, or a tick's own PUTs, are in
flight; a refused gate drops the op with a notice, and a save relabels the command's node in
place.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — maps the <code>nccode</code> kind to this
panel, and states the rule it belongs to: a single-purpose kind embeds its whole editor on the
command node.</li>
command node; mints the per-build stamp every command node under a rebuilt list carries.</li>
<li><code>wwwroot-src/src/api/mission.ts</code><code>loadNcCode</code> and <code>setNcCode</code>, and the command shape the panel
edits (NC code plus title).</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording: the NC Code label, the character
count, Trim Blank Lines, and the line count it shares with the other panels.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared strings the panel borrows: the &ldquo;Title
(optional)&rdquo; label on the Title Field and the Clear button's label.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the Clear confirmation's title and message.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the disabled caption and the notice a refused gate raises on
a row op.</li>
<li><code>Missions/MissionController.cs</code> — the single patch PUT for this command, whose NC-code and title
fields are each applied when the body carries them, and the command-snapshot arm that returns both
back to the panel.</li>
@@ -178,9 +223,9 @@ how this command's NC text persists with the project.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="NcFileCommand-panel.html">NcFileCommand Panel</a> — the other Program command: a path to a file instead of text stored in the project</li>
<li><a class="xref" href="NcFileCommand-panel.html">NcFileCommand Panel</a> — the other NC program command among the three kinds the Program catalog category holds (Program File, NC Code, Script): a path to a file instead of text stored in the project</li>
<li><a class="xref" href="script-command-panel.html">Script Command Panel</a> — the rich editor this folder does have, and why this command ships a plain text area instead</li>
<li><a href="../../../manual/run/playing-a-program.html">Playing a Program</a> — the task these two commands serve</li>
<li><a href="../../../manual/run/playing-a-program.html">Playing a Program</a> — the task the two NC program commands, Program File and NC Code, serve; the Script command has its own manual page</li>
<li><a href="index.html">Mission</a> — the rest of the command panels</li>
</ul>
@@ -99,9 +99,13 @@ command: at play time <a class="xref" href="../../../api/Hi.MachiningProcs.Sessi
together with the project's own base directory to the local project service, and that is where a
relative path acquires its root; the convention behind it is the
<a class="xref" href="../../conventions/gui-file-path-assignment.html">Load Pattern</a>.</p>
<p>The panel renders on the command's own node in the Mission branch of the Execution page's Control
Tree, below the move / duplicate / delete control bar. This command has no title of its own, so the
row and tree label read <code>Program File [path]</code>, the path being the detail the label brackets.</p>
<p>The panel renders on the command's own node under the Mission root of the Execution page's Control
Tree. The editor pane holds the editor and, while the command's tree checkbox is off, a one-line
disabled hint above it — nothing structural: the command's move up / move down / duplicate / delete
buttons and its drag handle sit on its tree row, and the Add Command <code>+</code> sits on the row of the
list that holds it. This command has no editable Title field — unlike NC Code, Script and List —
so its tree row label is composed from the path alone: <code>Program File [path]</code>, the path being the
detail the label brackets.</p>
<div class="NOTE">
<h5>Note</h5>
<p>The GUI labels this command &ldquo;Program File&rdquo;: it plays NC, CL (CLSF) and CSV files — the runner is
@@ -178,11 +182,17 @@ browser to preserve. It is the backstop for a brand extension the NC group does
Auto routes to the CL and CSV runners, and everything else falls through to brand NC.</p>
<h2 id="multi-pick-fan-out">Multi-Pick Fan-Out</h2>
<p>The dialog is multi-select, and this is the one command editor that grows the mission. The first
picked file lands on this command. Each further pick becomes a <strong>new</strong> Program File command, added
to the list this command sits in and moved into place directly after it, in pick order — the same
list, so a command inside a nested list fans out inside that nested list. The panel then reports a
structure change, which rebuilds the owning list's branch around the new rows and leaves the
selection on this command.</p>
picked file lands on this command. Each further pick becomes a <strong>new</strong> Program File command,
inserted into the list this command sits in at the index directly after it, in pick order — one
insert per pick, each at the next index, and the same list, so a command inside a nested list fans
out inside that nested list. The panel then reports the insert range — the first new index and
the count — and the command's slave panel scopes it to the owning list and names this command as
the selection. The Control Tree host takes that insert on its structure-changed path, which shares
two things with its own row ops — it holds the busy gate for the rebuild (a change arriving while
another op is in flight is not rebuilt) and carries the index-based ids (the expansion set and the
Add focus) over the new siblings — and not the third: the open panel's dirty gate, which every
row op runs first, is not part of this path. It then rebuilds the owning list's branch around the
new rows and leaves the selection on this command, whose own index is unchanged.</p>
<h2 id="where-the-program-text-is-edited">Where the Program Text Is Edited</h2>
<p>The command stores a path, and the panel does not edit the file. It assigns the path, picks the
runner, reports what the server knows about the file, and previews its first 100 lines read-only.
@@ -203,14 +213,27 @@ command stores.</li>
from; an empty extension list means all files.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — maps the <code>ncfile</code> kind to this
panel. A kind absent from that map is served by the generic field editor instead.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the command node's
slave panel: mounts this editor on the entry path, and re-scopes the fan-out's structure change
to the owning list, with the insert range and this command as the selection.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree rows: the drag handle
and the move / duplicate / delete buttons on the command's row, and the Add Command <code>+</code> on the
row of its list.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — takes the fan-out's insert on
its structure-changed path: the busy gate held for the rebuild, the index-based ids carried over
the new siblings (the two guarantees shared with the row ops; the dirty gate is not one of them),
the rebuild scoped to the owning list, and the selection the payload names.</li>
<li><code>wwwroot-src/src/api/mission.ts</code><code>loadNcFile</code>, <code>setNcFilePath</code>, <code>setNcFileNcKind</code>,
<code>getNcFileInfo</code> and <code>previewNcFile</code>, plus the kind union and the file-info shape.</li>
<code>getNcFileInfo</code> and <code>previewNcFile</code>, plus the kind union and the file-info shape; <code>addEntry</code>,
which the fan-out calls with the insert index and which answers the new entry's path.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording: the path hint, the Play As hint, the
four filter labels, the banner's found and not-found lines, and the preview titles.</li>
<li><code>Missions/MissionController.cs</code> — four endpoints serve this command: a PUT for the path, a PUT for
the NC kind, and the two server-side reads behind the banner and the preview (both POSTs, both
resolving a relative path against the project folder). The command-snapshot builder emits the file
path and the NC kind for this kind of command.</li>
resolving a relative path against the project folder). The fan-out's extra picks go through the
list-entry POST, which lands the new entry at the requested insert index — clamped to the list,
the tail when none is given — and answers its index and dotted path. The command-snapshot builder
emits the file path and the NC kind for this kind of command.</li>
</ul>
<h3 id="hiapi-engine">HiAPI Engine</h3>
<ul>
@@ -90,17 +90,24 @@
<p>The Mission branch root of the <a class="xref" href="../index.html">Execution page</a>'s Control Tree: route
<code>/execution</code>, Control-Tree path <code>execution/mission</code>, panel <code>MissionRootPanel.vue</code>. <code>/mission</code>
resolves to the same place — it redirects onto <code>/execution?tree=execution/mission</code>.</p>
<p>The panel edits one list of commands: the mission's own command, which is always a
<a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html">ListCommand</a>. The branch root is therefore a list editor — Add Command, the
entries in run order, and the operations that rewrite the list. Editing a command is not this
panel's job: clicking a row selects that command's tree node, and the command's own editor renders
on that node's panel.</p>
<p>The node roots one list of commands: the mission's own command, which is always a
<a class="xref" href="../../../api/Hi.SessionCommands.ListCommand.html">ListCommand</a>. Its entries are the node's tree children, in run order, and
the row operations that rewrite the list — Add Command, drag, move up, move down, duplicate, delete —
live on the Control Tree rows and run in the tree host; no panel carries a second copy of them. The
one panel-side insert is the Program File editor's multi-pick: picking several files at once keeps
the first on that command and adds one Program File command per further file right after it, in its
own list, then hands the host the inserted range so the same busy gate and id translation cover the
rebuild. The root's own panel is informational: the command count and the run-order hint. Editing a
command is not this node's job either: selecting a command row opens that command's own editor on
its node's panel.</p>
<p>Command nodes are addressed by index-derived tree ids and by dotted API paths (<code>&quot;0&quot;</code>, <code>&quot;1&quot;</code>, nested
<code>&quot;0.2&quot;</code>). A command node id extends its parent's, so a root entry is <code>execution/mission/{index}</code> and
an entry inside a nested list is <code>execution/mission/{index}/{index}</code>; a command's section children
extend it once more, as <code>execution/mission/{index}/{id}</code> keyed on the section. Both forms shift on
every structural change — deleting entry 2 slides the next command into path <code>&quot;2&quot;</code> — so each branch
build stamps its nodes afresh and the editor panels remount onto the new paths.</p>
build stamps its nodes afresh and the editor panels remount onto the new paths, and the host carries
its own index-based ids — the expansion set, the Add focus and the selection — through every row op
so they follow the rows rather than the slots.</p>
<h2 id="key-models">Key Models</h2>
<ul>
<li><strong>Primary</strong>: <a class="xref" href="../../../api/Hi.MachiningProcs.MachiningProject.html#Hi_MachiningProcs_MachiningProject_PlayerCommand">PlayerCommand</a> — the mission's command,
@@ -122,17 +129,19 @@ top-down.</li>
<ul>
<li>Mission Root Panel
<ul>
<li>Head Line
<li>No-Project Caption
<ul>
<li>Add Command Button
<ul>
<li>Opens the Add Command dialog. Disabled while no project is open.</li>
<li>&ldquo;No project loaded. Open or create a project first.&rdquo; while nothing is open; the head line,
separator and empty state are withheld with it.</li>
</ul>
</li>
<li>Head Line
<ul>
<li>Commands Caption</li>
<li>Command Count Badge
<ul>
<li>Outlined, and grey rather than primary while the list is empty.</li>
<li>The root list's own entries — the node's command children. Outlined, and grey rather than
primary while the list is empty.</li>
</ul>
</li>
</ul>
@@ -140,56 +149,101 @@ top-down.</li>
<li>Separator</li>
<li>Empty State
<ul>
<li>&ldquo;No project loaded&rdquo; while nothing is open; otherwise &ldquo;No commands yet&rdquo;, naming Add Command.</li>
</ul>
</li>
<li>(Each) Command Row
<ul>
<li>Drag Handle Icon
<ul>
<li>The whole row is draggable; the handle is the affordance for it.</li>
</ul>
</li>
<li>Command Label
<ul>
<li>The command's title as the engine composes it: the kind's localized display name, with a
title the user typed appended as <code>Name [title]</code>.</li>
</ul>
</li>
<li>Move Up Button / Move Down Button
<ul>
<li>Disabled at the ends of the list.</li>
</ul>
</li>
<li>Duplicate Button</li>
<li>Delete Button</li>
<li>A disabled command's row is dimmed, and its buttons stay live.</li>
<li>Clicking the row anywhere but on a button selects that command's tree node.</li>
</ul>
</li>
<li>Drop-Out Zone
<ul>
<li>Present in a nested list editor only, and shown only while a row is being dragged. It sits
below the rows, so the rows do not shift under the pointer at drag start.</li>
<li>&ldquo;No commands yet. Click Add Command to create one.&rdquo; while the list is empty.</li>
</ul>
</li>
<li>Run-Order Hint
<ul>
<li>Commands run top-down, and the checkbox on each command's tree item enables or disables it.</li>
<li>Always present. It names the row affordances: commands run top-down in the Control Tree; add
one with + on the Mission or a List row; drag a row to reorder it; a row's own buttons move,
duplicate or delete it; its checkbox enables or disables it.</li>
</ul>
</li>
</ul>
</li>
<li>Mission Root Row (Control Tree)
<ul>
<li>Add Command Button
<ul>
<li>The <code>+</code>, always visible and pinned to the row's right edge. Opens the Add Command dialog
scoped to the root list. Disabled while no project is open, and while the busy gate is held —
a mission op, or a checkbox toggle's save, in flight.</li>
<li>The row carries no drag handle and no command ops. It is a drop target: anywhere on its height
lands the dragged command in the root list.</li>
</ul>
</li>
</ul>
</li>
<li>(Each) Command Row
<ul>
<li>Enable Checkbox
<ul>
<li>The tree's tick, and the command's enable switch; a disabled command's label is dimmed.</li>
</ul>
</li>
<li>Drag Handle Icon
<ul>
<li>The whole row is draggable; the handle is the affordance for it. Plain and List command rows
carry one; the root row does not.</li>
</ul>
</li>
<li>Command Label
<ul>
<li>A real link to the node's <code>?tree=</code> URL, so the browser's context menu can open it in a new
tab, while a plain click keeps the in-app selection. The text is the command's title as the
engine composes it: the kind's localized display name, with the command's key value appended
in brackets where the kind has one — a List's or Script's typed title, a Program File's file,
a resolution's millimetres, a switch's On / Off.</li>
</ul>
</li>
<li>Move Up Button / Move Down Button / Duplicate Button / Delete Button
<ul>
<li>Hidden at rest, revealed on hover and on the selected row, and always shown where there is no
hover to reveal them with (touch). Move Up is disabled on the first entry and Move Down on the
last; all four while the busy gate is held — a mission op, or a checkbox toggle's save, in
flight.</li>
</ul>
</li>
<li>Add Command Button
<ul>
<li>On a List row only: the same <code>+</code>, scoped to that list, to the right of the row buttons.</li>
</ul>
</li>
<li>Drop Indicator
<ul>
<li>A primary edge line for a before / after landing, a tinted row for a landing into a list; the
dragged row itself dims.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Enable/disable is not a control on this panel. It is the tree item's checkbox on the command node; a
disabled command is skipped when the mission plays and stays fully editable.</p>
<p>Enable/disable is not a control on any panel. It is the tree item's checkbox on the command node; a
disabled command is skipped when the mission plays and stays fully editable. A toggle PUTs straight
to the Mission API and flips the node's own state, with no rebuild; on failure the checkbox snaps
back. The toggle holds the same busy gate as the structural ops while its save is on the wire — a
row op sent meanwhile could be applied first server-side and land the flag on whichever command
slid into the ticked path — so until the save lands every row button and <code>+</code> is disabled, no drag
starts, the tree selection is refused, an op that still reaches the host returns silently, and a
further checkbox click snaps back. The gate holds in the other direction too: the checkboxes and
the tree selection are read-only while a mission op is in flight, since a tick would address the
node's pre-op path.</p>
<h2 id="add-command">Add Command</h2>
<p>The button opens a search-first picker. The search box matches a command's display label, its kind
key, or one of its declared aliases — each alias searchable both by its English key and by its word
in the request language. Arrow keys walk the results and Enter takes the highlighted one. Results
are grouped by category, in the catalog's own display order.</p>
<p>The picked command is appended to the list this editor is scoped to, and the branch rebuilds in
place: the selection stays on the list rather than jumping into the new command's panel.</p>
<p>The <code>+</code> on the Mission root row, or on any List row, opens a search-first picker. The search box
matches a command's display label, its kind key, or one of its declared aliases — each alias
searchable both by its English key and by its word in the request language. Arrow keys walk the
results and Enter takes the highlighted one. Results are grouped by category, in the catalog's own
display order. One pick per opening: a second click or Enter before the dialog has closed adds
nothing.</p>
<p>The picked command is inserted right after the focused command when that command is a direct
entry of the list the <code>+</code> is scoped to, and at that list's tail otherwise. The focus is one for the
whole mission, not one per list: the most recently selected command anywhere in the tree (a section
row counts for its owning command), or the last command an Add landed on. So the Mission row's <code>+</code>
never chases a focus inside a nested list — and once a nested command has been selected, the root
row's <code>+</code> appends at the root's tail rather than after whichever root command was selected before
it. The branch rebuilds in place, the new row is revealed and becomes the focus, so a run of adds
lands in order below it, and the selection stays where it was rather than jumping into the new
command's panel.</p>
<p>The addable set is served by the backend rather than mirrored in a frontend menu. Every engine
command carrying <code>[CommandCatalog]</code> is reflected into the catalog, so attributing a new engine
command is all it takes for it to appear in the dialog. Thirteen kinds ship:</p>
@@ -207,42 +261,64 @@ Failure, Physics</li>
can override that choice.</p>
</div>
<h2 id="ordering-duplicating-and-deleting">Ordering, Duplicating and Deleting</h2>
<p>Every structural op runs through one path in the tree host: a busy gate that holds every row
affordance, the tree selection and the checkboxes read-only while the op is in flight — the one
gate a checkbox toggle also holds while its save is on the wire, so an op and a tick never
interleave on the server; the open editor's dirty flush — an editor with staged edits (Script, NC
Code, a List's title) saves or resolves them first, waiting for a save already on the wire, and a
refused flush drops the op with a notice rather than silently; the API call; a rebuild scoped to
the list the op rewrote; then the post-op selection. The selection follows the acted-on row only
when it already rode on it — that row, or a node under it — and otherwise stays on its own
re-indexed row, so the editor the user is in is left alone.</p>
<p>Move Up and Move Down move an entry within its own list; they never change which list owns it.
Duplicate deep-clones the entry through the same XML round-trip the project file uses — so a nested
list copies with its whole subtree — and the clone lands right after the source. Delete asks for
confirmation in a dialog naming the command.</p>
list copies with its whole subtree — and the clone lands right after the source; a selection riding
on the source moves onto the clone. Delete asks for confirmation in a dialog naming the command,
before the dirty gate runs, so a cancelled delete forces no save prompt on the open editor; a
selection riding on the deleted row lands on the nearest remaining entry of that list, or on the
list node itself once the list is empty, where Add Command waits.</p>
<p>Dragging a row has three landings:</p>
<ul>
<li>On another row — reorder within this list. A plain row splits at its midline into before and
after, and the whole new order is sent as one ordered path list.</li>
<li>On the middle band of a <code>list</code> row — move the entry inside that list. That row's outer quarters
still reorder around it.</li>
<li>On the drop-out zone of a nested list editor — move the entry out, landing right after the list
command itself in the owning list.</li>
<li>On a command row's edge — before or after it. A plain command row splits at its midline; a List
row's outer quarters are its edges. When both rows share a list this is a reorder, and the whole
new order is sent as one ordered path list; when they do not, the entry relocates into the
target's list at that slot — which is also the way out of a nested list.</li>
<li>On the middle band of a List row — move the entry inside that list, at its tail.</li>
<li>On the Mission root row — anywhere on its height moves the entry into the root list, at its tail.</li>
</ul>
<p>The last two are the same reparent call. The server resolves both lists to object references before
it mutates either, so the index shift the removal causes cannot misroute the insert, and it refuses
to move a list into itself or into one of its own descendants.</p>
<p>Which call a landing makes depends on whether the entry changes list, not on where the pointer
sat. A landing that keeps the list — an edge in the entry's own list, or an into-landing on the
list that already holds it (a List row's middle band, or the Mission root row for a root entry),
which sends it to the tail — is the reorder call, the whole new order as one ordered path list; a
landing that changes the list — into a list the entry is not in, or an edge in another list — is
the reparent call. The client refuses to drop a row onto itself or into its own subtree, starts no
drag while the busy gate is held — an op, or a checkbox toggle's save, in flight — and abandons a
drop whose row was re-stamped by a rebuild mid-drag. The server resolves both lists to object
references before it mutates either, so the index shift the removal causes cannot misroute the
insert, and it refuses to move a list into itself or into one of its own descendants. A move across
lists rebuilds from the nearest list above both, and the selection follows the dragged command when
it rode on it.</p>
<h2 id="nesting">Nesting</h2>
<p>A <code>list</code> entry grows the same structure one level down: its children are its own entries, so nested
lists read as sub-trees at any depth, and the very same panel edits them. The differences are the
scope — the root list, versus the node's own dotted path — and the drop-out zone, which appears only
in a nested editor. A nested list's node adds an optional title above the embedded editor, and that
title is appended to the List name in the row and tree label.</p>
<p>Because the mission's own command is always a list, the root and a nested list are one editor at two
scopes. A project file whose stored command is something else loads with that command wrapped into
the root list, so the panel always has a list to edit.</p>
lists read as sub-trees at any depth, and the very same row affordances act on them. The difference
is the scope — the root list, versus the node's own dotted path: each List row's <code>+</code> inserts into
its own list, and a drop into either lands there. A nested list's panel is the optional title alone,
and that title is appended to the List name in the tree label.</p>
<p>Because the mission's own command is always a list, the root and a nested list are one row shape at
two scopes. A project file whose stored command is something else loads with that command wrapped
into the root list, so the branch always has a list to root.</p>
<h2 id="where-a-command-is-edited">Where a Command Is Edited</h2>
<p>A command's editor renders on its own tree node, below the control bar carrying the operations that
rewrite the parent list (Up, Down, Duplicate, Delete):</p>
<p>A command's editor renders on its own tree node; the row operations that rewrite the parent list stay
on the tree row:</p>
<ul>
<li>Nine kinds have a bespoke editor: General Config, Machining Motion Resolution,
<a class="xref" href="NcFileCommand-panel.html">Program File</a>, <a class="xref" href="NcCodeCommand-panel.html">NC Code</a>,
<a class="xref" href="script-command-panel.html">Script</a>, <a class="xref" href="NcOptOption-panel.html">NC Optimization Config</a>,
<a class="xref" href="PostExecutionCommand-panel.html">Post-Execution</a>, Record Meshed Geometry and Export Meshed
Geometry.</li>
<li><code>list</code> is edited inline by the control-bar panel: the optional title over the embedded
<a class="xref" href="ListCommand-panel.html">list editor</a>.</li>
<li><code>list</code> shows only its optional title on its node; its entries are its tree children, and
<a class="xref" href="ListCommand-panel.html">List Command Panel</a> covers what a list is and what moving in and
out of one costs.</li>
<li>The four remaining Setup kinds — Machining Resolution, Collision Detection, Pause on Failure and
Physics — are served by the generic field editor, which renders the command's reflected
<code>[CommandField]</code> scalars with server-localized labels. That is what the app does instead of
@@ -254,38 +330,59 @@ reads a command, but the catalog does not offer it: a project file that stores t
the individual commands it stands for, and the panel serves projects that construct one through the
API.</p>
<p>The multi-card kinds — General Config, NC Optimization Config and Post-Execution — put their extra
cards on section child nodes of the command, one node per card, with the card's own enable flag
surfaced as that section node's tree checkbox. Post-Execution's Shot Files Output and Optimization
Output sections appear only while the physics preference is on.</p>
cards on section child nodes of the command, one node per card. Where a card has its own enable
flag — General Config's Meshed Geometry section and each of Post-Execution's five — that flag is the
section node's tree checkbox; NC Optimization Config's five sections (Distances, Feedrate, Motion
Dynamics, Force &amp; Safety, Compensation) carry no checkbox — its enable switches sit on the command
node's own panel instead. Post-Execution's Shot Files Output and Optimization Output sections appear
only while the physics preference is on.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<ul>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — this panel: Add Command, the entry
rows and their four actions, the three drag landings, and the delete confirmation. It serves the
Mission root and, embedded under a command's control bar, every nested <code>list</code> node.</li>
<li><code>wwwroot-src/src/components/controlTree/AddCommandDialog.vue</code> — the search-first catalog picker.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — this panel, informational only:
whether a project is open, the command count, the empty state and the run-order hint. It changes
nothing and emits nothing.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree pane that renders the
Mission rows' affordances: the drag handle, the hover-revealed move / duplicate / delete buttons on
every command row, the <code>+</code> on every list row (the Mission root included) and the Add Command
dialog it opens, plus the drop-indicator styles.</li>
<li><code>wwwroot-src/src/components/controlTree/useMissionTreeDrag.ts</code> — the HTML5 drag-and-drop
bookkeeping between Mission rows: which rows drag, which receive, the before / after / into mode
from the pointer position, and the stamp check that abandons a drop after a mid-drag rebuild.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the page's two roots,
Mission above Program; owns the five mission row ops — add, drag move, step, duplicate, delete —
through one <code>missionRowOp</code> helper (the busy gate, the open panel's dirty flush, the API call, the
scoped rebuild, the post-op selection) and the index translation that carries the expansion set,
the Add focus and the selection through each op; drives the command and section checkboxes,
holding the same busy gate while a tick's PUTs are on the wire.</li>
<li><code>wwwroot-src/src/components/controlTree/AddCommandDialog.vue</code> — the search-first catalog picker;
one pick per opening.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — one entry's panel: the
move / duplicate / delete control bar over the kind's editor, or over the title input and embedded
list editor of a <code>list</code> entry.</li>
kind's editor, or the optional title input alone for a <code>list</code> entry. It forwards the editor's
dirty-switch gate — and flushes a pending title save — to the host, and relays the Program File
editor's multi-pick as a structure change on the parent list carrying the inserted range.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionSectionPanel.vue</code> — the panel of a command's
section child, rendering that one card.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — the Mission wave of the Control
Tree: the item types, the child builders that carry the recursion, the per-kind editor map, the
section definitions and their enable flags, and the kind and category icons.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — builds the Execution root with
the Mission branch node above the Program branch, and drives the command and section checkboxes.</li>
Tree: the item types, the child builders that carry the recursion, the per-kind editor map, and
the section definitions and their enable flags.</li>
<li><code>wwwroot-src/src/components/controlTree/missionKindIcons.ts</code> — the kind and category icons the
Add Command dialog shows.</li>
<li><code>wwwroot-src/src/api/mission.ts</code> — typed wrapper over <code>/api/Mission/*</code>: <code>listCommandEntries</code>,
<code>addEntry</code>, <code>removeEntry</code>, <code>moveEntry</code>, <code>duplicateEntry</code>, <code>reparentEntry</code>, <code>reorderEntries</code>,
<code>setListTitle</code>, <code>getCommandCatalog</code>, <code>loadCommandFields</code> / <code>setCommandField</code>, and the per-kind
readers and writers.</li>
<code>addEntry</code> (with an optional insert index, resolving to the new entry's path), <code>removeEntry</code>,
<code>moveEntry</code>, <code>duplicateEntry</code>, <code>reparentEntry</code>, <code>reorderEntries</code>, <code>setListTitle</code>,
<code>getCommandCatalog</code>, <code>loadCommandFields</code> / <code>setCommandField</code>, and the per-kind readers and
writers.</li>
<li><code>wwwroot-src/src/components/mission/GenericCommandPanel.vue</code> — the fallback editor for a catalog
kind with no bespoke panel: the command's reflected <code>[CommandField]</code> scalars.</li>
<li>The bespoke editors:
<ul>
<li><code>wwwroot-src/src/components/mission/PreSettingCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/MachiningMotionResolutionCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code> — also the one panel-side
insert: its multi-pick adds the extra files as new Program File commands right after its own,
through <code>addEntry</code> at an insert index.</li>
<li><code>wwwroot-src/src/components/mission/NcCodeCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/ScriptCommandPanel.vue</code></li>
<li><code>wwwroot-src/src/components/mission/NcOptOptionCommandPanel.vue</code></li>
@@ -294,18 +391,24 @@ kind with no bespoke panel: the command's reflected <code>[CommandField]</code>
<li><code>wwwroot-src/src/components/mission/ExportMeshedGeomCommandPanel.vue</code></li>
</ul>
</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the <code>tree.mission.*</code> strings this panel renders: Add Command,
the Commands caption, the no-commands-yet state, the drop-out zone, the run-order hint, the kind
display names, the section names and the operation labels.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the <code>tree.mission.*</code> strings: Add Command, the Commands
caption, the no-commands-yet state, the run-order hint that names the row affordances, the
disabled-command hint, the cancelled-op notice, the kind display names, the section names and the
op-context phrases.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the row buttons' titles (Move up, Move down, Duplicate,
Delete), the List title's label and the no-project caption.</li>
<li><code>wwwroot-src/src/i18n/en/dialog.ts</code> — the Delete Command confirmation.</li>
<li><code>wwwroot-src/src/router/routes.ts</code> — resolves <code>/mission</code> onto <code>/execution?tree=execution/mission</code>.</li>
<li><code>Missions/MissionController.cs</code> — the entry lifecycle (<code>GET list-command/entries</code>,
<code>POST list-command/entries</code> and <code>POST list-command/entries/{path}</code> to add at the root or inside a
nested list, <code>DELETE list-command/entries/{path}</code>, <code>POST list-command/entries/{path}/move</code>,
nested list — at an optional <code>insertIndex</code>, answering the new entry's index and path —
<code>DELETE list-command/entries/{path}</code>, <code>POST list-command/entries/{path}/move</code>,
<code>PUT list-command/reorder</code>, <code>POST list-command/entries/{path}/duplicate</code> and
<code>POST list-command/entries/{path}/reparent</code>), the <code>GET command-catalog</code> the Add Command dialog
reads, and the per-command endpoints including the generic <code>commands/{path}/fields[/{key}]</code> pair.
<code>reparent</code> is what backs both drag-into-a-list and drop-out-to-the-parent, and it is the endpoint
that rejects moving a list into itself or its own descendants.</li>
<code>reparent</code> is what backs a drop that changes the entry's list — into another list, or an edge in
another list — and it is the endpoint that rejects moving a list into itself or its own
descendants; a drop that keeps the list, the tail landing included, goes through <code>reorder</code>.</li>
<li><code>Missions/MissionCommandCatalog.cs</code> — reflects every <code>[CommandCatalog]</code> command into the addable
set served to Add Command, and creates the picked kind.</li>
<li><code>Missions/MissionCommandFields.cs</code> — the reflection layer behind the generic field endpoints: it
@@ -326,8 +429,8 @@ one when a project is read.</li>
<ul>
<li><a class="xref" href="../program.html">Program Branch</a> — the read-only inspection twin of this list: what a
run actually read, one node per NC source file</li>
<li><a class="xref" href="ListCommand-panel.html">List Command Panel</a> — the same editor one level down, and what moving an entry in or out of a nested list costs</li>
<li><a href="../../../manual/run/building-a-mission.html">Building a Mission</a> — the task this panel serves, as a procedure</li>
<li><a class="xref" href="ListCommand-panel.html">List Command Panel</a> — the same rows one level down, and what moving an entry in or out of a nested list costs</li>
<li><a href="../../../manual/run/building-a-mission.html">Building a Mission</a> — the task this branch serves, as a procedure</li>
</ul>
</article>
@@ -93,14 +93,16 @@
the project file, so the script travels with the project. <code>Run</code> evaluates the text against the
session shell as the globals object, and when the script returns a sequence of actions that sequence
is yielded into the run.</p>
<p>The title is what the mission row and the tree node show: the label is composed as <code>Script [title]</code>,
falling back to a bare <code>Script</code> while the title is empty. A newly added command starts empty on both
clients — no title, no text.</p>
<p>The panel renders on the command's own node in the Mission branch of the Execution page's Control
Tree, below the move / duplicate / delete control bar. A single-purpose kind like this one embeds its
whole editor on that node rather than growing section child nodes. Its tree checkbox only decides
whether the run plays the command; a disabled script is skipped during play and its editor stays
fully usable.</p>
<p>The title is what the command's tree row shows: the label is composed as <code>Script [title]</code>, falling
back to a bare <code>Script</code> while the title is empty. A newly added command starts empty — no title, no
text.</p>
<p>The panel renders on the command's own node under the Mission root of the Execution page's Control
Tree. The editor pane holds nothing but the editor: the command's move up / move down / duplicate /
delete buttons and its drag handle sit on its tree row, and the Add Command <code>+</code> sits on the row of
the list that holds it. A single-purpose kind like this one embeds its whole editor on that node
rather than growing section child nodes. Its tree checkbox only decides whether the run plays the
command; a disabled script is skipped during play — a caption above the title says so — and its
editor stays fully usable.</p>
<h2 id="layout">Layout</h2>
<ul>
<li>Head Line
@@ -163,9 +165,20 @@ save handed back. Each field is applied only when the body carries it. The respo
hash to chain into the next save.</p>
<p>Two prompts guard the edges, each with three buttons because neither is a yes/no question:</p>
<ul>
<li><strong>Unsaved Changes</strong>, raised when the Control Tree tries to move the selection off a command whose
save is still pending: Save &amp; switch flushes and then allows the switch, Discard drops the pending
write, Cancel keeps the selection where it is.</li>
<li><strong>Unsaved Changes</strong>, raised by the switch gate the panel hands the Control Tree host, which runs
it before the selection moves off the command and before every mission row op — move, duplicate,
delete, drag, Add Command. The op rebuilds the list it acted on, and that rebuild remounts the
panel whenever this command sits inside that list (a nested List included); when the panel
survives instead, the discard below has only put the autosave to sleep, and the next keystroke
resumes it on the same, still-valid path. The gate first lets a save already on the wire land,
and prompts only when edits are still staged after it: Save &amp; switch flushes and proceeds only
when that save landed — a conflict answered with Cancel leaves the edit staged and refuses the
switch; Discard reverts the editor to the last confirmed value and puts the autosave to sleep
until the next edit; Cancel keeps the selection where it is. A refused gate drops a row op and
says so in a notice (&quot;Cancelled — the open editor keeps its unsaved edits.&quot;). A row op cannot
reach the prompt while another is in flight — the host's busy gate refuses it first — but a
selection change holds no such gate, so a <code>?tree=</code> change arriving while a tree click's prompt is
up shares the pending answer instead of replacing it.</li>
<li><strong>Script changed elsewhere</strong>, raised when the server rejects the save because the hash no longer
matches — another tab, or a project reload, changed the command underneath. The rejection carries
the server's current text, title and hash, so the panel can offer Discard &amp; reload, Force
@@ -173,8 +186,10 @@ overwrite, or Cancel without a second round trip.</li>
</ul>
<p>The panel snapshots its command path at mount, and the autosave closes over that snapshot rather
than over the live selection, so a flush can never land on whichever command happens to be selected
when it fires. After each successful save the tree relabels the node in place instead of rebuilding
the branch, which would remount the editor mid-edit.</p>
when it fires. That path is an index-based entry path which every structural op re-points, which is
why the row ops run the gate above before their rebuild; an op on another row leaves the selection
on this editor's own row, re-indexed. After each successful save the tree relabels the node in
place instead of rebuilding the branch, which would remount the editor mid-edit.</p>
<h2 id="the-compile-gate">The Compile Gate</h2>
<p>A script that does not compile does nothing at run time: the command reports the compile failure
into the run's message stream and yields no work, so the run continues without it. Starting a run
@@ -190,8 +205,15 @@ the whole mission.</p>
<ul>
<li><code>wwwroot-src/src/components/mission/ScriptCommandPanel.vue</code> — this panel: the title row sharing
its line with the autosave indicator, the editor below it, the 500 ms autosave, the two
three-button prompts, the path snapshot taken at mount, and the change notice that relabels the
three-button prompts, the path snapshot taken at mount, the switch gate that waits for a save on
the wire and hands a second caller the pending prompt, and the change notice that relabels the
tree node.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the command node's
slave panel: mounts this editor on the entry path, forwards its switch gate to the host, and
shows the disabled caption above the title.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree rows: the drag handle
and the move / duplicate / delete buttons on the command's row, and the Add Command <code>+</code> on its
list's row.</li>
<li><code>wwwroot-src/src/components/widgets/TextEditor.vue</code> — the CodeMirror 6 wrapper: language and
read-only swapped through compartments, CRLF normalised on the way in so an unedited file cannot
dirty the autosave, an opt-in completion source that overrides the language's own, and a widened
@@ -208,14 +230,23 @@ kind union that maps straight onto CodeMirror's completion types.</li>
loader, the save that carries the expected hash, and the conflict error that carries the server's
current copy.</li>
<li><code>wwwroot-src/src/composables/useAutoSave.ts</code> — the debounce, the state machine and the conflict
recovery behind the panel's saving, saved and error states.</li>
recovery behind the panel's saving, saved and error states; the discard that reverts the editor
and sleeps the autosave until the next edit, and the wait for a save already on the wire.</li>
<li><code>wwwroot-src/src/components/widgets/AutoSaveIndicator.vue</code> — the status pill beside the title.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — funnels every selection change
through the active panel's switch gate, which is how the Unsaved Changes prompt gets its say.</li>
and every mission row op through the active panel's switch gate, which is how the Unsaved
Changes prompt gets its say; selection is read-only while a row op is in flight, and a refused
gate drops the op with a notice.</li>
<li><code>wwwroot-src/src/components/controlTree/missionItemTypes.ts</code> — maps the <code>script</code> kind to this
panel and gives it no section children, so the whole editor lives on the command node.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording: both prompts, their buttons, and the
load and save error contexts.</li>
<li><code>wwwroot-src/src/i18n/en/mission.ts</code> — the panel's wording: both prompts' titles and messages,
the Save &amp; switch, Force overwrite and Discard &amp; reload buttons, and the load and save error
contexts.</li>
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared strings the panel borrows: the &ldquo;Title
(optional)&rdquo; label, the Cancel button both prompts carry, and the Unsaved Changes prompt's
Discard.</li>
<li><code>wwwroot-src/src/i18n/en/tree.ts</code> — the disabled caption and the notice a refused gate raises on
a row op.</li>
<li><code>Missions/ScriptCompletionController.cs</code><code>POST /api/script/completions</code>: a thin wrapper that
returns the items, 499 on cancellation and 500 with the message on failure.</li>
<li><code>Missions/ScriptCompletionService.cs</code> — the singleton holding one workspace, built from the same
@@ -87,8 +87,9 @@
<article data-uid="Anatomy-ProgramBranch">
<h1 id="program-branch">Program Branch</h1>
<p>Program is the lower of the two Control-Tree branches the Execution page hosts: route <code>/execution</code>,
Control-Tree path <code>execution/program</code>, one node per NC source file the session read. It is a
<p>Program is the lower of the two roots of the Execution page's Control Tree, Mission being the upper:
route <code>/execution</code>, Control-Tree path <code>execution/program</code>, one node per NC source file the session
read. It is a
read-only view of a run — the file's text, the passes that went over it, the per-line marks the run
left behind, and the writeback files the run wrote — assembled from session data rather than from
anything typed into it. Its node ids are positional, so an id addresses a node without surviving the
@@ -97,9 +98,12 @@ branch growing around it.</p>
<p>Mission is what a run executes; Program is what the run read. That one difference sets the rest of
the branch:</p>
<ul>
<li>Mission's nodes edit the project. Program's item types declare a panel and a child builder and
nothing else no enable checkbox (the tree hides the tick on every node that is not a Mission
command or an enable-bearing Mission section), no control bar, no add, delete, move or duplicate.</li>
<li>Mission's nodes edit the project. Program's item types declare a panel and, on the root and the
file node, a child builder — and nothing else: no enable checkbox (the tree hides the tick on
every node that is not a Mission command or an enable-bearing Mission section), none of the row
affordances a Mission row carries (no drag handle, no add, delete, move or duplicate button), and
no counterpart to Mission's one panel-side structural edit, the multi-pick insert of the
<a class="xref" href="mission/NcFileCommand-panel.html">Program File editor</a>.</li>
<li>Mission's shape is authored. Program's shape is a consequence: a subprogram file appears on it
only because a run followed a call into it.</li>
<li>Mission belongs to the project. Program's contents belong to the session — the index behind it is
@@ -115,10 +119,10 @@ merge uses, and they share one namespace, so a Program File path and an NC Code
alike collapse into one node. A Program File command whose path is still unset is skipped and seeds
nothing. A placeholder carries no invocations, so its panel shows the file's text with no marks and
an empty, disabled pass selector.</p>
<p>The transport bar is the one control the branch does carry, and it is not the branch's own: the
primary editor pane pins the <a href="execution-tool-bar.html">Execution Tool Bar</a> above the panel of the
Execution root and of every node beneath it, so a run can be started, stepped and reset while a
program file is on screen.</p>
<p>The branch carries no transport of its own either. The <a href="execution-tool-bar.html">Execution Tool Bar</a>
sits in the Control Tree pane's run header — above the tree scroller, beside the execution-status
badge, on the Execution page only — so a run can be started, stepped and reset while a program file
is on screen, and the editor pane below the tree shows nothing but the selected node's panel.</p>
<h2 id="the-nodes-the-branch-mints">The Nodes the Branch Mints</h2>
<p>Three item types, each keyed by position rather than by identity.</p>
<p><strong>The branch root</strong> (<code>execution/program</code>) carries the summary panel and the child builder that
@@ -229,8 +233,8 @@ when it mounts — or through a watcher, when it was already the selected panel.
before the first page of text resolves is parked again and re-applied once the line array is sized.</p>
<h2 id="when-the-branch-is-rebuilt">When the Branch Is Rebuilt</h2>
<ul>
<li><strong>Page initialization.</strong> Building the Execution root builds the branch, and only when a project is
loaded; without one it stays empty.</li>
<li><strong>Page initialization.</strong> Building the Execution page's two roots builds the branch, and only when
a project is loaded; without one the root stands with no children.</li>
<li><strong>Any change in the broadcast execution status.</strong> The status the hub reports is one of
<code>NoProject</code>, <code>Ready</code>, <code>Running</code>, <code>Paused</code> and <code>Finished</code>, and the host re-reads the branch
whenever that value changes. It deliberately does not take the structural-change path used
@@ -239,9 +243,10 @@ keep re-opening a branch the reader had closed. The panels pick up <strong>part<
the replaced nodes: the path caption, the pass selector, the inline wording and the executed-line
count are all derived from the node, but the file text is not — it is fetched once per page on
mount and on scroll, with no watcher on the path, and the panel is not remounted because its key
is built from the node id, which is positional and therefore unchanged. A rebuild that puts a
different file at the same index leaves the previous file's text on screen under the new node's
caption and marks.</li>
— the node id, the node key and the mission stamp — reduces on a Program node to the id alone,
the other two parts being empty, and the id is positional and therefore unchanged. A rebuild that
puts a different file at the same index leaves the previous file's text on screen under the new
node's caption and marks.</li>
<li><strong>The refresh button on the root panel.</strong> This one does take the structural-change path: it
re-reads the file tree from the current session and expands the branch.</li>
</ul>
@@ -315,16 +320,18 @@ selector, the Follow toggle, the paged line viewer, the execution marks, the ste
converted-line badges, and the cursor and hover wiring.</li>
<li><code>wwwroot-src/src/components/controlTree/ProgramConversionFilePanel.vue</code> — one written file: the
paged line viewer and the source-line links back into the file panel.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — declares the branch root under
the Execution root, re-reads it on every execution-status change without expanding it, and hides
the tick on every node that is not a Mission command or an enable-bearing Mission section.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — declares the branch root as the
Execution page's second root, below Mission, re-reads it on every execution-status change without
expanding it, and hides the tick on every node that is not a Mission command or an enable-bearing
Mission section.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the node shape carrying the program
bookkeeping, the merged item-type registry, and the eager subtree builder.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree pane that renders the
branch's nodes and binds the tree's strict tick strategy.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — mounts the selected node's panel,
forwards its selection request to the host, and pins the transport bar above every Execution-scope
node.</li>
branch's nodes, binds the tree's strict tick strategy, and pins the run header — the transport bar
and the execution-status badge — above the tree on the Execution page.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — mounts the selected node's panel
under a key built from the node id, the node key and the mission stamp — the last two empty on
every Program node — and forwards its selection request to the host.</li>
<li><code>wwwroot-src/src/api/ncProgram.ts</code> — the typed client for the file tree, the paged lines, the line
marks, the syntax piece, the sentence-to-step mapping and both directions of conversion links.</li>
<li><code>wwwroot-src/src/api/clStrip.ts</code> — the step-select and step-enter posts a line click and a line
@@ -363,8 +370,11 @@ inline node and whose text is that node's only source.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="mission/mission-root-panel.html">Mission Root Panel</a> — the editable twin: the command list this branch reports on, and
the only place the NC file list is changed</li>
<li><a class="xref" href="mission/mission-root-panel.html">Mission Root Panel</a> — the editable twin: the command list this branch reports on. Its
Add Command, drag, move, duplicate and delete ops live on the Control Tree rows and act on
commands of any kind; the root's own panel is informational — a no-project note when no project
is loaded, otherwise the command count with an Add Command prompt while that count is zero, and
the run-order hint in either case</li>
<li><a class="xref" href="index.html">Execution Page</a> — the page that hosts both branches, and the transport, canvas and charts
the branch's selections drive</li>
</ul>
@@ -92,8 +92,9 @@ page at <code>/execution</code> and the General Setup page at <code>/general-set
host — <code>useControlTreeHost('execution')</code> or <code>useControlTreeHost('equipment')</code> — provides it to its
panes under one injection key, and the host owns everything after that: the node graph, the branch
builders, the selection and the <code>?tree=</code> query that carries it, the editor panel a selection resolves
to, and the mission enable ticks. The Tool House page at <code>/tool-house</code> reuses the same node shape and
the same item-type registry but renders a branch as nested tabs and creates no host.</p>
to, the mission enable ticks, and every structural operation on the Mission branch. The Tool House
page at <code>/tool-house</code> reuses the same node shape and the same item-type registry but renders a branch
as nested tabs and creates no host.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Node ids are role paths, not identities that survive a version change. Anatomy makes no claim that
@@ -105,7 +106,10 @@ for the migration hop that keeps an older link resolving.</p>
<ul>
<li><code>id</code> — the node's role path, slash-separated, whose first segment is the owning page's scope
(<code>execution/…</code>, <code>equipment/…</code>, <code>toolhouse/…</code>). This is the addressing surface: the <code>?tree=</code> query,
the persisted expansion list and the persisted last selection all hold ids.</li>
the persisted expansion list and the persisted last selection all hold ids. The General Setup
page's root carries its scope as its own id, <code>equipment</code>; the Execution page has no node with the
bare <code>execution</code> id — its two roots are <code>execution/mission</code> and <code>execution/program</code>, and the
segment is a scope key only.</li>
<li><code>label</code>, <code>labelKey</code>, <code>labelParams</code> — the display text; see <a href="#display-labels">Display Labels</a>.</li>
<li><code>itemType</code> — the registry key that decides the node's editor and its child builder.</li>
<li><code>key</code> — the bound object. In the geometry and equipment branches it is an IndexService key,
@@ -126,7 +130,9 @@ therefore survives a rebuild, which is why expansion state, the URL and the land
keyed on ids. Two id families are positional rather than role-based: Mission entries are
<code>${parentId}/${index}</code> and Program files are <code>${parentId}/${index}</code>, so moving or deleting a mission
command renumbers its siblings and a link to one names a position in the list rather than that
command.</p>
command. The host keeps three id sets of its own that must follow the rows rather than the numbers —
the persisted expansion list, the Add Command focus and the selection — and translates all three
through every mission row operation it runs; see <a href="#the-structure-changed-event">the row operations</a>.</p>
<h2 id="the-item-type-registry">The Item-Type Registry</h2>
<p><code>ITEM_TYPES</code> maps an <code>itemType</code> string onto a <code>TreeItemDef</code> carrying up to three members:</p>
<ul>
@@ -144,10 +150,11 @@ binds it to the node's key. The two composite geometry kinds do not embed nested
<code>TransformationGeom</code> grows an inner-geometry and an
inner-transformer slot, and <code>GeomCombination</code> grows one slot per item, so the tree owns the structure
and every panel stays shallow.</p>
<p>The map is one flat namespace assembled in <code>itemTypes.ts</code>: the <code>Group</code> stem type, the geometry,
transformer, workpiece-leaf, thermal-condition and spindle entries, and the Execution root's own type
are written inline, and five per-wave registries are spread in beside them. The three roots the panel
host renders itself — Machine Tool, Fixture and Workpiece — have no entry here at all.</p>
<p>The map is one flat namespace assembled in <code>itemTypes.ts</code>: the <code>Group</code> stem type and the geometry,
transformer, workpiece-leaf, thermal-condition and spindle entries are written inline, and five
per-wave registries are spread in beside them. The Execution page's two roots are registry types —
<code>MissionRoot</code> and <code>ProgramRoot</code>, from their waves — and no wrapper type stands over them. The three
roots the panel host renders itself — Machine Tool, Fixture and Workpiece — have no entry here at all.</p>
<table>
<thead>
<tr>
@@ -160,7 +167,7 @@ host renders itself — Machine Tool, Fixture and Workpiece — have no entry he
<tr>
<td>Mission</td>
<td><code>missionItemTypes.ts</code></td>
<td><code>MissionRoot</code>, <code>MissionCommand</code>, <code>MissionSection</code>; section children at <code>${command.id}/${sectionId}</code>; the per-kind bespoke editors, with a generic field editor as the fallback</td>
<td><code>MissionRoot</code>, whose panel is informational — the command count and the run-order hint that names the row affordances; <code>MissionCommand</code>, whose panel is the kind's editor, or only a Title field for a List; <code>MissionSection</code>; section children at <code>${command.id}/${sectionId}</code>; the per-kind bespoke editors, with a generic field editor as the fallback</td>
</tr>
<tr>
<td>Program</td>
@@ -219,15 +226,50 @@ correct only while the change stays inside the emitting panel's own branch.</li>
<li><code>selectId</code> — the selection to adopt afterwards. It is assigned directly, <strong>bypassing the
dirty-switch gate</strong>, because the emitting panel is being replaced on purpose. A <code>null</code> clears the
selection.</li>
<li><code>inserted</code> — entries a panel inserted into a mission list itself: the list node's id, the child
index the run starts at and its count. The host then treats the rebuild as a mission operation —
it holds the busy gate and slides its index-based ids past the new siblings — rather than as a
plain branch refresh.</li>
</ul>
<p>A mission command's move, duplicate and delete re-scope to the parent list, because those operations
rewrite the parent's children, and they name the post-operation selection: the clone's slot after a
duplicate, the neighbouring index after a delete, the parent list when the delete emptied it. Moving
an entry out of a nested list re-scopes to the grandparent. The controller brand switch and the
runner's Object-Management install re-scope to the controller root. The Tool House type selectors and
a flute add re-scope to their own node; everything that rewrites the tool collection — a new tool, a
duplicate, a delete, an id rename, an Object-Management install — re-scopes to the tool-house root,
and a flute delete re-scopes to its fluting parent.</p>
<p>One command editor emits the event: the Program File editor, whose multi-pick turns every extra file
into a new Program File command inserted right after it, in pick order; the command panel hosting it
re-scopes the event to the parent list, keeps that command selected and passes the insert range
along. Two panels emit it with no payload and so take the default scope, their own node: the Program
root panel's refresh button, which re-reads the file tree from the current session and rebuilds the
Program branch through <code>rebuildBranch</code>, so the branch opens under it; and the <code>GeomCombination</code>
item panel after an item add, an item remove or a clear, since its item slots must be regrown. The
controller brand switch and the runner's Object-Management install re-scope to the controller
root. The Tool House type selectors and a flute add re-scope to their own node; everything that
rewrites the tool collection — a new tool, a duplicate, a delete, an id rename, an
Object-Management install — re-scopes to the tool-house root, and a flute delete re-scopes to its
fluting parent.</p>
<p>Every other structural change to the Mission branch is an operation of the host itself, not an
event, because the tree rows are the only surface that offers it: Add Command, a drag between rows,
Move up / Move down, Duplicate and Delete. A drag is HTML5 drag-and-drop between mission rows; every
command row drags, the Mission root row only receives, and where the pointer sits in the target row
decides the operation. A plain command row has no edge band: it splits at its midline, the upper
half putting the dragged command before that row and the lower half after it — a reorder when both
share a list, a relocation across lists otherwise. A List row keeps that before / after reading only
in its top and bottom quarters; its middle half, and the whole of the Mission root row, take the
command into that list, at the tail. A drop onto the dragged row's own subtree is
refused, and a drop aborts when a rebuild mid-drag re-stamped the dragged node, since its id may then
name another command. All five run through one helper that holds a busy gate
(one operation at a time), runs the open panel's dirty-switch gate, calls the Mission API, translates
the expansion list and the Add Command focus through the operation, rebuilds the branch of the list
or lists it rewrote — the one list, or the nearest list above both ends of a cross-list drag — and
only then translates the selection and names any post-operation selection, since the ids it names
exist only after the rebuild. A refused gate drops the operation and says so in a notification
(&quot;Cancelled — the open editor keeps its unsaved edits.&quot;); the delete's own confirm dialog runs
before the gate, so a cancelled delete forces no save prompt on the open editor.</p>
<p>The selection moves only when it rides on the acted-on row — that row itself, or a section or
nested entry under it. Then it follows a stepped or dragged command, lands on the clone after a
duplicate, and after a delete lands on the nearest remaining entry of that list, or on the list
node itself once the list is empty. Otherwise the editor the user is in stays on its own,
re-indexed row. Add Command never moves the selection: it reveals the new row and makes it the
focus, so a run of adds lands in order. The focus is the command most recently selected — a section
row counts for its owning command — or the last one added, and an Add Command from a list row's <code>+</code>
inserts right after that focus when the focus is a direct entry of that list, and at the list's
tail otherwise; the Mission root row's <code>+</code> therefore never chases a focus inside a nested list.</p>
<h3 id="whole-tree-rebuilds-and-the-refreshes-that-are-not-rebuilds">Whole-tree rebuilds, and the refreshes that are not rebuilds</h3>
<p><code>rebuildTree()</code> replaces the root array outright, and runs after an Object-Management swap of the
machine tool or the fixture, after a blank machining chain is created, and after either runner-suit
@@ -258,30 +300,38 @@ to the model, and the pages' canvases draw that model; the group stems and the M
Workpiece roots have no field editor to commit for at all.</p>
<h2 id="selection-and-the-dirty-switch-gate">Selection and the Dirty-Switch Gate</h2>
<p>Every selection change funnels through <code>requestSelect(id)</code>. It returns immediately when the id is
already selected; otherwise it awaits the mounted panel's optional <code>tryConsumeBeforeSwitch()</code> and
abandons the switch when that returns false.</p>
already selected; it refuses outright while a mission row operation, or a tick's own writes, are in
flight — the operation ends by naming the post-operation selection, which would silently undo a
change landed during its rebuild; otherwise it awaits the mounted panel's optional
<code>tryConsumeBeforeSwitch()</code> and abandons the switch when that returns false.</p>
<p>Three entrances use it: a click in the tree, a change to the URL's <code>?tree=</code> value, and a <code>select-node</code>
event from the mounted panel — the wire the panel host puts on every registry panel, so a group stem's
child list, a mission list's entry row and the Program branch's conversion jumps all arrive through
that one. A fourth path — the <code>selectId</code> of a structure change — assigns the selection directly and
skips the gate.</p>
child list and the Program branch's conversion jumps both arrive through that one. Two paths assign
the selection directly and skip the gate: the <code>selectId</code> of a structure change, and the
post-operation selection of a mission row operation, which has already run the gate itself.</p>
<p>There is no &ldquo;nothing selected&rdquo; state. Re-clicking the selected row makes the tree yield <code>null</code>, and
that is ignored; an empty <code>?tree=</code> lands on the persisted last selection for that page when the built
tree still contains it, and on the page's root otherwise. The editor row is therefore always alive,
tree still contains it, and otherwise on the page's root — General Setup on the equipment host, and
Mission, the first of its two roots, on the execution host. The editor row is therefore always alive,
and reclaiming its height is the row's own collapse toggle rather than a deselection.</p>
<p>The gate itself is registered by the panel host: the mounted registry panel is handed to the host
through a template ref, and the host asks that instance for <code>tryConsumeBeforeSwitch</code>. Panels that
write one field per request need no gate and register none. One panel exposes it —
<code>MissionCommandSlavePanel</code>:</p>
<ul>
<li>on a <code>list</code> entry it flushes the debounced title save while the command path is still live, then
allows the switch;</li>
<li>on any other entry it forwards its inner editor's gate. Today only the script editor has one: with
unsaved text it asks save, discard or cancel, returning false on cancel and on a flush that ends in
an error.</li>
<li>on a <code>list</code> entry it waits for a title save already on the wire, then flushes the debounced title
save while the command path is still live, and allows the switch only when that save landed;</li>
<li>on any other entry it forwards its inner editor's gate. Two editors have one. The Script editor
first lets a save already on the wire land, then with unsaved text asks save, discard or cancel:
cancel returns false, discard reverts to the last confirmed text and puts the autosave to sleep
until the next keystroke, and save proceeds only when the flush landed — a conflict answered with
Cancel leaves the edit staged and the switch refused. The NC Code editor waits for a save on the
wire, drains its debounced field save, and returns false when that save failed.</li>
</ul>
<p>The same gate is run before duplicating a command, so the clone is made from what has just been typed
rather than from the last saved state.</p>
<p>The same gate runs before every mission row operation, not only before a selection change, because
the operation's rebuild remounts the open editor and mission paths are index-based: a debounced save
that outlived the remount would land on whichever command slid into the path it captured at mount.
For the same reason both the NC Code editor and the list title cancel, never flush, on unmount.</p>
<h2 id="display-labels">Display Labels</h2>
<p><code>nodeDisplayLabel(n)</code> returns the translation of <code>labelKey</code> — with <code>labelParams</code> interpolated — when
one is set, and the verbatim <code>label</code> otherwise. <code>labelKey</code> is therefore the form every role label the
@@ -316,10 +366,14 @@ the selection's type registers no panel.</li>
key, and the mission stamp where the node has one. So the editor remounts when the selection moves,
when the bound object's key is re-minted, and when the Mission branch was rebuilt beneath it. That
third term is exactly why the mission label refresh above mutates the node instead of rebuilding.</p>
<p>On the Execution page the editor row grows a header: the transport bar is pinned above the scroll area
whenever the Execution root or any of its descendants is selected, so the run controls never scroll
away; that one instance also owns the transport's function-key shortcuts, which are attached for as
long as it is mounted.</p>
<p>The editor row carries nothing but the editor. The run controls sit in the tree pane instead: on the
Execution page the Control Tree pane pins a run header above its tree scroller — the transport bar
and the live execution-status badge — whatever the selection is. It rides the pane's content rather
than the dock's expansion header because that header is the same component on General Setup, which
renders neither piece. This is the page's sole <code>ExecutionToolBar</code>, so it owns the F5F8 shortcuts,
and because the dock keeps the tree pane mounted while its row is collapsed, a collapsed tree row
does not drop them. When the tree column narrows, the six-button strip scrolls horizontally and the
badge never shrinks, so the run state is the last thing a resize can hide.</p>
<p>A pure <code>Group</code> stem is not a node without an editor. <code>Group</code> registers <code>GroupInfoPanel</code>, whose body is
the stem's intro line followed by a clickable list of its children; a row click emits <code>select-node</code>
and the host moves the selection there. The General Setup root, the two Anchor stems, the workpiece
@@ -341,7 +395,10 @@ a mission section that carries its own enable flag. Writing the ticked set walks
any depth: a command whose state changed is PUT to the Mission API and, on success, has the new state
copied onto its section children; a section whose state changed is dispatched to its kind's writer. A
failed write raises a toast and leaves the model untouched, so the getter re-derives the old value and
the box snaps back.</p>
the box snaps back. The ticks share the mission operations' busy gate in both directions: a tick
during a row operation is ignored — it would write to the node's pre-operation path — and the box
snaps back the same way, while a tick's own writes hold the gate so no row operation can overtake
them on the server and land the enable flag on whichever command slid into the ticked path.</p>
<p>A row dims when it is itself disabled — a disabled command, or a section whose own flag is off — or
when an ancestor command is disabled, which is how a disabled command greys its whole subtree while
each descendant keeps its own state. The switch decides only whether the command runs: a disabled
@@ -350,17 +407,43 @@ command stays fully editable.</p>
<ul>
<li>Left Dock — both tree pages, one nav-bar button toggling the whole column
<ul>
<li><strong>Control Tree</strong> Expansion Row — collapses in place, keeping its header; stays mounted while
collapsed, so the tree keeps its scroll position
<li><strong>Control Tree</strong> Expansion Row — collapses in place, keeping its header, which is the same
page-neutral header on both pages; stays mounted while collapsed, so the tree keeps its scroll
position and the run header keeps its shortcuts
<ul>
<li>Tree — one root row per page: <strong>Execution</strong> or <strong>General Setup</strong>
<li>Run Header — Execution page only, pinned above the tree scroller and separated from it by a
rule
<ul>
<li>Execution Transport Bar — the six transport buttons; the strip scrolls horizontally when the
column is narrower than it</li>
<li>Execution Status Badge — the status label in its status colour; never shrinks</li>
</ul>
</li>
<li>Tree — the Execution page has two root rows, <strong>Mission</strong> above <strong>Program</strong>; the General Setup
page has one, <strong>General Setup</strong>
<ul>
<li>Drag Handle — on every mission command row, List rows included, immediately before the
label. It is the first thing the row's own header renders, but the tree puts the expand
arrow, on a row with children, and then the enable checkbox ahead of that header, so the
handle sits to the checkbox's right rather than at the row's left edge. The Mission root row
has none. While a drag is under way the dragged row dims, a line along the target row's top
or bottom edge marks a before / after drop, and a tinted target row marks a drop into that
list</li>
<li>Node Row — the label is a real link to this node's <code>?tree=</code> URL on the page, so the browser's
context menu offers open-in-new-tab and copy-link; a plain left click keeps the in-app
selection instead of navigating, while a modified or middle click opens a tab and leaves the
current selection where it is. No icons are rendered.</li>
current selection where it is. No node icons are rendered.</li>
<li>Node CheckBox — mission command rows, and mission section rows carrying an enable flag</li>
<li>Execution Status Badge — on the <strong>Execution</strong> root row only</li>
<li>Row Operations — on every mission command row, right-aligned: <strong>Move up</strong>, <strong>Move down</strong>,
<strong>Duplicate</strong> and <strong>Delete</strong>. Absent from the layout at rest, so a resting label keeps the
column's full width, and shown on the hovered row and on the selected row; on a device with
no hover they are always shown. Move up is disabled on a list's first entry, Move down on its
last, and all four while an operation is in flight</li>
<li>Add Command — the <code>+</code> at the right edge of the Mission root row and of every List row,
always visible, scoped to that row's list; disabled without an open project and while an
operation is in flight. It opens the Add Command dialog — a search box over the command
catalog, grouped by category, which fetches the catalog on every opening and accepts one
pick per opening</li>
</ul>
</li>
<li>Spinner and &ldquo;Loading project…&rdquo; — shown instead of the tree until the first build lands</li>
@@ -372,7 +455,6 @@ down to a 120 px editor floor and a 100 px tree floor</li>
<li><strong>Editor</strong> Expansion Row — its header carries the selection breadcrumb, the node path joined with
slashes, and falls back to <strong>Editor</strong>; stays mounted while collapsed
<ul>
<li>Execution Transport Bar — Execution page, while an Execution-scope node is selected</li>
<li>The selected node's editor panel, or &ldquo;Select an item in the Control Tree to edit it here.&rdquo;</li>
</ul>
</li>
@@ -405,17 +487,28 @@ when the project changes, and a host is therefore created once per project per p
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the host: the two scopes' root
builders, the selection gate, the <code>?tree=</code> sync, the mission ticks, the rebuild entry points, the
re-index and Object-Management handlers, and the reactive surface the panes consume.</li>
builders, the selection gate, the <code>?tree=</code> sync, the mission ticks, the five mission row
operations with their shared busy gate and id translation, the rebuild entry points, the re-index
and Object-Management handlers, and the reactive surface the panes consume.</li>
<li><code>wwwroot-src/src/components/controlTree/useMissionTreeDrag.ts</code> — the drag-and-drop bookkeeping
between mission rows: which rows drag, the drop mode from the pointer's position in the target
row, and the row classes behind the drop indicators.</li>
<li><code>wwwroot-src/src/components/controlTree/itemTypes.ts</code> — the <code>ControlNode</code> and <code>TreeItemDef</code> shapes,
the <code>SlotCtx</code> hooks, the structure-change payload, <code>nodeDisplayLabel</code> / <code>nodeDisplayInfo</code>, the
geometry and transformer builders, the <code>ITEM_TYPES</code> map and <code>buildSubtree</code>.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree column: the node anchors,
the click handling that keeps a plain click in-app, and the execution-status badge.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreePanel.vue</code> — the tree column: the Execution
page's run header with the transport bar and the execution-status badge, the node anchors, the
click handling that keeps a plain click in-app, the drag handles, the row operations, the Add
Command <code>+</code> and the dialog it opens.</li>
<li><code>wwwroot-src/src/components/execution/ExecutionToolBar.vue</code> — the six transport buttons, and the
F5F8 shortcuts the sole instance registers.</li>
<li><code>wwwroot-src/src/utils/executionStatusPresent.ts</code> — the badge's status label and colour.</li>
<li><code>wwwroot-src/src/components/controlTree/AddCommandDialog.vue</code> — the search-first command picker
over the server-served catalog.</li>
<li><code>wwwroot-src/src/components/controlTree/ControlTreeDock.vue</code> — the two-row dock and the height
divider that sets the editor row's stored pixel height.</li>
<li><code>wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue</code> — the panel host: the three inline
root types, the registry lookup, the remount key, the gate registration and the transport header.</li>
root types, the registry lookup, the remount key and the gate registration.</li>
<li><code>wwwroot-src/src/components/controlTree/ContentSlavePanel.vue</code> — the General Setup content column
and its unkeyed content panel.</li>
<li><code>wwwroot-src/src/components/controlTree/GroupInfoPanel.vue</code> — a group stem's intro and its
@@ -433,10 +526,22 @@ the core leaves every runner grows, and the snapshot flags that decide which bra
<li><code>wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts</code> — the Tool House wave and its
<code>tool-&lt;id&gt;</code> role paths.</li>
<li><code>wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts</code> — the two runner-suit leaves.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — the command control bar, and
the only panel that exposes a dirty-switch gate.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionRootPanel.vue</code> — the Mission root's informational
panel: the command count and the run-order hint.</li>
<li><code>wwwroot-src/src/components/controlTree/ProgramRootPanel.vue</code> — the Program root's summary panel
and the refresh button that emits a payload-less <code>structure-changed</code>.</li>
<li><code>wwwroot-src/src/components/controlTree/GeomCombinationTreePanel.vue</code> — the combination's item list,
whose add, remove and clear emit the same payload-less event.</li>
<li><code>wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue</code> — a command's editor host: a
List's Title field with its debounced save, and the only panel that exposes a dirty-switch gate.</li>
<li><code>wwwroot-src/src/components/mission/ScriptCommandPanel.vue</code> — the autosaving editor whose gate is
forwarded, with its save / discard / cancel prompt.</li>
<li><code>wwwroot-src/src/composables/useAutoSave.ts</code> — the Script editor's autosave: the flush, the wait
for a save on the wire, and the discard that sleeps the instance until the next edit.</li>
<li><code>wwwroot-src/src/components/mission/NcCodeCommandPanel.vue</code> — the second forwarded gate: the
debounced field save it drains, and the unmount that cancels instead of flushing.</li>
<li><code>wwwroot-src/src/components/mission/NcFileCommandPanel.vue</code> — the one command editor that emits
<code>structure-changed</code>, with the <code>inserted</code> range of its multi-pick.</li>
<li><code>wwwroot-src/src/components/panels/PanelExpansion.vue</code> — the expansion row both dock rows are built
from.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the device-local expansion list, last selections,
@@ -148,7 +148,7 @@ A bump discards every cached page and rebuilds it, so each page initializes on i
watching for project changes itself.</li>
<li>Bottom footer — the routine-progress bar: the latest foreground message with a recent-messages history menu on the left, the live session status in the middle, and in-flight background job progress on the right. See <a class="xref" href="bottom-message-bar.html">Message Section on Main Panel</a>.</li>
</ul>
<p>The menu bar carries no run tool bars at all: the transport controls ride the <a class="xref" href="../execution/index.html">Execution Page</a>&lsquo;s primary panel header and the view controls ride its canvas panel header. What the menu bar keeps instead is the page's set of column quick-toggles, the connection badge, the active page's title, the <code>Show Log</code> button and — when authentication is on — the logout button. The toggles follow whichever tree page is current: four on the Execution page (control dock, canvas, strip charts, step info) and three on General Setup (control dock, content, canvas); the Tool House page lays itself out with plain splitters and gets none. The connection badge is Execution-only — the other pages&rsquo; canvases carry their own in-panel badges.</p>
<p>The menu bar carries no run tool bars at all: the transport controls and the execution status badge ride the run header the <a class="xref" href="../execution/index.html">Execution Page</a>&lsquo;s Control Tree pane pins above its tree, and the view controls ride its canvas panel header. What the menu bar keeps instead is the page's set of column quick-toggles, the connection badge, the active page's title, the <code>Show Log</code> button and — when authentication is on — the logout button. The toggles follow whichever tree page is current: four on the Execution page (control dock, canvas, strip charts, step info) and three on General Setup (control dock, content, canvas); the Tool House page lays itself out with plain splitters and gets none. The connection badge is Execution-only — the other pages&rsquo; canvases carry their own in-panel badges.</p>
<h2 id="project-menu-behavior">Project Menu Behavior</h2>
<p>The <code>Project Path Text Field</code> displays the current project path when a project is loaded. It is implemented as a pure text field (not a button) that allows users to select and copy the path.</p>
<p>The <code>Project</code> Menu manages <a class="xref" href="../../api/Hi.MachiningProcs.MachiningProject.html">MachiningProject</a> with the following operations:</p>