Control Tree
The Control Tree is the engine shared by the two tree-shaped pages of the web client: the Execution
page at /execution and the General Setup page at /general-setup. Each page creates exactly one
host — useControlTreeHost('execution') or useControlTreeHost('equipment') — 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 ?tree= query that carries it, the editor panel a selection resolves
to, the mission enable ticks, and every structural operation on the Mission branch. The Tool House
page at /tool-house reuses the same node shape and the same item-type registry but renders a branch
as nested tabs and creates no host.
Important
Node ids are role paths, not identities that survive a version change. Anatomy makes no claim that
a ?tree= id is stable across releases; see Tree Ids and Routes
for the migration hop that keeps an older link resolving.
The Node
Every branch of every tree is built from one structure, ControlNode, declared in itemTypes.ts:
id— the node's role path, slash-separated, whose first segment is the owning page's scope (execution/…,equipment/…,toolhouse/…). This is the addressing surface: the?tree=query, 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,equipment; the Execution page has no node with the bareexecutionid — its two roots areexecution/missionandexecution/program, and the segment is a scope key only.label,labelKey,labelParams— the display text; see Display Labels.itemType— the registry key that decides the node's editor and its child builder.key— the bound object. In the geometry and equipment branches it is an IndexService key, re-minted on every re-index; in the Mission branch it is the Mission API command path (0,1, the dotted0.2of a nested list entry); in the Tool House branch it is the tool id as a string. Waves whose panels read a module-level state singleton rather than an indexed object leave it empty — the SoftNc controller leaves, the spindle sections, the Environment root's Ambient Temperature and Coolant leaves and the Program branch all do.ctx— the parent-providedSlotCtx: theafterChangecommit chain, a slot'sonCreatecreate-and-rebind hook, and a slot picker'sallowedKinds/allowNoneconstraints.children— grown by the builders, not declared by the tree column.selectable,info/infoKey, and themission/programbookkeeping records the Mission and Program waves stamp on their own nodes.
The split between id and key is the load-bearing one. A key is re-minted every time the host
re-indexes the model, so nothing durable may hang off it; an id names a role in the model and
therefore survives a rebuild, which is why expansion state, the URL and the landing selection are all
keyed on ids. Two id families are positional rather than role-based: Mission entries are
${parentId}/${index} and Program files are ${parentId}/${index}, 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. 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 the row operations.
The Item-Type Registry
ITEM_TYPES maps an itemType string onto a TreeItemDef carrying up to three members:
panel— the editor mounted in the dock's editor row for a node of this type.contentPanel— a large view for the General Setup page's content column.buildChildren— an async builder that returns this node's children.
Two flavours of type share the map. Slot types (Geometry, Transformer) have a kind picker as
their panel and grow at most one child, the concrete kind, at the kind-independent id
${slot.id}/type — so switching a slot's kind keeps both the selection and the expansion. A slot
holding nothing grows no child at all, which for the four geometry slots whose picker offers None is
an ordinary state rather than a failure. Kind types (Box3d, StaticTranslation, …) register
SoleEditorPanel, which looks the concrete editor up in the geometry and transformer editor maps and
binds it to the node's key. The two composite geometry kinds do not embed nested editors:
TransformationGeom grows an inner-geometry and an
inner-transformer slot, and GeomCombination grows one slot per item, so the tree owns the structure
and every panel stays shallow.
The map is one flat namespace assembled in itemTypes.ts: the Group 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 —
MissionRoot and ProgramRoot, 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.
| Wave | File | What it contributes |
|---|---|---|
| Mission | missionItemTypes.ts |
MissionRoot, whose panel is informational — the command count and the run-order hint that names the row affordances; MissionCommand, whose panel is the kind's editor, or only a Title field for a List; MissionSection; section children at ${command.id}/${sectionId}; the per-kind bespoke editors, with a generic field editor as the fallback |
| Program | programItemTypes.ts |
ProgramRoot, ProgramFile, ProgramConversionFile; writeback conversions at ${root.id}/dst-${index} |
| Tool House | toolHouseItemTypes.ts |
the tool collection and the per-tool editor branches under toolhouse/tool-<id>/… |
| SoftNc controller | softNcItemTypes.ts |
the controller root and its leaves on two planes — …/machine/<seg> and …/program-data/<seg> — a core every runner grows plus the brand-driven ones |
| Runner suits | runnerSuitItemTypes.ts |
CsvRunnerRoot and ClRunnerRoot, flat single-panel leaves with no builder |
The Program branch is fetched whole: its root builder takes one file-tree response plus one
conversions response and stashes each file's raw subtree on the node, so the file nodes' builder only
maps what is already in hand. The SoftNc branch is the opposite — its builder reads the runner
snapshot once, and with no runner installed grows nothing at all. Where one resolves, the two plane
stems appear over a fixed core that stands for every brand: six machine leaves, and Work Coordinates
and Tool Offsets on the program-data plane. The leaves beside that core are the brand-driven ones —
each appears only where the snapshot reports its backing table, so a brand switch changes which nodes
exist. One flag does double duty: the Siemens $TC_DP flag adds its own two leaves and renames the
core Tool Offsets leaf to its ISO G43 H reading, so the two offset ledgers do not read as one.
The two runner-suit nodes are conditional, and the host — not the registry — decides. With a project
open, a CSV or CL controller node is built when its Preference checkbox is on, or when the current
?tree= value starts with that node's id. The checkbox is device-local and off by default; the route
is the only other
reveal, deliberately not the persisted last selection, so a node switched off while it is selected
does not resurrect itself. Stepping off comes first: when the selection sits on a node the user has
just unchecked, the host re-points the URL at the page root before rebuilding.
Building a Branch
buildSubtree(node, services, depth) is the whole builder. It looks the node's type up, returns
without touching children when the type declares no buildChildren or when depth has reached
MAX_DEPTH (12, a guard against a self-referencing model), and otherwise awaits the builder, recurses
into each returned child, and assigns the result. Building is eager: a branch is materialised in full
at build time rather than lazily on expansion.
Builders receive one service, registerKey, which registers a freshly minted IndexService key with
the page's cleanup hub — see Webapi with Hub-Cleanup Assistance.
rebuildBranch(node) is buildSubtree plus one thing: it adds the node's id to the expansion list so
the fresh children are visible, and reports a failure as a toast named after the node's display label.
The structure-changed event
A panel that has changed the shape of the model emits structure-changed (or type-changed, wired
to the same handler). The optional payload is a StructureChangeRequest:
scopeId— the branch to rebuild. Without it the scope is the selected node itself, which is correct only while the change stays inside the emitting panel's own branch.selectId— the selection to adopt afterwards. It is assigned directly, bypassing the dirty-switch gate, because the emitting panel is being replaced on purpose. Anullclears the selection.inserted— 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.
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 rebuildBranch, so the branch opens under it; and the GeomCombination
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.
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 ("Cancelled — the open editor keeps its unsaved edits."); the delete's own confirm dialog runs before the gate, so a cancelled delete forces no save prompt on the open editor.
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 +
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 + therefore never chases a focus inside a nested list.
Whole-tree rebuilds, and the refreshes that are not rebuilds
rebuildTree() 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
preference flips. On the execution host, an
execution-status transition re-runs buildSubtree on the Program root directly rather than through
rebuildBranch, so a run does not force that branch open on every transition.
Two refreshes deliberately mutate existing nodes instead of rebuilding:
- A mission command's title is re-read from the entry list and written onto the node's label.
- A file-backed geometry leaf's label is re-read when its source file changes.
The mission one exists because a rebuild of the Mission branch re-mints the per-build stamp that
forms part of the editor's remount key, and remounting an open editor mid-edit would cost the user
their cursor and any staged autosave. The geometry one is the same principle without the stamp: a
title change and a source-file change alter no structure, so the label is all that has to move, and
replacing the node under an open file editor would buy nothing.
The commit chain
The tree has no component bubbling, so the full post-edit commit chain lives on each node as
ctx.afterChange, composed level by level as the branch is built. A GeomCombination item's chain
cleans that combination's aggregated cache before running its parent's chain; the fixture's chain ends
in a geometry-cache clear; the two workpiece geometry slots re-commit the swap-in before clearing
their own cache, and skip that re-commit when the slot has been set to none; the fixture's two anchor
transformers re-commit and then clear the geometry cache, while the workpiece's two re-commit and
clear nothing, since a placement change invalidates no cached solid. Tool-house nodes all share one
refresh. Every other family carries a no-op: the Mission, Program, spindle, SoftNc and
thermal-condition nodes and the workpiece's Mesh and Material leaves have panels that write straight
to the model, and the pages' canvases draw that model; the group stems and the Machine Tool and
Workpiece roots have no field editor to commit for at all.
Selection and the Dirty-Switch Gate
Every selection change funnels through requestSelect(id). It returns immediately when the id is
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
tryConsumeBeforeSwitch() and abandons the switch when that returns false.
Three entrances use it: a click in the tree, a change to the URL's ?tree= value, and a select-node
event from the mounted panel — the wire the panel host puts on every registry panel, so a group stem's
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 selectId of a structure change, and the
post-operation selection of a mission row operation, which has already run the gate itself.
There is no “nothing selected” state. Re-clicking the selected row makes the tree yield null, and
that is ignored; an empty ?tree= lands on the persisted last selection for that page when the built
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.
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 tryConsumeBeforeSwitch. Panels that
write one field per request need no gate and register none. One panel exposes it —
MissionCommandSlavePanel:
- on a
listentry 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; - 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.
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.
Display Labels
nodeDisplayLabel(n) returns the translation of labelKey — with labelParams interpolated — when
one is set, and the verbatim label otherwise. labelKey is therefore the form every role label the
client translates itself takes, while label is the storage for text it cannot: server-composed
mission titles, file-backed geometry paths and engine type names. The translator function is read
inside the call rather than captured, so a language change re-renders the whole tree's role labels.
Two labels are composed rather than looked up. A mission command's label is the title the server
composes for it — the command's kind name in the request language, with the command's own text in
brackets when there is one, as in Script [Warm-Up]. That title always arrives filled in, so the
node's labelKey is always dropped and the tree's own kind-name fallback is a safety net that never
fires in practice. Because titles are composed server-side per request, a language change also
re-pulls the command entries and rewrites those labels in place. A file-backed
geometry leaf carries its source file in brackets, so sibling instances read apart:
StlFile [Geom/x.stl]. The voxel kind is renamed for display, appearing as MeshedGeomFile [...]
rather than by its type name.
Group stems take the same treatment for their intro text: infoKey wins over info, and a stem with
neither falls back to a sentence naming the group.
The Panel Host
PrimarySlavePanel decides what the editor row shows, in this order:
- The three inline root types —
MachineToolRoot,FixtureRoot,WorkpieceRoot— are rendered by the panel itself as a summary, and are never resolved from the registry. The two with a stand-alone file surface, Machine Tool and Fixture, carry an Object-Management button above it; the workpiece has none, since it is authored entirely through its child tree items. - Otherwise the registry's
panelfor the selection's item type, mounted with the node as its only prop, withchanged,type-changed,structure-changed,select-nodeanderrorwired to the host. - A spinner while the first build is still running, and the select-an-item hint after that when the selection's type registers no panel.
What decides a remount is the mounted component's key, ${id}|${key}|${stamp} — the node id, the node
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.
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 ExecutionToolBar, so it owns the F5–F8 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.
A pure Group stem is not a node without an editor. Group registers GroupInfoPanel, whose body is
the stem's intro line followed by a clickable list of its children; a row click emits select-node
and the host moves the selection there. The General Setup root, the two Anchor stems, the workpiece
Material stem and the controller's two plane stems are all of this kind.
The General Setup page adds a second slave, ContentSlavePanel, which resolves contentPanel from
the same registry. Three item types declare one — the spindle root and its scalar and contour section
types — and all three name the same contours view, so the charts stay mounted while the selection
moves across the spindle branch: the component takes no props and is mounted unkeyed on purpose.
Every other selection shows the column's empty hint.
NodeTabCascade is the third consumer of the registry. It renders a branch as nested tabs rather than
tree rows: the node's own panel on top, a single child inlined below it, several children as a dense
tab strip in which only the active child mounts. The active tab is remembered per role path at module
scope, with the per-tool segment wildcarded, so switching tools keeps every level's tab. Its panels
report through an injected cascade host that carries the emitting node, because — unlike the tree's
one-panel-at-a-time model — several panels are mounted at once.
The Tick Column
The tree runs a strict tick strategy, and a node hides its checkbox unless it is a mission command or a mission section that carries its own enable flag. Writing the ticked set walks every command node at 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. 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.
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 command stays fully editable.
Layout
- Left Dock — both tree pages, one nav-bar button toggling the whole column
- Control Tree 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
- Run Header — Execution page only, pinned above the tree scroller and separated from it by a
rule
- Execution Transport Bar — the six transport buttons; the strip scrolls horizontally when the column is narrower than it
- Execution Status Badge — the status label in its status colour; never shrinks
- Tree — the Execution page has two root rows, Mission above Program; the General Setup
page has one, General Setup
- 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
- Node Row — the label is a real link to this node's
?tree=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 node icons are rendered. - Node CheckBox — mission command rows, and mission section rows carrying an enable flag
- Row Operations — on every mission command row, right-aligned: Move up, Move down, Duplicate and Delete. 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
- Add Command — the
+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
- Spinner and “Loading project…” — shown instead of the tree until the first build lands
- Run Header — Execution page only, pinned above the tree scroller and separated from it by a
rule
- Row Divider — a 5 px bar between the rows, shown only while both are open; dragging it upwards grows the editor row, which keeps a stored pixel height while the tree above absorbs the change, down to a 120 px editor floor and a 100 px tree floor
- Editor Expansion Row — its header carries the selection breadcrumb, the node path joined with
slashes, and falls back to Editor; stays mounted while collapsed
- The selected node's editor panel, or “Select an item in the Control Tree to edit it here.”
- Control Tree 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
- Content Column — General Setup page only, one nav-bar button
- The selected node's large content view, or “The selected item has no expanded content.”
Both dock rows and both column widths are device-local browser preferences, shared by the two pages through one preferences module; each page keeps its own visibility record, and both pages share one expansion list because their id spaces do not overlap.
Lifecycle
The host is created while its page's script runs and provided before the dock mounts; the page's
onMounted then awaits initialize(). That call connects the cleanup hub and builds the tree — on
the equipment host, after re-indexing the machine tool, the workpiece and the fixture in that order —
and finishes by adopting whatever the URL's ?tree= names. A loading flag is held for the whole call,
which is what the tree and editor rows render their spinners from; later rebuilds mutate an
already-populated tree and never re-enter it. A missing project, or a project without a fixture or
workpiece, answers 404, and those are treated as empty states rather than errors, so no toast appears.
Without an open project the Mission, Program and controller branches are still declared, as childless
stems: their builders run only once a project is loaded.
Because the layout keys its keep-alive wrapper on a project epoch, every page is destroyed and rebuilt when the project changes, and a host is therefore created once per project per page.
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
HiNC-2025-webservice (Quasar CLI SPA):
wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the host: the two scopes' root builders, the selection gate, the?tree=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.wwwroot-src/src/components/controlTree/useMissionTreeDrag.ts— 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.wwwroot-src/src/components/controlTree/itemTypes.ts— theControlNodeandTreeItemDefshapes, theSlotCtxhooks, the structure-change payload,nodeDisplayLabel/nodeDisplayInfo, the geometry and transformer builders, theITEM_TYPESmap andbuildSubtree.wwwroot-src/src/components/controlTree/ControlTreePanel.vue— 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+and the dialog it opens.wwwroot-src/src/components/execution/ExecutionToolBar.vue— the six transport buttons, and the F5–F8 shortcuts the sole instance registers.wwwroot-src/src/utils/executionStatusPresent.ts— the badge's status label and colour.wwwroot-src/src/components/controlTree/AddCommandDialog.vue— the search-first command picker over the server-served catalog.wwwroot-src/src/components/controlTree/ControlTreeDock.vue— the two-row dock and the height divider that sets the editor row's stored pixel height.wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— the panel host: the three inline root types, the registry lookup, the remount key and the gate registration.wwwroot-src/src/components/controlTree/ContentSlavePanel.vue— the General Setup content column and its unkeyed content panel.wwwroot-src/src/components/controlTree/GroupInfoPanel.vue— a group stem's intro and its navigating child list.wwwroot-src/src/components/controlTree/SoleEditorPanel.vue— a kind node's editor, resolved from the geometry and transformer editor maps.wwwroot-src/src/components/controlTree/NodeTabCascade.vue— the same branches rendered as nested tabs, with the remembered per-role tab and the cascade host injection.wwwroot-src/src/components/controlTree/missionItemTypes.ts— the Mission wave: node bookkeeping, the section table and its enable-flag readers and writers, the per-kind editors and display names.wwwroot-src/src/components/controlTree/programItemTypes.ts— the Program wave: the file tree from one response, and the writeback conversion nodes.wwwroot-src/src/components/controlTree/softNcItemTypes.ts— the controller wave: the two planes, the core leaves every runner grows, and the snapshot flags that decide which brand leaves join them.wwwroot-src/src/components/controlTree/toolHouseItemTypes.ts— the Tool House wave and itstool-<id>role paths.wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts— the two runner-suit leaves.wwwroot-src/src/components/controlTree/MissionRootPanel.vue— the Mission root's informational panel: the command count and the run-order hint.wwwroot-src/src/components/controlTree/ProgramRootPanel.vue— the Program root's summary panel and the refresh button that emits a payload-lessstructure-changed.wwwroot-src/src/components/controlTree/GeomCombinationTreePanel.vue— the combination's item list, whose add, remove and clear emit the same payload-less event.wwwroot-src/src/components/controlTree/MissionCommandSlavePanel.vue— a command's editor host: a List's Title field with its debounced save, and the only panel that exposes a dirty-switch gate.wwwroot-src/src/components/mission/ScriptCommandPanel.vue— the autosaving editor whose gate is forwarded, with its save / discard / cancel prompt.wwwroot-src/src/composables/useAutoSave.ts— 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.wwwroot-src/src/components/mission/NcCodeCommandPanel.vue— the second forwarded gate: the debounced field save it drains, and the unmount that cancels instead of flushing.wwwroot-src/src/components/mission/NcFileCommandPanel.vue— the one command editor that emitsstructure-changed, with theinsertedrange of its multi-pick.wwwroot-src/src/components/panels/PanelExpansion.vue— the expansion row both dock rows are built from.wwwroot-src/src/composables/useViewPrefs.ts— the device-local expansion list, last selections, dock sizes and the two runner-suit checkboxes.wwwroot-src/src/composables/useCleanupHub.ts— the key registration the builders call.wwwroot-src/src/router/treeRoutes.ts— the id migration hop and the page a given id belongs to.wwwroot-src/src/pages/ExecutionPage.vue— creates and provides theexecutionhost.wwwroot-src/src/pages/GeneralSetupPage.vue— creates and provides theequipmenthost, and adds the content column.wwwroot-src/src/pages/ToolHousePage.vue— the registry consumer that renders tabs instead of a tree.wwwroot-src/src/layouts/MainLayout.vue— the project epoch that decides when a host is rebuilt.wwwroot-src/src/i18n/en/tree.ts— the dock labels, the group intros and the node role labels.
See Also
- Tree Ids and Routes — how a node id becomes a URL, which page owns a given id, and how an older id keeps resolving
- Session State — the project epoch and the stores a host is rebuilt around
- Execution Page — the first live host: the run cockpit's Mission and Program branches
- General Setup Page — the second live host: the equipment branches and the content column
- Controller Branch — the one branch whose node set is not fixed: it is regrown from a snapshot, so which children exist depends on the controller in the project