Canonical Deep Link¶
Verified against the code · 19d408be · 2026-08-25
All code-checkable claims match current code: canonicalNodeShareUrl/copyShareLink (frontend/app.js:4481-4497) build the link from the UUID extracted via uriToId (app.js:6326-6329) + BASE_URL, never the raw @id; notifications.py:66-67 builds {base}/node/{uuid} from req.node_id; export_service.canonical_web_uris (export_service.py:62-77) stamps the primary origin on every hasDomain edge regardless of per-domain publicURI, exactly as described; resolveDomainBaseForNode/nodeToTreeIndex (app.js:2768-2777) and the non_local_node/proxyRoutes 404 fallback in loadNodeDetail (app.js:4846-4864, 6352-6365) are present and match the described consumer-side resolution.
A node deep link — the URL you share from the app, receive by email, or follow from an export to open one specific node — is always the clean, domain-agnostic form:
{PRIMARY_ORIGIN}/node/{uuid}
wrapped for the single-page app as a hash link:
{PRIMARY_ORIGIN}/#{urlencode({PRIMARY_ORIGIN}/node/{uuid})}
It carries no domain and no transport. It is never the proxy form
{origin}/api/proxy/{domain}/node/{uuid}. The owning domain is not part of a node's
address — the consumer discovers it at open time.
Why one form¶
The format drifted into two shapes. The node-share email built the clean form from
the node UUID, while the in-app copy-share-link copied the node's @id, and the FME
webURI was the domain's publicURI. For a secondary-domain node both of the latter
are the proxy form (because a secondary's publicURI is the /api/proxy/{domain} routing
path), so the same node produced two different links — and a proxy-form link breaks the
moment the baked-in domain is wrong. A node's identity must not encode how it was fetched
(Contract 02.01: identity, not transport). The clean form is the canonical one because it
is the simplest, and because the backend already resolves a bare UUID to its domain.
Producers (all emit the clean form)¶
- In-app share button —
copyShareLinkbuilds the link from the node UUID +BASE_URLviacanonicalNodeShareUrl(), never from the node@id. - Node-share email —
notifications.pybuilds{base}/node/{uuid}from the UUID. - FME
webURI(consumed by GraphAnalyzer) —export_service.canonical_web_uris()stamps the canonical primary origin on everyhasDomainedge, so GA'swebURI + "/node/" + idyields the clean form.
Consumer (resolves the domain)¶
restoreFromHash / navigateToUri resolve the owning domain from the loaded trees
(resolveDomainBaseForNode → nodeToTreeIndex → allTrees[idx].domainBase). For a node
not in any loaded tree, loadNodeDetail follows the backend's structured
non_local_node 404 (which lists proxyRoutes) to the owning secondary domain. The clean
link therefore resolves for any domain without the domain ever appearing in the URL.
Authority¶
EIDOS Explorer defines this contract. Adjacent systems — GraphAnalyzer, DocuGraph, any
FME consumer — conform: they consume whatever canonical webURI EIDOS emits and must
not reintroduce a domain- or transport-coupled link. See the
Domain Routing Key invariant for the matching rule on
the fetch side (route by domain identity, never the emitted host).