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

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 (93) hide show
  1. package/CHANGELOG.md +30 -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/size.js +2 -2
  25. package/dist/cli/check/stages/tree.js +1 -1
  26. package/dist/cli/check/stages/validate/index.js +1 -1
  27. package/dist/cli/check/stages/validate/nav-contract.js +1 -1
  28. package/dist/cli/check/stages/validate/orchestrator.js +1 -1
  29. package/dist/cli/check/workspace.js +2 -2
  30. package/dist/cli/dev/build-css.js +1 -1
  31. package/dist/cli/dev/discover.js +2 -2
  32. package/dist/cli/dev/inspect.js +1 -1
  33. package/dist/cli/dev/island-bootstrap.js +1 -1
  34. package/dist/cli/dev/island-canvas-bootstrap.js +37 -5
  35. package/dist/cli/dev/section-page.js +1 -1
  36. package/dist/cli/dev/server.js +9 -3
  37. package/dist/cli/dev/tailwind.js +1 -1
  38. package/dist/cli/dev/tsconfig-paths-plugin.js +1 -1
  39. package/dist/cli/dev/workspace.js +2 -2
  40. package/dist/cli/link/index.js +1 -1
  41. package/dist/cli/link/overlay.js +1 -1
  42. package/dist/cli/new/emit.js +1 -1
  43. package/dist/cli/new/index.js +1 -1
  44. package/dist/cli/new/scaffold-assets.js +2 -2
  45. package/dist/cli/pack/collect.js +1 -1
  46. package/dist/cli/pack/guards.js +1 -1
  47. package/dist/cli/pack/index.js +1 -1
  48. package/dist/cli/theme/generate.js +1 -1
  49. package/dist/cli/theme/load-theme.js +1 -1
  50. package/dist/dev-client/assets/index-CJa9G1Na.css +1 -0
  51. package/dist/dev-client/assets/index-C_1atZs4.js +450 -0
  52. package/dist/dev-client/index.html +2 -2
  53. package/dist/islands/client-directive.d.ts +7 -0
  54. package/dist/{eslint/is-client-file.js → islands/client-directive.js} +6 -3
  55. package/dist/islands/detect.d.ts +1 -1
  56. package/dist/islands/detect.js +1 -1
  57. package/dist/islands/discover.js +1 -1
  58. package/dist/islands/esbuild-plugin.js +1 -1
  59. package/dist/islands/wrap.js +1 -1
  60. package/dist/package.js +1 -1
  61. package/dist/rules.d.ts +176 -0
  62. package/dist/rules.js +209 -0
  63. package/dist/ssr.d.ts +2 -2
  64. package/dist/ssr.js +2 -2
  65. package/dist/tokens.css +21 -0
  66. package/guide/INDEX.md +2 -2
  67. package/guide/check.md +5 -5
  68. package/guide/dev.md +39 -30
  69. package/guide/eslint.md +13 -10
  70. package/guide/llms.txt +4 -3
  71. package/guide/quickstart.md +1 -1
  72. package/guide/rules/INDEX.md +3 -2
  73. package/guide/upgrading.md +18 -2
  74. package/package.json +8 -4
  75. package/tsconfig.json +1 -1
  76. package/dist/dev-client/assets/index-3Gn34X_P.js +0 -50
  77. package/dist/dev-client/assets/index-DxQeiHfu.css +0 -1
  78. package/dist/eslint/is-client-file.d.ts +0 -4
  79. package/dist/eslint/rules/image-bare-needs-reason.js +0 -43
  80. package/dist/eslint/rules/no-client-directive-in-contract.js +0 -31
  81. package/dist/eslint/rules/no-client-runtime-in-server.js +0 -103
  82. package/dist/eslint/rules/no-css-import-from-render-path.js +0 -29
  83. package/dist/eslint/rules/no-hex.js +0 -188
  84. package/dist/eslint/rules/no-inline-style.js +0 -27
  85. package/dist/eslint/rules/no-nondeterminism.js +0 -48
  86. package/dist/eslint/rules/no-raw-element.js +0 -38
  87. package/dist/eslint/rules/props-from-schema.js +0 -37
  88. package/dist/eslint/rules/require-island-editor.js +0 -74
  89. package/dist/eslint/rules/serializable-island-props.js +0 -112
  90. package/dist/eslint/rules/slot-marker-literal.js +0 -38
  91. package/dist/eslint.d.ts +0 -6
  92. package/dist/eslint.js +0 -80
  93. package/dist/rules/registry.js +0 -16
@@ -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
@@ -166,6 +166,7 @@ async function sendTemplatePage(ctx, templateKey, canvas, res) {
166
166
  canvas
167
167
  }));
168
168
  }
169
+ const INITIAL_CSS_REV = "0";
169
170
  async function sendTemplateSchema(ctx, url, res) {
170
171
  const templateKey = url.searchParams.get("template");
171
172
  if (!templateKey) {
@@ -184,6 +185,7 @@ async function sendTemplateSchema(ctx, url, res) {
184
185
  const load = (dir, file) => ctx.vite.ssrLoadModule(join(dir, file));
185
186
  const instances = await resolveManifestInstances(template);
186
187
  const out = [];
188
+ const islands = {};
187
189
  for (const inst of instances) {
188
190
  const { schemaSlots } = await loadSectionBundle(inst.sectionDir, load);
189
191
  out.push({
@@ -192,10 +194,14 @@ async function sendTemplateSchema(ctx, url, res) {
192
194
  fixture: inst.fixture,
193
195
  slotSchema: projectSlotSchema(schemaSlots)
194
196
  });
197
+ Object.assign(islands, await buildSectionIslandMap(ctx.workspace.root, ctx.workspace.templatesDir, inst.sectionDir));
195
198
  }
196
199
  sendJson(res, {
197
200
  template: templateKey,
198
- instances: out
201
+ instances: out,
202
+ islands,
203
+ bootstrapUrl: ctx.canvasIslandBootstrapUrl,
204
+ cssRev: INITIAL_CSS_REV
199
205
  });
200
206
  }
201
207
  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,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}