Catalogue navigation
How links between catalogue entries resolve in generated files and in the catalogue itself.
Delivery Status
Implemented by the in-frame navigation plan.
Pages and documents share the
screen/flow link and breadcrumb rules. The hydrated shell and
frame adapters apply every marker, sandbox, target,
and outer-navigation rule in Serve, export, and embedded hosts, and controlled
and uncontrolled unknown-destination handling is implemented. Path-addressed
links and /view/<path>/ URLs are implemented, as are the Specs and Components
sections, browse-only folder rows, Overview rows, and the breadcrumb folder
links and reveals of the folder contract.
Scope
This document defines how links authored inside generated mockup documents behave in portable output, served Browse, and the deployed Browse preview. It also defines how the Browse shell keeps its route identity and catalogue tree aligned after every navigation.
Mokly owns navigation between catalogue entries. Consumers continue to own their product’s application routes and the behavior of ordinary document, asset, external, download, and same-document links.
Folders follow the folder contract: a
folder row only expands or collapses and is never a destination; an entry row
navigates; a folder’s own page is its first child row, labelled with its title
or Overview, or, for a screen or component index, the folder renders as that
entry’s row; and a breadcrumb folder segment opens the folder page when one
exists and otherwise expands the folder. Empty folders have no row.
Every row uses the single search rule. Changes
activation uses the built tree under the branch-point lookup.
Component Navigation
Registered component and component variant paths share the one catalogue namespace. A portable link to a component parent resolves to its first variant entry’s view in the effective viewport and color scheme, and a link to a variant resolves to that variant’s own view; Browse opens the component page, which shows the first variant, or the variant entry. The explorer contract owns usage queries and selecting an actual consumer instance; variant rows follow the variant contract. View/theme swaps replace iframe history; outer Back/Forward continues between catalogue entries. Only authenticated immediate frames receive inspection/link enhancement. Temporary control documents retain the same script-disabled boundary and do not grant nested frames shell access.
Logical Catalogue Links
The opt-in MockLink asChild extension and its static-control adaptation
contract are specified in Styled catalogue link controls.
The target helper API is mockLink(to, fragment?) and
<MockLink to={to} fragment={fragment}>. The destination and optional fragment
remain separate authoring concepts: to and the first function argument hold
only the destination, a complete path, a relative path, or a definition
reference under the authoring contract, while
fragment is a bare HTML id without # or percent-encoding. The helpers reject
overloaded or encoded target syntax such as path#fragment, path%23fragment,
or mock:path in to or the first argument; an unknown but well-formed path
fails later during the catalogue build, naming the new path when the target
moved. The helpers emit a complete mock:<path>[#fragment] value, and authors
may still use that raw form in supported navigation attributes.
A complete document that contains an activatable logical href must not
contain an HTML <base href> element. The base URL would change the browser’s
effective portable destination without changing the link attribute bytes that
Browse authenticates. The builder enforces this restriction on rendered
documents during logical-link rewriting. A metadata-only data-nav-href does not
activate the restriction, and <base target> remains supported under the
target rules below.
A logical href is valid only on an HTML <a>/<area> or SVG <a> and makes
that native link eligible for Browse activation. A logical href on every
other element, including HTML resource elements and SVG use/image, fails the
build instead of becoming an accidental resource request. Authors use
data-nav-href for metadata-only references; it is valid on any element but
does not invent click or keyboard semantics. A logical data-nav-href may
coexist with an eligible logical href, in which case both must name the same
destination. The entry’s path is its Browse location, /view/<path>/. A folder
is not a destination; its own page, when it has one, shares its path. A use-case
destination opens the use-case page, even though its portable fragment fallback
resolves through the first screen in that use case.
A logical fragment contains one or more Unicode letters, numbers or combining
marks, _, :, ., or -. It names an HTML id, not a CSS
selector. The builder validates that it exists in every generated mobile,
desktop, light, and applicable dark artifact that Browse may show for the
destination screen, or in every generated document of a destination page or
document. For a use case, this requirement applies to its first screen. This
cross-view rule lets one canonical outer route drive every visible frame without
silently losing the anchor in another viewport or color scheme.
The builder must:
- validate the destination path and optional fragment before writing output;
- rewrite the actual
hrefanddata-nav-hrefto the relative generated artifact for the source viewport and color scheme, retaining light fallback; - add one package-owned
data-mokly-linkmarker holding the resolved complete<path>[#fragment]destination only to an activatable native link; and - reject a logical
hrefon any other element, a<base href>in a document with an activatable logical link, a reserved marker supplied by consumer output, duplicate occurrences of either reserved navigation attribute, or conflicting logical destinations carried by two navigation attributes on one element.
The final link validation contract defines
reference records, final anchor checks and Markdown safety. No consumer
transformation or source-path ownership header participates. Authored
data-mokly-target is stripped by Browse; markers never grant resource access.
The marker is inert metadata, not a second resource URL. HTML escaping must be deterministic, and link/resource validation continues to inspect the portable attributes rather than treating the marker as a file reference.
Raw relative links do not become catalogue links merely because they happen to resolve to a generated fragment. This keeps catalogue intent explicit and prevents Mokly from taking over product or asset navigation accidentally.
Portable And Comparison Output
Generated documents under mokly-generated/ keep relative artifact href values
to other generated documents and authored closure files. They must remain
navigable when opened directly or copied with the matching closure without the
Browse shell. Comparison snapshot trees copy the same portable documents and do
not promote links into Browse routes; the pane guard cancels activation and
reveals same-document anchors.
Browse Presentation
When an eligible marked native link from a manifest-owned generated document is
presented beneath /static/mokly-generated/ in served Browse or in the deployed Browse preview,
Mokly authenticates its marker for trusted parent enhancement while retaining
the portable href and live target. The trusted-document set is exactly every
current manifest screen fragment, including dark fragments, plus every
generated page and document in that manifest. The accepted in-memory
generation must contain it. The plain marker does not authenticate its source.
The parent resolves the marker’s path through its catalogue read model to the
canonical /view/<path>/ or /view/<path>/?fragment=<encoded-fragment>
destination; it never trusts the portable URL as entry identity. The adapter removes any
consumer-authored data-mokly-target, resolves the eligible link’s effective
request from its own target or the document’s applicable first <base target>,
parses it, and retains a valid non-self request only in newly derived inert
metadata. An invalid target receives no trusted metadata, so parent enhancement
declines it. A download link is not promoted, and data-nav-href without an
eligible logical href stays portable metadata rather than becoming a Browse
interaction.
The portable file on disk must not be mutated. The development server and
preview builder share one deterministic Browse-document adapter for marker
authentication and derived target metadata. Every HTML response or preview
copy beneath /static/ passes through it. Only a route in the trusted set above
whose bytes match the accepted generated document may promote a marker. The
adapter recomputes the source view’s expected portable href and requires the
marked link to match it exactly. On any other HTML route, including
authored closure files, it removes data-mokly-link and
data-mokly-target from the adapted copy and never promotes them; removal
covers every raw occurrence even when HTML parsing hides duplicates. A missing
or mismatched compiled document, duplicate reserved attribute, malformed or
manifest-invalid marker, or mismatched portable href on a trusted route
yields HTTP 500 without serving that document and fails the preview build. A
trusted document that carries an activatable marker and <base href> fails
closed by the same rule, defending against post-build tampering. Neither
environment promotes an unmarked relative link or rewrites a live href,
target, formtarget, or <base target>.
One pure target parser is shared by the adapter and parent client. It does not trim its input and returns exactly one typed state:
- absent, empty, or an ASCII-case-insensitive
_selfisself; - ASCII-case-insensitive
_top,_parent, and_blankare their corresponding reserved states; /^[A-Za-z0-9][A-Za-z0-9._:-]*$/is a named target, preserving its case; and- every other value, including whitespace or control characters, is invalid.
The parent reparses adapter-produced target metadata with this same contract and declines an invalid or context-inappropriate state.
Default same-origin Browse grants a fragment frame access only so trusted parent code
can inspect its immediate document. It never grants allow-top-navigation,
allow-top-navigation-by-user-activation, allow-popups, allow-forms,
allow-scripts, or allow-downloads. The top-navigation restriction remains
active for the generated document and is inherited by every descendant
browsing context it creates, including srcdoc, local, and cross-origin child
frames. Consumer-authored _top, _parent, named, <base target>, and
formtarget values therefore cannot replace the shell even when they live in
nested content the adapter cannot inspect. Trusted parent code is the only
outer-navigation authority. Portable and comparison documents retain their original
bytes; viewer-owned, script-disabled comparison panes cancel navigation.
In served Browse, /view/<path>/?fragment=<encoded-fragment> carries the
optional request-visible fragment query. The server accepts at most one
value, decodes it exactly once, checks the grammar and cross-view anchor
existence above, and returns HTTP 400 without injecting a fragment when
validation fails. It renders the validated value as an encoded hash on every
current screen iframe source and its light/dark swap sources. A use-case page
applies it only to the first step, matching the portable fallback. The query
remains on the outer history URL while scheme changes retain the iframe hashes.
The deployed preview is a static snapshot and has no request handler that can
render query-dependent HTML. Its hydrated parent shell reads at most one
fragment value from the URL, validates the grammar, and renders the encoded
hash into src, data-fragment-light, and data-fragment-dark wherever each
attribute exists on every current screen frame. On a use-case page it updates only the first
step. Authored links already carry the builder’s cross-view anchor proof, and
updating every swap source preserves the anchor through light/dark changes. A
direct preview URL whose syntactically valid fragment names no anchor simply
remains at the top of that frame. Without parent enhancement, served and preview
links keep their portable in-frame behavior; neither environment promises outer
page navigation or logical-fragment transport from a click. A top-level
#fragment is never logical-fragment transport because URL hashes are not sent
in an HTTP request.
Enhanced Navigation And Safe Degradation
With the hydrated shell available, an unmodified primary or keyboard
activation of a default/_self marked link asks the outer shell to navigate
through its latest-wins route transition. The shell resolves the destination in
its catalogue read model and renders it; it does not fetch a shell document.
The resulting history entry has the canonical /view/<path>/[?fragment=...]
URL; the title, breadcrumbs, heading, details inspector, frames, focus, and
status announcement all describe the destination. Back and Forward return
through those outer route entries and restore their route-owned scroll.
Once React owns a same-origin frame session, that ownership is continuous while
the adapter replaces its document only when the still-visible document is the
exact Document that an earlier same-origin mount authenticated for that frame.
The shell transfers its navigation receiver to that authenticated document
before starting the replacement, so a valid marked activation during a
viewport, scheme, variant, fragment, or route handoff still navigates the parent
exactly once. A document the session did not authenticate remains frame-owned
until a different replacement document passes assigned-resource authentication.
On the frame’s first same-origin mount, a matching server-rendered starting
document may authenticate for hydration. On every later mount, the exact
starting Document is ineligible for URL-based authentication when it was not
already authenticated, even if it has the requested replacement URL. The
receiver follows the new same-origin document as soon as its exact resource
identity can be authenticated; pending images, fonts, or other subresources
cannot reopen a native-navigation gap before load. Readiness gates inspection,
not logical navigation. Disposing or unsubscribing the session removes the
receiver; before hydration, after failed hydration, or without a receiver, the
portable link remains frame-owned as described below.
Outer same-document links, including the shell’s skip link, keep native fragment focus and scrolling. Document identity includes origin, pathname and query but excludes the hash. A history event within the displayed document must not change the rendered route, reinstall its workspace, or move focus away from the native target. It invalidates any pending route transition so an obsolete result cannot replace the retained view. Saved scroll positions may be restored without a reload. A changed route or query still uses in-shell navigation and its normal history restoration.
In every delivery the shell resolves that trusted path through its catalogue
read model to the exact /view/<path>/ URL before rendering or opening any
context; there is no alias page or redirect. See
Static export delivery.
For a frame path that names no current or removed entry, standalone and
uncontrolled shells show the shared missing destination and any later selection
reinstalls its canonical URL. A controlled Viewer emits one frame error, changes
no selection/display, and emits no selection/navigation event.
Path-derived artifact paths owns viewHref.
The same trusted parent enhancement exclusively handles modified pointer
activation and explicit non-self targets after validating the marker and
canonical destination. Modified activation includes Meta-, Ctrl-, or
Shift-modified click and middle-button auxclick (button === 1). A requested
_top or _parent uses the normal outer route transition. When a new or named
browsing context is requested, package-owned parent code opens the canonical
Mokly URL with noopener; it never delegates popup creation to the consumer
frame. If the shell is not hydrated or hydration fails, the portable live link
remains frame-owned and subject to the sandbox; Mokly does not grant native
outer-navigation fallback.
Consumer scripts remain disabled in default Browse. It permits same-origin inspection but does not grant script, form, popup, download, or either top-navigation capability to consumer documents. Comparison panes are viewer-owned, script-disabled documents under the pane contract.
External, raw relative, download, same-document hash, metadata-only, and
unmarked links retain their existing frame-owned behavior subject to the
sandbox. Consumer-authored targets remain byte-preserved, but the sandbox denies
their access to the outer shell and to popups. Mokly must not infer product
navigation from URLs, data-nav-href, or visible labels.
Frame Adapter Boundary
The frame link contract defines shared fragment scope, local and cross-origin transports, and host revalidation.
Active Catalogue Visibility
Every successful outer route change, including in-shell navigation, Back,
and Forward, establishes one navigation invariant: when the route has a
catalogue row, that row is visible and marked aria-current="page".
To establish the invariant, Browse must:
- remove
aria-currentfrom every other row; - open each ancestor
[data-nav-disclosure]of the active row, including its Specs or Components section and, for an entry with variants or one of its variants, the parent row’s variant list defined by the variant contract. That list is a container rather than a<details>, so opening it clears itshiddenstate and presses its disclosure button instead of settingopen; - preserve unrelated folder disclosures;
- clear a search query only when it would hide the destination;
- switch Changes to All only when the destination is not changed;
- reapply navigation visibility after those adjustments; and
- scroll the active row into the nearest visible part of the catalogue pane.
The responsive drawer closes after navigation so it does not cover the new screen. Its tree retains the opened destination path for the next time it is opened. A user may collapse the active path afterward; the next route change re-establishes the invariant.
Each user edit to search or the All/Changes filter opens groups to reveal the rows matching the updated constraints. Reapplying the same active constraints during a route change or watched-reload restoration must instead preserve groups the user subsequently collapsed; only the destination row’s ancestor path may be reopened. Clearing every filtering constraint restores the disclosure state captured before filtering began, except that an ancestor opened for the navigated destination remains open so the active row stays visible.
Enhanced navigation preserves the selected viewport, color scheme, and details disclosure. It collapses an expanded frame before installing the destination. Filters and search remain unchanged when the destination is already visible.
Destination queries for the view axes and removed-entry snapshots follow the shell destination contract.
Verification Contract
Coverage must prove:
- helper-level path/fragment separation and grammar, portable output, eligible
native-link markers, metadata-only
data-nav-href, rejection of logicalhrefon resource/non-link elements, rejection of<base href>in rendered documents, dual navigation attributes, hashes, use-case paths, dark-to-light fallback, conflicts, and reserved-marker errors; - served and preview adaptation without mutating generated fragments, including LF/CRLF current-notice stripping, unowned reserved-metadata removal, secure target parsing, portable live attributes, and request-visible fragment transport;
- served cross-view fragment validation and JavaScript-disabled anchor injection, plus enhanced static-preview current/swap-source injection, scheme-toggle retention, and first-step use-case scoping;
- enhanced primary, keyboard, modified, non-self, Back, and Forward navigation
from
MockLink, raw HTML anchors and areas, SVG anchors, mobile and desktop frames, flow steps, and generated page embeds; - safe degradation without outer navigation when the shell is not hydrated or hydration fails;
- active-row selection, ancestor disclosure, conditional filter/search reset, nearest scrolling, responsive drawer closure, and preserved shell state; and
- continued script denial and top-navigation denial across direct,
srcdoc, local, and cross-origin nested contexts; ancestor/named-context denial across HTML and SVG links, forms, marked, unmarked, download, and base targets; marked and unmarked links in every nested context ignored by outer navigation; frame-owned external/download/hash behavior; and unchanged comparison-pane links.