@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.
- package/CHANGELOG.md +61 -1
- package/README.md +2 -2
- package/dist/canvas-core/chrome-css.d.ts +24 -0
- package/dist/canvas-core/chrome-css.js +403 -0
- package/dist/canvas-core/chrome.d.ts +49 -0
- package/dist/canvas-core/chrome.js +147 -0
- package/dist/canvas-core/frame.d.ts +20 -0
- package/dist/canvas-core/frame.js +23 -0
- package/dist/canvas-core/hidden-tabs.d.ts +16 -0
- package/dist/canvas-core/hidden-tabs.js +84 -0
- package/dist/canvas-core/resolve-target.js +55 -0
- package/dist/canvas-core/scroll.d.ts +10 -0
- package/dist/canvas-core/scroll.js +15 -0
- package/dist/canvas-core/section-dom.d.ts +6 -0
- package/dist/canvas-core/section-dom.js +43 -0
- package/dist/canvas-core/srcdoc.d.ts +8 -0
- package/dist/canvas-core/srcdoc.js +77 -0
- package/dist/canvas-core/types.d.ts +76 -0
- package/dist/canvas-core.d.ts +9 -0
- package/dist/canvas-core.js +9 -0
- package/dist/cli/check/css.js +2 -2
- package/dist/cli/check/loader.js +1 -1
- package/dist/cli/check/stages/deps.js +1 -1
- package/dist/cli/check/stages/render.js +3 -2
- package/dist/cli/check/stages/size.js +2 -2
- package/dist/cli/check/stages/tree.js +1 -1
- package/dist/cli/check/stages/validate/index.js +1 -1
- package/dist/cli/check/stages/validate/nav-contract.js +4 -3
- package/dist/cli/check/stages/validate/orchestrator.js +1 -1
- package/dist/cli/check/workspace.js +2 -2
- package/dist/cli/dev/build-css.js +1 -1
- package/dist/cli/dev/discover.js +2 -2
- package/dist/cli/dev/inspect.js +1 -1
- package/dist/cli/dev/island-bootstrap.js +1 -1
- package/dist/cli/dev/island-canvas-bootstrap.js +37 -5
- package/dist/cli/dev/section-page.js +1 -1
- package/dist/cli/dev/server.js +12 -5
- package/dist/cli/dev/tailwind.js +1 -1
- package/dist/cli/dev/tsconfig-paths-plugin.js +1 -1
- package/dist/cli/dev/workspace.js +2 -2
- package/dist/cli/link/index.js +1 -1
- package/dist/cli/link/overlay.js +1 -1
- package/dist/cli/new/emit.js +1 -1
- package/dist/cli/new/index.js +1 -1
- package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +4 -4
- package/dist/cli/new/scaffold-assets.js +2 -2
- package/dist/cli/pack/collect.js +1 -1
- package/dist/cli/pack/guards.js +1 -1
- package/dist/cli/pack/index.js +1 -1
- package/dist/cli/theme/generate.js +1 -1
- package/dist/cli/theme/load-theme.js +1 -1
- package/dist/dev-client/assets/index-CJa9G1Na.css +1 -0
- package/dist/dev-client/assets/index-C_1atZs4.js +450 -0
- package/dist/dev-client/index.html +2 -2
- package/dist/fixtures/sample-property.d.ts +4 -7
- package/dist/fixtures/sample-property.js +41 -97
- package/dist/fixtures/scenario.d.ts +1 -1
- package/dist/fixtures/scenarios/for-rent-condo.js +24 -44
- package/dist/fixtures/scenarios/luxury-multi-unit.js +1 -1
- package/dist/fixtures/scenarios/sparse-single-family.js +14 -26
- package/dist/fixtures.d.ts +2 -2
- package/dist/fixtures.js +1 -2
- package/dist/islands/client-directive.d.ts +7 -0
- package/dist/{eslint/is-client-file.js → islands/client-directive.js} +6 -3
- package/dist/islands/detect.d.ts +1 -1
- package/dist/islands/detect.js +1 -1
- package/dist/islands/discover.js +1 -1
- package/dist/islands/esbuild-plugin.js +1 -1
- package/dist/islands/wrap.js +1 -1
- package/dist/manifest.json +940 -0
- package/dist/media/pack.d.ts +49 -0
- package/dist/media/pack.js +69 -0
- package/dist/media/resolve-fixture-media.js +73 -0
- package/dist/media/resolve-media.js +195 -0
- package/dist/package.js +1 -1
- package/dist/rules.d.ts +176 -0
- package/dist/rules.js +209 -0
- package/dist/schema/property-facts.d.ts +2 -2
- package/dist/ssr.d.ts +14 -2
- package/dist/ssr.js +28 -13
- package/dist/tokens.css +21 -0
- package/guide/INDEX.md +2 -2
- package/guide/check.md +5 -5
- package/guide/dev.md +39 -30
- package/guide/eslint.md +13 -10
- package/guide/llms.txt +4 -3
- package/guide/quickstart.md +1 -1
- package/guide/recipes/image-slot-crop.md +6 -6
- package/guide/recipes/pick-a-scenario.md +13 -5
- package/guide/rules/INDEX.md +3 -2
- package/guide/schema-system.md +36 -12
- package/guide/upgrading.md +18 -2
- package/package.json +8 -4
- package/tsconfig.json +1 -1
- package/dist/dev-client/assets/index-3Gn34X_P.js +0 -50
- package/dist/dev-client/assets/index-DxQeiHfu.css +0 -1
- package/dist/eslint/is-client-file.d.ts +0 -4
- package/dist/eslint/rules/image-bare-needs-reason.js +0 -43
- package/dist/eslint/rules/no-client-directive-in-contract.js +0 -31
- package/dist/eslint/rules/no-client-runtime-in-server.js +0 -103
- package/dist/eslint/rules/no-css-import-from-render-path.js +0 -29
- package/dist/eslint/rules/no-hex.js +0 -188
- package/dist/eslint/rules/no-inline-style.js +0 -27
- package/dist/eslint/rules/no-nondeterminism.js +0 -48
- package/dist/eslint/rules/no-raw-element.js +0 -38
- package/dist/eslint/rules/props-from-schema.js +0 -37
- package/dist/eslint/rules/require-island-editor.js +0 -74
- package/dist/eslint/rules/serializable-island-props.js +0 -112
- package/dist/eslint/rules/slot-marker-literal.js +0 -38
- package/dist/eslint.d.ts +0 -6
- package/dist/eslint.js +0 -80
- package/dist/rules/registry.js +0 -16
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,10 +1,11 @@
|
|
|
1
1
|
import { ResolvedFixture } from "./schema/fixture-schema.js";
|
|
2
2
|
import "./schema/index.js";
|
|
3
|
-
import { hasClientDirective } from "./
|
|
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";
|
|
7
7
|
import { islandTransformEsbuildPlugin } from "./islands/esbuild-plugin.js";
|
|
8
|
+
import { DEV_MEDIA_URL_PREFIX, MediaAsset, MediaKind, getMediaAsset, listMediaAssets, mediaPackHasFile, packRelativeFromUrl } from "./media/pack.js";
|
|
8
9
|
import { ComponentType } from "react";
|
|
9
10
|
//#region src/ssr/index.d.ts
|
|
10
11
|
type LoadedSection = {
|
|
@@ -16,6 +17,11 @@ type LoadedSection = {
|
|
|
16
17
|
editable_by_user?: boolean;
|
|
17
18
|
required?: boolean;
|
|
18
19
|
}>;
|
|
20
|
+
unresolvedMedia: {
|
|
21
|
+
fixture: string;
|
|
22
|
+
field: string;
|
|
23
|
+
id: string;
|
|
24
|
+
}[];
|
|
19
25
|
};
|
|
20
26
|
type SectionModuleLoader = (dir: string, file: string) => Promise<unknown>;
|
|
21
27
|
declare const nativeSectionLoader: SectionModuleLoader;
|
|
@@ -23,6 +29,12 @@ declare const nativeSectionLoader: SectionModuleLoader;
|
|
|
23
29
|
* Load a bundle's Renderer plus its resolved snapshot and invariant fixture
|
|
24
30
|
* sets from the bundle directory `dir`. `load` resolves each of the four
|
|
25
31
|
* contract files; it defaults to native dynamic import.
|
|
32
|
+
*
|
|
33
|
+
* Media references resolve here, against the dev media pack: fixtures state the
|
|
34
|
+
* asset id `content_doc` holds, and a Renderer must receive the post-resolution
|
|
35
|
+
* `url`. This is the single fixture loader, so every Node-side caller — the
|
|
36
|
+
* registry's render + invariant gates, the dev server — gets resolved media
|
|
37
|
+
* without asking for it.
|
|
26
38
|
*/
|
|
27
39
|
declare function loadSectionBundle(dir: string, load?: SectionModuleLoader): Promise<LoadedSection>;
|
|
28
40
|
/**
|
|
@@ -31,4 +43,4 @@ declare function loadSectionBundle(dir: string, load?: SectionModuleLoader): Pro
|
|
|
31
43
|
*/
|
|
32
44
|
declare function renderSectionHtml(renderer: ComponentType<Record<string, unknown>>, props: Record<string, unknown>): string;
|
|
33
45
|
//#endregion
|
|
34
|
-
export { LoadedSection, SectionModuleLoader, findSectionIslands, hasClientDirective, isClientComponentFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, wrapClientModuleSource };
|
|
46
|
+
export { DEV_MEDIA_URL_PREFIX, LoadedSection, type MediaAsset, type MediaKind, SectionModuleLoader, findSectionIslands, getMediaAsset, hasClientDirective, isClientComponentFile, isClientFile, islandKey, islandTransformEsbuildPlugin, listMediaAssets, loadSectionBundle, mediaPackHasFile, nativeSectionLoader, packRelativeFromUrl, renderSectionHtml, resetClientFileCache, resolveImportedFile, wrapClientModuleSource };
|
package/dist/ssr.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { FixtureModuleShape, buildFixtureSet, buildInvariantFixtures } from "./schema/fixture-schema.js";
|
|
2
|
-
import {
|
|
2
|
+
import { DEV_MEDIA_URL_PREFIX, getMediaAsset, listMediaAssets, mediaPackHasFile, packRelativeFromUrl } from "./media/pack.js";
|
|
3
|
+
import { findUnresolvedMedia, resolveFixtureSetMedia } from "./media/resolve-fixture-media.js";
|
|
4
|
+
import { hasClientDirective, isClientFile, resetClientFileCache, resolveImportedFile } from "./islands/client-directive.js";
|
|
3
5
|
import { isClientComponentFile, islandKey } from "./islands/detect.js";
|
|
4
6
|
import { wrapClientModuleSource } from "./islands/wrap.js";
|
|
5
7
|
import { findSectionIslands } from "./islands/discover.js";
|
|
@@ -15,6 +17,12 @@ const nativeSectionLoader = (dir, file) => import(pathToFileURL(join(dir, file))
|
|
|
15
17
|
* Load a bundle's Renderer plus its resolved snapshot and invariant fixture
|
|
16
18
|
* sets from the bundle directory `dir`. `load` resolves each of the four
|
|
17
19
|
* contract files; it defaults to native dynamic import.
|
|
20
|
+
*
|
|
21
|
+
* Media references resolve here, against the dev media pack: fixtures state the
|
|
22
|
+
* asset id `content_doc` holds, and a Renderer must receive the post-resolution
|
|
23
|
+
* `url`. This is the single fixture loader, so every Node-side caller — the
|
|
24
|
+
* registry's render + invariant gates, the dev server — gets resolved media
|
|
25
|
+
* without asking for it.
|
|
18
26
|
*/
|
|
19
27
|
async function loadSectionBundle(dir, load = nativeSectionLoader) {
|
|
20
28
|
const rendererMod = await load(dir, "Renderer.tsx");
|
|
@@ -22,19 +30,26 @@ async function loadSectionBundle(dir, load = nativeSectionLoader) {
|
|
|
22
30
|
const schemaMod = await load(dir, "schema.ts");
|
|
23
31
|
const parsed = FixtureModuleShape.parse(fixturesMod.default);
|
|
24
32
|
const selfAnchor = schemaMod.schema.meta?.anchor ?? basename(dir);
|
|
33
|
+
const snapshot = buildFixtureSet({
|
|
34
|
+
schema: schemaMod.schema,
|
|
35
|
+
module: parsed,
|
|
36
|
+
selfAnchor
|
|
37
|
+
});
|
|
38
|
+
const invariant = buildInvariantFixtures({
|
|
39
|
+
schema: schemaMod.schema,
|
|
40
|
+
module: parsed,
|
|
41
|
+
selfAnchor
|
|
42
|
+
});
|
|
43
|
+
const unresolvedMedia = invariant.flatMap((fixture) => findUnresolvedMedia(fixture.props).map((miss) => ({
|
|
44
|
+
fixture: fixture.id,
|
|
45
|
+
...miss
|
|
46
|
+
})));
|
|
25
47
|
return {
|
|
26
48
|
Renderer: rendererMod.default,
|
|
27
|
-
snapshotFixtures:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}),
|
|
32
|
-
invariantFixtures: buildInvariantFixtures({
|
|
33
|
-
schema: schemaMod.schema,
|
|
34
|
-
module: parsed,
|
|
35
|
-
selfAnchor
|
|
36
|
-
}),
|
|
37
|
-
schemaSlots: schemaMod.schema.slots
|
|
49
|
+
snapshotFixtures: resolveFixtureSetMedia(snapshot),
|
|
50
|
+
invariantFixtures: resolveFixtureSetMedia(invariant),
|
|
51
|
+
schemaSlots: schemaMod.schema.slots,
|
|
52
|
+
unresolvedMedia
|
|
38
53
|
};
|
|
39
54
|
}
|
|
40
55
|
/**
|
|
@@ -46,4 +61,4 @@ function renderSectionHtml(renderer, props) {
|
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
//#endregion
|
|
49
|
-
export { findSectionIslands, hasClientDirective, isClientComponentFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, wrapClientModuleSource };
|
|
64
|
+
export { DEV_MEDIA_URL_PREFIX, findSectionIslands, getMediaAsset, hasClientDirective, isClientComponentFile, isClientFile, islandKey, islandTransformEsbuildPlugin, listMediaAssets, loadSectionBundle, mediaPackHasFile, nativeSectionLoader, packRelativeFromUrl, renderSectionHtml, resetClientFileCache, resolveImportedFile, wrapClientModuleSource };
|
package/dist/tokens.css
ADDED
|
@@ -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-
|
|
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
|
|
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-
|
|
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
|
|
52
|
-
|
|
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.
|
package/guide/dev.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The `template-kit dev` command — the local preview server, what it serves, its URLs, and the agent-facing render endpoints.
|
|
3
3
|
status: living
|
|
4
4
|
related: [INDEX.md, theme-and-css.md, schema-system.md, check.md, islands.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-19
|
|
6
6
|
---
|
|
7
7
|
# template-kit dev
|
|
8
8
|
|
|
@@ -134,9 +134,14 @@ own right.
|
|
|
134
134
|
## Canvas mirror
|
|
135
135
|
|
|
136
136
|
`/canvas/<template>` — open it from the home launcher's **Canvas mirror** card,
|
|
137
|
-
or directly —
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
or directly — renders the template inside the platform's own editor canvas
|
|
138
|
+
frame: a rounded page card that auto-scales to fit the available width, on the
|
|
139
|
+
same dotted canvas surface and with the same selection chrome the platform's
|
|
140
|
+
content editor uses. Each manifest section instance loads independently and is
|
|
141
|
+
injected into the page in manifest order as it arrives, so instances can appear
|
|
142
|
+
progressively rather than all at once; one instance failing to render shows an
|
|
143
|
+
error placeholder in its own spot without blocking the rest. A paint-only
|
|
144
|
+
overlay sits on top of the page, scoped per section instance —
|
|
140
145
|
the same slot name in two different sections selects independently. Click a slot
|
|
141
146
|
to select it (a gold ring); hover to preview one (a blue ring). Only slots that
|
|
142
147
|
instance's schema marks `editable_by_user: true` select individually — clicking
|
|
@@ -152,38 +157,42 @@ The current template and selection are encoded in the URL as
|
|
|
152
157
|
a repeating slot (`<instanceId>~<slotId>~<itemIndex>`) — so a canvas link
|
|
153
158
|
reloads and shares exactly the view you're looking at.
|
|
154
159
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
A **floating edit panel**, a fixed-width card anchored over the canvas's right
|
|
161
|
+
edge, is always open and shows one section instance at a time: whichever
|
|
162
|
+
instance is currently selected, or — before any selection, or after Escape
|
|
163
|
+
clears the ring — the last instance shown, defaulting to the template's first
|
|
164
|
+
manifest instance on initial load. The panel lists every one of that
|
|
165
|
+
instance's `editable_by_user: true` slots at once, each as its own row: one
|
|
166
|
+
JSON editor handles every slot type — you edit the slot's value in its own
|
|
167
|
+
JSON shape rather than switching between per-type widgets — alongside a
|
|
168
|
+
**fill-state badge** (filled / empty / missing). Selecting a slot scrolls its
|
|
169
|
+
row into view and highlights it. Above the slot rows sits a per-instance
|
|
170
|
+
**fixture picker**: it lists that section's whole fixture corpus, including
|
|
171
|
+
its synthetic empty-content fixtures, so you can drive a slot into an empty
|
|
172
|
+
or missing state without touching `fixtures.ts`. Applying a JSON edit,
|
|
173
|
+
switching the fixture, or resetting the instance re-renders that section
|
|
174
|
+
instance alone, the same way it renders in the stack, and swaps its HTML into
|
|
175
|
+
the iframe in place — no page reload, no other instance disturbed — then
|
|
176
|
+
re-anchors the selection ring to the refreshed markup, and re-hydrates that
|
|
177
|
+
instance's islands. The kit dev server is the harness's only backend, so this
|
|
178
|
+
loop never touches fixture files, a database, or another service. Edit state
|
|
179
|
+
lives only in the tab: nothing is written to disk, and reloading the page
|
|
180
|
+
loses it.
|
|
173
181
|
|
|
174
182
|
An island whose component declares `editor.live: true` (see [Islands](islands.md))
|
|
175
183
|
hydrates in the canvas the same way it does on a published page, but behind a
|
|
176
184
|
click-through shield until it's selected: the first click on a live island
|
|
177
185
|
selects its owning slot or section instance instead of reaching the
|
|
178
186
|
component, dropping the shield; a second click reaches the component itself.
|
|
179
|
-
Pressing Escape clears the
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
+
Pressing Escape clears the select ring and re-arms every shield; it does not
|
|
188
|
+
close the edit panel, which keeps showing the instance it was last showing.
|
|
189
|
+
An island with no `editor` export, or `editor.live: false`, stays static
|
|
190
|
+
markup in the canvas — same as a published page's non-live islands. Some
|
|
191
|
+
islands own no slot of their own (e.g. a slider or map sitting beside a
|
|
192
|
+
`<Slot>` rather than wrapped by one); their shield only ever drops under a
|
|
193
|
+
section instance selection. The edit panel always includes a read-only
|
|
194
|
+
**Islands** list for its current instance, showing every top-level island in
|
|
195
|
+
that section instance, whether it's live or static, its shield placement, and
|
|
187
196
|
whether it's currently interactive or shielded.
|
|
188
197
|
|
|
189
198
|
## Agent-facing endpoints
|
package/guide/eslint.md
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
-
purpose: The authoring-lint preset shipped
|
|
2
|
+
purpose: The authoring-lint preset shipped as `@homepages/eslint-plugin-template` — setup and scope. Per-rule reference lives in `rules/`.
|
|
3
3
|
status: living
|
|
4
|
-
related: [INDEX.md, rules/INDEX.md, check.md, islands.md, theme-and-css.md, primitives.md]
|
|
5
|
-
updated: 2026-07-
|
|
4
|
+
related: [INDEX.md, rules/INDEX.md, check.md, islands.md, theme-and-css.md, primitives.md, upgrading.md]
|
|
5
|
+
updated: 2026-07-19
|
|
6
6
|
---
|
|
7
7
|
# ESLint preset
|
|
8
8
|
|
|
9
|
-
`@homepages/template
|
|
9
|
+
`@homepages/eslint-plugin-template` is a flat-config ESLint preset carrying every
|
|
10
10
|
authoring rule the kit enforces one file at a time, under the `template-kit/*`
|
|
11
11
|
rule namespace.
|
|
12
12
|
|
|
13
13
|
## Setup
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
npm i -D eslint @typescript-eslint/parser
|
|
16
|
+
npm i -D @homepages/eslint-plugin-template eslint @typescript-eslint/parser
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
```js
|
|
20
20
|
// eslint.config.mjs
|
|
21
|
-
import kit from "@homepages/template
|
|
21
|
+
import kit from "@homepages/eslint-plugin-template";
|
|
22
22
|
export default kit;
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -26,10 +26,13 @@ That is the whole setup — no plugin registration, no parser wiring, no tsconfi
|
|
|
26
26
|
The preset registers its rules and sets `languageOptions` (parser included) itself,
|
|
27
27
|
so those two lines are a complete config.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
The preset ships as its own package rather than a kit subpath because it needs a
|
|
30
|
+
different dependency tree: ESLint and the parser, not React. That keeps a linter out
|
|
31
|
+
of the production install of every app that renders a template — the kit's main entry
|
|
32
|
+
is a set of React primitives, and it must not drag one in.
|
|
33
|
+
|
|
34
|
+
ESLint and the parser are peer dependencies, so you install them yourself; the preset
|
|
35
|
+
resolves them from your workspace. If you see
|
|
33
36
|
`Cannot find package '@typescript-eslint/parser'`, that is the install line above
|
|
34
37
|
missing.
|
|
35
38
|
|
package/guide/llms.txt
CHANGED
|
@@ -80,10 +80,11 @@ and [Pick a scenario](recipes/pick-a-scenario.md).
|
|
|
80
80
|
template — where an asset file lives (template level, or a section folder if the
|
|
81
81
|
section references it), referencing it from a section's CSS with `url()`/`@font-face`
|
|
82
82
|
or importing it into a component, and the asset-vs-content-image distinction.
|
|
83
|
-
- [ESLint preset](eslint.md): `@homepages/template
|
|
83
|
+
- [ESLint preset](eslint.md): `@homepages/eslint-plugin-template`, the flat-config preset —
|
|
84
84
|
a two-line config with no parser wiring, over an ESLint + `@typescript-eslint/parser`
|
|
85
|
-
install of your own (
|
|
86
|
-
|
|
85
|
+
install of your own (both are peer dependencies of the preset and resolve from your
|
|
86
|
+
workspace; the kit itself depends on neither). Its own version line, separate from the
|
|
87
|
+
kit's. Its three-entry glob scope. Per-rule reference lives in `rules/`.
|
|
87
88
|
- [Check](check.md): the `template-kit check` CLI command — the toolchain it runs (your
|
|
88
89
|
TypeScript, ESLint and esbuild, resolved from your workspace), its six per-template
|
|
89
90
|
stages, the once-per-invocation dependency gates, `--all`/`--json`, the report shape,
|
package/guide/quickstart.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: Add an image slot the editor crops to a fixed ratio.
|
|
3
3
|
status: living
|
|
4
4
|
related: [../schema-system.md, ../primitives.md, ../rules/image-bare-needs-reason.md, pick-a-scenario.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-19
|
|
6
6
|
---
|
|
7
7
|
# Image slot with a locked crop
|
|
8
8
|
|
|
@@ -51,20 +51,20 @@ interior_image: {
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
```ts
|
|
54
|
-
// fixtures.ts —
|
|
55
|
-
import {
|
|
54
|
+
// fixtures.ts — the photo's asset id, exactly as a saved page states it
|
|
55
|
+
import { scenarios } from "@homepages/template-kit/fixtures";
|
|
56
56
|
|
|
57
57
|
const interiorImage = scenarios.luxuryMultiUnit.photos[3]!;
|
|
58
58
|
// ...
|
|
59
59
|
interior_image: {
|
|
60
60
|
photo_id: interiorImage.id,
|
|
61
|
-
url: interiorImage.url,
|
|
62
61
|
alt: interiorImage.alt,
|
|
63
|
-
// Height set to a 4:3 ratio to match the slot's locked crop.
|
|
64
|
-
responsive: mkResponsive(interiorImage.url, 1600, 1200),
|
|
65
62
|
},
|
|
66
63
|
```
|
|
67
64
|
|
|
65
|
+
The `url` and the responsive ladder the Renderer reads are attached when the fixture
|
|
66
|
+
loads — see [Media is stated by id](../schema-system.md#media-is-stated-by-id-never-by-url).
|
|
67
|
+
|
|
68
68
|
An image slot supports two independent geometry configs — use one, not both, per
|
|
69
69
|
slot:
|
|
70
70
|
|