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

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 (112) hide show
  1. package/CHANGELOG.md +61 -1
  2. package/README.md +2 -2
  3. package/dist/canvas-core/chrome-css.d.ts +24 -0
  4. package/dist/canvas-core/chrome-css.js +403 -0
  5. package/dist/canvas-core/chrome.d.ts +49 -0
  6. package/dist/canvas-core/chrome.js +147 -0
  7. package/dist/canvas-core/frame.d.ts +20 -0
  8. package/dist/canvas-core/frame.js +23 -0
  9. package/dist/canvas-core/hidden-tabs.d.ts +16 -0
  10. package/dist/canvas-core/hidden-tabs.js +84 -0
  11. package/dist/canvas-core/resolve-target.js +55 -0
  12. package/dist/canvas-core/scroll.d.ts +10 -0
  13. package/dist/canvas-core/scroll.js +15 -0
  14. package/dist/canvas-core/section-dom.d.ts +6 -0
  15. package/dist/canvas-core/section-dom.js +43 -0
  16. package/dist/canvas-core/srcdoc.d.ts +8 -0
  17. package/dist/canvas-core/srcdoc.js +77 -0
  18. package/dist/canvas-core/types.d.ts +76 -0
  19. package/dist/canvas-core.d.ts +9 -0
  20. package/dist/canvas-core.js +9 -0
  21. package/dist/cli/check/css.js +2 -2
  22. package/dist/cli/check/loader.js +1 -1
  23. package/dist/cli/check/stages/deps.js +1 -1
  24. package/dist/cli/check/stages/render.js +3 -2
  25. package/dist/cli/check/stages/size.js +2 -2
  26. package/dist/cli/check/stages/tree.js +1 -1
  27. package/dist/cli/check/stages/validate/index.js +1 -1
  28. package/dist/cli/check/stages/validate/nav-contract.js +4 -3
  29. package/dist/cli/check/stages/validate/orchestrator.js +1 -1
  30. package/dist/cli/check/workspace.js +2 -2
  31. package/dist/cli/dev/build-css.js +1 -1
  32. package/dist/cli/dev/discover.js +2 -2
  33. package/dist/cli/dev/inspect.js +1 -1
  34. package/dist/cli/dev/island-bootstrap.js +1 -1
  35. package/dist/cli/dev/island-canvas-bootstrap.js +37 -5
  36. package/dist/cli/dev/section-page.js +1 -1
  37. package/dist/cli/dev/server.js +12 -5
  38. package/dist/cli/dev/tailwind.js +1 -1
  39. package/dist/cli/dev/tsconfig-paths-plugin.js +1 -1
  40. package/dist/cli/dev/workspace.js +2 -2
  41. package/dist/cli/link/index.js +1 -1
  42. package/dist/cli/link/overlay.js +1 -1
  43. package/dist/cli/new/emit.js +1 -1
  44. package/dist/cli/new/index.js +1 -1
  45. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +4 -4
  46. package/dist/cli/new/scaffold-assets.js +2 -2
  47. package/dist/cli/pack/collect.js +1 -1
  48. package/dist/cli/pack/guards.js +1 -1
  49. package/dist/cli/pack/index.js +1 -1
  50. package/dist/cli/theme/generate.js +1 -1
  51. package/dist/cli/theme/load-theme.js +1 -1
  52. package/dist/dev-client/assets/index-CJa9G1Na.css +1 -0
  53. package/dist/dev-client/assets/index-C_1atZs4.js +450 -0
  54. package/dist/dev-client/index.html +2 -2
  55. package/dist/fixtures/sample-property.d.ts +4 -7
  56. package/dist/fixtures/sample-property.js +41 -97
  57. package/dist/fixtures/scenario.d.ts +1 -1
  58. package/dist/fixtures/scenarios/for-rent-condo.js +24 -44
  59. package/dist/fixtures/scenarios/luxury-multi-unit.js +1 -1
  60. package/dist/fixtures/scenarios/sparse-single-family.js +14 -26
  61. package/dist/fixtures.d.ts +2 -2
  62. package/dist/fixtures.js +1 -2
  63. package/dist/islands/client-directive.d.ts +7 -0
  64. package/dist/{eslint/is-client-file.js → islands/client-directive.js} +6 -3
  65. package/dist/islands/detect.d.ts +1 -1
  66. package/dist/islands/detect.js +1 -1
  67. package/dist/islands/discover.js +1 -1
  68. package/dist/islands/esbuild-plugin.js +1 -1
  69. package/dist/islands/wrap.js +1 -1
  70. package/dist/manifest.json +940 -0
  71. package/dist/media/pack.d.ts +49 -0
  72. package/dist/media/pack.js +69 -0
  73. package/dist/media/resolve-fixture-media.js +73 -0
  74. package/dist/media/resolve-media.js +195 -0
  75. package/dist/package.js +1 -1
  76. package/dist/rules.d.ts +176 -0
  77. package/dist/rules.js +209 -0
  78. package/dist/schema/property-facts.d.ts +2 -2
  79. package/dist/ssr.d.ts +14 -2
  80. package/dist/ssr.js +28 -13
  81. package/dist/tokens.css +21 -0
  82. package/guide/INDEX.md +2 -2
  83. package/guide/check.md +5 -5
  84. package/guide/dev.md +39 -30
  85. package/guide/eslint.md +13 -10
  86. package/guide/llms.txt +4 -3
  87. package/guide/quickstart.md +1 -1
  88. package/guide/recipes/image-slot-crop.md +6 -6
  89. package/guide/recipes/pick-a-scenario.md +13 -5
  90. package/guide/rules/INDEX.md +3 -2
  91. package/guide/schema-system.md +36 -12
  92. package/guide/upgrading.md +18 -2
  93. package/package.json +8 -4
  94. package/tsconfig.json +1 -1
  95. package/dist/dev-client/assets/index-3Gn34X_P.js +0 -50
  96. package/dist/dev-client/assets/index-DxQeiHfu.css +0 -1
  97. package/dist/eslint/is-client-file.d.ts +0 -4
  98. package/dist/eslint/rules/image-bare-needs-reason.js +0 -43
  99. package/dist/eslint/rules/no-client-directive-in-contract.js +0 -31
  100. package/dist/eslint/rules/no-client-runtime-in-server.js +0 -103
  101. package/dist/eslint/rules/no-css-import-from-render-path.js +0 -29
  102. package/dist/eslint/rules/no-hex.js +0 -188
  103. package/dist/eslint/rules/no-inline-style.js +0 -27
  104. package/dist/eslint/rules/no-nondeterminism.js +0 -48
  105. package/dist/eslint/rules/no-raw-element.js +0 -38
  106. package/dist/eslint/rules/props-from-schema.js +0 -37
  107. package/dist/eslint/rules/require-island-editor.js +0 -74
  108. package/dist/eslint/rules/serializable-island-props.js +0 -112
  109. package/dist/eslint/rules/slot-marker-literal.js +0 -38
  110. package/dist/eslint.d.ts +0 -6
  111. package/dist/eslint.js +0 -80
  112. package/dist/rules/registry.js +0 -16
