@homepages/template-kit 0.9.0-dev-20260719224319 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +29 -59
  2. package/README.md +14 -4
  3. package/dist/contracts/fill-treatments.d.ts +23 -0
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +2 -2
  6. package/dist/islands/discover.js +1 -1
  7. package/dist/islands/esbuild-plugin.js +1 -1
  8. package/dist/islands/wrap.d.ts +9 -1
  9. package/dist/media/resolve-fixture-media.d.ts +22 -0
  10. package/dist/package.js +1 -1
  11. package/dist/schema/resolve-section-ref.d.ts +4 -0
  12. package/dist/schema/rows.d.ts +9 -0
  13. package/dist/schema/section-nav.d.ts +5 -1
  14. package/dist/schema/slot-types.d.ts +1 -1
  15. package/dist/ssr.d.ts +7 -2
  16. package/dist/ssr.js +6 -2
  17. package/guide/INDEX.md +5 -0
  18. package/guide/check.md +1 -0
  19. package/guide/dev.md +1 -0
  20. package/guide/llms.txt +11 -3
  21. package/guide/upgrading.md +12 -8
  22. package/package.json +4 -16
  23. package/dist/canvas-core/chrome-css.d.ts +0 -24
  24. package/dist/canvas-core/chrome-css.js +0 -403
  25. package/dist/canvas-core/chrome.d.ts +0 -49
  26. package/dist/canvas-core/chrome.js +0 -147
  27. package/dist/canvas-core/frame.d.ts +0 -20
  28. package/dist/canvas-core/frame.js +0 -23
  29. package/dist/canvas-core/hidden-tabs.d.ts +0 -16
  30. package/dist/canvas-core/hidden-tabs.js +0 -84
  31. package/dist/canvas-core/resolve-target.js +0 -55
  32. package/dist/canvas-core/scroll.d.ts +0 -10
  33. package/dist/canvas-core/scroll.js +0 -15
  34. package/dist/canvas-core/section-dom.d.ts +0 -6
  35. package/dist/canvas-core/section-dom.js +0 -43
  36. package/dist/canvas-core/srcdoc.d.ts +0 -8
  37. package/dist/canvas-core/srcdoc.js +0 -77
  38. package/dist/canvas-core/types.d.ts +0 -76
  39. package/dist/canvas-core.d.ts +0 -9
  40. package/dist/canvas-core.js +0 -9
  41. package/dist/cli/check/config.js +0 -41
  42. package/dist/cli/check/css.js +0 -131
  43. package/dist/cli/check/diagnostics.js +0 -37
  44. package/dist/cli/check/index.js +0 -94
  45. package/dist/cli/check/loader.js +0 -149
  46. package/dist/cli/check/paths.js +0 -15
  47. package/dist/cli/check/relativize.js +0 -18
  48. package/dist/cli/check/render-invariants.js +0 -24
  49. package/dist/cli/check/resolve-tool.js +0 -38
  50. package/dist/cli/check/section-assets.js +0 -58
  51. package/dist/cli/check/stages/deps.js +0 -337
  52. package/dist/cli/check/stages/lint.js +0 -46
  53. package/dist/cli/check/stages/render.js +0 -98
  54. package/dist/cli/check/stages/size.js +0 -274
  55. package/dist/cli/check/stages/tree.js +0 -206
  56. package/dist/cli/check/stages/typecheck.js +0 -46
  57. package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +0 -18
  58. package/dist/cli/check/stages/validate/facts.js +0 -19
  59. package/dist/cli/check/stages/validate/formatter-contract.js +0 -14
  60. package/dist/cli/check/stages/validate/group-contract.js +0 -21
  61. package/dist/cli/check/stages/validate/index.js +0 -66
  62. package/dist/cli/check/stages/validate/list-scalar-contract.js +0 -22
  63. package/dist/cli/check/stages/validate/nav-contract.js +0 -93
  64. package/dist/cli/check/stages/validate/nested-slot-contract.js +0 -12
  65. package/dist/cli/check/stages/validate/object-list-contract.js +0 -31
  66. package/dist/cli/check/stages/validate/orchestrator.js +0 -241
  67. package/dist/cli/check/stages/validate/produced-by-contract.js +0 -18
  68. package/dist/cli/check/stages/validate/row-contract.js +0 -31
  69. package/dist/cli/check/stages/validate/select-contract.js +0 -13
  70. package/dist/cli/check/stages/validate/source-contract.js +0 -21
  71. package/dist/cli/check/stages/validate/variant-contract.js +0 -15
  72. package/dist/cli/check/workspace.js +0 -99
  73. package/dist/cli/dev/build-css.js +0 -97
  74. package/dist/cli/dev/catalog.js +0 -35
  75. package/dist/cli/dev/compose-template.js +0 -39
  76. package/dist/cli/dev/content-override.js +0 -58
  77. package/dist/cli/dev/diagnostics.js +0 -68
  78. package/dist/cli/dev/discover.js +0 -35
  79. package/dist/cli/dev/fill-state.js +0 -77
  80. package/dist/cli/dev/index.js +0 -42
  81. package/dist/cli/dev/inspect.js +0 -38
  82. package/dist/cli/dev/island-bootstrap.js +0 -38
  83. package/dist/cli/dev/island-canvas-bootstrap.js +0 -204
  84. package/dist/cli/dev/island-map.js +0 -35
  85. package/dist/cli/dev/island-transform.js +0 -31
  86. package/dist/cli/dev/manifest-instances.js +0 -31
  87. package/dist/cli/dev/render-section.js +0 -24
  88. package/dist/cli/dev/screenshot-target.js +0 -24
  89. package/dist/cli/dev/section-page.js +0 -91
  90. package/dist/cli/dev/server.js +0 -511
  91. package/dist/cli/dev/slot-schema.js +0 -12
  92. package/dist/cli/dev/structure-summary.js +0 -119
  93. package/dist/cli/dev/tailwind.js +0 -32
  94. package/dist/cli/dev/tsconfig-paths-plugin.js +0 -90
  95. package/dist/cli/dev/vite-server.js +0 -39
  96. package/dist/cli/dev/workspace.js +0 -63
  97. package/dist/cli/link/index.js +0 -74
  98. package/dist/cli/link/overlay.js +0 -59
  99. package/dist/cli/link/watch.js +0 -25
  100. package/dist/cli/new/emit.js +0 -48
  101. package/dist/cli/new/index.js +0 -69
  102. package/dist/cli/new/scaffold/section/_Renderer.tsx +0 -20
  103. package/dist/cli/new/scaffold/section/_fill-spec.ts +0 -18
  104. package/dist/cli/new/scaffold/section/_fixtures.ts +0 -13
  105. package/dist/cli/new/scaffold/section/_schema.ts +0 -24
  106. package/dist/cli/new/scaffold/template/_manifest.json +0 -9
  107. package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +0 -45
  108. package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +0 -40
  109. package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +0 -14
  110. package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +0 -29
  111. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +0 -41
  112. package/dist/cli/new/scaffold/template/sections/hero/schema.ts +0 -60
  113. package/dist/cli/new/scaffold/template/theme.css +0 -24
  114. package/dist/cli/new/scaffold/template/theme.ts +0 -27
  115. package/dist/cli/new/scaffold-assets.js +0 -20
  116. package/dist/cli/pack/collect.js +0 -44
  117. package/dist/cli/pack/guards.js +0 -57
  118. package/dist/cli/pack/index.js +0 -66
  119. package/dist/cli/pack/manifest.js +0 -15
  120. package/dist/cli/pack/zip.js +0 -86
  121. package/dist/cli/theme/generate.js +0 -43
  122. package/dist/cli/theme/index.js +0 -33
  123. package/dist/cli/theme/load-theme.js +0 -69
  124. package/dist/cli.d.ts +0 -1
  125. package/dist/cli.js +0 -138
  126. package/dist/dev-client/assets/index-CJa9G1Na.css +0 -1
  127. package/dist/dev-client/assets/index-C_1atZs4.js +0 -450
  128. package/dist/dev-client/index.html +0 -13
  129. package/dist/tokens.css +0 -21
