Skip to content
ReferenceCatalogue navigation

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.

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 href and data-nav-href to the relative generated artifact for the source viewport and color scheme, retaining light fallback;
  • add one package-owned data-mokly-link marker holding the resolved complete <path>[#fragment] destination only to an activatable native link; and
  • reject a logical href on 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 _self is self;
  • ASCII-case-insensitive _top, _parent, and _blank are 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:

  1. remove aria-current from every other row;
  2. 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 its hidden state and presses its disclosure button instead of setting open;
  3. preserve unrelated folder disclosures;
  4. clear a search query only when it would hide the destination;
  5. switch Changes to All only when the destination is not changed;
  6. reapply navigation visibility after those adjustments; and
  7. 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 logical href on 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.