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
@@ -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