@@ -1,204 +0,0 @@
1
- import { mkdir, writeFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
-
4
- //#region src/cli/dev/island-canvas-bootstrap.ts
5
- const CACHE_DIR = ".tk-dev-cache";
6
- const BOOTSTRAP_FILE = "island-canvas-bootstrap.js";
7
- const BOOTSTRAP_SOURCE = `import RefreshRuntime from "/@react-refresh";
8
- import { hydrateIslands, unmountIslands } from "@homepages/template-kit/island-runtime";
9
- import { createElement, useLayoutEffect } from "react";
10
-
11
- // React Refresh preamble. @vitejs/plugin-react compiles every client component
12
- // to assert this ran and throws "can't detect preamble" otherwise, so any page
13
- // that was not built by Vite's own index-html transform must supply it.
14
- //
15
- // It lives HERE, in the bootstrap, rather than in the page that loads the
16
- // bootstrap, because the two canvas surfaces reach islands by different routes:
17
- // the server-rendered ?canvas=1 page emits the preamble itself (section-page.ts)
18
- // before this script, but the shared-kernel srcdoc canvas is assembled by
19
- // canvas-core, whose CanvasAssets port carries stylesheets, an island map and
20
- // ONE host script — it has no channel for a dev-only inline prelude, and adding
21
- // one would push a Vite-specific concern into a kernel the app also ships.
22
- // Emitting it from the one module both surfaces already load covers both.
23
- //
24
- // Guarded so the server-rendered page, which already installed it, does not
25
- // re-enter injectIntoGlobalHook.
26
- if (!window.__vite_plugin_react_preamble_installed__) {
27
- RefreshRuntime.injectIntoGlobalHook(window);
28
- window.$RefreshReg$ = () => {};
29
- window.$RefreshSig$ = () => (type) => type;
30
- window.__vite_plugin_react_preamble_installed__ = true;
31
- }
32
-
33
- const SHIELD_CLASS = "tr-canvas-shield";
34
- const LIVE_ATTR = "data-tr-live";
35
-
36
- const modules = import.meta.glob("/templates/**/*.{tsx,jsx}");
37
- const map = window.__TR_ISLAND_MAP || {};
38
-
39
- // Per-island hydration-commit signal, keyed by the \`editor\` object THIS load()
40
- // call clones for that island — never by island key. A section rendering N of
41
- // one island calls load() N times; keying by key would let the last call's
42
- // commit gate every handle, un-shielding N-1 of them before their own root has
43
- // actually committed.
44
- const commitOf = new WeakMap();
45
-
46
- // Wrap the loaded default export so it reports its own hydration commit.
47
- // Renders no DOM of its own (the tree still matches the SSR markup exactly),
48
- // and useLayoutEffect fires after the commit but before paint, so the canvas
49
- // never shows an unshielded frame.
50
- function withCommitSignal(component, signalCommit) {
51
- return function IslandCommitSignal(props) {
52
- useLayoutEffect(signalCommit, []);
53
- return createElement(component, props);
54
- };
55
- }
56
-
57
- async function load(key) {
58
- const url = map[key];
59
- const importer = url ? modules[url] : undefined;
60
- if (!importer) return Promise.reject(new Error("template-kit dev: no island module for " + key));
61
- const mod = await importer();
62
-
63
- let signalCommit;
64
- const committed = new Promise((resolve) => { signalCommit = resolve; });
65
- const editor = mod.editor ? { ...mod.editor } : undefined;
66
- if (editor) commitOf.set(editor, { committed, signalCommit });
67
-
68
- return { ...mod, editor, default: withCommitSignal(mod.default, signalCommit) };
69
- }
70
-
71
- function makeShield(doc) {
72
- const shield = doc.createElement("div");
73
- shield.className = SHIELD_CLASS;
74
- shield.setAttribute("aria-hidden", "true");
75
- return shield;
76
- }
77
-
78
- // "fill-self": surface made position:relative, shield appended as its last
79
- // child (unit cards). "fill-parent" (map canvas, slider viewport): the
80
- // shield is an inset:0 sibling after the surface, so the surface's PARENT is
81
- // made position:relative instead — otherwise the shield escapes to the
82
- // initial containing block and covers the whole canvas. Idempotent in both
83
- // modes.
84
- function installShield(surface, mode) {
85
- const doc = surface.ownerDocument;
86
- const win = doc.defaultView;
87
- if (mode === "fill-self") {
88
- const pos = win ? win.getComputedStyle(surface).position : "";
89
- if (!pos || pos === "static") surface.style.position = "relative";
90
- if (Array.from(surface.children).some((c) => c.classList.contains(SHIELD_CLASS))) return;
91
- surface.appendChild(makeShield(doc));
92
- return;
93
- }
94
- const parent = surface.parentElement;
95
- if (parent) {
96
- const pos = win ? win.getComputedStyle(parent).position : "";
97
- if (!pos || pos === "static") parent.style.position = "relative";
98
- }
99
- const next = surface.nextElementSibling;
100
- if (next && next.classList.contains(SHIELD_CLASS)) return;
101
- surface.insertAdjacentElement("afterend", makeShield(doc));
102
- }
103
-
104
- function shieldHandle(handle) {
105
- const editor = handle.editor;
106
- if (!editor || !editor.live) return;
107
- const surfaces = editor.liveSurface ? editor.liveSurface(handle.element) : [handle.element];
108
- const mode = editor.shieldMode || "fill-parent";
109
- for (const surface of surfaces) installShield(surface, mode);
110
- // Stamped for island-inspect.ts's read-only panel — not read by any hydration logic.
111
- handle.element.setAttribute("data-tr-island-live", "true");
112
- handle.element.setAttribute("data-tr-shield-mode", mode);
113
- }
114
-
115
- // Every TOP-LEVEL island marker under root, excluding nested ones (a marker
116
- // inside another marker is hydrated by its parent island's own React root —
117
- // see hydrateIslands' own filter in island-runtime; this mirrors it so the
118
- // inspector never double-reports or mis-marks a nested child).
119
- function topLevelMarkers(root) {
120
- return Array.from(root.querySelectorAll("[data-tr-island]")).filter(
121
- (el) => !el.parentElement || !el.parentElement.closest("[data-tr-island]"),
122
- );
123
- }
124
-
125
- let styleInstalled = false;
126
- function ensureShieldStyle() {
127
- if (styleInstalled || document.getElementById("tr-canvas-shield-style")) return;
128
- styleInstalled = true;
129
- const style = document.createElement("style");
130
- style.id = "tr-canvas-shield-style";
131
- style.textContent =
132
- "." + SHIELD_CLASS + "{position:absolute;inset:0;z-index:1;cursor:pointer;background:transparent;}" +
133
- "[" + LIVE_ATTR + "] ." + SHIELD_CLASS + "{display:none;}";
134
- document.head.appendChild(style);
135
- }
136
-
137
- // Hydrate every live island under \`root\`, install its shield(s), and mark every
138
- // top-level island (live or not) with data-tr-island-live for the inspector.
139
- async function hydrateRoot(root) {
140
- ensureShieldStyle();
141
- const before = new Set(topLevelMarkers(root));
142
-
143
- const handles = await hydrateIslands({
144
- root,
145
- load,
146
- shouldHydrate: (ctx) => (ctx.editor ? ctx.editor.live === true : false),
147
- applyEditorProps: true,
148
- onError: (error, ctx) => {
149
- console.error("template-kit dev: canvas island failed to hydrate:", ctx.key, error);
150
- // Settle its commit: a failed island never signals one, and the wait
151
- // below must not stall the rest of this root's shields on it.
152
- if (ctx.editor) { const c = commitOf.get(ctx.editor); if (c) c.signalCommit(); }
153
- },
154
- });
155
-
156
- // hydrateRoot (React's) flushes on a scheduler task, so hydrateIslands
157
- // returns before React has committed. Shielding here would mutate a
158
- // not-yet-hydrated tree: in fill-self mode the shield lands INSIDE a
159
- // React-owned surface, and React then discards the whole SSR tree and
160
- // client-renders, wiping shield and inline position both. Waiting on the
161
- // real commit — not a task-turn guess — is what makes the mutation stick.
162
- await Promise.all(
163
- handles
164
- .map((h) => (h.editor ? commitOf.get(h.editor) : undefined))
165
- .filter((c) => c !== undefined)
166
- .map((c) => c.committed),
167
- );
168
-
169
- for (const handle of handles) {
170
- shieldHandle(handle);
171
- before.delete(handle.element);
172
- }
173
- // Whatever's left in \`before\` is a top-level island that did not pass
174
- // shouldHydrate (editor.live !== true, or no editor at all) — static by its
175
- // own contract; mark it so for the inspector.
176
- for (const el of before) el.setAttribute("data-tr-island-live", "false");
177
-
178
- return handles;
179
- }
180
-
181
- function unmountRoot(root) {
182
- unmountIslands(root);
183
- root.querySelectorAll("." + SHIELD_CLASS).forEach((s) => s.remove());
184
- }
185
-
186
- window.__trCanvasIslands = { hydrateRoot, unmountRoot };
187
- void hydrateRoot(document.body);
188
- `;
189
- /**
190
- * Write the shared canvas bootstrap into the workspace cache dir and return
191
- * the URL the canvas page loads it from. Same mechanics as
192
- * writeIslandBootstrap: lives under the root so Vite serves + transforms it,
193
- * reached via Vite's `/@fs/` prefix.
194
- */
195
- async function writeIslandCanvasBootstrap(root) {
196
- const dir = join(root, CACHE_DIR);
197
- await mkdir(dir, { recursive: true });
198
- const abs = join(dir, BOOTSTRAP_FILE);
199
- await writeFile(abs, BOOTSTRAP_SOURCE, "utf8");
200
- return `/@fs/${abs}`;
201
- }
202
-
203
- //#endregion
204
- export { writeIslandCanvasBootstrap };
@@ -1,35 +0,0 @@
1
- import { islandKey } from "../../islands/detect.js";
2
- import { findSectionIslands } from "../../islands/discover.js";
3
- import { basename, extname, relative, sep } from "node:path";
4
-
5
- //#region src/cli/dev/island-map.ts
6
- /**
7
- * Derive an island key from a client component's absolute path, relative to the
8
- * workspace's `templates/` dir: `<templateFolder>/<section>:<ComponentName>`.
9
- * A component name is the file basename. Returns null for a path that is not a
10
- * `templates/<folder>/sections/<section>/…` component.
11
- */
12
- function islandKeyForFile(templatesDir, absFile) {
13
- const rel = relative(templatesDir, absFile);
14
- if (rel.startsWith("..")) return null;
15
- const parts = rel.split(sep);
16
- if (parts.length < 4 || parts[1] !== "sections") return null;
17
- return islandKey(`${parts[0]}/${parts[2]}`, basename(absFile, extname(absFile)));
18
- }
19
- /**
20
- * The map the browser bootstrap reads: island key → the module's root-relative
21
- * URL (what Vite serves it at, and what `import.meta.glob("/templates/**")` keys
22
- * it by). Empty when the section has no islands.
23
- */
24
- async function buildSectionIslandMap(root, templatesDir, sectionDir) {
25
- const map = {};
26
- for (const file of await findSectionIslands(sectionDir)) {
27
- const key = islandKeyForFile(templatesDir, file);
28
- if (!key) continue;
29
- map[key] = "/" + relative(root, file).split(sep).join("/");
30
- }
31
- return map;
32
- }
33
-
34
- //#endregion
35
- export { buildSectionIslandMap, islandKeyForFile };
@@ -1,31 +0,0 @@
1
- import { wrapClientModuleMagic } from "../../islands/wrap.js";
2
- import { islandKeyForFile } from "./island-map.js";
3
- import { extname, join, sep } from "node:path";
4
-
5
- //#region src/cli/dev/island-transform.ts
6
- const COMPONENT_EXTS = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
7
- function islandTransformPlugin(opts) {
8
- const templatesDir = join(opts.root, "templates");
9
- const templatesPrefix = templatesDir + sep;
10
- return {
11
- name: "template-kit:island-transform",
12
- enforce: "pre",
13
- transform(code, id, options) {
14
- if (!options?.ssr) return null;
15
- const file = id.split("?", 1)[0] ?? id;
16
- if (!file.startsWith(templatesPrefix)) return null;
17
- if (!COMPONENT_EXTS.has(extname(file))) return null;
18
- const key = islandKeyForFile(templatesDir, file);
19
- if (!key) return null;
20
- const s = wrapClientModuleMagic(code, key);
21
- if (s === null) return null;
22
- return {
23
- code: s.toString(),
24
- map: s.generateMap({ hires: true })
25
- };
26
- }
27
- };
28
- }
29
-
30
- //#endregion
31
- export { islandTransformPlugin };
@@ -1,31 +0,0 @@
1
- import { TemplateManifest } from "../../schema/manifest.js";
2
- import { discoverSections } from "./discover.js";
3
- import { readFile } from "node:fs/promises";
4
-
5
- //#region src/cli/dev/manifest-instances.ts
6
- function selectManifestInstances(sections, manifestSections) {
7
- const out = [];
8
- for (const inst of manifestSections) {
9
- const info = sections.find((s) => s.name === inst.section);
10
- if (!info) continue;
11
- out.push({
12
- instanceId: inst.instance_id,
13
- section: inst.section,
14
- sectionDir: info.dir,
15
- fixture: "typical",
16
- options: inst.options
17
- });
18
- }
19
- return out;
20
- }
21
- async function resolveManifestInstances(template) {
22
- const manifest = TemplateManifest.parse(JSON.parse(await readFile(template.manifestPath, "utf8")));
23
- return selectManifestInstances(await discoverSections(template.dir), manifest.sections);
24
- }
25
- /** The single manifest instance with this id, or null (author WIP / unknown id). */
26
- async function findManifestInstance(template, instanceId) {
27
- return (await resolveManifestInstances(template)).find((i) => i.instanceId === instanceId) ?? null;
28
- }
29
-
30
- //#endregion
31
- export { findManifestInstance, resolveManifestInstances, selectManifestInstances };
@@ -1,24 +0,0 @@
1
- import { loadSectionBundle, renderSectionHtml } from "../../ssr.js";
2
- import { applyContentOverride } from "./content-override.js";
3
- import { projectSlotSchema } from "./slot-schema.js";
4
- import { join } from "node:path";
5
-
6
- //#region src/cli/dev/render-section.ts
7
- async function renderSection(vite, req) {
8
- const load = (dir, file) => vite.ssrLoadModule(join(dir, file));
9
- const { Renderer, invariantFixtures, schemaSlots } = await loadSectionBundle(req.sectionDir, load);
10
- const fx = invariantFixtures.find((f) => f.id === req.fixture);
11
- if (!fx) throw new Error(`fixture "${req.fixture}" not found (have: ${invariantFixtures.map((r) => r.id).join(", ")})`);
12
- const props = applyContentOverride(fx.props, req.override);
13
- return {
14
- html: renderSectionHtml(Renderer, props),
15
- resolvedProps: props,
16
- fixtureLabel: fx.label,
17
- slotSchema: projectSlotSchema(schemaSlots),
18
- schemaSlots,
19
- nullCorpus: invariantFixtures.filter((f) => f.id.startsWith("null_")).map((f) => f.id)
20
- };
21
- }
22
-
23
- //#endregion
24
- export { renderSection };
@@ -1,24 +0,0 @@
1
- //#region src/cli/dev/screenshot-target.ts
2
- function resolveScreenshotPath(t) {
3
- if (!t.template) return {
4
- ok: false,
5
- error: "screenshot requires a template"
6
- };
7
- const hasSection = Boolean(t.section);
8
- if (hasSection !== Boolean(t.fixture)) return {
9
- ok: false,
10
- error: "provide section and fixture together for a section screenshot, or neither for a full-template screenshot"
11
- };
12
- const enc = encodeURIComponent;
13
- if (hasSection) return {
14
- ok: true,
15
- path: `/sections/${enc(t.template)}/${enc(t.section)}/${enc(t.fixture)}`
16
- };
17
- return {
18
- ok: true,
19
- path: `/template/${enc(t.template)}`
20
- };
21
- }
22
-
23
- //#endregion
24
- export { resolveScreenshotPath };
@@ -1,91 +0,0 @@
1
- //#region src/cli/dev/section-page.ts
2
- function wrapSectionHtml(opts) {
3
- const t = encodeURIComponent(opts.template);
4
- const s = encodeURIComponent(opts.section);
5
- const v = encodeURIComponent(opts.cssRev);
6
- return `<!doctype html>
7
- <html lang="en">
8
- <head>
9
- <meta charset="utf-8" />
10
- <meta name="viewport" content="width=device-width, initial-scale=1" />
11
- <title>Section Preview</title>
12
- <link rel="stylesheet" href="/runtime/base.css?v=${v}" />
13
- <link rel="stylesheet" href="/runtime/template.css?template=${t}&v=${v}" />
14
- <style>html, body { margin: 0; padding: 0; background: #fff; }</style>
15
- </head>
16
- <body>
17
- <div id="section-host">${opts.sectionHtml}</div>${islandBootstrap(opts.islands, opts.bootstrapUrl)}
18
- <script>${HMR_CLIENT_JS}<\/script>${diagnosticsBanner(opts.section, t, s)}
19
- </body>
20
- </html>`;
21
- }
22
- function diagnosticsBanner(section, template, encodedSection) {
23
- if (section === "") return "";
24
- return `
25
- <script>${diagnosticsClientJs(template, encodedSection)}<\/script>`;
26
- }
27
- function islandBootstrap(islands, bootstrapUrl) {
28
- if (Object.keys(islands).length === 0) return "";
29
- const mapJson = JSON.stringify(islands).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
30
- return `
31
- ${REACT_REFRESH_PREAMBLE}
32
- <script>window.__TR_ISLAND_MAP = ${mapJson};<\/script>
33
- <script type="module" src="${bootstrapUrl}"><\/script>`;
34
- }
35
- const REACT_REFRESH_PREAMBLE = `<script type="module">
36
- import RefreshRuntime from "/@react-refresh";
37
- RefreshRuntime.injectIntoGlobalHook(window);
38
- window.$RefreshReg$ = () => {};
39
- window.$RefreshSig$ = () => (type) => type;
40
- window.__vite_plugin_react_preamble_installed__ = true;
41
- <\/script>`;
42
- const HMR_CLIENT_JS = `
43
- (function () {
44
- try {
45
- var src = new EventSource("/__hmr");
46
- src.addEventListener("section:changed", function () { window.location.reload(); });
47
- src.addEventListener("tailwind:changed", function () {
48
- var link = document.querySelector('link[href*="/runtime/template.css"]');
49
- if (link) {
50
- var href = link.getAttribute("href");
51
- var base = href.split("&v=")[0];
52
- link.setAttribute("href", base + "&v=" + Date.now());
53
- }
54
- });
55
- } catch (e) { /* dev-only; ignore */ }
56
- })();
57
- `.trim();
58
- function diagnosticsClientJs(template, section) {
59
- return `
60
- (function () {
61
- function render(violations) {
62
- var banner = document.createElement("div");
63
- banner.setAttribute("data-diagnostics-banner", "");
64
- banner.style.cssText = "position:sticky;top:0;z-index:9999;background:#fee2e2;color:#7f1d1d;" +
65
- "font:13px/1.4 monospace;padding:8px 12px;border-bottom:1px solid #fca5a5;";
66
- var dismiss = document.createElement("button");
67
- dismiss.textContent = "dismiss";
68
- dismiss.style.cssText = "float:right;cursor:pointer;";
69
- dismiss.onclick = function () { banner.remove(); };
70
- var text = document.createElement("pre");
71
- text.style.cssText = "margin:0;white-space:pre-wrap;";
72
- text.textContent = violations.map(function (v) {
73
- var loc = v.file ? " (" + v.file + (v.line ? ":" + v.line : "") + ")" : "";
74
- return "[" + v.rule + "] " + v.message + loc;
75
- }).join("\\n");
76
- banner.appendChild(dismiss);
77
- banner.appendChild(text);
78
- document.body.insertBefore(banner, document.body.firstChild);
79
- }
80
- fetch("/api/diagnostics?template=${template}&section=${section}")
81
- .then(function (r) { return r.json(); })
82
- .then(function (body) {
83
- if (body.available && body.violations && body.violations.length > 0) render(body.violations);
84
- })
85
- .catch(function () { /* dev-only; ignore */ });
86
- })();
87
- `.trim();
88
- }
89
-
90
- //#endregion
91
- export { wrapSectionHtml };