@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
@@ -4,8 +4,8 @@
4
4
  <meta charset="utf-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>template-kit dev</title>
7
- <script type="module" crossorigin src="/dev-client/assets/index-3Gn34X_P.js"></script>
8
- <link rel="stylesheet" crossorigin href="/dev-client/assets/index-DxQeiHfu.css">
7
+ <script type="module" crossorigin src="/dev-client/assets/index-C_1atZs4.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/dev-client/assets/index-CJa9G1Na.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -0,0 +1,7 @@
1
+ //#region src/islands/client-directive.d.ts
2
+ declare function hasClientDirective(text: string): boolean;
3
+ declare function resetClientFileCache(): void;
4
+ declare function isClientFile(filename: string): boolean;
5
+ declare function resolveImportedFile(specifier: string, fromFile: string): string | null;
6
+ //#endregion
7
+ export { hasClientDirective, isClientFile, resetClientFileCache, resolveImportedFile };
@@ -1,7 +1,7 @@
1
- import { readFileSync, statSync } from "node:fs";
2
1
  import { dirname, resolve } from "node:path";
2
+ import { readFileSync, statSync } from "node:fs";
3
3
 
4
- //#region src/eslint/is-client-file.ts
4
+ //#region src/islands/client-directive.ts
5
5
  function hasClientDirective(text) {
6
6
  let i = 0;
7
7
  while (i < text.length) {
@@ -29,6 +29,9 @@ function hasClientDirective(text) {
29
29
  return false;
30
30
  }
31
31
  const cache = /* @__PURE__ */ new Map();
32
+ function resetClientFileCache() {
33
+ cache.clear();
34
+ }
32
35
  function isClientFile(filename) {
33
36
  const absolute = resolve(filename);
34
37
  let mtimeMs;
@@ -76,4 +79,4 @@ function resolveImportedFile(specifier, fromFile) {
76
79
  }
77
80
 
78
81
  //#endregion
79
- export { hasClientDirective, isClientFile, resolveImportedFile };
82
+ export { hasClientDirective, isClientFile, resetClientFileCache, resolveImportedFile };
@@ -1,4 +1,4 @@
1
- import { hasClientDirective } from "../eslint/is-client-file.js";
1
+ import { hasClientDirective } from "./client-directive.js";
2
2
  //#region src/islands/detect.d.ts
3
3
  /**
4
4
  * True iff `file` is a client component — an on-disk source file whose directive
@@ -1,4 +1,4 @@
1
- import { hasClientDirective, isClientFile } from "../eslint/is-client-file.js";
1
+ import { hasClientDirective, isClientFile } from "./client-directive.js";
2
2
 
3
3
  //#region src/islands/detect.ts
4
4
  /**
@@ -1,6 +1,6 @@
1
1
  import { isClientComponentFile } from "./detect.js";
2
- import { extname, join } from "node:path";
3
2
  import { readdir } from "node:fs/promises";
3
+ import { extname, join } from "node:path";
4
4
 
5
5
  //#region src/islands/discover.ts
6
6
  const COMPONENT_EXTS = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
@@ -1,7 +1,7 @@
1
1
  import { islandKey } from "./detect.js";
2
2
  import { wrapClientModuleSource } from "./wrap.js";
3
- import { basename, extname } from "node:path";
4
3
  import { readFile } from "node:fs/promises";
4
+ import { basename, extname } from "node:path";
5
5
 
6
6
  //#region src/islands/esbuild-plugin.ts
7
7
  const LOADER = {
@@ -1,4 +1,4 @@
1
- import { hasClientDirective } from "../eslint/is-client-file.js";
1
+ import { hasClientDirective } from "./client-directive.js";
2
2
  import MagicString from "magic-string";
3
3
 
4
4
  //#region src/islands/wrap.ts
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.8.1-dev-20260718224903";
2
+ var version = "0.9.0-dev-20260719205406";
3
3
 
4
4
  //#endregion
5
5
  export { version };
@@ -0,0 +1,176 @@
1
+ //#region src/rules/registry.d.ts
2
+ type RuleVenue = "eslint" | "check";
3
+ interface RuleEntry {
4
+ /** Bare id — "no-hex". Never the qualified "template-kit/no-hex". */
5
+ id: string;
6
+ /** Which engine emits it. */
7
+ venue: RuleVenue;
8
+ /** One line, for the docs index. */
9
+ summary: string;
10
+ }
11
+ declare const RULE_IDS: readonly [{
12
+ readonly id: "no-nondeterminism";
13
+ readonly venue: "eslint";
14
+ readonly summary: "No clock or randomness reads in server-rendered code.";
15
+ }, {
16
+ readonly id: "no-client-runtime-in-server";
17
+ readonly venue: "eslint";
18
+ readonly summary: "No effects, state, event handlers, network, or browser globals in server-rendered code.";
19
+ }, {
20
+ readonly id: "serializable-island-props";
21
+ readonly venue: "eslint";
22
+ readonly summary: "Props handed to an island must be JSON-serializable.";
23
+ }, {
24
+ readonly id: "require-island-editor";
25
+ readonly venue: "eslint";
26
+ readonly summary: "Every island must declare whether it is live in the editor canvas.";
27
+ }, {
28
+ readonly id: "no-client-directive-in-contract";
29
+ readonly venue: "eslint";
30
+ readonly summary: "No \"use client\" in a section's four contract files.";
31
+ }, {
32
+ readonly id: "props-from-schema";
33
+ readonly venue: "eslint";
34
+ readonly summary: "A Renderer's Props type must be derived from schema.ts, not hand-written.";
35
+ }, {
36
+ readonly id: "no-inline-style";
37
+ readonly venue: "eslint";
38
+ readonly summary: "No inline style= in server-rendered JSX. Islands are exempt.";
39
+ }, {
40
+ readonly id: "no-raw-element";
41
+ readonly venue: "eslint";
42
+ readonly summary: "No raw `<section>`, `<img>` or `<video>` — use `<Section>`, `<Image>` and `<Video>`.";
43
+ }, {
44
+ readonly id: "image-bare-needs-reason";
45
+ readonly venue: "eslint";
46
+ readonly summary: "`<Image bare>` needs a `// bare:` comment saying why it skips the frame.";
47
+ }, {
48
+ readonly id: "slot-marker-literal";
49
+ readonly venue: "eslint";
50
+ readonly summary: "A slot-marker key must be a string literal, not an expression.";
51
+ }, {
52
+ readonly id: "no-hex";
53
+ readonly venue: "eslint";
54
+ readonly summary: "No hard-coded colours — the one legal colour function is a `color-mix()` over theme-derived arguments. Islands are NOT exempt.";
55
+ }, {
56
+ readonly id: "no-css-import-from-render-path";
57
+ readonly venue: "eslint";
58
+ readonly summary: "No relative/absolute .css import from a section's render path — a package's is the sanctioned channel.";
59
+ }, {
60
+ readonly id: "bundle-incomplete";
61
+ readonly venue: "check";
62
+ readonly summary: "A section folder is missing one of its four required files.";
63
+ }, {
64
+ readonly id: "bundle-binary-asset";
65
+ readonly venue: "check";
66
+ readonly summary: "An unreferenced binary asset sits in a section folder.";
67
+ }, {
68
+ readonly id: "schema-invalid";
69
+ readonly venue: "check";
70
+ readonly summary: "A section's schema.ts or fill-spec.ts fails a contract assertion, or cannot be loaded.";
71
+ }, {
72
+ readonly id: "fixtures-invalid";
73
+ readonly venue: "check";
74
+ readonly summary: "A section's fixtures.ts does not satisfy the fixture-module shape.";
75
+ }, {
76
+ readonly id: "manifest-invalid";
77
+ readonly venue: "check";
78
+ readonly summary: "A template's manifest.json is missing, unparseable, or violates the manifest contract.";
79
+ }, {
80
+ readonly id: "missing-slot-marker";
81
+ readonly venue: "check";
82
+ readonly summary: "An editable slot in schema.ts has no marker in the section's JSX.";
83
+ }, {
84
+ readonly id: "slot-group-marker";
85
+ readonly venue: "check";
86
+ readonly summary: "A group with two or more members has no `<SlotGroup>` wrapper.";
87
+ }, {
88
+ readonly id: "css-reason";
89
+ readonly venue: "check";
90
+ readonly summary: "A hand-written CSS file must open with a `css-reason:` comment.";
91
+ }, {
92
+ readonly id: "no-bare-css-import";
93
+ readonly venue: "check";
94
+ readonly summary: "No @import in hand-written CSS — the browser drops it silently.";
95
+ }, {
96
+ readonly id: "unlayered-fence";
97
+ readonly venue: "check";
98
+ readonly summary: "An `/* unlayered: */` escape hatch must be balanced and give a reason.";
99
+ }, {
100
+ readonly id: "sidebar-order";
101
+ readonly venue: "check";
102
+ readonly summary: "Schema slot order must match the order the page renders them in.";
103
+ }, {
104
+ readonly id: "typecheck";
105
+ readonly venue: "check";
106
+ readonly summary: "The template must compile under TypeScript.";
107
+ }, {
108
+ readonly id: "parse-error";
109
+ readonly venue: "check";
110
+ readonly summary: "A file could not be parsed at all — a syntax error, not a rule violation.";
111
+ }, {
112
+ readonly id: "determinism-drift";
113
+ readonly venue: "check";
114
+ readonly summary: "Rendering the same fixture twice must produce byte-identical HTML.";
115
+ }, {
116
+ readonly id: "render-invariant";
117
+ readonly venue: "check";
118
+ readonly summary: "Rendered output must carry no [object Object], bare null/undefined/NaN leaf, or src-less `<img>`.";
119
+ }, {
120
+ readonly id: "lockfile-missing";
121
+ readonly venue: "check";
122
+ readonly summary: "The workspace has no package-lock.json.";
123
+ }, {
124
+ readonly id: "lockfile-stale";
125
+ readonly venue: "check";
126
+ readonly summary: "The lockfile does not satisfy package.json — an `npm ci` would fail.";
127
+ }, {
128
+ readonly id: "single-react";
129
+ readonly venue: "check";
130
+ readonly summary: "The installed tree carries more than one copy of react or react-dom.";
131
+ }, {
132
+ readonly id: "audit-severity";
133
+ readonly venue: "check";
134
+ readonly summary: "A dependency carries a high or critical security advisory.";
135
+ }, {
136
+ readonly id: "license-denied";
137
+ readonly venue: "check";
138
+ readonly summary: "A production dependency's license is not on the allowlist.";
139
+ }, {
140
+ readonly id: "size-renderer-bundle";
141
+ readonly venue: "check";
142
+ readonly summary: "A section's renderer bundle exceeds its byte budget.";
143
+ }, {
144
+ readonly id: "size-section-css";
145
+ readonly venue: "check";
146
+ readonly summary: "A section's compiled CSS exceeds its byte budget.";
147
+ }, {
148
+ readonly id: "size-island-bundle";
149
+ readonly venue: "check";
150
+ readonly summary: "A section's island (browser) bundle exceeds its byte budget.";
151
+ }, {
152
+ readonly id: "size-assets";
153
+ readonly venue: "check";
154
+ readonly summary: "A section's static assets exceed their byte budget.";
155
+ }, {
156
+ readonly id: "no-templates";
157
+ readonly venue: "check";
158
+ readonly summary: "The workspace has no template to check at all.";
159
+ }];
160
+ type RuleId = (typeof RULE_IDS)[number]["id"];
161
+ /** The namespace every id is printed under, in both venues. */
162
+ declare const RULE_NAMESPACE = "template-kit";
163
+ /** `no-hex` → `template-kit/no-hex`. */
164
+ declare function qualify(id: RuleId): string;
165
+ /**
166
+ * `no-hex` → `guide/rules/no-hex.md`.
167
+ *
168
+ * Package-relative on purpose: the caller decides what to resolve it against. A
169
+ * printer prefixes the resolved package root so an author sees a path they can open
170
+ * with no network (`node_modules/@homepages/template-kit/guide/rules/no-hex.md`);
171
+ * ESLint's `meta.docs.url` uses it as-is. This module never touches the filesystem —
172
+ * the ESLint preset imports it.
173
+ */
174
+ declare function docsPathForRule(id: RuleId): string;
175
+ //#endregion
176
+ export { RULE_IDS, RULE_NAMESPACE, RuleEntry, RuleId, RuleVenue, docsPathForRule, qualify };
package/dist/rules.js ADDED
@@ -0,0 +1,209 @@
1
+ //#region src/rules/registry.ts
2
+ const RULE_IDS = [
3
+ {
4
+ id: "no-nondeterminism",
5
+ venue: "eslint",
6
+ summary: "No clock or randomness reads in server-rendered code."
7
+ },
8
+ {
9
+ id: "no-client-runtime-in-server",
10
+ venue: "eslint",
11
+ summary: "No effects, state, event handlers, network, or browser globals in server-rendered code."
12
+ },
13
+ {
14
+ id: "serializable-island-props",
15
+ venue: "eslint",
16
+ summary: "Props handed to an island must be JSON-serializable."
17
+ },
18
+ {
19
+ id: "require-island-editor",
20
+ venue: "eslint",
21
+ summary: "Every island must declare whether it is live in the editor canvas."
22
+ },
23
+ {
24
+ id: "no-client-directive-in-contract",
25
+ venue: "eslint",
26
+ summary: "No \"use client\" in a section's four contract files."
27
+ },
28
+ {
29
+ id: "props-from-schema",
30
+ venue: "eslint",
31
+ summary: "A Renderer's Props type must be derived from schema.ts, not hand-written."
32
+ },
33
+ {
34
+ id: "no-inline-style",
35
+ venue: "eslint",
36
+ summary: "No inline style= in server-rendered JSX. Islands are exempt."
37
+ },
38
+ {
39
+ id: "no-raw-element",
40
+ venue: "eslint",
41
+ summary: "No raw `<section>`, `<img>` or `<video>` — use `<Section>`, `<Image>` and `<Video>`."
42
+ },
43
+ {
44
+ id: "image-bare-needs-reason",
45
+ venue: "eslint",
46
+ summary: "`<Image bare>` needs a `// bare:` comment saying why it skips the frame."
47
+ },
48
+ {
49
+ id: "slot-marker-literal",
50
+ venue: "eslint",
51
+ summary: "A slot-marker key must be a string literal, not an expression."
52
+ },
53
+ {
54
+ id: "no-hex",
55
+ venue: "eslint",
56
+ summary: "No hard-coded colours — the one legal colour function is a `color-mix()` over theme-derived arguments. Islands are NOT exempt."
57
+ },
58
+ {
59
+ id: "no-css-import-from-render-path",
60
+ venue: "eslint",
61
+ summary: "No relative/absolute .css import from a section's render path — a package's is the sanctioned channel."
62
+ },
63
+ {
64
+ id: "bundle-incomplete",
65
+ venue: "check",
66
+ summary: "A section folder is missing one of its four required files."
67
+ },
68
+ {
69
+ id: "bundle-binary-asset",
70
+ venue: "check",
71
+ summary: "An unreferenced binary asset sits in a section folder."
72
+ },
73
+ {
74
+ id: "schema-invalid",
75
+ venue: "check",
76
+ summary: "A section's schema.ts or fill-spec.ts fails a contract assertion, or cannot be loaded."
77
+ },
78
+ {
79
+ id: "fixtures-invalid",
80
+ venue: "check",
81
+ summary: "A section's fixtures.ts does not satisfy the fixture-module shape."
82
+ },
83
+ {
84
+ id: "manifest-invalid",
85
+ venue: "check",
86
+ summary: "A template's manifest.json is missing, unparseable, or violates the manifest contract."
87
+ },
88
+ {
89
+ id: "missing-slot-marker",
90
+ venue: "check",
91
+ summary: "An editable slot in schema.ts has no marker in the section's JSX."
92
+ },
93
+ {
94
+ id: "slot-group-marker",
95
+ venue: "check",
96
+ summary: "A group with two or more members has no `<SlotGroup>` wrapper."
97
+ },
98
+ {
99
+ id: "css-reason",
100
+ venue: "check",
101
+ summary: "A hand-written CSS file must open with a `css-reason:` comment."
102
+ },
103
+ {
104
+ id: "no-bare-css-import",
105
+ venue: "check",
106
+ summary: "No @import in hand-written CSS — the browser drops it silently."
107
+ },
108
+ {
109
+ id: "unlayered-fence",
110
+ venue: "check",
111
+ summary: "An `/* unlayered: */` escape hatch must be balanced and give a reason."
112
+ },
113
+ {
114
+ id: "sidebar-order",
115
+ venue: "check",
116
+ summary: "Schema slot order must match the order the page renders them in."
117
+ },
118
+ {
119
+ id: "typecheck",
120
+ venue: "check",
121
+ summary: "The template must compile under TypeScript."
122
+ },
123
+ {
124
+ id: "parse-error",
125
+ venue: "check",
126
+ summary: "A file could not be parsed at all — a syntax error, not a rule violation."
127
+ },
128
+ {
129
+ id: "determinism-drift",
130
+ venue: "check",
131
+ summary: "Rendering the same fixture twice must produce byte-identical HTML."
132
+ },
133
+ {
134
+ id: "render-invariant",
135
+ venue: "check",
136
+ summary: "Rendered output must carry no [object Object], bare null/undefined/NaN leaf, or src-less `<img>`."
137
+ },
138
+ {
139
+ id: "lockfile-missing",
140
+ venue: "check",
141
+ summary: "The workspace has no package-lock.json."
142
+ },
143
+ {
144
+ id: "lockfile-stale",
145
+ venue: "check",
146
+ summary: "The lockfile does not satisfy package.json — an `npm ci` would fail."
147
+ },
148
+ {
149
+ id: "single-react",
150
+ venue: "check",
151
+ summary: "The installed tree carries more than one copy of react or react-dom."
152
+ },
153
+ {
154
+ id: "audit-severity",
155
+ venue: "check",
156
+ summary: "A dependency carries a high or critical security advisory."
157
+ },
158
+ {
159
+ id: "license-denied",
160
+ venue: "check",
161
+ summary: "A production dependency's license is not on the allowlist."
162
+ },
163
+ {
164
+ id: "size-renderer-bundle",
165
+ venue: "check",
166
+ summary: "A section's renderer bundle exceeds its byte budget."
167
+ },
168
+ {
169
+ id: "size-section-css",
170
+ venue: "check",
171
+ summary: "A section's compiled CSS exceeds its byte budget."
172
+ },
173
+ {
174
+ id: "size-island-bundle",
175
+ venue: "check",
176
+ summary: "A section's island (browser) bundle exceeds its byte budget."
177
+ },
178
+ {
179
+ id: "size-assets",
180
+ venue: "check",
181
+ summary: "A section's static assets exceed their byte budget."
182
+ },
183
+ {
184
+ id: "no-templates",
185
+ venue: "check",
186
+ summary: "The workspace has no template to check at all."
187
+ }
188
+ ];
189
+ /** The namespace every id is printed under, in both venues. */
190
+ const RULE_NAMESPACE = "template-kit";
191
+ /** `no-hex` → `template-kit/no-hex`. */
192
+ function qualify(id) {
193
+ return `${RULE_NAMESPACE}/${id}`;
194
+ }
195
+ /**
196
+ * `no-hex` → `guide/rules/no-hex.md`.
197
+ *
198
+ * Package-relative on purpose: the caller decides what to resolve it against. A
199
+ * printer prefixes the resolved package root so an author sees a path they can open
200
+ * with no network (`node_modules/@homepages/template-kit/guide/rules/no-hex.md`);
201
+ * ESLint's `meta.docs.url` uses it as-is. This module never touches the filesystem —
202
+ * the ESLint preset imports it.
203
+ */
204
+ function docsPathForRule(id) {
205
+ return `guide/rules/${id}.md`;
206
+ }
207
+
208
+ //#endregion
209
+ export { RULE_IDS, RULE_NAMESPACE, docsPathForRule, qualify };
package/dist/ssr.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ResolvedFixture } from "./schema/fixture-schema.js";
2
2
  import "./schema/index.js";
3
- import { hasClientDirective } from "./eslint/is-client-file.js";
3
+ import { hasClientDirective, isClientFile, resetClientFileCache, resolveImportedFile } from "./islands/client-directive.js";
4
4
  import { isClientComponentFile, islandKey } from "./islands/detect.js";
5
5
  import { wrapClientModuleSource } from "./islands/wrap.js";
6
6
  import { findSectionIslands } from "./islands/discover.js";
@@ -31,4 +31,4 @@ declare function loadSectionBundle(dir: string, load?: SectionModuleLoader): Pro
31
31
  */
32
32
  declare function renderSectionHtml(renderer: ComponentType<Record<string, unknown>>, props: Record<string, unknown>): string;
33
33
  //#endregion
34
- export { LoadedSection, SectionModuleLoader, findSectionIslands, hasClientDirective, isClientComponentFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, wrapClientModuleSource };
34
+ export { LoadedSection, SectionModuleLoader, findSectionIslands, hasClientDirective, isClientComponentFile, isClientFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, resetClientFileCache, resolveImportedFile, wrapClientModuleSource };
package/dist/ssr.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { FixtureModuleShape, buildFixtureSet, buildInvariantFixtures } from "./schema/fixture-schema.js";
2
- import { hasClientDirective } from "./eslint/is-client-file.js";
2
+ import { hasClientDirective, isClientFile, resetClientFileCache, resolveImportedFile } from "./islands/client-directive.js";
3
3
  import { isClientComponentFile, islandKey } from "./islands/detect.js";
4
4
  import { wrapClientModuleSource } from "./islands/wrap.js";
5
5
  import { findSectionIslands } from "./islands/discover.js";
@@ -46,4 +46,4 @@ function renderSectionHtml(renderer, props) {
46
46
  }
47
47
 
48
48
  //#endregion
49
- export { findSectionIslands, hasClientDirective, isClientComponentFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, wrapClientModuleSource };
49
+ export { findSectionIslands, hasClientDirective, isClientComponentFile, isClientFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, resetClientFileCache, resolveImportedFile, wrapClientModuleSource };
@@ -0,0 +1,21 @@
1
+ /* Design tokens the editor surface around the canvas frame reads.
2
+ The kernel itself reads none of these — the iframe is a srcdoc document
3
+ that cannot inherit the host's Tailwind, so its own chrome CSS is built
4
+ from literals on purpose. These are shipped beside the kernel so a
5
+ vendored copy's host stylesheet resolves them identically instead of
6
+ degrading silently to an inherited value.
7
+
8
+ The application's global stylesheet is the source of truth; this partial
9
+ is the shipped mirror and a colocated test fails on any drift between
10
+ them. Change a value there, not here. */
11
+
12
+ @theme inline {
13
+ --color-canvas: #ffffff;
14
+ --color-line: #ebebe6;
15
+ }
16
+
17
+ :root {
18
+ --frame: clamp(1px, calc(1px + (100vw - 1536px) * 0.0006076), 1.41667px);
19
+ --content-follow: 0.25;
20
+ --content: calc(1px + (var(--frame) - 1px) * var(--content-follow));
21
+ }
package/guide/INDEX.md CHANGED
@@ -2,7 +2,7 @@
2
2
  purpose: Router for the guide corpus that ships inside the @homepages/template-kit npm package.
3
3
  status: living
4
4
  related: []
5
- updated: 2026-07-16
5
+ updated: 2026-07-19
6
6
  ---
7
7
  # template-kit guide — router
8
8
 
@@ -22,7 +22,7 @@ detail.
22
22
  | `assets.md` | Static assets (svg/images/fonts): where they live (template level or a section folder, if referenced), referencing them from a section's CSS with `url()`/`@font-face` or importing them into a component, and the asset-vs-content-image distinction | shipping a logo, texture, icon, or brand font with a template |
23
23
  | `theme-and-css.md` | The two CSS entries, the one-Tailwind-import rule, `TokenTheme` (your own token names → your own Tailwind utilities), the `template-kit theme` command that generates `theme.css` from `theme.ts`, the breakpoint ladder, and the opt-in `content-visibility` recipe for long, image-heavy pages | a template's theme, its CSS entry, or a long page with a live map or full-screen overlay |
24
24
  | `islands.md` | Interactivity: `"use client"` components, the serializable-props rule, editor options, the loader | anything interactive in a section |
25
- | `eslint.md` | The `@homepages/template-kit/eslint` preset: the two-line config, the ESLint + parser install it expects, and its glob scope | wiring up lint in a template workspace |
25
+ | `eslint.md` | The `@homepages/eslint-plugin-template` preset: the two-line config, the ESLint + parser install it expects, and its glob scope | wiring up lint in a template workspace |
26
26
  | `check.md` | The `template-kit check` CLI command: the toolchain it resolves from your workspace, its stages, `--all`/`--json`, output shape, and exit code | running `check` locally or wiring it into CI |
27
27
  | `dev.md` | The `template-kit dev` CLI command: the local preview server, its home launcher and two tool cards (full template viewer, canvas mirror), the canvas mirror's selection overlay and per-slot edit panel, live reload, the content-override protocol, and the render/inspect/screenshot endpoints | previewing sections as you author them |
28
28
  | `new.md` | The `template-kit new` / `new-section` CLI commands: scaffolding a new template with a working starter section, or adding a bare section to an existing template | adding a template or a section to a workspace |
package/guide/check.md CHANGED
@@ -2,7 +2,7 @@
2
2
  purpose: The `template-kit check` command — what it runs, its flags, its output shape, and its exit code.
3
3
  status: living
4
4
  related: [INDEX.md, rules/INDEX.md, eslint.md, theme-and-css.md, dev.md, upgrading.md]
5
- updated: 2026-07-16
5
+ updated: 2026-07-19
6
6
  ---
7
7
  # template-kit check
8
8
 
@@ -28,7 +28,7 @@ one or pass `--all`.
28
28
  ### What it needs installed
29
29
 
30
30
  ```bash
31
- npm i -D typescript eslint @typescript-eslint/parser esbuild
31
+ npm i -D typescript eslint @typescript-eslint/parser esbuild @homepages/eslint-plugin-template
32
32
  ```
33
33
 
34
34
  `check` runs **your** toolchain, not a copy of its own: it resolves TypeScript, ESLint
@@ -48,9 +48,9 @@ one class of fix at a time:
48
48
 
49
49
  1. **typecheck** — the workspace's own TypeScript compiles the template's files, judged
50
50
  against the workspace's own `tsconfig.json`.
51
- 2. **lint** — the workspace's own ESLint, running the kit's shipped preset over the
52
- template's files. See [the ESLint preset](eslint.md) for the rules this stage shares
53
- with your editor.
51
+ 2. **lint** — the workspace's own ESLint, running your own `eslint.config.mjs` and
52
+ so the [authoring preset](eslint.md) it imports — over the template's files. See
53
+ that page for the rules this stage shares with your editor.
54
54
  3. **validate** — the schema, fill-spec, fixtures, and manifest contracts: every
55
55
  cross-reference between a section's contract files, and a template's `manifest.json`
56
56
  against the sections it composes.