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.

Delivery Status

Implemented portable serving for consumer export, with Cloudflare normalization confined to its repository adapter. Public catalogue, viewer, inspector, and removed previews share delivery descriptor v5 and one path-derived /view/<path>/ shell page per entry.

Hosting Contract

Deploy the export 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, worker, API or server-side rendering is required.

The artifact includes a 404.html catalogue page. 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. Configure revalidation for shell HTML, mutable assets, and aliases; comparison generation URLs must not be rewritten to another generation. Serve comparison resources with X-Content-Type-Options: nosniff and the existing no-store policy. Generic deployments document these header requirements; provider adapters may emit the host’s metadata files for them. Correctness must not depend on a generic static server interpreting _headers or _redirects.

For cross-origin catalogue and viewer consumers, public fetch paths are mokly-viewer/catalogue.json, 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 GET/HEAD and error responses. Use Vary: Origin when dynamically selecting an allowed origin. No wildcard CORS, cookies, authorization headers or credentials are used; clients fetch with credentials: "omit". CORS is unnecessary for same-origin reads. Existing revalidation and comparison no-store policies apply; see the catalogue fetch rules. Removed-preview and comparison HTML beneath an advertised 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.

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.

Artifact Routes

Paths below are relative to the export directory. Every 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 Meaning
index.html Full catalogue home
view/<path>/index.html Full shell for current and removed entries, served at /view/<path>/
static/<public-path> Adapted current views, documents, and public consumer resources
mokly-viewer/ Required shell CSS, fonts, browser modules, and comparison generation
mokly-viewer/catalogue.json Public catalogue read model v5
mokly-viewer/client/inspector.js Inert cross-origin frame inspector
404.html Existing catalogue not-found view
.mokly-export-artifact Public-safe versioned ownership inventory with per-file digests

Shell documents are index.html files, so /view/<path>/ needs no rewrite; static/ views and documents keep their validated .html suffixes, and public .htm documents keep their filenames. Do not apply the preview script’s Cloudflare-specific extension stripping. Generated resource references must address actual exported 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. Include use cases, whole-document pages, and Markdown documents. Empty registries remain invalid under the existing build contract; exporting one preserves the previous artifact. Missing views remain explicit in added/removed comparison data; never synthesize content. The shell keeps Added entries in Current without exposing comparison modes. Removed screens likewise show a Removed badge without comparison modes, opening the packaged baseline views the shell resolves from that descriptor under removed previews. Removed component variants remain eligible for comparison.

Every manifest, generated, copied, and adapter-added path enters a single collision-checked inventory, including file/directory prefix collisions. Reject incompatible duplicate paths, aliases, or reserved paths before installation. Shared byte-identical resources may be deduplicated. Current and removed entries cannot share a path within a kind under the Changes contract.

Adapter aliases enter the same case-folded path namespace as files, including the final ownership marker. Each alias is a safe relative, file-like route whose target is an existing exported file, not another alias. Reject exact alias/file matches even when their bytes would agree, case-folded matches, and ancestor/descendant collisions between aliases or between aliases and files, independent of insertion order. Distinct sibling aliases may share one target. These checks happen before installation and preserve any previous export on failure. They also apply to the preview adapter’s extensionless HTML aliases; ordinary consumer exports retain their real .html routes.

Static Navigation

Embed a typed, versioned static-delivery descriptor in shell-owned metadata, not consumer documents. It supplies the canonical path of the current page and the generation-specific comparison URL. Validate it against the catalogue while exporting and at the client boundary. All targets must stay same-origin under the expected Mokly prefixes. Consumer markup cannot supply or override this descriptor; serialize it safely in HTML. The root html element carries data-mokly-static="" and an escaped data-mokly-delivery JSON attribute with exactly schemaVersion: 5, canonicalPath, comparisonUrl, and deploymentId. canonicalPath is /, /404.html, or the page’s own /view/<path>/. Static mode with missing, malformed, or other-version metadata fails closed instead of requesting a development endpoint. Both identity values use 64 lowercase SHA-256 hex characters. Repository previews without Changes explicitly set comparisonUrl: null; deployment identity remains required. Null disables comparison requests and never falls back to a development endpoint. Consumer CLI exports always include their validated generation URL when Changes is ready; an incompatible base uses null. The descriptor carries no path-to-file map: the shell derives every URL from the entry’s path through the shared path module.

Static frame-link enhancement uses the same canonical path and activation rules as Browse navigation. Canonical /view/<path>/ pages need no alias or redirect. A fetched redirect document cannot stand in for the real shell page.

Every shell page embeds consistent shell metadata. In-shell navigation, reload, Back/Forward, new tabs, and browser-normalized response URLs retain the same entry identity and existing scroll/disclosure behavior. Unknown paths are unavailable; the shell renders only entries present in its catalogue read model and never invents a catch-all route.

A host serving view/<path>/index.html for /view/<path>, /view/<path>/, or /view/<path>/index.html needs no adapter: parseViewHref in the artifact path contract accepts all three forms and providerNormalizedHtmlPath names the form a host serves. The shell resolves the path to a current or removed entry; a removed entry needs no query when its path is unique, and a present snapshot must match the published record. Unknown, stale, and mismatched identities remain unavailable.

