Tree Ids and Routes
The web client addresses itself with two things: a vue-router path, and — on the Execution and
Equipment pages — a tree query argument naming the selected tree node, as in
/equipment?tree=equipment/mechanism/workpiece/raw-geometry. The third Control-Tree page, Tool
House, spells its selection in the path instead. This page is the reference for both surfaces: the
route table, the redirect-only paths that keep older bookmarks resolving, the id-migration chain,
and the resolver that decides which page owns a given tree id. The router runs in HTML5 history
mode, so every one of these paths is a real URL the server has to answer.
Important
Nothing here promises that a Control-Tree id is stable. The guarantee is narrower and exact: an
id keeps resolving, because every regroup adds a migration hop to migrateLegacyTreeId
rather than rewriting the previous one. The ids themselves have moved repeatedly, and a page that
quotes one is quoting a value that can move again.
The Route Table
/ is the shell-layout record itself; every route below it except the login and catch-all records
is one of its children, so each renders inside the Main Panel's frame and menu bar.
| Path | Route name | What it owns |
|---|---|---|
/ |
— | The shell layout. It carries no page of its own; its empty child redirects to the Execution route. |
/execution |
execution |
The run cockpit and the Execution Control Tree, whose selection rides ?tree=execution/…. |
/equipment |
equipment |
The equipment Control Tree, whose selection rides ?tree=equipment/…. |
/tool-house/:toolId(\d+)?/:node(.*)* |
tool-house |
The tool list and the picked tool's Control Tree. The optional tool number and the node catch-all after it spell the selected node's tree id. |
/preference/log |
preference-log |
The Log Viewer, reached from the menu bar's Help dropdown, below its separator. It is not under /util/. |
/util/file-explorer/:location(.*)* |
util-file-explorer |
The File Explorer. Its catch-all mirrors the browsed location — root display name followed by the relative segments — into the path. |
/login |
login |
The sign-in form. It sits outside the shell layout and carries its own Quasar layout. |
/:catchAll(.*)* |
— | The 404 page, likewise outside the shell layout: it prints the unmatched full path and offers one button back to the Execution route. |
The menu bar's page switches name four of these, in setup order: Tool House, Equipment and
Execution, then File Explorer. Each switch is keyed by route name, so any
URL under that route name — a ?tree= selection, a Tool House node path, an explorer deep path, a
redirect once it lands — lights it. The Log Viewer has an entry in the Help dropdown rather than a switch.
The Tool House route's tool segment is constrained to digits and optional, and the node catch-all
after it absorbs any number of segments, so every path under /tool-house/ matches the record and
none reaches the catch-all. A non-numeric segment in the tool position is not rejected: the router
leaves the tool parameter unset and reads that segment as the first node segment, which is what
makes /tool-house/cutter a working deep link to the Cutter root of the tool already shown — the
house's first tool on a fresh load — after which the URL gains that tool's number. A node path the
tool does not grow is not an error either: the page clamps it to the deepest node it does grow, and
/tool-house/abc/def/ghi, which names no root at all, lands on the first root, as
The Tool House Translation sets out.
Redirect-Only Paths
These records carry no component. They exist so that a link minted before the corresponding editor
moved into a Control Tree, or before its page was renamed, still lands on the editor. The exception
is the three links onto the Spindle Capability and Environment roots, which the Equipment tree
builds only while Show Physics Options is on: with it off, the /spindle-capability,
/equipment/spindle and /equipment/background-coolant targets name no built node, and the page
ignores them as it ignores any unknown id (step 4 of What applyRouteSelection Does, below).
| Path | Lands on |
|---|---|
/general-setup |
/equipment, with the whole query carried over, so a ?tree= bookmark lands on the same node. It is the path the page answered to under its earlier name, General Setup. |
/controller/:rest(.*)* |
/equipment?tree=equipment/controller — any trailing path is discarded, the retired Controller page's /<tab> segment included, so every form lands on the branch root. |
/spindle-capability/:tab? |
/equipment?tree=equipment/spindle, with /<tab> appended when the segment is one of thermal, gear-shift, dry-run, power, torque. An unrecognised segment is dropped and the branch root is selected. |
/equipment/spindle |
/equipment?tree=equipment/spindle |
/fixture/:rest(.*)* |
/equipment?tree=equipment/mechanism/fixture — any trailing path is discarded. |
/workpiece/:rest(.*)* |
/equipment?tree=equipment/mechanism/workpiece — any trailing path is discarded. |
/machine-tool |
/equipment?tree=equipment/mechanism/machine-tool. The path belonged to a stand-alone load-and-show page; the Machine Tool root carries its Load, identity and XML view. |
/util/mech-builder |
/equipment?tree=equipment/mechanism/machine-tool, the Machine Tool root. The path belonged to the Mechanism Builder, a stand-alone editor for a lone mechanism file; a chain is loaded from a preset on that root, edited in the Topology column and written out with the root's Save As. |
/equipment/background-coolant |
/equipment?tree=equipment/background. That id is the Environment root over the Ambient Temperature and Coolant leaves (equipment/background/environment-temperature, equipment/background/coolant); the redirect lands on the root (the ids and the route keep the legacy background spelling). |
/mission |
/execution?tree=execution/mission |
The five spindle tab names are declared in treeRoutes.ts as SPINDLE_TABS, and imported by
wwwroot-src/src/router/routes.ts — which is the list the redirect tests its :tab? segment
against. The equipment tree does not import that constant: it spells the same five segments as
literals while building the equipment/spindle/<seg> children, so redirect and branch agree by
convention rather than through a shared list — renaming a spindle section is therefore an edit in
two places.
The ?tree= Query
Selection and URL are synced two ways by the Control-Tree host, one instance per tree page. This section is the two query pages' side; the Tool House host runs the same two watchers over its path parameters, as The Tool House Translation sets out.
URL to selection. A watcher on the query drives applyRouteSelection(), and it is registered
immediate, so an id belonging to another page redirects on the first tick rather than after this
page's first tree build. The host also calls the same function once the tree has actually been
built, which is when a deep link can finally be honoured.
Selection to URL. A watcher on the selected id replaces the tree argument, preserving the
rest of the query. It uses router.replace, so browsing the tree does not fill the browser's
history with one entry per node. The comparison that breaks the loop is made against the raw
query rather than the migrated form, which is what makes an older id canonicalise: opening
?tree=controller selects the node and then rewrites the URL to ?tree=equipment/controller.
Neither watcher touches the URL unless the current route name is the page's own. The shell layout keeps every visited page alive, so a host whose page is not showing keeps receiving route changes, and without the guard it would write another page's URL. The selection watcher does one thing before that guard: it records the id as this page's last selection. That ordering is load-bearing, because a selection can move while its page is off screen — unticking a CSV or CL Controller checkbox steps the equipment host off that node onto the Controller root, and the Preference menu that carries those checkboxes is open on every page — and the landing selection has to have followed it.
That same checkbox flip is a third writer of the tree argument. The equipment host watches both
checkboxes, and when the standing selection is a node the flip removes, it replaces tree with
equipment/controller — the rest of the query preserved — before stepping the selection off. It
applies the same route-name guard: while another page is showing, only the selection moves, and the
URL is left alone.
What applyRouteSelection Does
- The change is ignored unless this page owns the current route.
- The raw query is migrated into today's id. If its first segment names a different page and that segment is one of the known roots, the location is replaced with the route that owns it and the pass stops.
- An empty query falls back to the landing selection: the persisted last id for this page when that id exists in the tree just built, otherwise the page's first root — Mechanism on the Equipment page, Mission on the Execution page — which is always present once the tree is built, so the editor column always has something to show. The one exception is the Execution page while its Program branch's live follow is on: follow owns the selection then, so returning to the page with no query leaves the current selection standing.
- An id the built tree does not contain is ignored: whatever selection stands stays (on a fresh load there is none yet, so the editor column shows its select-an-item hint), and the URL keeps the unrecognised value until the next selection overwrites it. This is deliberate: the redirect in step 2 fires only for known roots, because sending an unroutable id back to the same page would loop.
- Otherwise every row above the node on its built path is added to the expanded set so the node is visible, and the selection is requested through the same gate a tree click uses. A panel holding unsaved edits may refuse the switch, and a refusal re-points the URL at the selection that actually stands.
On the Tool House page the pass leaves this list after step 2, and adopts the path against the tree of the tool it names instead.
The Id the URL Names, Before the Tree Exists
treePathOfRoute() — the raw query put through migrateLegacyTreeId — is not read only by the
selection logic. The equipment tree consults it while it is being built, to decide whether to
materialise the CSV Controller and CL Controller nodes: each is normally shown only when its
Preference checkbox is ticked, and a URL naming one reveals it regardless. That reveal has to
happen at build time precisely because step 4 above refuses an id the built tree lacks, so a
bookmark to a switched-off node would otherwise dead-end. Only the URL reveals a node this way; the
persisted last selection does not, so a node the user has just unticked while standing on it does
not resurrect itself.
Id Migration
migrateLegacyTreeId is one pass of ordered guards over the whole id; the first that matches
returns, and an id matching none is returned unchanged.
| An id shaped like | Becomes |
|---|---|
controller, controller/… |
equipment/controller, equipment/controller/… |
spindle, spindle/… |
equipment/spindle, equipment/spindle/… |
mission, mission/… |
execution/mission, execution/mission/… |
equipment/mission, equipment/mission/… |
execution/mission, execution/mission/… |
execution |
execution/mission — the bare id named a wrapper root over the Execution page's branches; it lands on Mission, the page's first root |
equipment |
equipment/mechanism — the bare id named a wrapper root over the Equipment page's items; it lands on Mechanism, the page's first root |
equipment/machine-tool, equipment/fixture, equipment/workpiece, and anything below them |
the same id under equipment/mechanism/; child segments ride along |
equipment/mechanism/fixture/geometry, …/fixture/geom-to-workpiece, …/fixture/geom-to-table, …/fixture/anchor, equipment/mechanism/workpiece/anchor, and anything below them |
the mechanism root that stands for the retired item — equipment/mechanism/fixture or equipment/mechanism/workpiece. Those ids named the fixture's and the workpiece's slot and anchor items; the anchors, branches, transformers and solids behind them are edited in the Equipment page's Topology column, which no ?tree= id names, so the link lands on the root whose panel binds the roles those anchors play |
equipment/coolant |
equipment/background/coolant |
any id ending in /contours/tray, or containing /contours/tray/ |
the same id with that segment spelled fluting |
A pre-regroup equipment/background named the background-temperature leaf and now names the
Environment group; no rule re-points it, because the group lists that leaf and re-pointing the id
would leave the group itself unreachable by URL.
The last rule renames one segment in place and leaves its children — baseline, flute-<i>,
side, bottom — untouched, so a deep link into a flute contour survives the engine's Fluting
type naming. The two mission rules cover the two id shapes separately rather than chaining, because
the first matching guard returns. The mechanism regroup rule is the one exception: it rewrites the
id in place and falls through instead of returning, so the retired-item rule after it also catches
an id minted under the wrapper root, such as equipment/fixture/geometry, and lands it on the same
mechanism root.
The function is applied at four points, which is what makes the guarantee hold in practice:
- to the raw
?tree=query, on every read; - to the persisted last selection, before it is looked up in the tree;
- to the persisted list of expanded node ids, once when a host is constructed, so an older
expansion set still unfolds the branches it names — there a bare
equipmentexpands bothequipment/mechanismandequipment/backgroundinstead of taking the one-id rule above; - as the first step of
routeForTreeId, so the id-to-page resolver never sees an unmigrated id.
Note what the first rule implies: a bare ?tree=controller selects the equipment/controller
branch — the controller editor on the Equipment page — and the redirect-only /controller path
above lands on the same branch, so a bookmark from the retired Controller page's era resolves in
either form.
Landing an Id on Its Page
TREE_PAGE_ROOTS lists the first segments that name a page: execution, equipment and
toolhouse. spindle is absent on purpose — migration folds it into equipment/… before any root
check runs, so no root check ever sees it.
routeForTreeId migrates the id, then dispatches on its first segment:
toolhouse— the Tool House route, with the id's segments translated into path params (below).equipment— the Equipment route, carrying the migrated id as?tree=.- anything else — the Execution route, carrying the migrated id as
?tree=. This is a fall-through, not a test forexecution, so an unrecognised id handed to this function lands on the Execution page. Reaching it with a genuinely unroutable id is prevented upstream: the host redirects through it only for ids whose root is inTREE_PAGE_ROOTS, and its other callers pass ids of built nodes. Those callers are the Tool House host spelling its own selection, and the Control Tree's row links, which resolve each row's id through it so that a modified or middle click, or the browser's link menu, opens the row in a new browser tab or window at its page's URL, while a plain click only selects it.
The returned location replaces the whole current location. Only the tree argument survives the
hop; any other query argument on the URL being redirected away from is dropped, and the Tool House
form carries no query at all.
The Tool House Translation
The Tool House page keeps its selection in the path rather than a query: the tool number, then the
selected node's id with its toolhouse/tool-<n> prefix removed, so
/tool-house/3/cutter/contours/fluting/flute-0/side names the node
toolhouse/tool-3/cutter/contours/fluting/flute-0/side. toolNodePathOf() takes the node half
off an id, everything after toolhouse/tool-<n>, and toolTreeId() joins a tool number and a node
path back into an id. routeForTreeId makes the same split:
segment two contributes toolId when it is tool- followed by an integer, and every segment after
it rides the node catch-all. The translation is lossless in both directions — whatever depth the
tree grows, a single flute contour included, arrives whole. An id whose second segment is not
tool-<n>, a bare toolhouse among them, carries neither parameter and lands on a bare
/tool-house.
No valid-name list guards either half; the tree is the authority. The host watches the id the two
parameters spell, and once the page has read the tool list it walks the node path down the built
tree of the tool the URL names and selects the deepest node it reaches. A third flute on a
two-flute cutter lands on the fluting node above it; a path whose first segment is no root of this
tool — Int. Holder while Show Physics Options is off, or /tool-house/abc/def/ghi — lands on the
first root, General. A :toolId the house does not hold falls back to the house's first tool.
Either way the URL is then rewritten to the node actually selected, with router.replace, as every
URL write the page makes itself is, the tool list's links included.
Because the node half carries no tool number, a row of the tool list links to the current node path
under its own :toolId, so switching tools keeps the editor on the same node. The host also
remembers the node path last asked for, in desiredNodePath, seeded from the persisted last
selection. A clipped selection leaves that memory alone, and while the URL's path is a prefix of it
the list rows link to the remembered path, so the deeper node comes back on the next tool that
grows it. The URL is the authority over that memory: only a URL that names neither tool nor node
lands on it — a fresh load of a bare /tool-house on the house's first tool, and a re-root while
the URL names no tool (a Show Physics Options flip or a tool-list change, made on the bare page or
from another page while this one is kept alive) on the tool already shown. Any other URL is taken
literally. The menu bar's page switch links to the bare /tool-house, but coming back through it
re-runs nothing: the kept-alive page shows the selection it already had, and the URL stays a bare
/tool-house until the next selection writes it.
The Tool House page never reads or writes ?tree=. A ?tree= link naming a toolhouse/… id
reaches it only by way of the redirect above, issued by the Execution or Equipment host, and the
whole id arrives in the path.
Reading an Id
An id's first segment names the page that owns it, and therefore the folder that documents it:
| Id root | Route | Documented under |
|---|---|---|
execution/… |
/execution |
Execution Page |
equipment/… |
/equipment |
Equipment Page |
toolhouse/… |
/tool-house/… |
Tool House Page |
The remaining segments are the branch path, one segment per level of the tree, and the branch's own
page sits in that folder — equipment/mechanism/workpiece/material/cutting-parameter is the Cutting Parameter
item of the Workpiece branch on the Equipment page. On Tool House the path is led by tool-<n>,
the tool's number, which has no row of its own. Most segments are kebab-case role names;
list-backed children use a positional index instead — bare for Mission entries and Program files,
prefixed for a Program file's written conversions (dst-<i>), a geometry combination's items
(item-<i>) and a free fluting's contours (flute-<i>).
Every segment comes from the node id, never from the label the user reads: the Equipment
page's first root is labelled Mechanism and carries the id equipment/mechanism, whose leading
segment is the page's scope key rather than a node of its own. A label is a display
string — the translation of the node's labelKey where it has one, and otherwise raw data such as
an NC file name or a mission command's user-typed title, as Control Tree
sets out — while an id is neither translated nor taken from data. Two consequences follow. A link
naming an id is locale-independent, and an id can never be read off what is on screen.
Titles
A route's meta.title holds an i18n key, such as routes.execution, not a title. The router
resolves it through the active locale in an afterEach hook and sets the document title to the
translated name followed by - HiNC; a route with no key — the catch-all — leaves the bare
product name. The sibling name: is the route id used by every programmatic navigation and is
never translated; the two must not be confused, which is why the route table above lists them side
by side.
The same resolver is handed to the i18n module through registerRetitle, so that switching
language re-titles a parked tab without a navigation. The registration is indirect because the
router already imports the i18n module for its translation function, and importing back would close
a cycle.
Serving These URLs
History mode means the browser sends the whole path to the server on a refresh or a pasted link, so the back end maps two SPA fallbacks. The bare fallback carries an implicit constraint that rejects any URL whose last segment contains a dot, which is exactly the shape a File Explorer deep link takes, since those mirror real file names. An explicit fallback for the explorer's path is therefore mapped ahead of it with no such constraint. Static assets cannot be shadowed by either, because static-file serving runs earlier in the pipeline. Both fallbacks allow anonymous access, so the SPA itself always loads and the sign-in decision is made in the client.
That decision is a navigation guard: it hydrates the authentication status once, lets every
navigation through when the back end reports the feature disabled, and otherwise sends an
unauthenticated visitor to the login route with the original full path — ?tree= included — parked
in a redirect query. A successful sign-in replays that path as a full page load rather than a
router navigation, so the application re-initialises with the cookie present. A visitor who is
already signed in, or who arrives while the feature is off, is sent straight back out of the login
route to the same parked path.
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
HiNC-2025-webservice (Quasar CLI SPA):
wwwroot-src/src/router/routes.ts— the route table, the redirect-only records, the login route outside the shell layout, and the trailing catch-all.wwwroot-src/src/router/treeRoutes.ts—migrateLegacyTreeId,TREE_PAGE_ROOTS,routeForTreeId,toolNodePathOf, which takes the node half off a Tool House id, andtoolTreeId, which joins a tool number and a node path back into one, andSPINDLE_TABS, which only the spindle redirect reads.wwwroot-src/src/router/index.ts— the history-mode router factory, the authentication guard, and the retitle hook registered with the i18n module.wwwroot-src/src/components/controlTree/useControlTreeHost.ts— the tree-page host:rawTreePathOfRoute,treePathOfRoute,locationForSelection, the two selection/URL watchers and the controller-checkbox watcher beside them,landingSelection,applyRouteSelectionandsyncUrlBack, plus the build-time reveal of a URL-named CSV or CL controller node. In itstoolhousescope:resolveToolId,deepestBuiltId,adoptToolhouseSelection,desiredNodePathandtoolLocation, the list row's link.wwwroot-src/src/pages/ToolHousePage.vue— creates thetoolhouse-scope host and renders the tool list asreplacerouter links totoolLocation: another:toolIdover the current node path, lengthened to the remembered one while the URL's path is a prefix of it.wwwroot-src/src/components/controlTree/ControlTreePanel.vue— renders each row's label as a link to the URLrouteForTreeIdgives its id.wwwroot-src/src/pages/FileExplorerPage.vue— mirrors the browsed location into the route's catch-all param and back.wwwroot-src/src/pages/NotFoundPage.vue— the catch-all component, carrying its own layout.wwwroot-src/src/pages/LoginPage.vue— reads the guard'sredirectargument and replays it.wwwroot-src/src/composables/useViewPrefs.ts— the browser-local store holding the per-page last selected id and the expanded-node list that migration is applied to.wwwroot-src/src/components/AppMenuBar.vue— the page switches, which navigate by route name and light by it.wwwroot-src/src/i18n/en/routes.ts— the English strings themeta.titlekeys resolve to.wwwroot-src/src/i18n/index.ts—registerRetitleand the locale switch that calls it.wwwroot-src/quasar.config.ts— selects history mode and names the build output folder the server serves.Program.cs— the two SPA fallbacks, the explorer-specific one ahead of the bare one.
See Also
- Control Tree — the tree these ids name: how a node id is formed and what a branch is made of
- Main Panel — the shell whose menus navigate to these routes and whose keep-alive cache makes the route-name guards necessary
- Execution Page — the busiest consumer of
?tree=, and the page a fall-through id lands on - Equipment Topology Column — the Equipment page's Topology column, which no
?tree=id names: the retired fixture and workpiece item ids land on their mechanism root instead - Machine Tool — the Machine Tool root the
/machine-tooland/util/mech-builderredirects land on - Tool House Page — the one tree page whose selection rides the path: its tool list,
tree and editor behind the
:toolId/:nodeURL