Hidden Controller Branches
Two Control-Tree nodes on the General Setup page are absent from a fresh installation: CSV
Controller and CL Controller, on /general-setup under the ids equipment/controller-csv and
equipment/controller-cl. Each is the entire editing face of one resident non-brand runner suit —
the CSV control-table pipeline and the NX-CL (CLSF) pipeline — and each is minted only when its
device-local Preference checkbox is on or the current URL targets it. This page is the answer to
“why is that node not in the tree”, because the equipment tree withholds a node in two different ways
and only one of them can be talked out of it.
Important
Anatomy makes no claim that any id named here is stable across versions. The migration hop that
keeps an older ?tree= value resolving is described in
Tree Ids and Routes.
Two Kinds of Invisibility
| Preference-gated | Brand-gated | |
|---|---|---|
| Which nodes | equipment/controller-csv, equipment/controller-cl |
every gated leaf under equipment/controller |
| What decides | a checkbox stored in the browser | the active runner's dependency list |
| Where the decision lives | one device | the project |
| Reachable by a link anyway | yes, and deliberately | no |
| What a link to it does | mints the node and selects it | nothing at all |
The distinction is not a matter of degree. A preference-gated node exists in the code that builds the tree and is simply not pushed; the same build reads the URL, so naming the node in a link is enough to bring it back for that visit. A brand-gated node has no such second door: the branch builder never creates it, and the selection code refuses an id the built tree does not carry, so there is nothing for a link to select. Which leaf that branch grows on which brand, and the snapshot flag behind each one, is Brand Matrix.
Preference-Gated: The Two Runner-Suit Nodes
The equipment tree host mints each of the two nodes when a project is open and either the node's checkbox is on or the route's tree id begins with that node's id. All three parts matter.
With no project open neither node is built, whatever the checkboxes say — the same guard that
keeps the Controller branch childless before a project is loaded. The consequence is not a dead end:
loading a project bumps the layout's project epoch, which destroys and rebuilds the page, and the
rebuilt host runs the build again and then adopts whatever ?tree= the URL still carries. A link
opened against an empty application therefore lands on its node once a project is opened, without
the link being touched.
The checkboxes are device-local. Both are held in one browser-storage record of view preferences, off by default, written back a moment after each flip, and never sent to the server — so the setting belongs to a browser rather than to a user account or to a project. They are the CSV Controller and CL Controller entries of the app menu bar's Preference dropdown, described in Preference Menu.
Only the route reveals — not the persisted last selection. The host also remembers the last node
selected on each tree page, and that memory deliberately mints nothing: the landing selection is
honoured only when the id it names is already in the built tree, and falls back to the equipment
group root when it is not. Reveal-by-route has to happen while the tree is being built, because the
code that adopts a ?tree= id afterwards refuses an id the tree lacks.
Switching a box off while standing on its node
The host watches both checkboxes and rebuilds the whole equipment tree when either flips. Before the rebuild it checks whether the current selection is one of the nodes that just lost its box, and when it is, it does two things in this order:
- Replaces the URL's
treequery withequipment. - Moves the selection to the
equipmentgroup root.
Only then does the tree rebuild. The order is the point: the URL already mirrors the selection, and the URL is exactly what reveals an unchecked node, so a selection left standing there would leave its id in the query, and the next build's prefix test would re-mint the node that was just switched off. The reader lands on the General Setup group panel, whose clickable child list no longer carries the row. The selection is moved directly rather than through the host's switch request, which would matter only for a panel registering the before-switch gate; neither of these two does.
Switching a box off while the selection is elsewhere takes no such detour — the rebuild simply drops the node from the tree and from the group panel's child list.
What the two nodes are not
Both are childless leaves. Neither declares a child builder, so nothing grows beneath them; neither registers a content view, so the General Setup content column keeps its own empty hint for both; and neither panel reports a structural change, so nothing either panel does rebuilds any part of the tree. There is no Object-Management menu on either: a CSV or CL pipeline is not loaded, pasted or saved as a file of its own the way a brand controller is.
Brand-Gated: The Node That Was Never Minted
Under equipment/controller the node set is grown from a snapshot of the active NC runner, and a
leaf whose backing dependency that runner does not resolve is never created. Nothing reveals it. A
link naming such an id — equipment/controller/program-data/frames on a project running a Fanuc
runner, say — reaches the host, finds no such node in the built tree, and is dropped: the selection
is left as it was, the URL keeps the id it was given, and no redirect and no message follow. On a
first load that means the editor row shows its own hint, "Select an item in the Control Tree to edit
it here.", under a tree in which the node the link names is simply not present.
The two behaviours meet at the same line of code from opposite sides. The runner-suit nodes are read out of the URL while the tree is built, so by the time that check runs they exist; a brand-gated leaf is not, so the check is the end of the road for it. That is why a checkbox can be overruled by a link and a runner cannot.
What the Preference Captions Mean
Under each checkbox sits a caption reading either “This project plays CSV” / “This project plays CL” or “Not used by this project”. It is the informed-choice half of the design: the menu answers whether the loaded project plays that kind, so the decision to show a node is made with evidence rather than by trial. The caption is read when the Preference dropdown opens rather than on page load, and both runner snapshots are fetched together; with no project open both captions are blank, and a snapshot that fails to load leaves its own caption blank rather than asserting either answer.
The evidence is a scan of the loaded project, per kind:
- A mission Program File command whose effective kind is that kind — its explicit kind, or, for
the default Auto, the kind detected from the file extension:
.cl,.clsand.clsfare CL,.csvis CSV, and every other extension is brand NC code (DetectByPath(API)). - A mission script command whose text contains
CsvFile(orClFile(. It is a bare substring test rather than a list of verbs. The play verbs end in it — PlayCsvFile(API) and PlayClFile(API) — and on the CSV side so do the two sensor-mapping verbs, MapSingleByCsvFile(API) and MapSeriesByCsvFile(API), so a script that only maps recorded telemetry onto steps counts as CSV evidence though it plays no control table. The CL side carries no such neighbour: every member whose call text ends inClFile(plays or runs a CLSF file. - For CL only, a machining chain that is a ClMillingDevice, since a pure-CL project plays nothing else.
Three properties of that scan are easy to guess wrong. It walks the whole mission, the entries of a
nested list command included, and it walks through each command's enable wrapper without reading
it — a Program File command that is switched off still counts as evidence. The script test cuts the
other way too: a play routed through the generic NC entry point with a .csv path is deliberately
not matched, because a bare .csv appears in step and shot output templates far more often than in
play paths. And the answer feeds the caption and nothing else — usage evidence mints no node.
The tree is built from the checkbox and the route, and from nothing the scan reports.
How Both Panels Edit
Neither node belongs to the Controller branch, so that branch's Editing Contract does not govern them. Both panels nonetheless follow most of its shape, and the places they do not are worth naming.
Shared with the contract. Each panel fetches its own snapshot once on mount and owns no store. Each opens with two guards, in order: "No project loaded." from the snapshot's own project flag, then a line naming the absent config — "No CSV column config on the CSV runner." or "No CLSF config on the CL runner." Every control commits on its own, with no save button and no dirty marker. A failed write raises one negative toast, three seconds, composed as the panel's localized context followed by the server's own English sentence, with a console line beside it, and leaves no inline error state behind once it expires. Both REST surfaces answer a missing config inside a success envelope rather than with an error status, and the shared write helper turns that envelope into a thrown error; the read helper inspects no envelope, and neither endpoint has a non-200 path, so a read fails only on transport. A failed read therefore leaves the empty snapshot in place and the panel renders its "No project loaded." line — the same body an absent project produces, with the toast as the only signal that the two differ.
Where they part from it. The CSV panel is not optimistic: its text fields are bound to a local draft, so the screen already shows the new text, and the request is awaited before the panel's server mirror is updated; a failure restores the field from that mirror. The CL panel is optimistic in the contract's own shape, assigning first and restoring on failure, and its two numeric fields are the shared numeric widget, committing on blur or on Enter and never per keystroke; the full widget contract is Numeric Input.
Both panels guard on equality, and the guard is the panel's rather than the widget's. Every commit handler on both — the CSV tag fields, the two CL rates and the tool-house toggle — compares the incoming value against the fetched server mirror first and returns when the two match, so committing an untouched field, or blurring after an Enter that already succeeded, sends nothing. That is the departure from the Controller branch, whose numeric leaves carry no such comparison and lean on the shared widget alone, and the widget emits on every blur and every Enter. The one control across the two panels with no such comparison is the CL chip field.
CSV Controller
The node is the face of the project's resident CSV runner suit (CsvRunnerSuit), a whole runner suit constructed alongside the brand one and never absent from a loaded project. Its runner is a SoftNcRunner assembled as the CSV pipeline by the factory GeneralCsvRunner, and it replays a CSV control table. A project file that stores the column configuration flat, outside any suit, has that element migrated into the suit's pipeline as it loads, so such a file keeps its tags.
The whole face is one flat configuration, CsvRunnerConfig — no brand
presets, no per-case tables and no native parameter form, which is why the node is a leaf rather than
a branch. The panel's caption says what it edits: "Column tags of the CSV control table (matched
against the header line). Played by a mission Program File of CSV kind or a script's
PlayCsvFile("…")." The header line is the file's first row, split on commas; each title then has
quote characters stripped from its two ends and is trimmed of whitespace, in that order, so a title
whose opening quote sits behind a space keeps that quote and never matches its tag.
| Field label | Default tag | What reads it |
|---|---|---|
| Machine coordinate prefix | MC. |
the machine-coordinate columns, composed as the prefix plus X, Y, Z, A, B, C |
| Cutter location prefix | CL. |
nothing in the shipped pipeline |
| Tool id column | ToolId |
the tool-change section |
| Spindle speed column (rpm) | SpindleSpeed_rpm |
the spindle section |
| Spindle direction column | Spd.Dir. |
the spindle section, parsed as a direction name; a speed with no direction turns clockwise |
| Feedrate column (mm/min) | Feedrate_mmdmin |
the feedrate section |
| Step duration column (s) | StepDuration |
the recorded-timing section |
| Actual time column | ActualTime |
the recorded-timing section |
| Coolant column | Coolant |
the coolant section |
| Line-begin C# script column | LineBeginCsScript |
the script section, run before the row |
| Line-end C# script column | LineEndCsScript |
the script section, run after the row |
Four things that table does not show on its own:
- The cutter-location prefix is stored, serialized and editable, and no syntax in the shipped CSV pipeline reads it. Columns under it are not consumed as coordinates; they survive into the residual telemetry below.
- Naming a column consumes it. Each syntax removes the columns its tags name from the decoded row, and whatever is left over is carried onto the step as recorded data — sensor channels, file and line bookkeeping, anything else the file holds. So clearing a tag does more than stop the value being used: it moves that column into the residual set.
- The duration cell is read as a time span rather than as a number of seconds, despite the
field's own
(s). A cell that does not parse falls back to the difference between this row's actual time and the previous parseable one, and a resolved duration longer than one minute is clamped to one minute, on the reasoning that a longer gap is spliced recordings rather than machining time. - An empty field is a real edit. The write applies every field the payload carries, and the panel sends exactly the field that changed, so blanking a box clears that tag on the model rather than leaving it alone.
Each field commits on blur or on Enter — never while typing — and sends only itself. There is no validation on either side of the wire: no trimming, no bounds, no uniqueness.
CL Controller
The node is the face of the project's resident NX-CL suit
(ClsfRunnerSuit), constructed the same way beside the brand
suit and the CSV suit, its runner a SoftNcRunner assembled as the CLSF pipeline
by the factory NxClRunner. Its caption reads "Plays NX
cutter-location files (CLSF) — a mission Program File of CL kind or a script's PlayClFile("…")."
Everything on the panel is one ClsfRunnerConfig, and every field on it answers something a cutter-location file cannot say for itself.
Rapid feedrate (assumed), suffixed mm/min, and Rotary rapid feedrate (assumed), suffixed
deg/min, default to 20000 and 36000. A CLSF carries no machine axes, so a RAPID move has nothing
to be timed against — but the two rates are not read on the same path, and the machining chain
decides which. On a pure-CL chain the CLSF pipeline's own motion semantic reads the linear rate
straight off this configuration and times every rapid from it; the rotary rate is never consulted.
On a machine-tool chain, where each CLSF block is re-expressed in machine coordinates and routed to
the motion semantics reused from the NC pipeline, the configuration doubles as the pipeline's
rapid-feedrate provider and answers axis-uniformly — every linear axis gets the first rate, every
rotary axis the second — and that is the only place the rotary rate is read.
Both fields carry a minimum of 1: a smaller value is refused inside the widget itself, which shows Must be ≥ 1 under the box and emits nothing, so no request is made and no toast appears. The server's own positivity check sits behind that and is not reachable from this panel. A cleared or non-finite entry is dropped by the panel before the request, which leaves the box showing what was typed while the stored rate stands; selecting another node and returning restores the box.
Prefer Tool House on LOAD/TOOL is a toggle, on by default, committing on the click. Its caption sits below it permanently rather than as a field hint: "On: a LOAD/TOOL id already configured in the Tool House keeps that tool (CLSF TLDATA geometry ignored). Off: TLDATA overwrites the tool-house entry on every load." That is what the code does — a tool change requesting an id the tool house already holds is left alone while the toggle is on, and the file's own tool geometry rebuilds the entry when it is off. Either way an id the house does not hold is built from the file's tool data, and an id with neither is reported as a configuration error.
Excluded record words is a chip field: a word typed and entered joins the list, and there is no
dropdown, because the option list is deliberately empty. Its preset list is PAINT, TOOLNO,
LOADTL, TOOL PATH, TOOLPATH and END-OF-PATH. A CLSF record that reaches the end of the
pipeline unhandled is otherwise reported as a validation warning naming the record word; a word on
this list is consumed silently instead, matched whole and without regard to case. Adding or removing
one chip writes the whole list, the server trims each word and drops the blank ones before replacing
the stored list, and the panel keeps what was typed — so a word entered with stray whitespace reads
differently on screen from what was stored, until the panel is remounted.
Layout
- App Menu Bar
- Preference ▾ Dropdown — fetches both runner snapshots as it opens, and only with a project
open
- CSV Controller CheckBox
- Usage Caption — “This project plays CSV”, “Not used by this project”, or blank
- CL Controller CheckBox
- Usage Caption — the same three states, for CL
- CSV Controller CheckBox
- Preference ▾ Dropdown — fetches both runner snapshots as it opens, and only with a project
open
- General Setup Control Tree — the left dock of
/general-setup- General Setup Group Row —
equipment- Controller Node Row — the brand branch, built ahead of the two rows below
- CSV Controller Node Row —
equipment/controller-csv; present only under the conditions above, and pushed before the CL row when both are - CL Controller Node Row —
equipment/controller-cl; the last row the group can carry. Each is a plain label with no icon, no checkbox and no children
- General Setup Group Row —
- Editor Row — the panel of whichever node is selected
- General Setup Group Panel — where a switched-off node's selection lands: the group's intro caption over a bordered, separated list of its children, one clickable row each
- CSV Controller Panel
- No-Project Body — "No project loaded."
- No-Config Body — "No CSV column config on the CSV runner."
- Editor Body
- Intro Caption — the column-tags line, with
PlayCsvFile("…")set in code - Tag Text Field, one per row, in this order: Machine coordinate prefix (hint e.g. “MC.” → MC.X / MC.Y / MC.Z), Cutter location prefix (hint e.g. “CL.” → CL.X / CL.Y / CL.Z), Tool id column, Spindle speed column (rpm), Spindle direction column, Feedrate column (mm/min), Step duration column (s) (hint Overrides feedrate-derived timing.), Actual time column (hint Wall-clock instant of the row.), Coolant column (hint Flood / Mist / Off or on/off.), Line-begin C# script column (hint Runs before the row.), Line-end C# script column (hint Runs after the row.)
- Intro Caption — the column-tags line, with
- CL Controller Panel
- No-Project Body — "No project loaded."
- No-Config Body — "No CLSF config on the CL runner."
- Editor Body
- Intro Caption — the plays-CLSF line, with
PlayClFile("…")set in code - Rapid feedrate (assumed) Numeric Field — suffixed
mm/min, minimum 1 - Rotary rapid feedrate (assumed) Numeric Field — suffixed
deg/min, minimum 1 - Prefer Tool House on LOAD/TOOL Toggle
- Policy Caption — the On: … Off: … explanation, always shown
- Excluded record words Chip Field — free-text entry with no dropdown list, hint Record words consumed silently as intentional skips (case-insensitive). Type + Enter to add.
- Intro Caption — the plays-CLSF line, with
- Toast — negative, three seconds, the panel's context followed by the server's own message
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 build rule that mints the two nodes and where in the equipment group they sit, the checkbox watch that steps the selection off a node losing its box before the rebuild, and the route-adoption guard that refuses an id the built tree lacks.wwwroot-src/src/components/controlTree/runnerSuitItemTypes.ts— the two item types and the panels they bind to; its own docblock names the usage evidence and the persisted selection as reveal conditions, which the host's build rule does not.wwwroot-src/src/components/controlTree/CsvRunnerPanel.vue— the column-tag form: the two guards, the field list with its labels and hints, the blur-and-Enter commit with its equality guard against the fetched mirror, and the single-field payload.wwwroot-src/src/components/controlTree/ClRunnerPanel.vue— the CLSF form: the two rapid rates, the cleared and non-finite values it drops before the request, the equality guard on each rate and on the tool-house toggle, and the unguarded whole-list chip write.wwwroot-src/src/composables/useViewPrefs.ts— the device-local browser-storage record behind both checkboxes: their default, their persistence, and the record they share with the tree's expansion and last-selection memory.wwwroot-src/src/components/AppMenuBar.vue— the Preference dropdown's two checkboxes, and the caption refresh that fetches both snapshots as the menu opens.wwwroot-src/src/api/csvRunner.tsandwwwroot-src/src/api/clRunner.ts— the two snapshot shapes, their pre-fetch empty values, and the typed wrappers over each REST surface.wwwroot-src/src/api/http.ts— the plain-JSON read that inspects no envelope, beside the envelope write that throws asuccess: falsemessage.wwwroot-src/src/components/widgets/NumericInput.vue— the field behind the two rapid rates: commit on blur or Enter, and the minimum that reports inline without emitting.wwwroot-src/src/components/controlTree/PrimarySlavePanel.vue— the editor row: the remount key that refetches when the selection moves, and the select-an-item hint a refused deep link leaves behind.wwwroot-src/src/components/controlTree/GroupInfoPanel.vue— the General Setup group's editor, whose clickable child list gains and loses these two rows with the tree.wwwroot-src/src/components/controlTree/itemTypes.ts— the registry the two item types are spread into, and the panel lookup the editor row resolves through.wwwroot-src/src/router/treeRoutes.ts— the id migration applied to a?tree=value before the reveal test reads it, and the page roots a foreign id is redirected by.wwwroot-src/src/pages/GeneralSetupPage.vue— the route that creates the equipment-scoped tree host and calls its initialization.wwwroot-src/src/layouts/MainLayout.vue— the project epoch that rebuilds the page when a project loads, which is what lets a link opened against an empty application land afterwards.wwwroot-src/src/i18n/en/tree.ts— the two node labels, both panels' intros, empty lines, field labels, hints and error contexts.wwwroot-src/src/i18n/en/menu.ts— the two checkbox labels and the two usage-caption strings.wwwroot-src/src/i18n/en/common.ts— the shared “No project loaded.” line both panels open with.Mech/CsvRunnerController.cs— the CSV suit's REST surface: the snapshot, the write that applies only the fields the payload carries, and the success envelope a missing config is reported in.Mech/ClRunnerController.cs— the CL suit's REST surface: the same envelope, the positivity checks on the two rates, and the trim-and-replace of the excluded-word list.Mech/NcSuitUsage.cs— the usage scan behind the two captions: the mission walk that reads through a command's enable wrapper, the two script substrings, the extension detection for an Auto Program File, and the CL-only chain test.
HiAPI Engine
HiNc/MachiningProcs/MachiningProject.cs— the two resident suits as property-initialized members, their load paths with the fall back to a fresh pipeline, and the migration of the older flat CSV column element into the suit.HiNc/MachiningProcs/LocalProjectService.cs— the re-bind that wires both suits to the host alongside the brand suit, and the chain walk that clears a generic axis configuration before stamping the chain's axes onto it.HiNc/MachiningProcs/SessionShell.cs— the script verbs whose call text the caption's evidence scan matches: the two play verbs, and the two CSV sensor-mapping verbs that end in the same substring.HiNc/SessionCommands/NcFileCommand.cs— the Program File command whose kind the scan reads, and its Auto default.HiNc/SessionCommands/EnablingWrapper.cs— the enable wrapper the scan walks through without reading, which is why a switched-off command still counts as evidence.HiMech/MachiningProcs/NcKind.cs— the kind enum and the closed extension sets behind the Auto detection.HiMech/Numerical/CsvParsers/GeneralCsvRunner.cs— the factory that assembles the suit's runner as the CSV pipeline, and the place its column configuration sits in that pipeline.HiMech/Numerical/CsvParsers/CsvRunnerConfig.cs— the eleven tags, their serialization, and the constants they default to.HiMech/Mapping/MappingUtil.cs— those constants.HiMech/Numerical/CsvParsers/CsvSegmenter.cs— the header row the tags are matched against, and the quote-then-whitespace trim applied to each title.HiMech/Numerical/CsvParsers/RowToSyntaxs/RowToMachineCoordinateSyntax.cs— the six axis names the machine-coordinate prefix is composed with, and the consumption of those columns whether or not they resolved.HiMech/Numerical/CsvParsers/CsvSemantics/CsvTimingUtil.cs— the duration parse, the actual-time fallback and the one-minute clamp.HiMech/Numerical/CsvParsers/CsvSemantics/CsvActDataSemantic.cs— the residual columns carried onto the step, which is where a column no tag names ends up.HiMech/Numerical/ClsfParsers/NxClRunner.cs— the factory that assembles the CLSF pipeline, and the configuration's place in it.HiMech/Numerical/ClsfParsers/ClsfRunnerConfig.cs— the four values, their defaults, and the axis-uniform rapid-rate provider the machine-coordinate motion semantics read.HiMech/Numerical/ClsfParsers/ClMotionValveSemantic.cs— the router that sends a block carrying machine coordinates to the semantics reused from the NC pipeline and every other block to the pure-CL one, which is what decides whether the rotary rate is read at all.HiMech/Numerical/ClsfParsers/ClMotionSemantic.cs— the pure-CL motion semantic, timing a rapid from the linear rate taken directly off the configuration.HiMech/Numerical/ClsfParsers/ClsfRecordCleanupSyntax.cs— the excluded-word match, and the validation warning it replaces with a silent consume.HiMech/Numerical/ClsfParsers/ClsfToolBuildSemantic.cs— the tool-house policy: the entry that wins, the file's tool data that builds a missing one, and the error when neither answers.HiMech/Milling/ClMillingDevice.cs— the chain type that is CL usage evidence on its own.
See Also
- General Setup Page — the page whose tree these two nodes are minted into, and the equipment items they sit after
- Preference Menu Dropdown — the dropdown holding the two checkboxes and their usage captions, and the other per-user settings beside them
- Brand Matrix — the other kind of missing node: which leaf the brand branch grows on which brand, and the snapshot flag behind each one