This repository has been archived on 2026-10-11. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
HiNC-2025-win-desktop-Deploy/App/wwwroot/HiAPI-docsite/anatomy/platform/tree-ids-and-routes.html
T
2026-09-12 18:16:27 +08:00

509 lines
30 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Tree Ids and Routes | HiAPI-C# 2025 </title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="title" content="Tree Ids and Routes | HiAPI-C# 2025 ">
<link rel="icon" href="../../img/HiAPI.favicon.ico">
<link rel="stylesheet" href="../../public/docfx.min.css">
<link rel="stylesheet" href="../../public/main.css">
<meta name="docfx:navrel" content="../../toc.html">
<meta name="docfx:tocrel" content="toc.html">
<meta name="docfx:rel" content="../../">
<meta name="loc:inThisArticle" content="In this article">
<meta name="loc:searchResultsCount" content="{count} results for &quot;{query}&quot;">
<meta name="loc:searchNoResults" content="No results for &quot;{query}&quot;">
<meta name="loc:tocFilter" content="Filter by title">
<meta name="loc:nextArticle" content="Next">
<meta name="loc:prevArticle" content="Previous">
<meta name="loc:themeLight" content="Light">
<meta name="loc:themeDark" content="Dark">
<meta name="loc:themeAuto" content="Auto">
<meta name="loc:changeTheme" content="Change theme">
<meta name="loc:copy" content="Copy">
<meta name="loc:downloadPdf" content="Download PDF">
<script type="module" src="./../../public/docfx.min.js"></script>
<script>
const theme = localStorage.getItem('theme') || 'auto'
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
</script>
</head>
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
<header class="bg-body border-bottom">
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
<div class="container-xxl flex-nowrap">
<a class="navbar-brand" href="../../index.html">
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
</a>
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
<i class="bi bi-three-dots"></i>
</button>
<div class="collapse navbar-collapse" id="navpanel">
<div id="navbar">
<form class="search" role="search" id="search">
<i class="bi bi-search"></i>
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
</form>
</div>
</div>
</div>
</nav>
</header>
<main class="container-xxl">
<div class="toc-offcanvas">
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<nav class="toc" id="toc"></nav>
</div>
</div>
</div>
<div class="content">
<div class="actionbar">
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
<nav id="breadcrumb"></nav>
</div>
<article data-uid="Anatomy-TreeIdsAndRoutes">
<h1 id="tree-ids-and-routes">Tree Ids and Routes</h1>
<p>The web client addresses itself with two things: a vue-router path, and — on the two Control-Tree
pages — a <code>tree</code> query argument naming the selected tree node, as in
<code>/general-setup?tree=equipment/fixture/geometry</code>. 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 <code>?tree=</code> id. The router runs in HTML5 history
mode, so every one of these paths is a real URL the server has to answer.</p>
<div class="IMPORTANT">
<h5>Important</h5>
<p>Nothing here promises that a Control-Tree id is stable. The guarantee is narrower and exact: an
id keeps <strong>resolving</strong>, because every regroup adds a migration hop to <code>migrateLegacyTreeId</code>
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.</p>
</div>
<h2 id="the-route-table">The Route Table</h2>
<p><code>/</code> 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.</p>
<table>
<thead>
<tr>
<th>Path</th>
<th>Route name</th>
<th>What it owns</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/</code></td>
<td>—</td>
<td>The shell layout. It carries no page of its own; its empty child redirects to the Execution route.</td>
</tr>
<tr>
<td><code>/execution</code></td>
<td><code>execution</code></td>
<td>The run cockpit and the Execution Control Tree, whose selection rides <code>?tree=execution/…</code>.</td>
</tr>
<tr>
<td><code>/general-setup</code></td>
<td><code>general-setup</code></td>
<td>The equipment Control Tree, whose selection rides <code>?tree=equipment/…</code>.</td>
</tr>
<tr>
<td><code>/machine-tool</code></td>
<td><code>machine-tool</code></td>
<td>The machining chain on a canvas of its own, beside a left column that shows either a read-only identity panel or the chain's XML source — two states of one column, chosen with a GUI/XML toggle. A load button installs a chain file into the project. Carries no Page-menu entry and is reached by URL.</td>
</tr>
<tr>
<td><code>/tool-house/:toolId(\d+)?/:tab?/:subtab?</code></td>
<td><code>tool-house</code></td>
<td>The tool library and the per-tool editor. The three optional segments carry the selected tool and the two nested tab levels.</td>
</tr>
<tr>
<td><code>/preference/log</code></td>
<td><code>preference-log</code></td>
<td>The Log Viewer, reached from the menu bar's <code>Show Log</code> button. It is not under <code>/util/</code>.</td>
</tr>
<tr>
<td><code>/util/file-explorer/:location(.*)*</code></td>
<td><code>util-file-explorer</code></td>
<td>The File Explorer. Its catch-all mirrors the browsed location — root display name followed by the relative segments — into the path.</td>
</tr>
<tr>
<td><code>/util/mech-builder</code></td>
<td><code>util-mech-builder</code></td>
<td>The Mechanism Builder.</td>
</tr>
<tr>
<td><code>/login</code></td>
<td><code>login</code></td>
<td>The sign-in form. It sits outside the shell layout and carries its own Quasar layout.</td>
</tr>
<tr>
<td><code>/:catchAll(.*)*</code></td>
<td>—</td>
<td>The 404 page, likewise outside the shell layout: it prints the unmatched full path and offers one button back to the Execution route.</td>
</tr>
</tbody>
</table>
<p>The menu bar's Page dropdown holds every one of these that is menu-reachable, in setup order: Tool
House, General Setup and Execution, then File Explorer and Mechanism Builder below a separator. The
Log Viewer has a button of its own rather than a menu entry, and <code>/machine-tool</code> has neither — it is
reached by URL.</p>
<p>The Tool House route's tool segment is constrained to digits, but all three of its segments are
optional, so a non-numeric segment in the tool position is not rejected: the router skips the tool
parameter and reads that segment as the tab, which is what makes <code>/tool-house/cutter</code> a working tab
deep link with no tool named. The consequence is that the record absorbs three segments when the
first is a number and only two when it is not, and a URL carrying more than the record can absorb —
<code>/tool-house/abc/def/ghi</code> — falls through to the catch-all. An unrecognised tab name is not an error
either: the page canonicalises it to that level's default, as
<a href="#the-tool-house-translation">The Tool House Translation</a> sets out.</p>
<h2 id="redirect-only-paths">Redirect-Only Paths</h2>
<p>These records carry no component. They exist so that a link minted before the corresponding editor
moved into a Control Tree still lands on the editor.</p>
<table>
<thead>
<tr>
<th>Path</th>
<th>Lands on</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/controller/:rest(.*)*</code></td>
<td><code>/general-setup?tree=equipment/controller</code> — any trailing path is discarded, the retired Controller page's <code>/&lt;tab&gt;</code> segment included, so every form lands on the branch root.</td>
</tr>
<tr>
<td><code>/spindle-capability/:tab?</code></td>
<td><code>/general-setup?tree=equipment/spindle</code>, with <code>/&lt;tab&gt;</code> appended when the segment is one of <code>thermal</code>, <code>gear-shift</code>, <code>dry-run</code>, <code>power</code>, <code>torque</code>. An unrecognised segment is dropped and the branch root is selected.</td>
</tr>
<tr>
<td><code>/equipment/spindle</code></td>
<td><code>/general-setup?tree=equipment/spindle</code></td>
</tr>
<tr>
<td><code>/fixture/:rest(.*)*</code></td>
<td><code>/general-setup?tree=equipment/fixture</code> — any trailing path is discarded.</td>
</tr>
<tr>
<td><code>/workpiece/:rest(.*)*</code></td>
<td><code>/general-setup?tree=equipment/workpiece</code> — any trailing path is discarded.</td>
</tr>
<tr>
<td><code>/equipment/background-coolant</code></td>
<td><code>/general-setup?tree=equipment/background</code>. Background and Coolant are two separate tree nodes; the redirect picks Background.</td>
</tr>
<tr>
<td><code>/mission</code></td>
<td><code>/execution?tree=execution/mission</code></td>
</tr>
</tbody>
</table>
<p>The five spindle tab names are declared in <code>treeRoutes.ts</code> as <code>SPINDLE_TABS</code>, and imported by
<code>wwwroot-src/src/router/routes.ts</code> — which is the list the redirect tests its <code>:tab?</code> segment
against. The equipment tree does not import that constant: it spells the same five segments as
literals while building the <code>equipment/spindle/&lt;seg&gt;</code> 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 Tool House name lists below are the other case, genuinely shared.</p>
<h2 id="the-tree-query">The <code>?tree=</code> Query</h2>
<p>Selection and URL are synced two ways by the Control-Tree host, one instance per tree page.</p>
<p><strong>URL to selection.</strong> A watcher on the query drives <code>applyRouteSelection()</code>, and it is registered
<code>immediate</code>, 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.</p>
<p><strong>Selection to URL.</strong> A watcher on the selected id replaces the <code>tree</code> argument, preserving the
rest of the query. It uses <code>router.replace</code>, 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 <strong>raw</strong>
query rather than the migrated form, which is what makes an older id canonicalise: opening
<code>?tree=controller</code> selects the node and then rewrites the URL to <code>?tree=equipment/controller</code>.</p>
<p>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 equipment root, and the Preference menu
that carries those checkboxes is open on every page — and the landing selection has to have
followed it.</p>
<p>That same checkbox flip is a third writer of the <code>tree</code> argument, and the one place the guard is
absent. The equipment host watches both checkboxes, and when the standing selection is a node the
flip removes, it replaces <code>tree</code> with <code>equipment</code> — the rest of the query preserved — before
stepping the selection off. The route name is not consulted there, so the replacement goes to
whichever route is showing.</p>
<h3 id="what-applyrouteselection-does">What <code>applyRouteSelection</code> Does</h3>
<ol>
<li>The change is ignored unless this page owns the current route.</li>
<li>The raw query is migrated into today's id. If its first segment names a <strong>different</strong> page and
that segment is one of the known roots, the location is replaced with the route that owns it and
the pass stops.</li>
<li>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 root node — which is always present
once the tree is built, so the editor column always has something to show.</li>
<li>An id the built tree does not contain is <strong>ignored</strong> — the current selection stands 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.</li>
<li>Otherwise every ancestor prefix of the id 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.</li>
</ol>
<h3 id="the-id-the-url-names-before-the-tree-exists">The Id the URL Names, Before the Tree Exists</h3>
<p><code>treePathOfRoute()</code> — the raw query put through <code>migrateLegacyTreeId</code> — is not read only by the
selection logic. The equipment tree consults it while it is being <strong>built</strong>, 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.</p>
<h2 id="id-migration">Id Migration</h2>
<p><code>migrateLegacyTreeId</code> is one pass of ordered guards over the whole id; the first that matches
returns, and an id matching none is returned unchanged.</p>
<table>
<thead>
<tr>
<th>An id shaped like</th>
<th>Becomes</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>controller</code>, <code>controller/…</code></td>
<td><code>equipment/controller</code>, <code>equipment/controller/…</code></td>
</tr>
<tr>
<td><code>spindle</code>, <code>spindle/…</code></td>
<td><code>equipment/spindle</code>, <code>equipment/spindle/…</code></td>
</tr>
<tr>
<td><code>mission</code>, <code>mission/…</code></td>
<td><code>execution/mission</code>, <code>execution/mission/…</code></td>
</tr>
<tr>
<td><code>equipment/mission</code>, <code>equipment/mission/…</code></td>
<td><code>execution/mission</code>, <code>execution/mission/…</code></td>
</tr>
<tr>
<td>any id ending in <code>/contours/tray</code>, or containing <code>/contours/tray/</code></td>
<td>the same id with that segment spelled <code>fluting</code></td>
</tr>
</tbody>
</table>
<p>The last rule renames one segment in place and leaves its children — <code>baseline</code>, <code>flute-&lt;i&gt;</code>,
<code>side</code>, <code>bottom</code> — 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.</p>
<p>The function is applied at four points, which is what makes the guarantee hold in practice:</p>
<ul>
<li>to the raw <code>?tree=</code> query, on every read;</li>
<li>to the persisted last selection, before it is looked up in the tree;</li>
<li>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;</li>
<li>as the first step of <code>routeForTreeId</code>, so the id-to-page resolver never sees an unmigrated id.</li>
</ul>
<p>Note what the first rule implies: a bare <code>?tree=controller</code> selects the <code>equipment/controller</code>
branch — the controller editor on the General Setup page — and the redirect-only <code>/controller</code> path
above lands on the same branch, so a bookmark from the retired Controller page's era resolves in
either form.</p>
<h2 id="landing-an-id-on-its-page">Landing an Id on Its Page</h2>
<p><code>TREE_PAGE_ROOTS</code> lists the first segments that name a page: <code>execution</code>, <code>equipment</code> and
<code>toolhouse</code>. <code>spindle</code> is absent on purpose — migration folds it into <code>equipment/…</code> before any root
check runs, so no root check ever sees it.</p>
<p><code>routeForTreeId</code> migrates the id, then dispatches on its first segment:</p>
<ul>
<li><code>toolhouse</code> — the Tool House route, with the id's segments translated into path params (below).</li>
<li><code>equipment</code> — the General Setup route, carrying the migrated id as <code>?tree=</code>.</li>
<li>anything else — the Execution route, carrying the migrated id as <code>?tree=</code>. This is a
fall-through, not a test for <code>execution</code>, 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
calls the function only for ids whose root is in <code>TREE_PAGE_ROOTS</code>.</li>
</ul>
<p>The returned location replaces the whole current location. Only the <code>tree</code> 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.</p>
<h2 id="the-tool-house-translation">The Tool House Translation</h2>
<p>The Tool House page keeps its state in path segments rather than a query, so an id arriving from
another page has to be translated. <code>routeForTreeId</code> walks the segments of
<code>toolhouse/tool-&lt;n&gt;/&lt;tab&gt;/&lt;subtab&gt;</code>:</p>
<ul>
<li>segment two contributes <code>toolId</code> when it is <code>tool-</code> followed by an integer;</li>
<li>segment three contributes <code>tab</code> when it is one of <code>general</code>, <code>cutter</code>, <code>holder</code>, <code>clamping</code>,
<code>intelligent</code>;</li>
<li>segment four contributes <code>subtab</code> when it is one of the cutter sections — <code>material</code>, <code>profile</code>,
<code>contours</code>, <code>upper-beam</code>, <code>opt</code> — or one of the holder sections, <code>geometry</code> and <code>resolution</code>.</li>
</ul>
<p>Only the sub-tab step is nested inside the tab step: an unrecognised tab discards the sub-tab with
it, whereas the tool step is tested on its own, so a second segment that is not <code>tool-&lt;n&gt;</code> costs the
<code>toolId</code> param alone and the tab still translates. Everything deeper than the last segment the URL
can carry is discarded either way — an id pointing at a single flute contour under
<code>…/cutter/contours/fluting/…</code> lands on the nearest tab the URL can express. The same three name
lists are imported by the page itself, so the tab set and the translation cannot drift apart.</p>
<p>Once on the page, the tab segments are kept in step with the tabs by <code>useRouteTabs</code>, a composable
whose only consumer this page is. It gives each tab level one route param, a valid-name set and a
default; a nested level may declare which parent values make it apply, and while its parent is
something else the level is <strong>dormant</strong> — its segment is dropped from the URL while its reference
quietly remembers the last value, so returning to that parent tab returns to the sub-tab it had.
The Tool House sub-tab level applies under <code>cutter</code> and <code>holder</code> only, and both its valid set and
its default depend on which of the two is showing. A segment the level does not recognise resolves
to that default rather than failing. Canonicalisation runs on the first mount and on every
keep-alive re-entry, which is why a bare <code>/tool-house</code> acquires its tab segments. The tool segment
is not one of the composable's levels: the page fills it in itself, adopting the URL's tool when the
project holds it and the first tool in the list otherwise, then replacing the URL with what it
adopted. That resolver reacts to the
<code>toolId</code> param and to the tool list rather than to page activation, so it also re-points the URL
when the list changes underneath it.</p>
<p>The Tool House page never reads or writes <code>?tree=</code>. A <code>toolhouse/…</code> id reaches it only by way of
the redirect above, which is issued by one of the two tree pages' hosts.</p>
<h2 id="reading-an-id">Reading an Id</h2>
<p>An id's first segment names the page that owns it, and therefore the folder that documents it:</p>
<table>
<thead>
<tr>
<th><code>?tree=</code> root</th>
<th>Route</th>
<th>Documented under</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>execution/…</code></td>
<td><code>/execution</code></td>
<td><a class="xref" href="../execution/index.html">Execution Page</a></td>
</tr>
<tr>
<td><code>equipment/…</code></td>
<td><code>/general-setup</code></td>
<td><a class="xref" href="../general-setup/index.html">General Setup Page</a></td>
</tr>
<tr>
<td><code>toolhouse/…</code></td>
<td><code>/tool-house/…</code></td>
<td><a class="xref" href="../tool-house/index.html">Tool House Page</a></td>
</tr>
</tbody>
</table>
<p>The remaining segments are the branch path, one segment per level of the tree, and the branch's own
page sits in that folder — <code>equipment/workpiece/material/cutting-parameter</code> is the Cutting Parameter
item of the Workpiece branch on the General Setup page. Most segments are kebab-case role names;
the two list-backed branches, Mission entries and Program files, use a positional index instead.</p>
<p>Every segment comes from the <strong>node id</strong>, never from the label the user reads: the General Setup
page's root node is labelled <code>General Setup</code> and carries the id <code>equipment</code>. A label is a display
string — the translation of the node's <code>labelKey</code> where it has one, and otherwise raw data such as
an NC file name or a mission command's user-typed title, as <a class="xref" href="control-tree.html">Control Tree</a>
sets out — while an id is neither translated nor taken from data. Two consequences follow. A
<code>?tree=</code> link is locale-independent, and an id can never be read off what is on screen.</p>
<h2 id="titles">Titles</h2>
<p>A route's <code>meta.title</code> holds an i18n <strong>key</strong>, such as <code>routes.execution</code>, not a title. The router
resolves it through the active locale in an <code>afterEach</code> hook and sets the document title to the
translated name followed by <code> - HiNC</code>; a route with no key — the catch-all — leaves the bare
product name. The sibling <code>name:</code> 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.</p>
<p>The same resolver is handed to the i18n module through <code>registerRetitle</code>, 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.</p>
<h2 id="serving-these-urls">Serving These URLs</h2>
<p>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.</p>
<p>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 — <code>?tree=</code> included — parked
in a <code>redirect</code> 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.</p>
<h2 id="source-code-path">Source Code Path</h2>
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
<h3 id="web-application">Web Application</h3>
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
<ul>
<li><code>wwwroot-src/src/router/routes.ts</code> — the route table, the redirect-only records, the login route
outside the shell layout, and the trailing catch-all.</li>
<li><code>wwwroot-src/src/router/treeRoutes.ts</code> — <code>migrateLegacyTreeId</code>, <code>TREE_PAGE_ROOTS</code>,
<code>routeForTreeId</code>, the three tab-name lists it shares with the Tool House page, and
<code>SPINDLE_TABS</code>, which only the spindle redirect reads.</li>
<li><code>wwwroot-src/src/router/index.ts</code> — the history-mode router factory, the authentication guard,
and the retitle hook registered with the i18n module.</li>
<li><code>wwwroot-src/src/components/controlTree/useControlTreeHost.ts</code> — the tree-page host:
<code>rawTreePathOfRoute</code>, <code>treePathOfRoute</code>, the two selection/URL watchers and the unguarded
controller-checkbox watcher beside them, <code>landingSelection</code>, <code>applyRouteSelection</code> and
<code>syncUrlBack</code>, plus the build-time reveal of a URL-named CSV or CL controller node.</li>
<li><code>wwwroot-src/src/composables/useRouteTabs.ts</code> — the tab-to-param sync the Tool House route uses,
including dormant levels and the keep-alive re-entry canonicalisation.</li>
<li><code>wwwroot-src/src/pages/ToolHousePage.vue</code> — consumes the shared tab lists, resolves the tool from
its path param, and navigates by route name when the tool changes.</li>
<li><code>wwwroot-src/src/pages/FileExplorerPage.vue</code> — mirrors the browsed location into the route's
catch-all param and back.</li>
<li><code>wwwroot-src/src/pages/MachineToolPage.vue</code> — the component behind the URL-only machine-tool
route.</li>
<li><code>wwwroot-src/src/pages/NotFoundPage.vue</code> — the catch-all component, carrying its own layout.</li>
<li><code>wwwroot-src/src/pages/LoginPage.vue</code> — reads the guard's <code>redirect</code> argument and replays it.</li>
<li><code>wwwroot-src/src/composables/useViewPrefs.ts</code> — the browser-local store holding the per-page last
selected id and the expanded-node list that migration is applied to.</li>
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the menu entries that navigate by route name.</li>
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the English strings the <code>meta.title</code> keys resolve to.</li>
<li><code>wwwroot-src/src/i18n/index.ts</code> — <code>registerRetitle</code> and the locale switch that calls it.</li>
<li><code>wwwroot-src/quasar.config.ts</code> — selects history mode and names the build output folder the
server serves.</li>
<li><code>Program.cs</code> — the two SPA fallbacks, the explorer-specific one ahead of the bare one.</li>
</ul>
<h2 id="see-also">See Also</h2>
<ul>
<li><a class="xref" href="control-tree.html">Control Tree</a> — the tree these ids name: how a node id is formed and what a branch
is made of</li>
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the shell whose menus navigate to these routes and whose keep-alive
cache makes the route-name guards necessary</li>
<li><a class="xref" href="../execution/index.html">Execution Page</a> — the busiest consumer of <code>?tree=</code>, and the page a fall-through id lands
on</li>
</ul>
</article>
<div class="contribution d-print-none">
</div>
<div class="next-article d-print-none border-top" id="nextArticle"></div>
</div>
<div class="affix">
<nav id="affix"></nav>
</div>
</main>
<div class="container-xxl search-results" id="search-results"></div>
<footer class="border-top text-secondary">
<div class="container-xxl">
<div class="flex-fill">
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
</div>
</div>
</footer>
</body>
</html>