import type { Manifest } from '../core/manifest'
import type { ThemeSignal } from '../index'
import type { Theme } from '../ui/shell-core'
import {
resolveThemeSignals,
themeRootAttrs,
themeSignalsSeedScript,
} from './theme-signals'
/**
* Production HTML document templates for a published build. They mirror the dev
* server's documents but drop every development-only inject — no live-reload SSE
* client, no `process/Bun is not defined` error overlay — and reference the
* content-hashed asset URLs the production bundle emits (so a host can cache them
* indefinitely). The React trees themselves are produced by the *shared*
* renderers (`ssr-shell`, `ssr-render`, `ssr-primer`); only the envelope here
* differs from dev, by necessity (hashed assets vs. a fixed dev path).
*/
const FONT_LINKS =
'' +
'' +
''
/** Content-hashed entry URLs the production build emitted (already base-prefixed).
* `render` is a per-component map (componentId → bundle URL): each component is
* built into its own bundle so the catalog is never built as one graph. */
export interface DocAssets {
browser: string
render: Record
primer: string
/** Resolves each externalized bare specifier (the shared runtime libraries) to
* its one content-hashed vendor bundle — the ``
}
/** The browse shell document: pre-rendered chrome + inlined seed, hydrated by the
* browser entry. No dev injects. */
export function shellDoc(opts: {
title: string
tokensCss: string
globalCss: string
vitrineCss: string
theme: Theme
/** The effective theme root signals to emit (see the `theme` config). */
signals: readonly ThemeSignal[]
markup: string
ssr: boolean
manifest: Manifest
a11y: boolean
assets: DocAssets
}): string {
// `color-scheme` matches the theme so user-agent surfaces (scrollbars, default
// control chrome) follow it rather than rendering in their light defaults.
const reset = `html,body{margin:0;height:100%;background:var(--dc-bg)}html{color-scheme:${opts.theme}}`
const rootAttrs = themeRootAttrs(
resolveThemeSignals(opts.theme, opts.signals),
)
const seed = JSON.stringify({
manifest: opts.manifest,
theme: opts.theme,
a11y: opts.a11y,
})
return `${opts.title}${FONT_LINKS}${importMap(opts.assets.importmap)}
${opts.markup}
${themeSignalsSeedScript(opts.signals)}`
}
// The isolated case render document used to live here, alongside the shell and
// primer documents. It now belongs to the DOM substrate (`substrate/dom.ts`):
// the document envelope is where a medium's assumptions live — the theme signals
// on the root, the body surface, the mount, the script tag — so it is the
// substrate's to shape, and both the dev server and this production host render
// it through `substrate.document()`. The shell and primer documents below stay:
// they are the browse *chrome*, which remains a DOM application by design.
/** The primer reading-page document. */
export function primerDoc(opts: {
tokensCss: string
globalCss: string
vitrineCss: string
theme: Theme
/** The effective theme root signals to emit (see the `theme` config). */
signals: readonly ThemeSignal[]
markup: string
ssr: boolean
/** Style-engine output, placed after the static ${opts.headStyles ?? ''}${importMap(opts.assets.importmap)}${opts.markup}${themeSignalsSeedScript(opts.signals)}