Skip to content
ReferenceStatic export delivery

Static export delivery

How a hosting provider must serve an exported catalogue, and what the browser expects from it.

Hosting Contract

Deploy the output directory’s contents as the HTTP(S) origin’s document root. The host must serve ordinary files with their correct MIME types, index.html directory indexes, query-insensitive file lookup, and normal missing-file responses. Serve mokly-viewer/ and static/ under the portable-name rules. No SPA fallback, URL rewriting, or server-side rendering is required; no process renders shell HTML, so the host runs nothing. Every local request of the output resolves inside the output. No request reaches the consumer project, .git, node_modules, or a Mokly process.

The output includes a 404.html stub. Hosts may configure it as their error document; producing the HTTP 404 status for arbitrary unknown URLs is host configuration, not something static HTML can guarantee. The consumer publishes atomically or as an immutable host deployment to avoid serving a mix of builds; the shell’s deployment fence turns a mixed read into a full page load. build adds no host configuration file such as _headers or _redirects; a deployment step may add one beside the output, and correctness never depends on a generic static server interpreting it. Generic deployments document the header requirements below.

The default iframe sandbox stays allow-same-origin. Cross-origin hosts must explicitly use the postMessage adapter, a distinct real frameOrigin, and sandbox="allow-same-origin allow-scripts". Opaque null origins are rejected. Query-insensitive hosting preserves the adapter’s mokly-host parameter. This exception enables document scripts on the isolated origin; it adds no script permission locally, nor forms, popups, downloads, or top-navigation permission. The adapter policy applies to current documents; removed previews and comparison panes use viewer-owned, script-disabled srcdoc frames under their linked contracts.

Routes

Paths are relative to the output directory and match the Serve routes of the shell delivery contract, so a static host needs no rewrite rules. Every rendered file name derives from its entry’s path under the artifact path contract and keeps the authored case of each segment when written into URLs.

