List Command Panel
A ListCommand is the container command: an optional title plus a list of enable-wrapped commands. Running one walks CommandEntryList top-down and skips a disabled entry together with everything nested under it — the wrapper yields nothing while its flag is clear. The mission's own command is always a list, so the Mission branch root is a list, and every nested one is the same shape one level down.
List 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, 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.
This page covers what a list is, how its title reads, and what moving commands in and out of one costs. The Control Tree row affordances every list shares — Add Command, the row buttons, the three drag landings — are documented once, on Mission Root Panel.
Key Models
- Primary: ListCommand — Title and CommandEntryList.
- Supporting:
- EnablingWrapper — one entry: Command plus IsEnabled.
- ITitleCommand —
ListCommandimplements it, and GetCommandTitle composes the label a row shows. - PlayerCommand — the mission's command, a list.
- CommandCatalogAttribute and CommandCategory
— what makes
Listaddable, and the group it is offered under.
The Panel of a List Entry
Selecting a list command in the Control Tree opens, top to bottom:
- List Entry Panel
- Disabled Caption
- Present only while the command is disabled: skipped during play, still editable.
- Title (optional) Input
- Disabled Caption
Nothing else is on the panel. The list's entries are the node's children in the tree, and the
operations that rewrite the parent 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 + on that row adds a command into this list.
The Title input holds the raw title — empty when unset — while the label the tree row shows is
composed by the engine: List when the title is blank, List [title] 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: it reloads as the empty title, and the label reads List both before and after,
so a save cannot come back as a different label on the next load.
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.
The root list is the one list without a title input: the branch root reads Mission.
Enabling, and What a Disabled List Skips
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 runs: 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.
Paths
A command node's key is its Mission API path — "0", "1", nested "0.2" for entry 2 inside the
list at index 0. Every structural change re-mints them: deleting entry 2 slides the next command
into path "2". Each branch build therefore stamps its nodes, so the editor panel remounts onto the
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.
Moving Commands In and Out
- Into a list — 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.
- Out of a list — 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.
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 "02", "+2" and " 2" all parse to index 2.
Two more properties of a list follow from the same API:
- A new command lands right after the focused command when that command is a direct entry of
the list the
+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+, 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. - Duplicate is a deep copy. 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.
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 Program File 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.
Which Commands a List Can Hold
The addable set is the server's command catalog: every engine command carrying [CommandCatalog],
reflected into the picker Add Command opens, which searches by display label, kind key and alias
and takes one pick per opening.
List 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
frontend change.
A command without that attribute stays loadable from a project file but is never offered for creation. General Config is the one such command with an editor of its own: it is the legacy 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.
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.
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.
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue— a list entry's panel: the 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.wwwroot-src/src/components/controlTree/ControlTreePanel.vue— 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+on every list row (the Mission root included) and the Add Command dialog it opens, and the drop-indicator styles.wwwroot-src/src/components/controlTree/useMissionTreeDrag.ts— 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.wwwroot-src/src/components/controlTree/MissionRootPanel.vue— the branch root's panel, the one without a title input: the command count and the run-order hint.wwwroot-src/src/components/controlTree/AddCommandDialog.vue— the search-first catalog picker; one pick per opening.wwwroot-src/src/components/controlTree/missionItemTypes.ts— the child builders that recurse into a list's own entries, the per-kind editor override map, and the per-build stamp that forces the panel remount.wwwroot-src/src/components/controlTree/missionKindIcons.ts— the kind and category icons the picker shows.wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the five row ops — add, drag move, step, duplicate, delete — through onemissionRowOphelper (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+'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.wwwroot-src/src/api/mission.ts— the typed wrapper over the Mission API and the path convention (rootor empty = the root list,0.2= a nested entry);addEntrytakes the optional insert index and resolves to the new entry's path.wwwroot-src/src/components/mission/GenericCommandPanel.vue— the editor a catalog kind gets when it ships no bespoke panel, built from the command's declared scalar fields.wwwroot-src/src/components/mission/NcFileCommandPanel.vue— the multi-pick file dialog: the first pick lands on the command, each further pick becomes another Program File command right after it.wwwroot-src/src/pages/ExecutionPage.vue— the page-level splitters; the left-most parts the left dock from the central area.wwwroot-src/src/components/controlTree/ControlTreeDock.vue— the left dock's two rows and the height divider between the tree and the editor.wwwroot-src/src/i18n/en/tree.ts— thetree.mission.*strings: the kind names, the run-order hint that names the row affordances, the disabled hint, the cancelled-op notice and the operation labels.wwwroot-src/src/i18n/en/common.ts— the row buttons' titles and the Title (optional) label.wwwroot-src/src/i18n/en/dialog.ts— the Delete Command confirmation.Missions/MissionController.cs— the entry lifecycle:GET list-command/entries(recursive),POST list-command/entriesandPOST list-command/entries/{path}(into the root or a nested list, at an optionalinsertIndexclamped to the list and otherwise at its tail, answering the new entry's index and path),DELETE list-command/entries/{path},POST list-command/entries/{path}/move(the row's Move up / Move down),PUT list-command/reorder(a drop that keeps the owning list),POST list-command/entries/{path}/duplicate(the XML-round-trip deep clone),POST list-command/entries/{path}/reparent(a drop that changes the owning list, with the self/descendant guard),PUT commands/{path}/listcommandfor the title, andPUT commands/{path}/enabledfor an entry's enable flag.GetCommandTitlecomposes every entry label the tree shows.Missions/MissionCommandCatalog.cs— reflects the[CommandCatalog]commands into the addable set and constructs the picked kind.
HiAPI Engine
HiNc/SessionCommands/ListCommand.cs— the container: the optional title, the entry list a run walks top-down, the whitespace-only-title-is-unset XML rule, the Flow catalog attribute, and theList/List [title]composition.HiNc/SessionCommands/EnablingWrapper.cs— the enable flag plus the wrapped command that together make one entry.HiNc/SessionCommands/ITitleCommand.cs— the interface a command implements to compose its own title from the caller's vocabulary.HiNc/SessionCommands/CommandCatalogAttribute.cs—CommandCategoryand the attribute (category, order, kind key, aliases) that makes the addable set backend-owned.HiNc/SessionCommands/PreSettingCommand.cs— the General Config bundle: no catalog attribute, and a loaded project expands a stored one into the split setting commands.HiNc/MachiningProcs/MachiningProject.cs— declares the mission's command and keeps it a list.
See Also
- Mission Root Panel — the branch root, and the Control Tree row affordances every list shares, documented once
- Building a Mission — the task this command serves: grouping a mission and moving entries between groups
- Mission — the rest of the command panels