@@ -1,5 +1,6 @@
1
1
  import { FixtureModuleShape, buildInvariantFixtures } from "../../../schema/fixture-schema.js";
2
2
  import { loadSection } from "../loader.js";
3
+ import { resolveFixtureSetMedia } from "../../../media/resolve-fixture-media.js";
3
4
  import { flattenLayout } from "../../../schema/rows.js";
4
5
  import { renderSectionHtml } from "../../../ssr.js";
5
6
  import { checkRenderInvariants } from "../render-invariants.js";
@@ -50,11 +51,11 @@ async function renderStage(workspaceRoot, template) {
50
51
  let fixtures;
51
52
  try {
52
53
  loaded = await loadSection(workspaceRoot, section);
53
- fixtures = buildInvariantFixtures({
54
+ fixtures = resolveFixtureSetMedia(buildInvariantFixtures({
54
55
  schema: loaded.schema,
55
56
  module: FixtureModuleShape.parse(loaded.fixtures),
56
57
  selfAnchor: section.name
57
- });
58
+ }));
58
59
  } catch {
59
60
  continue;
60
61
  }
@@ -2,9 +2,9 @@ import { loadEsbuild } from "../resolve-tool.js";
2
2
  import { ASSET_LOADERS, ASSET_NAMES, ASSET_PUBLIC_PATH, compileSectionPackageCss, sectionCssFiles } from "../css.js";
3
3
  import { CHECK_CONFIG } from "../config.js";
4
4
  import { findSectionIslands } from "../../../islands/discover.js";
5
- import { existsSync } from "node:fs";
6
- import { basename, join, relative } from "node:path";
7
5
  import { readFile } from "node:fs/promises";
6
+ import { basename, join, relative } from "node:path";
7
+ import { existsSync } from "node:fs";
8
8
  import { gzipSync } from "node:zlib";
9
9
 
10
10
  //#region src/cli/check/stages/size.ts
@@ -2,8 +2,8 @@ import { ASSET_LOADERS, findImportLine, sectionCssFiles } from "../css.js";
2
2
  import { CONTRACT_FILES, loadSection } from "../loader.js";
3
3
  import { flattenLayout } from "../../../schema/rows.js";
4
4
  import { referencedSectionAssets } from "../section-assets.js";
5
- import { extname, join, relative } from "node:path";
6
5
  import { readFile, readdir } from "node:fs/promises";
6
+ import { extname, join, relative } from "node:path";
7
7
 
8
8
  //#region src/cli/check/stages/tree.ts
9
9
  const ASSET_EXTENSIONS = new Set(Object.keys(ASSET_LOADERS));
@@ -1,8 +1,8 @@
1
1
  import { MissingContractFile, MissingSectionExport, loadSection } from "../../loader.js";
2
2
  import { relativizePaths } from "../../relativize.js";
3
3
  import { validateFixtureModule, validateOneSection, validateTemplateManifest } from "./orchestrator.js";
4
- import { existsSync } from "node:fs";
5
4
  import { join } from "node:path";
5
+ import { existsSync } from "node:fs";
6
6
 
7
7
  //#region src/cli/check/stages/validate/index.ts
8
8
  async function validateStage(workspaceRoot, template) {
@@ -1,12 +1,13 @@
1
1
  import { FixtureModuleShape, buildFixtureSet } from "../../../../schema/fixture-schema.js";
2
2
  import { TemplateManifest } from "../../../../schema/manifest.js";
3
3
  import { loadSection } from "../../loader.js";
4
+ import { resolveFixtureSetMedia } from "../../../../media/resolve-fixture-media.js";
4
5
  import { renderSectionHtml } from "../../../../ssr.js";
5
6
  import { resolveSectionRef } from "../../../../schema/resolve-section-ref.js";
6
7
  import { materializeSection } from "./orchestrator.js";
7
8
  import { resolveAnchorMap } from "../../../../schema/section-nav.js";
8
- import { join } from "node:path";
9
9
  import { readFile } from "node:fs/promises";
10
+ import { join } from "node:path";
10
11
 
11
12
  //#region src/cli/check/stages/validate/nav-contract.ts
12
13
  const ALLOWED_EXTERNAL_ANCHORS = ["#privacy-policy"];
@@ -68,11 +69,11 @@ async function assertNavContract(workspaceRoot, template) {
68
69
  let html = "";
69
70
  for (const section of composed) {
70
71
  const { anchor, schema } = materializedByName.get(section.name);
71
- const base = buildFixtureSet({
72
+ const base = resolveFixtureSetMedia(buildFixtureSet({
72
73
  schema,
73
74
  module: FixtureModuleShape.parse(fixturesByName.get(section.name)),
74
75
  selfAnchor: anchor
75
- }).find((f) => f.id === "typical");
76
+ })).find((f) => f.id === "typical");
76
77
  if (!base) throw new Error(`Template "${template.key}": section "${section.name}" has no "typical" base fixture.`);
77
78
  html += renderSectionHtml(rendererByName.get(section.name), {
78
79
  slots: base.props.slots,
@@ -18,9 +18,9 @@ import { assertRowContract } from "./row-contract.js";
18
18
  import { assertSelectContract } from "./select-contract.js";
19
19
  import { assertSourceContract } from "./source-contract.js";
20
20
  import { assertVariantContract } from "./variant-contract.js";
21
- import { join } from "node:path";
22
21
  import { isDeepStrictEqual } from "node:util";
23
22
  import { readFile } from "node:fs/promises";
23
+ import { join } from "node:path";
24
24
 
25
25
  //#region src/cli/check/stages/validate/orchestrator.ts
26
26
  function serializeVariants(variants) {
@@ -1,5 +1,5 @@
1
- import { dirname, join, resolve } from "node:path";
2
1
  import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { dirname, join, resolve } from "node:path";
3
3
 
4
4
  //#region src/cli/check/workspace.ts
5
5
  const isDir = async (p) => {
@@ -22,7 +22,7 @@ async function findWorkspaceRoot(from) {
22
22
  for (;;) {
23
23
  if (await isFile(join(dir, "package.json")) && await isDir(join(dir, "templates"))) return dir;
24
24
  const parent = dirname(dir);
25
- if (parent === dir) throw new Error("not inside a workspace — no parent directory has both a package.json and a templates/ folder.\nRun this from your workspace root, or create one with `npm create homepages-workspace`.");
25
+ if (parent === dir) throw new Error("not inside a workspace — no parent directory has both a package.json and a templates/ folder.\nRun this from your workspace root, or create one with `npm create @homepages/workspace`.");
26
26
  dir = parent;
27
27
  }
28
28
  }
@@ -2,8 +2,8 @@ import { compileThemeToCss } from "../../design-system/theme.js";
2
2
  import { loadEsbuild } from "../check/resolve-tool.js";
3
3
  import { ASSET_LOADERS, ASSET_NAMES, ASSET_PUBLIC_PATH, sectionCssFiles } from "../check/css.js";
4
4
  import { runTailwind } from "./tailwind.js";
5
- import { basename, join, relative } from "node:path";
6
5
  import { mkdir, readFile, writeFile } from "node:fs/promises";
6
+ import { basename, join, relative } from "node:path";
7
7
 
8
8
  //#region src/cli/dev/build-css.ts
9
9
  /**
@@ -1,6 +1,6 @@
1
- import { existsSync } from "node:fs";
2
- import { join } from "node:path";
3
1
  import { readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { existsSync } from "node:fs";
4
4
 
5
5
  //#region src/cli/dev/discover.ts
6
6
  const REQUIRED_SECTION_FILES = [
@@ -1,8 +1,8 @@
1
1
  import { renderSection } from "./render-section.js";
2
2
  import { collectRenderedSlotIds, computeSlotFills, schemaSlotLines } from "./fill-state.js";
3
3
  import { summarizeHtml } from "./structure-summary.js";
4
- import { join } from "node:path";
5
4
  import { readFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
6
 
7
7
  //#region src/cli/dev/inspect.ts
8
8
  async function inspectSection(vite, args) {
@@ -1,5 +1,5 @@
1
- import { join } from "node:path";
2
1
  import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
3
 
4
4
  //#region src/cli/dev/island-bootstrap.ts
5
5
  const CACHE_DIR = ".tk-dev-cache";
@@ -1,12 +1,35 @@
1
- import { join } from "node:path";
2
1
  import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
3
 
4
4
  //#region src/cli/dev/island-canvas-bootstrap.ts
5
5
  const CACHE_DIR = ".tk-dev-cache";
6
6
  const BOOTSTRAP_FILE = "island-canvas-bootstrap.js";
7
- const BOOTSTRAP_SOURCE = `import { hydrateIslands, unmountIslands } from "@homepages/template-kit/island-runtime";
7
+ const BOOTSTRAP_SOURCE = `import RefreshRuntime from "/@react-refresh";
8
+ import { hydrateIslands, unmountIslands } from "@homepages/template-kit/island-runtime";
8
9
  import { createElement, useLayoutEffect } from "react";
9
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
+
10
33
  const SHIELD_CLASS = "tr-canvas-shield";
11
34
  const LIVE_ATTR = "data-tr-live";
12
35
 
@@ -53,17 +76,26 @@ function makeShield(doc) {
53
76
  }
54
77
 
55
78
  // "fill-self": surface made position:relative, shield appended as its last
56
- // child (unit cards). "fill-parent": shield is a sibling after the surface
57
- // (map canvas, slider viewport). Idempotent in both modes.
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.
58
84
  function installShield(surface, mode) {
59
85
  const doc = surface.ownerDocument;
86
+ const win = doc.defaultView;
60
87
  if (mode === "fill-self") {
61
- const pos = doc.defaultView ? doc.defaultView.getComputedStyle(surface).position : "";
88
+ const pos = win ? win.getComputedStyle(surface).position : "";
62
89
  if (!pos || pos === "static") surface.style.position = "relative";
63
90
  if (Array.from(surface.children).some((c) => c.classList.contains(SHIELD_CLASS))) return;
64
91
  surface.appendChild(makeShield(doc));
65
92
  return;
66
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
+ }
67
99
  const next = surface.nextElementSibling;
68
100
  if (next && next.classList.contains(SHIELD_CLASS)) return;
69
101
  surface.insertAdjacentElement("afterend", makeShield(doc));
@@ -26,7 +26,7 @@ function diagnosticsBanner(section, template, encodedSection) {
26
26
  }
27
27
  function islandBootstrap(islands, bootstrapUrl) {
28
28
  if (Object.keys(islands).length === 0) return "";
29
- const mapJson = JSON.stringify(islands).replace(/</g, "\\u003c");
29
+ const mapJson = JSON.stringify(islands).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
30
30
  return `
31
31
  ${REACT_REFRESH_PREAMBLE}
32
32
  <script>window.__TR_ISLAND_MAP = ${mapJson};<\/script>
@@ -20,10 +20,10 @@ import { writeIslandCanvasBootstrap } from "./island-canvas-bootstrap.js";
20
20
  import { resolveScreenshotPath } from "./screenshot-target.js";
21
21
  import { createDevViteServer } from "./vite-server.js";
22
22
  import { resolveWorkspace } from "./workspace.js";
23
- import { existsSync } from "node:fs";
24
- import { dirname, extname, join, resolve, sep } from "node:path";
25
23
  import { readFile } from "node:fs/promises";
24
+ import { dirname, extname, join, resolve, sep } from "node:path";
26
25
  import { fileURLToPath } from "node:url";
26
+ import { existsSync } from "node:fs";
27
27
  import { createServer } from "node:http";
28
28
 
29
29
  //#region src/cli/dev/server.ts
@@ -69,11 +69,12 @@ async function startDevServer(opts) {
69
69
  const sectionAssets = /* @__PURE__ */ new Map();
70
70
  const islandBootstrapUrl = await writeIslandBootstrap(workspace.root);
71
71
  const canvasIslandBootstrapUrl = await writeIslandCanvasBootstrap(workspace.root);
72
- vite.watcher.on("change", () => {
72
+ vite.watcher.on("change", (path) => {
73
73
  vite.moduleGraph.invalidateAll();
74
74
  cssCache.clear();
75
75
  sectionAssets.clear();
76
- hmr.broadcast("section:changed", "{}");
76
+ if (path.endsWith(".css")) hmr.broadcast("tailwind:changed", "{}");
77
+ else hmr.broadcast("section:changed", "{}");
77
78
  });
78
79
  const ctx = {
79
80
  workspace,
@@ -166,6 +167,7 @@ async function sendTemplatePage(ctx, templateKey, canvas, res) {
166
167
  canvas
167
168
  }));
168
169
  }
170
+ const INITIAL_CSS_REV = "0";
169
171
  async function sendTemplateSchema(ctx, url, res) {
170
172
  const templateKey = url.searchParams.get("template");
171
173
  if (!templateKey) {
@@ -184,6 +186,7 @@ async function sendTemplateSchema(ctx, url, res) {
184
186
  const load = (dir, file) => ctx.vite.ssrLoadModule(join(dir, file));
185
187
  const instances = await resolveManifestInstances(template);
186
188
  const out = [];
189
+ const islands = {};
187
190
  for (const inst of instances) {
188
191
  const { schemaSlots } = await loadSectionBundle(inst.sectionDir, load);
189
192
  out.push({
@@ -192,10 +195,14 @@ async function sendTemplateSchema(ctx, url, res) {
192
195
  fixture: inst.fixture,
193
196
  slotSchema: projectSlotSchema(schemaSlots)
194
197
  });
198
+ Object.assign(islands, await buildSectionIslandMap(ctx.workspace.root, ctx.workspace.templatesDir, inst.sectionDir));
195
199
  }
196
200
  sendJson(res, {
197
201
  template: templateKey,
198
- instances: out
202
+ instances: out,
203
+ islands,
204
+ bootstrapUrl: ctx.canvasIslandBootstrapUrl,
205
+ cssRev: INITIAL_CSS_REV
199
206
  });
200
207
  }
201
208
  async function findSection(ctx, match) {
@@ -1,5 +1,5 @@
1
- import { dirname, join } from "node:path";
2
1
  import { promisify } from "node:util";
2
+ import { dirname, join } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { createRequire } from "node:module";
5
5
  import { execFile } from "node:child_process";
@@ -1,6 +1,6 @@
1
1
  import { resolveFromWorkspace } from "../check/resolve-tool.js";
2
- import { existsSync, statSync } from "node:fs";
3
2
  import { isAbsolute, join, resolve } from "node:path";
3
+ import { existsSync, statSync } from "node:fs";
4
4
 
5
5
  //#region src/cli/dev/tsconfig-paths-plugin.ts
6
6
  const RESOLVE_EXTENSIONS = [
@@ -1,6 +1,6 @@
1
- import { existsSync } from "node:fs";
2
- import { basename, dirname, join, relative, resolve, sep } from "node:path";
3
1
  import { readFile, readdir } from "node:fs/promises";
2
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
3
+ import { existsSync } from "node:fs";
4
4
 
5
5
  //#region src/cli/dev/workspace.ts
6
6
  /** Walk up from `cwd` to the nearest ancestor that contains a `templates/` dir. */
@@ -1,8 +1,8 @@
1
1
  import { KIT_PKG_SUBPATH, findKitRoot, overlayKit, readLinkMarker } from "./overlay.js";
2
2
  import { findWorkspaceRoot } from "../check/workspace.js";
3
- import { join } from "node:path";
4
3
  import { parseArgs, promisify } from "node:util";
5
4
  import { readFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
6
  import { execFile } from "node:child_process";
7
7
 
8
8
  //#region src/cli/link/index.ts
@@ -1,6 +1,6 @@
1
1
  import { KIT_VERSION } from "../../index.js";
2
- import { dirname, join, resolve } from "node:path";
3
2
  import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { dirname, join, resolve } from "node:path";
4
4
 
5
5
  //#region src/cli/link/overlay.ts
6
6
  const KIT_PKG_SUBPATH = join("node_modules", "@homepages", "template-kit");
@@ -1,5 +1,5 @@
1
- import { join, relative } from "node:path";
2
1
  import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
3
 
4
4
  //#region src/cli/new/emit.ts
5
5
  const RENAME = {
@@ -1,8 +1,8 @@
1
1
  import { findWorkspaceRoot } from "../check/workspace.js";
2
2
  import { emitTree } from "./emit.js";
3
3
  import { sectionAssetsDir, templateAssetsDir } from "./scaffold-assets.js";
4
- import { existsSync } from "node:fs";
5
4
  import { join } from "node:path";
5
+ import { existsSync } from "node:fs";
6
6
 
7
7
  //#region src/cli/new/index.ts
8
8
  const KEY_RE = /^[a-z][a-z0-9-]*$/;
@@ -1,5 +1,5 @@
1
1
  import type { FixtureModule } from "@homepages/template-kit";
2
- import { luxuryMultiUnit, mkResponsive } from "@homepages/template-kit/fixtures";
2
+ import { luxuryMultiUnit } from "@homepages/template-kit/fixtures";
3
3
 
4
4
  // Base content the section is designed for. Nullability coverage (each optional
5
5
  // slot rendered empty) is derived automatically from schema.ts — no fixture
@@ -12,12 +12,12 @@ const fixtures: FixtureModule = {
12
12
  base: {
13
13
  slots: {
14
14
  headline: luxuryMultiUnit.facts.address.display,
15
+ // State the asset id, exactly as a saved page does — the preview server
16
+ // resolves it to a URL and a responsive ladder, the same way the API does
17
+ // when the page is published. A fixture never states a URL.
15
18
  hero_image: {
16
19
  photo_id: heroImage.id,
17
- url: heroImage.url,
18
20
  alt: heroImage.alt,
19
- // Height set to a 16:9 ratio to match the slot's locked crop.
20
- responsive: mkResponsive(heroImage.url, 2048, 1152),
21
21
  },
22
22
  blurb: "A 12-unit mansard Victorian in Jamaica Plain, moments from the Emerald Necklace.",
23
23
  },
@@ -1,13 +1,13 @@
1
- import { existsSync } from "node:fs";
2
1
  import { dirname, join } from "node:path";
3
2
  import { fileURLToPath } from "node:url";
3
+ import { existsSync } from "node:fs";
4
4
 
5
5
  //#region src/cli/new/scaffold-assets.ts
6
6
  const here = dirname(fileURLToPath(import.meta.url));
7
7
  function assetsRoot() {
8
8
  const dist = join(here, "scaffold");
9
9
  if (existsSync(dist)) return dist;
10
- return join(here, "..", "..", "..", "..", "create-homepages-workspace", "scaffold");
10
+ return join(here, "..", "..", "..", "..", "create-workspace", "scaffold");
11
11
  }
12
12
  function templateAssetsDir() {
13
13
  return join(assetsRoot(), "template");
@@ -1,5 +1,5 @@
1
- import { join, relative, sep } from "node:path";
2
1
  import { readFile, readdir } from "node:fs/promises";
2
+ import { join, relative, sep } from "node:path";
3
3
  import { createHash } from "node:crypto";
4
4
 
5
5
  //#region src/cli/pack/collect.ts
@@ -1,7 +1,7 @@
1
1
  import { readLinkMarker } from "../link/overlay.js";
2
2
  import { z } from "zod";
3
- import { join } from "node:path";
4
3
  import { readFile, stat } from "node:fs/promises";
4
+ import { join } from "node:path";
5
5
 
6
6
  //#region src/cli/pack/guards.ts
7
7
  const depsSchema = z.object({
@@ -4,9 +4,9 @@ import { collectTemplateFiles, contentHash } from "./collect.js";
4
4
  import { preflightGuards } from "./guards.js";
5
5
  import { buildPackManifest } from "./manifest.js";
6
6
  import { buildZip } from "./zip.js";
7
- import { join } from "node:path";
8
7
  import { parseArgs } from "node:util";
9
8
  import { readFile, writeFile } from "node:fs/promises";
9
+ import { join } from "node:path";
10
10
 
11
11
  //#region src/cli/pack/index.ts
12
12
  function refusal(lines) {
@@ -1,7 +1,7 @@
1
1
  import { TokenThemeSchema, compileThemeToCss } from "../../design-system/theme.js";
2
2
  import { loadTheme } from "./load-theme.js";
3
- import { join } from "node:path";
4
3
  import { writeFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
5
 
6
6
  //#region src/cli/theme/generate.ts
7
7
  const OUT_FILE = "theme.css";
@@ -1,6 +1,6 @@
1
1
  import { loadEsbuild } from "../check/resolve-tool.js";
2
- import { join } from "node:path";
3
2
  import { mkdir, readFile, rm, stat } from "node:fs/promises";
3
+ import { join } from "node:path";
4
4
  import { createHash } from "node:crypto";
5
5
  import { pathToFileURL } from "node:url";
6
6
 
@@ -0,0 +1 @@
1
+ @theme inline{ --color-canvas: #ffffff; --color-line: #ebebe6; }:root{--frame: clamp(1px, calc(1px + (100vw - 1536px) * .0006076), 1.41667px);--content-follow: .25;--content: calc(1px + (var(--frame) - 1px) * var(--content-follow))}*{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%;background:#0f1115;color:#e6e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,Helvetica,Arial,sans-serif}a{color:inherit;text-decoration:none}button,select,input,textarea{font:inherit;color:inherit;background:#1a1d24;border:1px solid #2c313a;border-radius:4px;padding:6px 10px}button{cursor:pointer}button:hover{background:#232831}button:disabled{opacity:.4;cursor:default}.empty{padding:40px;color:#9ca0a8;text-align:center}.boot-error{padding:24px;color:#ff8b8b;font:13px/1.5 ui-monospace,Menlo,monospace;white-space:pre-wrap}.canvas-mirror{display:flex;flex-direction:column;height:100%;min-height:0}.canvas-iframe{width:100%;height:100%;border:0;background:#fff}.home{max-width:880px;margin:0 auto;padding:40px 24px}.home-header{margin-bottom:28px}.home-title{font-size:15px;font-weight:600}.home-tagline{font-size:13px;color:#9ca0a8;margin-top:4px}.home-template{display:inline-flex;align-items:center;gap:8px;font-size:12px;color:#9ca0a8;margin-bottom:24px}.home-template select{min-width:160px}.home-tools{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.tool-card{display:flex;flex-direction:column;gap:12px;background:#14171c;border:1px solid #21252d;border-radius:8px;padding:20px}.tool-card h2{font-size:14px;font-weight:600;margin:0}.tool-card p{font-size:13px;color:#9ca0a8;margin:0;line-height:1.5}.tool-card .controls{display:flex;flex-direction:column;gap:8px}.tool-card label{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12px;color:#9ca0a8}.tool-card label select{min-width:140px}.open-btn{margin-top:auto;text-align:center;padding:8px 12px;border:1px solid #2c313a;border-radius:6px;background:#1a1d24;color:inherit;cursor:pointer}.open-btn:hover{background:#232831}.open-btn:disabled{opacity:.4;cursor:default}.home-hint{font-size:12px;color:#6b7280}.canvas-mirror__stage{position:relative;min-height:0;flex:1;overflow:hidden}.canvas-mirror__stage .canvas-iframe{position:absolute;top:0;right:0;bottom:0;left:0}.canvas-mirror__sidebar{position:absolute;top:20px;right:20px;bottom:20px;z-index:20;width:calc(400 * var(--frame));display:flex;flex-direction:column;min-height:0}@media(max-width:1100px){.canvas-mirror__sidebar{width:388px}}.instance-panel{flex:1 1 auto;min-height:0;overflow-y:auto;border-left:1px solid #e5e7eb;padding:12px;background:#fff;font:13px system-ui,sans-serif;display:flex;flex-direction:column;gap:10px}.instance-panel__head{display:flex;align-items:flex-end;justify-content:space-between;gap:8px;padding-bottom:10px;border-bottom:1px solid #e5e7eb}.instance-panel__slot{display:flex;align-items:center;gap:8px}.instance-panel__type{color:#6b7280}.instance-panel__fill{margin-left:auto;padding:1px 7px;border-radius:999px;font-size:11px}.instance-panel__fill--filled{background:#dcfce7;color:#166534}.instance-panel__fill--empty{background:#f3f4f6;color:#6b7280}.instance-panel__fill--missing{background:#fee2e2;color:#991b1b}.instance-panel__fixture{display:flex;flex-direction:column;gap:3px;color:#6b7280}.instance-panel__json{width:100%;min-height:180px;font:12px ui-monospace,monospace;resize:vertical}.instance-panel__error{color:#b91c1c;white-space:pre-wrap}.instance-panel__actions{display:flex;gap:8px}.instance-panel__slot-editor{display:flex;flex-direction:column;gap:8px;padding:10px 0;border-top:1px solid #f0f1f3}.instance-panel__slot-editor:first-of-type{border-top:0;padding-top:0}.instance-panel__slot-editor--selected{border-left:3px solid #b8860b;padding-left:9px;margin-left:-12px}