Path Content
index.html the shell stub
view/<path>/index.html the same stub, one per current and removed entry
404.html the same stub
mokly-viewer/index.json the catalogue index
mokly-viewer/deployment.json the deployment file v1
mokly-viewer/entries/<entry path>.json one entry detail per current entry
mokly-viewer/diffs/generations/<generation>/** the comparison generation
static/mokly-generated/<route> rendered views and documents
static/<closure path> referenced authored resources
mokly-viewer/client/**, mokly-viewer/shell.css, mokly-viewer/fonts/** the shell bundle, the inspector, styles, and fonts
.mokly-export-artifact the ownership marker

The format inventory gives the version of each JSON file.

Shell documents are index.html files, so /view/<path>/ needs no rewrite. A host serving view/<path>/index.html for /view/<path>, /view/<path>/, or /view/<path>/index.html needs no adapter: parseViewHref accepts all three forms and providerNormalizedHtmlPath names the form a host serves. static/ views and documents keep their .html suffixes, and public .htm documents keep their filenames. Rendered resource references address actual written files. Hosts that normalize HTML URLs stay compatible when their redirects preserve the query and resolve to the same page.

Folders are not routed pages; an index entry is its folder’s page. Use cases, whole-document pages, and Markdown documents are included. An empty catalogue fails the build and preserves the earlier output. Missing views stay explicit in added and removed comparison data; nothing is synthesized. The shell keeps Added entries in Current without comparison modes. Removed screens show a Removed badge without comparison modes and open the packaged base views under removed previews. Removed component variants stay eligible for comparison.

Every rendered, copied, and Mokly-written path enters one collision-checked inventory, including file and directory prefix collisions, compared after case folding. Duplicate, colliding, and reserved paths are rejected before installation, and any earlier output is preserved. Byte-identical resources may be deduplicated. A current and a removed entry never share a path within a kind under the Changes contract.

Headers, Caching, And Revalidation

Every JSON file is served as application/json; charset=utf-8, and every mokly-viewer/ and static/ response carries X-Content-Type-Options: nosniff.

A new deployment replaces the stubs, the index, the deployment file, the details, the documents, the resources, the bundle, the styles, and the fonts at the same paths. Serve those paths with revalidation, Cache-Control: no-cache or an equivalent validator policy, so the next navigation sees the new deployment. The deployment file, index, and details of one deployment never change, so a validator answers 304 until the next deployment. The shell keeps the index and details for the page’s lifetime and requests the deployment file at each check, with credentials: "omit". A comparison generation is immutable at its content-addressed path: a host may cache its files for the deployment’s lifetime or serve them with Cache-Control: no-store, and it must not rewrite one generation’s URL to another generation. When a detail keeps its detailId, its bytes stay unchanged across deployments.

For cross-origin catalogue and viewer consumers, the public fetch paths are mokly-viewer/index.json, mokly-viewer/deployment.json, mokly-viewer/entries/**, static/mokly-generated/<route>, static/<referenced closure path>, mokly-viewer/client/**, mokly-viewer/shell.css, mokly-viewer/fonts/**, and mokly-viewer/diffs/generations/**. Send correct MIME types, Access-Control-Allow-Origin: <exact app origin>, and X-Content-Type-Options: nosniff, including on GET, HEAD, and error responses. Use Vary: Origin when selecting an allowed origin dynamically. No wildcard CORS, cookies, authorization headers, or credentials are used; clients fetch with credentials: "omit". CORS is unnecessary for same-origin reads. The shell delivery fences own the client side. Removed-preview and comparison HTML beneath a generation’s snapshots/before/ and snapshots/after/ trees is fetched and validated by the viewer rather than loaded as a frame URL; it needs the same CORS and nosniff headers and a text/html MIME type.

Static Navigation

The stub is identical for every route; the shell reads the route from location, fetches mokly-viewer/index.json, resolves the route against the index, and fetches the route’s details under the fetch rules. A current component variant reads its Used by and Affected lists from its parent’s detail. The shell fetches no other shell page for evidence. The first load does not request the deployment file. During in-shell navigation, each detail request also requests mokly-viewer/deployment.json, and the shell requests it before it opens a comparison or removed entry. Static mode with a missing, malformed, or other-version index or detail fails closed instead of requesting a development endpoint. A static build never falls back to a live endpoint for any request.

In-shell navigation commits the destination from the index at once and fetches its details, including the parent’s detail for a current component variant. A deployment file with another deploymentId, or a detail whose detailId differs from the index record, means another deployment replaced the files, so the shell performs a full page load of the requested URL rather than mixing deployments. An open tab keeps its loaded deployment for the page’s lifetime; reload the page to adopt a newer one. In-shell navigation, reload, Back/Forward, new tabs, and browser-normalized response URLs keep the same entry identity, scroll, and disclosure behaviour. Unknown paths are unavailable: the shell renders only entries present in its index and never invents a catch-all route.

A removed entry resolves from the index’s removedEntries; it needs no query when its path is unique, and a present snapshot must match the published record. The shell checks the deployment file before it opens the removed entry. Its detail is fetched from the generation that comparisonUrl names and accepted only under the removed-entry fence. Unknown, stale, and mismatched identities stay unavailable.

Static frame-link enhancement uses the same canonical path and activation rules as Browse navigation. Canonical /view/<path>/ pages need no alias or redirect. The navigation contract applies unchanged: trusted ownership-checked link markers only, immediate-frame parent enhancement, portable fallback hrefs, sandbox restrictions, latest-wins cancellation, focus, announcements, active-tree visibility, search and filter state, and the fragment grammar. The static fragment handler applies a single validated fragment query to all current light and dark sources; a use case applies it only to its first step. Invalid or duplicate fragment values are not injected, and a direct URL with a syntactically valid missing anchor keeps the static fallback.

Static Comparisons

Each build with Changes packages one complete comparison, with every before and after document, the removed entries’ details, the removed previews, and the transitive resources under one generation root:

text
mokly-viewer/diffs/generations/<generation>/

The generation keeps the snapshot document bytes and the relative snapshot paths of the comparison unchanged. It holds no review file.

The index’s comparisonUrl names this immutable root directly. A selected diff loads only the snapshot documents that it shows, resolved against that root; the comparison’s view states and evidence come from the selected entry’s detail. An opened removed entry loads its detail and its historical documents from the same root. A static build needs no selection endpoint and requests no generation root; Serve keeps its selection endpoint.

Current is the default after navigation and reload. Browsing, Changes filtering, and scheme or viewport switches request no snapshot file. A build with Changes carries the same per-view states as Serve, so its shell marks hidden changed views and lists them in Details from the index and the details. Added entries keep their current preview without comparison modes. Removed screens and pages have no comparison modes; selecting one requests the packaged historical version under removed previews. Side by side, Overlay, and Difference keep the UI and the missing-current state for removed component variants. Refresh and retry reload the same generation; only another build and deployment produce new comparison content. Hosts may keep prior generations for old tabs; if they remove them, the comparison failure state applies until the page reloads. Cancellation and failure keep the catalogue usable and cannot replace a different screen.

The output excludes the watcher, the event stream, the live descriptor, the control routes, and every server module; on a static host /mokly-viewer/events and /mokly-viewer/live.json are ordinary missing files. All product data, counts, and comparison results come from the real catalogue and Git inputs. No publishing, sandbox, or environment label is added to product screens. The light and dark, mobile and desktop shell design applies unchanged.

A current-only build has no generation and comparisonUrl: null. A base whose sources fail the current validation leaves comparisonUrl: null and changesStatus: "unavailable" while the build completes, under the publication contract.

Browser Runtime And Deployment Identity

The browser module inventory and the deployment identity algorithm follow the separate static build browser contract.