An exported page embeds a compact shell bootstrap containing its path, shell context, catalogue identity, and content/evidence revisions. It references the single owned /mokly-viewer/catalogue.json; it does not repeat the catalogue read model in every HTML document. Before hydration, the standalone entry first validates the root delivery descriptor, then fetches that exact same-origin path with cache: no-store and omitted credentials. The response must remain on that path, pass the public catalogue reader, carry the finalized deployment identity, and match both bootstrap revisions and the catalogue identity. Only then may React hydrate the existing server tree. A missing, redirected, malformed, or mismatched catalogue leaves the complete server-rendered page and its ordinary links in place without installing partial interaction. Serve retains its self-contained entry-scoped read model, performs no initial catalogue fetch, and never puts omitted in the shared catalogue.

The pre-hydration disclosure and navigation-width handoff remains active until the asynchronous static catalogue resolution reaches the actual hydration boundary. A native choice made after load but before that resolution wins over stored state and hydrates without a mismatch. Static destination-page evidence resolves the compact destination bootstrap against the already installed catalogue after deployment fencing; it does not issue another catalogue fetch.

Each exported screen and component page also embeds its path-scoped workspace evidence as inert JSON. After an in-shell route transition, React may read the destination’s canonical shell page to recover evidence that is intentionally absent from the public catalogue, including affected consumers, related components, supplied-input changes, and resource evidence. This read never swaps or executes fetched markup. Accept only one shell bootstrap and one workspace payload from a successful same-origin response whose final path is an accepted /view/<path>/ form identifying the requested entry. The root delivery descriptor must retain the mounted deployment and comparison URL; the bootstrap must retain the catalogue identity, revisions, base, and exact destination path; and the workspace entry must match that path and kind. Abort the read when navigation replaces the path. A rejected, failed, or obsolete read leaves the already-committed public workspace in place and never falls back to a live endpoint.

Preserve the existing navigation contract: trusted ownership-checked link markers only, immediate-frame parent enhancement, portable fallback hrefs, sandbox restrictions, latest-wins cancellation, focus, announcements, active-tree visibility, search/filter state, and fragment grammar. The static fragment handler applies a single validated fragment query to all current light/dark sources; a use case applies it only to its first step. Invalid/duplicate fragment values are not injected, and a direct URL with a syntactically valid missing anchor retains the current static fallback.

Static Comparisons

Each export packages one complete comparison, with all referenced before/after documents and transitive resources under the same generation root:

text
mokly-viewer/diffs/generations/<generation>/review.json

Retain the engine’s JSON and document bytes and relative snapshot paths. Do not change the review schema or rebase only some of its resource references.

The static descriptor points directly to this immutable JSON URL. Shell diffs.tsx requests it only for a selected diff and resolves snapshots from the actual response URL. Generic export needs no redirect from /mokly-viewer/diffs/review.json. Development keeps its stable endpoint; the repository’s Cloudflare adapter keeps its stable redirect for compatibility.

Current remains the default after navigation/reload. Browsing, Changes filtering, and scheme/viewport switches do not request comparison JSON or snapshot files. An export with Changes carries the same per-view states as Serve, so its shell marks hidden changed views and lists them in Details without fetching that JSON. Added entries retain their current preview without comparison modes. Removed screens and pages retain no comparison modes; selecting one requests the packaged previous version delivered under removed previews. Side by side, Overlay, and Difference retain the existing UI and missing-current state for Removed component variants. Refresh/retry reload the same exported generation; only another export and deployment produces new comparison content. An open tab retains its loaded deployment’s descriptor; reload the page to adopt a newer deployment. In-shell navigation encountering a different deployment identity performs a full page load rather than mixing its new route with the old catalogue navigation. Hosts may retain prior generations for old tabs; if they remove them, the existing comparison failure state applies until page reload. Cancellation and failure keep the catalogue usable and cannot replace a different screen.

Exclude the watcher entrypoint, event stream connections, and all Node/server modules. The browser graph must be complete without unused server dependencies. All product data, counts, and comparison results come from the real captured catalogue and Git inputs. No publishing, sandbox, or environment labels are added to product screens. The existing light/dark, mobile/desktop shell design applies.

When the selected base was built by an earlier Mokly, export contains no comparison generation, sets comparisonUrl: null, and advertises Changes as unavailable while still completing successfully, exactly as defined by baseline compatibility.

Browser Runtime And Deployment Identity

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

Browser Acceptance

Use a basic HTTP fixture that serves exact files and directory indexes, handles GET/HEAD with proper MIME types, and supplies no Mokly or provider rewrites. Copy only the export to a separate directory before serving; its requests must not reach the consumer project, .git, Node modules, or a live Mokly process.

Verify direct and reloaded nested pages with and without enhancement, provider-normalized URLs, folder navigation, search/tags/Changes, details, both viewports and schemes, use cases, component variants, ordinary fallback links, all logical-link activation modes, fragments, Back/Forward, and removed screens. Assert that every local request resolves and that Current makes no comparison or event-stream requests.

Exercise all three diff modes, explicit missing sides, ignored/shared impacts, refresh, errors, interrupted navigation, and resource isolation after the source tree changes or disappears. Retain browser coverage under the actual Cloudflare Pages local runtime for normalized URLs, stable redirects, headers, and the repository preview’s existing public URLs. New delivery plumbing does not add or redesign a screen; visual smoke tests reuse the owning mobile/desktop mocks.