@homepages/template-kit 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/README.md +96 -17
  3. package/dist/asset-modules.d.ts +81 -0
  4. package/dist/base.css +9 -0
  5. package/dist/cli/check/config.js +41 -0
  6. package/dist/cli/check/css.js +131 -0
  7. package/dist/cli/check/diagnostics.js +32 -0
  8. package/dist/cli/check/index.js +87 -0
  9. package/dist/cli/check/loader.js +146 -0
  10. package/dist/cli/check/paths.js +15 -0
  11. package/dist/cli/check/relativize.js +18 -0
  12. package/dist/cli/check/render-invariants.js +24 -0
  13. package/dist/cli/check/resolve-tool.js +38 -0
  14. package/dist/cli/check/section-assets.js +58 -0
  15. package/dist/cli/check/stages/deps.js +337 -0
  16. package/dist/cli/check/stages/lint.js +46 -0
  17. package/dist/cli/check/stages/render.js +101 -0
  18. package/dist/cli/check/stages/size.js +274 -0
  19. package/dist/cli/check/stages/tree.js +206 -0
  20. package/dist/cli/check/stages/typecheck.js +46 -0
  21. package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +18 -0
  22. package/dist/cli/check/stages/validate/facts.js +18 -0
  23. package/dist/cli/check/stages/validate/formatter-contract.js +14 -0
  24. package/dist/cli/check/stages/validate/group-contract.js +21 -0
  25. package/dist/cli/check/stages/validate/index.js +66 -0
  26. package/dist/cli/check/stages/validate/list-scalar-contract.js +22 -0
  27. package/dist/cli/check/stages/validate/nav-contract.js +93 -0
  28. package/dist/cli/check/stages/validate/nested-slot-contract.js +12 -0
  29. package/dist/cli/check/stages/validate/object-list-contract.js +31 -0
  30. package/dist/cli/check/stages/validate/orchestrator.js +241 -0
  31. package/dist/cli/check/stages/validate/produced-by-contract.js +18 -0
  32. package/dist/cli/check/stages/validate/row-contract.js +31 -0
  33. package/dist/cli/check/stages/validate/select-contract.js +13 -0
  34. package/dist/cli/check/stages/validate/source-contract.js +21 -0
  35. package/dist/cli/check/stages/validate/variant-contract.js +15 -0
  36. package/dist/cli/check/workspace.js +86 -0
  37. package/dist/cli/dev/build-css.js +91 -0
  38. package/dist/cli/dev/catalog.js +35 -0
  39. package/dist/cli/dev/compose-template.js +38 -0
  40. package/dist/cli/dev/content-override.js +58 -0
  41. package/dist/cli/dev/discover.js +35 -0
  42. package/dist/cli/dev/fill-state.js +75 -0
  43. package/dist/cli/dev/index.js +32 -0
  44. package/dist/cli/dev/inspect.js +38 -0
  45. package/dist/cli/dev/island-bootstrap.js +38 -0
  46. package/dist/cli/dev/island-map.js +35 -0
  47. package/dist/cli/dev/island-transform.js +31 -0
  48. package/dist/cli/dev/manifest-instances.js +31 -0
  49. package/dist/cli/dev/render-section.js +24 -0
  50. package/dist/cli/dev/screenshot-target.js +24 -0
  51. package/dist/cli/dev/section-page.js +54 -0
  52. package/dist/cli/dev/server.js +480 -0
  53. package/dist/cli/dev/slot-schema.js +12 -0
  54. package/dist/cli/dev/structure-summary.js +118 -0
  55. package/dist/cli/dev/tailwind.js +32 -0
  56. package/dist/cli/dev/vite-server.js +33 -0
  57. package/dist/cli/dev/workspace.js +63 -0
  58. package/dist/cli/link/index.js +74 -0
  59. package/dist/cli/link/overlay.js +59 -0
  60. package/dist/cli/link/watch.js +25 -0
  61. package/dist/cli/new/emit.js +48 -0
  62. package/dist/cli/new/index.js +69 -0
  63. package/dist/cli/new/scaffold/section/_Renderer.tsx +20 -0
  64. package/dist/cli/new/scaffold/section/_fill-spec.ts +18 -0
  65. package/dist/cli/new/scaffold/section/_fixtures.ts +13 -0
  66. package/dist/cli/new/scaffold/section/_schema.ts +24 -0
  67. package/dist/cli/new/scaffold/template/_manifest.json +9 -0
  68. package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +45 -0
  69. package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +40 -0
  70. package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +14 -0
  71. package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +29 -0
  72. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +41 -0
  73. package/dist/cli/new/scaffold/template/sections/hero/schema.ts +60 -0
  74. package/dist/cli/new/scaffold/template/theme.css +24 -0
  75. package/dist/cli/new/scaffold/template/theme.ts +27 -0
  76. package/dist/cli/new/scaffold-assets.js +20 -0
  77. package/dist/cli/pack/collect.js +44 -0
  78. package/dist/cli/pack/guards.js +57 -0
  79. package/dist/cli/pack/index.js +66 -0
  80. package/dist/cli/pack/manifest.js +15 -0
  81. package/dist/cli/pack/zip.js +86 -0
  82. package/dist/cli/theme/generate.js +43 -0
  83. package/dist/cli/theme/index.js +33 -0
  84. package/dist/cli/theme/load-theme.js +69 -0
  85. package/dist/cli.js +100 -16
  86. package/dist/contracts/fill-treatments.js +47 -0
  87. package/dist/design-system/theme.d.ts +30 -300
  88. package/dist/design-system/theme.js +112 -90
  89. package/dist/dev-client/assets/index-DfEfrp6P.css +1 -0
  90. package/dist/dev-client/assets/index-bpMP0b0W.js +50 -0
  91. package/dist/dev-client/index.html +13 -0
  92. package/dist/eslint/is-client-file.d.ts +4 -0
  93. package/dist/eslint/rules/no-hex.js +78 -6
  94. package/dist/eslint.js +1 -1
  95. package/dist/index.d.ts +3 -2
  96. package/dist/index.js +2 -2
  97. package/dist/islands/detect.d.ts +16 -0
  98. package/dist/islands/detect.js +22 -0
  99. package/dist/islands/discover.d.ts +5 -0
  100. package/dist/islands/discover.js +19 -0
  101. package/dist/islands/esbuild-plugin.d.ts +13 -0
  102. package/dist/islands/esbuild-plugin.js +39 -0
  103. package/dist/islands/wrap.d.ts +11 -0
  104. package/dist/islands/wrap.js +48 -0
  105. package/dist/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +77 -0
  106. package/dist/node_modules/magic-string/dist/magic-string.es.js +939 -0
  107. package/dist/package.js +1 -1
  108. package/dist/primitives/Image.js +1 -1
  109. package/dist/schema/fill-spec.d.ts +80 -590
  110. package/dist/schema/fixture-schema.d.ts +5 -81
  111. package/dist/schema/manifest.d.ts +39 -475
  112. package/dist/schema/resolve-section-ref.js +10 -0
  113. package/dist/schema/rows.js +10 -0
  114. package/dist/schema/section-nav.js +9 -0
  115. package/dist/schema/section-schema.d.ts +51 -437
  116. package/dist/schema/slot-types.d.ts +12 -16
  117. package/dist/ssr.d.ts +31 -0
  118. package/dist/ssr.js +46 -0
  119. package/dist/styles.css +31 -88
  120. package/docs/INDEX.md +12 -3
  121. package/docs/assets.md +128 -0
  122. package/docs/check.md +124 -0
  123. package/docs/dev.md +187 -0
  124. package/docs/eslint.md +18 -8
  125. package/docs/islands.md +5 -2
  126. package/docs/llms.txt +59 -9
  127. package/docs/new.md +27 -0
  128. package/docs/overview.md +57 -0
  129. package/docs/pack.md +46 -0
  130. package/docs/primitives.md +10 -6
  131. package/docs/quickstart.md +102 -0
  132. package/docs/recipes/INDEX.md +27 -0
  133. package/docs/recipes/bind-property-fact.md +58 -0
  134. package/docs/recipes/collection-slot.md +100 -0
  135. package/docs/recipes/fill-spec-decision.md +66 -0
  136. package/docs/recipes/fixture-states.md +68 -0
  137. package/docs/recipes/image-slot-crop.md +98 -0
  138. package/docs/recipes/interactive-island.md +100 -0
  139. package/docs/recipes/organize-section-folder.md +72 -0
  140. package/docs/recipes/prepare-submission.md +55 -0
  141. package/docs/recipes/second-template.md +49 -0
  142. package/docs/recipes/select-slot.md +59 -0
  143. package/docs/recipes/static-asset.md +103 -0
  144. package/docs/recipes/third-party-package.md +90 -0
  145. package/docs/rules/INDEX.md +14 -5
  146. package/docs/rules/bundle-binary-asset.md +83 -0
  147. package/docs/rules/bundle-incomplete.md +2 -2
  148. package/docs/rules/css-reason.md +3 -3
  149. package/docs/rules/fixtures-invalid.md +96 -0
  150. package/docs/rules/license-denied.md +11 -3
  151. package/docs/rules/manifest-invalid.md +99 -0
  152. package/docs/rules/no-bare-css-import.md +8 -8
  153. package/docs/rules/no-css-import-from-render-path.md +7 -8
  154. package/docs/rules/no-hex.md +76 -10
  155. package/docs/rules/no-templates.md +87 -0
  156. package/docs/rules/parse-error.md +76 -0
  157. package/docs/rules/schema-invalid.md +96 -0
  158. package/docs/rules/size-assets.md +88 -0
  159. package/docs/rules/size-island-bundle.md +123 -0
  160. package/docs/rules/size-section-css.md +19 -14
  161. package/docs/schema-system.md +28 -28
  162. package/docs/theme-and-css.md +157 -92
  163. package/docs/vocabulary.md +98 -0
  164. package/package.json +14 -11
  165. package/tsconfig.json +1 -1
package/dist/ssr.js ADDED
@@ -0,0 +1,46 @@
1
+ import { FixtureModuleShape, buildFixtureSet, buildInvariantFixtures } from "./schema/fixture-schema.js";
2
+ import { hasClientDirective } from "./eslint/is-client-file.js";
3
+ import { isClientComponentFile, islandKey } from "./islands/detect.js";
4
+ import { findSectionIslands } from "./islands/discover.js";
5
+ import { wrapClientModuleSource } from "./islands/wrap.js";
6
+ import { islandTransformEsbuildPlugin } from "./islands/esbuild-plugin.js";
7
+ import { createElement } from "react";
8
+ import { basename, join } from "node:path";
9
+ import { pathToFileURL } from "node:url";
10
+ import { renderToStaticMarkup } from "react-dom/server";
11
+
12
+ //#region src/ssr/index.ts
13
+ const nativeSectionLoader = (dir, file) => import(pathToFileURL(join(dir, file)).href);
14
+ /**
15
+ * Load a bundle's Renderer plus its resolved snapshot and invariant fixture
16
+ * sets from the bundle directory `dir`. `load` resolves each of the four
17
+ * contract files; it defaults to native dynamic import.
18
+ */
19
+ async function loadSectionBundle(dir, load = nativeSectionLoader) {
20
+ const rendererMod = await load(dir, "Renderer.tsx");
21
+ const fixturesMod = await load(dir, "fixtures.ts");
22
+ const schemaMod = await load(dir, "schema.ts");
23
+ const parsed = FixtureModuleShape.parse(fixturesMod.default);
24
+ const selfAnchor = schemaMod.schema.meta?.anchor ?? basename(dir);
25
+ return {
26
+ Renderer: rendererMod.default,
27
+ snapshotFixtures: buildFixtureSet({
28
+ schema: schemaMod.schema,
29
+ module: parsed,
30
+ selfAnchor
31
+ }),
32
+ invariantFixtures: buildInvariantFixtures({
33
+ schema: schemaMod.schema,
34
+ module: parsed,
35
+ selfAnchor
36
+ }),
37
+ schemaSlots: schemaMod.schema.slots
38
+ };
39
+ }
40
+ /** Render a resolved fixture's props to static markup — the byte-for-byte page render. */
41
+ function renderSectionHtml(renderer, props) {
42
+ return renderToStaticMarkup(createElement(renderer, props));
43
+ }
44
+
45
+ //#endregion
46
+ export { findSectionIslands, hasClientDirective, isClientComponentFile, islandKey, islandTransformEsbuildPlugin, loadSectionBundle, nativeSectionLoader, renderSectionHtml, wrapClientModuleSource };
package/dist/styles.css CHANGED
@@ -1,15 +1,26 @@
1
1
  /*
2
- * @homepages/template-kit — theme tokens + kit source registration.
2
+ * @homepages/template-kit — the global Tailwind preamble.
3
3
  *
4
- * Import this AFTER `@import "tailwindcss";` in your own CSS entry:
4
+ * Import this AFTER `@import "tailwindcss";` in your own CSS entry, and import your
5
+ * template's compiled theme after it:
5
6
  *
6
7
  * @import "tailwindcss";
7
8
  * @import "@homepages/template-kit/styles.css";
9
+ * @import "./theme.css"; // compileThemeToCss(yourTheme) output
8
10
  *
9
11
  * This file must NOT import Tailwind itself. Exactly one `@import "tailwindcss"` may
10
12
  * exist per build graph — your entry owns it. Two double-emit Tailwind's preflight
11
13
  * reset (box-sizing declared twice, ~4 KB of duplicate base layer).
12
14
  *
15
+ * WHAT IS HERE: only what is true of EVERY template — the breakpoint ladder, the
16
+ * spacing base unit, and the motion defaults.
17
+ *
18
+ * WHAT IS NOT: your design tokens. Colors, fonts, type, radii and shadows are yours.
19
+ * Declare them in `theme.ts` under names you choose, and `compileThemeToCss` generates
20
+ * the `@theme inline` alias layer that turns each one into a utility. Nothing about
21
+ * your visual system is shared, and there is no global file for you to edit to make a
22
+ * token of yours usable.
23
+ *
13
24
  * The @source below resolves relative to THIS FILE'S SHIPPED LOCATION (dist/styles.css),
14
25
  * registering the kit's compiled JS for class scanning — that is how the utility classes
15
26
  * baked into the kit's primitives reach your compiled CSS. It also overrides Tailwind's
@@ -23,107 +34,39 @@
23
34
  * utilities and 19 stray @property registrations, ~2.5 KB. If a primitive ever moves out
24
35
  * of primitives/, widen this — an unstyled <Image> in every template is the failure mode,
25
36
  * and scripts/verify-consumer.mjs is what catches it.
26
- *
27
- * The @theme block aliases Tailwind's utility namespace onto the `--tr-*` custom
28
- * properties your template's theme.ts supplies (`text-ink` → `var(--tr-color-ink)`).
29
- * The aliases are the cross-template contract; the values behind them are yours.
30
37
  */
31
38
  @source "./primitives/**/*.js";
32
39
 
40
+ /* `@theme inline` so a spacing/breakpoint value is substituted straight into the
41
+ * utilities Tailwind generates (`.gap-2 { gap: .5rem }`), rather than emitted as a
42
+ * `:root` custom property the utilities then read through `var()`. These are fixed
43
+ * global constants, not per-page-overridable tokens, so inlining is both correct and
44
+ * what keeps the compiled output stable. Nothing here may reference a token a theme.ts
45
+ * defines. */
33
46
  @theme inline {
34
- /* ---------- Colors (alias to the --tr-color-* values a theme.ts supplies) ---------- */
35
- --color-ink: var(--tr-color-ink);
36
- --color-ink-soft: var(--tr-color-ink-soft);
37
- --color-ink-mute: var(--tr-color-ink-mute);
38
- --color-background: var(--tr-color-background);
39
- --color-surface: var(--tr-color-surface);
40
- --color-surface-alt: var(--tr-color-surface-alt);
41
- --color-border: var(--tr-color-border);
42
- --color-border-strong: var(--tr-color-border-strong);
43
- --color-accent: var(--tr-color-accent);
44
- --color-accent-strong: var(--tr-color-accent-strong);
45
- --color-accent-contrast: var(--tr-color-accent-contrast);
46
- --color-primary: var(--tr-color-primary);
47
- --color-primary-fg: var(--tr-color-primary-fg);
48
- --color-primary-darker: var(--tr-color-primary-darker);
49
- --color-overlay: var(--tr-color-overlay);
50
- --color-overlay-strong: var(--tr-color-overlay-strong);
51
- --color-overlay-light: var(--tr-color-overlay-light);
52
- --color-footer-bg: var(--tr-color-footer-bg);
53
- --color-footer-fg: var(--tr-color-footer-fg);
54
- --color-footer-fg-mute: var(--tr-color-footer-fg-mute);
55
- --color-footer-divider: var(--tr-color-footer-divider);
56
-
57
- /* Decorative brand swatches — the open `palette` escape hatch in TokenTheme.
58
- * A theme that omits one leaves its utility resolving to an unset custom
59
- * property; only reach for these when a semantic role genuinely doesn't fit. */
60
- --color-yellow-deep: var(--tr-color-yellow-deep);
61
- --color-yellow-200: var(--tr-color-yellow-200);
62
- --color-yellow-100: var(--tr-color-yellow-100);
63
- --color-brown: var(--tr-color-brown);
64
- --color-green: var(--tr-color-green);
65
- --color-blue: var(--tr-color-blue);
66
- --color-rule: var(--tr-color-rule);
67
- --color-gray-light: var(--tr-color-gray-light);
68
- --color-team-gold: var(--tr-color-team-gold);
69
- --color-mute: var(--tr-color-mute);
70
- --color-scrim: var(--tr-color-scrim);
71
- --color-footer-line: var(--tr-color-footer-line);
72
- --color-footer-mute: var(--tr-color-footer-mute);
73
- --color-divider-40: var(--tr-color-divider-40);
74
- --color-contact-deep: var(--tr-color-contact-deep);
75
- --color-header-deep: var(--tr-color-header-deep);
76
-
77
- /* ---------- Fonts ---------- */
78
- --font-sans: var(--tr-font-sans);
79
- --font-display: var(--tr-font-sans);
80
- --font-serif: var(--tr-font-serif);
81
- --font-num: var(--tr-font-num);
82
- --font-label: var(--tr-font-label);
83
-
84
- /* ---------- Type scale (mobile baseline; layer up with md:/lg:) ---------- */
85
- --text-xs: var(--tr-text-xs);
86
- --text-sm: var(--tr-text-sm);
87
- --text-base: var(--tr-text-base);
88
- --text-lg: var(--tr-text-lg);
89
- --text-xl: var(--tr-text-xl);
90
- --text-2xl: var(--tr-text-2xl);
91
- --text-3xl: var(--tr-text-3xl);
92
- --text-4xl: var(--tr-text-4xl);
93
- --text-5xl: var(--tr-text-5xl);
94
- --text-display: var(--tr-text-display);
95
-
96
- /* ---------- Spacing (Tailwind's own scale — no custom aliases) ---------- */
47
+ /* ---------- Spacing ---------- */
97
48
  --spacing: 0.25rem; /* v4 base unit; Tailwind multiplies (e.g. p-4 = 1rem) */
98
49
 
99
- /* ---------- Radii ---------- */
100
- --radius-sm: var(--tr-radius-sm);
101
- --radius-md: var(--tr-radius-md);
102
- --radius-lg: var(--tr-radius-lg);
103
- --radius-full: var(--tr-radius-full);
104
-
105
- /* ---------- Shadows ---------- */
106
- --shadow-sm: var(--tr-shadow-sm);
107
- --shadow-md: var(--tr-shadow-md);
108
- --shadow-lg: var(--tr-shadow-lg);
109
-
110
- /* ---------- Motion ---------- */
50
+ /* ---------- Motion ----------
51
+ * The --tr-* values these read are defined in base.css, which every page loads. */
111
52
  --default-transition-duration: var(--tr-duration-fast);
112
53
  --default-transition-timing-function: var(--tr-ease);
113
54
 
114
55
  /* ---------- Canonical breakpoint ladder ----------
115
- * Eight canonical widths — the same set the preview grid and the screenshot
116
- * harness verify a section against (VERIFICATION_WIDTHS is the matching TS
117
- * constant). 350 is the mobile-first BASE (no prefix); the other seven are
118
- * named breakpoints:
56
+ * Eight canonical widths — the same set the preview grid and the screenshot harness
57
+ * verify a section against. `VERIFICATION_WIDTHS` (design-system/breakpoints.ts) is
58
+ * the matching TS constant; CSS cannot import it, so `src/css/styles.test.ts` fails
59
+ * the build if the two ever drift.
60
+ *
61
+ * 350 is the mobile-first BASE (no prefix); the other seven are named breakpoints:
119
62
  *
120
63
  * bp390 (390) · bp425 (425) · bp512 (512) · md (768) · lg (1024)
121
64
  * · bp1440 (1440) · bp1920 (1920)
122
65
  *
123
66
  * `md` and `lg` keep Tailwind's own names and values. The off-ladder defaults
124
- * (sm/xl/2xl) are reset to `initial` so a section cannot come to depend on a
125
- * width nobody screenshots. A swap-heavy section may still reach for a raw
126
- * `@media` micro-breakpoint at an off-ladder width (e.g. 640 / 769).
67
+ * (sm/xl/2xl) are reset to `initial` so a section cannot come to depend on a width
68
+ * nobody screenshots. A swap-heavy section may still reach for a raw `@media`
69
+ * micro-breakpoint at an off-ladder width (e.g. 640 / 769).
127
70
  */
128
71
  --breakpoint-sm: initial;
129
72
  --breakpoint-bp390: 390px;
package/docs/INDEX.md CHANGED
@@ -2,7 +2,7 @@
2
2
  purpose: Router for the docs corpus that ships inside the @homepages/template-kit npm package.
3
3
  status: living
4
4
  related: []
5
- updated: 2026-07-14
5
+ updated: 2026-07-16
6
6
  ---
7
7
  # template-kit docs — router
8
8
 
@@ -13,10 +13,19 @@ detail.
13
13
 
14
14
  | Doc | What it covers | Read when you're touching... |
15
15
  |---|---|---|
16
+ | `quickstart.md` | The cold-start tutorial: create a workspace, preview the starter's `hero` section, read its four files, make one edit, and check it — owns the author-loop anchor every recipe links to | your first section, end to end |
17
+ | `overview.md` | The one-page mental model: workspace → templates → sections, the four reserved files, and the server-vs-client mapping | orienting yourself before your first section |
16
18
  | `llms.txt` | Machine-readable index of this corpus for an agent working inside an installed kit | orienting an agent in a consuming workspace |
17
19
  | `schema-system.md` | The section authoring contract: `schema.ts` / `fill-spec.ts` / `fixtures.ts` / `Renderer.tsx`, `SectionProps` inference, the closed source/transform vocabulary, and the golden property fixtures | authoring or changing any section |
20
+ | `vocabulary.md` | The closed authoring vocabulary — the 9 slot types, the `direct()` source fields, and the `derived()` transforms, with selection semantics | choosing which slot type / source / transform to use |
18
21
  | `primitives.md` | The five contract primitives (`Section`, `Slot`, `SlotItem`, `SlotGroup`, `Image`) and the marker attributes they emit | writing a section's markup |
19
- | `theme-and-css.md` | The two CSS entries, the one-Tailwind-import rule, `TokenTheme`, 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 |
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
+ | `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 |
20
24
  | `islands.md` | Interactivity: `"use client"` components, the serializable-props rule, editor options, the loader | anything interactive in a section |
21
- | `eslint.md` | The `@homepages/template-kit/eslint` preset: zero-config setup and its glob scope | wiring up lint in a template workspace |
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 |
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
+ | `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
+ | `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 |
29
+ | `pack.md` | The `template-kit pack` CLI command: builds a template's submission zip — template source, the workspace lockfile, and a stamped `pack-manifest.json`; refuses on a failing `check`, a missing lockfile, or a local-protocol dependency | packaging a template for submission |
22
30
  | `rules/` | One page per rule-id (`template-kit/*`), across both venues — the id printed by a lint or `check` failure resolves here. `rules/INDEX.md` routes them; `rules/server-vs-client.md` is the concept the server-rendering rules assume | any lint or `check` failure |
31
+ | `recipes/` | Task recipes: how do I add a slot, bind a fact, add an island, package a template… Each is a minimal delta from the starter's `hero` section, ending in the author loop. `recipes/INDEX.md` routes them by task | doing something specific and not sure which file to touch |
package/docs/assets.md ADDED
@@ -0,0 +1,128 @@
1
+ ---
2
+ purpose: Authoring static assets (svg, images, fonts) at the template level or in a section folder, and how they differ from content images.
3
+ status: living
4
+ related: [INDEX.md, primitives.md, theme-and-css.md, rules/size-assets.md, rules/bundle-binary-asset.md, rules/no-raw-element.md]
5
+ updated: 2026-07-16
6
+ ---
7
+ # Static assets
8
+
9
+ A **static asset** is a file that is part of the template itself — a logo, a
10
+ background texture, an icon, a brand webfont. It is authored once, is the
11
+ **same for every deliverable** rendered from the template, and is referenced
12
+ either from your CSS or from a component.
13
+
14
+ ## Where an asset lives
15
+
16
+ An asset lives in one of two places:
17
+
18
+ - **Template level** — e.g. `templates/<template>/assets/` — shared across every
19
+ section in the template. Reference it from a section's CSS with a `../../assets/…`
20
+ path (below).
21
+ - **Section folder** — e.g. `sections/<section>/assets/` — local to that one
22
+ section. A section-folder asset must be *referenced*: imported in `Renderer.tsx`
23
+ or `url()`-referenced from one of the section's CSS files. An unreferenced binary
24
+ in a section folder is flagged by [`bundle-binary-asset`](rules/bundle-binary-asset.md) —
25
+ it has no build meaning and can never be served.
26
+
27
+ Keep a logo, texture, or font that several sections share in the template-level
28
+ folder; keep one that only a single section uses alongside that section.
29
+
30
+ ## Supported files
31
+
32
+ ```
33
+ .svg .png .jpg .jpeg .gif .webp .avif .ico images
34
+ .woff .woff2 .ttf .otf .eot fonts
35
+ .mp4 .webm .mov .mp3 .wav audio / video
36
+ .pdf .zip documents
37
+ ```
38
+
39
+ ## Referencing an asset — from CSS with `url()`
40
+
41
+ Reference a template-level asset from a section's `styles.css` with `url()` —
42
+ a `@font-face` `src` or a `background-image` resolves to the asset's built URL,
43
+ identical in the `template-kit dev` preview, in the editor, and on the published
44
+ page. Hand-CSS opens with a `css-reason:` comment (see
45
+ [`css-reason`](rules/css-reason.md)):
46
+
47
+ ```css
48
+ /* css-reason: brand webfont + crest watermark, both template assets */
49
+ @font-face {
50
+ font-family: "Brand Serif";
51
+ src: url("../../assets/fonts/brand.woff2") format("woff2");
52
+ font-display: swap;
53
+ }
54
+
55
+ .hero-crest {
56
+ background-image: url("../../assets/crest.svg");
57
+ }
58
+ ```
59
+
60
+ The `../../assets/…` path climbs out of the section folder to the template-level
61
+ `assets/` directory. A font you declare here is available to the whole section.
62
+ If the family you want is one the template's theme already loads, reference that
63
+ instead — see [Theme and CSS](theme-and-css.md); a font the page already has
64
+ costs nothing to reuse.
65
+
66
+ A **section-folder** asset is referenced the same way, with a section-relative
67
+ path instead — no climbing out of the folder:
68
+
69
+ ```css
70
+ /* css-reason: hero watermark, local to this section */
71
+ .hero-crest {
72
+ background-image: url("./assets/crest.svg");
73
+ }
74
+ ```
75
+
76
+ ## Referencing an asset — from a component with `import`
77
+
78
+ An asset you render as an element — a logo, a crest — can be imported in the component and
79
+ handed to the `Image` primitive, whose `src` accepts a URL string:
80
+
81
+ ```tsx
82
+ import { Image } from "@homepages/template-kit";
83
+ import crest from "./assets/crest.svg";
84
+
85
+ export default function Renderer() {
86
+ return <Image src={crest} alt="" />;
87
+ }
88
+ ```
89
+
90
+ The import resolves to the asset's built `/section-assets/…` URL — the same URL in the
91
+ `dev` preview, the editor, and the published page. The kit ships an ambient TypeScript
92
+ declaration that types every supported asset import as that URL string, so the import
93
+ typechecks with no per-workspace setup. Use `alt=""` for a purely decorative
94
+ asset. (Never a raw `<img>` — `Image` carries the responsive/fallback contract; a raw
95
+ `<img>` is rejected by [`no-raw-element`](rules/no-raw-element.md).)
96
+
97
+ ## Which to use
98
+
99
+ - CSS decoration (backgrounds, `@font-face`, masks) → `url()`.
100
+ - An asset rendered as an element → `import` + `<Image>`.
101
+ - A tiny glyph or icon → inline `<svg>` markup — no file at all.
102
+
103
+ ## Assets vs. content images
104
+
105
+ These are two different things; keep them apart.
106
+
107
+ - A **static asset** is authored template decoration — a logo, a texture, a brand
108
+ font. It ships inside the template, is the same on every deliverable, and you
109
+ reference it from your CSS with `url()` or by importing it into a component (above).
110
+ - A **content image** is the property's own content — listing photos, floor
111
+ plans, a headshot. It differs on every deliverable and flows through the
112
+ section's slots via the [`Image`](primitives.md) descriptor, never as a file in
113
+ your template. Declare an image slot in your schema and render it with the
114
+ `Image` primitive; the platform supplies the actual photo per deliverable.
115
+
116
+ Rule of thumb: if the picture is the same for every home the template ever
117
+ renders, it's an asset — place it at the template level or in the section folder
118
+ and reference it from CSS or a component `import`. If it changes with the
119
+ property, it's content — give it a slot. Never hard-code a listing photo as an
120
+ asset.
121
+
122
+ ## Keep them lean
123
+
124
+ Every asset a section pulls in counts toward its asset budget — see
125
+ [`size-assets`](rules/size-assets.md). Fonts and images are already compressed, so
126
+ the budget is raw bytes: subset a webfont to the weights and characters you use,
127
+ export images at the size they render, and drop anything a section doesn't
128
+ actually reference.
package/docs/check.md ADDED
@@ -0,0 +1,124 @@
1
+ ---
2
+ purpose: The `template-kit check` command — what it runs, its flags, its output shape, and its exit code.
3
+ status: living
4
+ related: [INDEX.md, rules/INDEX.md, eslint.md, theme-and-css.md, dev.md]
5
+ updated: 2026-07-15
6
+ ---
7
+ # template-kit check
8
+
9
+ `template-kit check` is the one command that answers "will the platform accept this
10
+ template?" — the same gate you run locally and the one the registry re-runs when you
11
+ submit. Every failure prints a stable `template-kit/<id>`, a `file:line` where one
12
+ applies, and a one-line fix hint. The id resolves to its own page under
13
+ [`rules/`](rules/INDEX.md).
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ template-kit check # infer the template from cwd, or the workspace's one template
19
+ template-kit check <template> # check one named template by its key
20
+ template-kit check --all # check every template in the workspace
21
+ template-kit check --json # machine-readable report (combine with any of the above)
22
+ ```
23
+
24
+ Run it from the workspace root, or from inside a template's own folder — a bare `check`
25
+ infers which template you mean. A workspace with more than one template must either name
26
+ one or pass `--all`.
27
+
28
+ ### What it needs installed
29
+
30
+ ```bash
31
+ npm i -D typescript eslint @typescript-eslint/parser esbuild
32
+ ```
33
+
34
+ `check` runs **your** toolchain, not a copy of its own: it resolves TypeScript, ESLint
35
+ and esbuild from your workspace, so the compiler that judges your types is the one you
36
+ pinned, and a linter and a bundler never end up in the production install of a package
37
+ whose job is to render sections. A missing one is reported by name, with the install
38
+ line to fix it.
39
+
40
+ ## What it runs
41
+
42
+ Two granularities, because a workspace has one `package.json` and one lockfile, but can
43
+ hold many templates.
44
+
45
+ **Six authoring stages**, run in order, once per selected template. None of them abort
46
+ the run — a template with several unrelated problems reports all of them in one pass, not
47
+ one class of fix at a time:
48
+
49
+ 1. **typecheck** — the workspace's own TypeScript compiles the template's files, judged
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.
54
+ 3. **validate** — the schema, fill-spec, fixtures, and manifest contracts: every
55
+ cross-reference between a section's contract files, and a template's `manifest.json`
56
+ against the sections it composes.
57
+ 4. **tree** — the rules a one-file-at-a-time linter cannot express: a missing contract
58
+ file, a slot declared in `schema.ts` with no marker anywhere in the section's JSX, a
59
+ hand-written CSS file's own rules.
60
+ 5. **render** — a determinism probe (every fixture is rendered twice; the two outputs
61
+ must be byte-identical) and a scan of the rendered HTML for objective defects — a
62
+ stray `[object Object]`, a bare `null`/`undefined`/`NaN` leaf, a `src`-less `<img>` —
63
+ plus, for the `typical` fixture, that the editor's sidebar order matches the page's
64
+ real render order.
65
+ 6. **size** — every section's renderer bundle, compiled stylesheet, and largest island
66
+ (browser) bundle, gzipped, against a fixed byte budget; and its static assets
67
+ (svg/images/fonts), budgeted by raw bytes since they are already compressed.
68
+
69
+ **The dependency gates** run once per invocation, never once per template — they are
70
+ properties of the workspace's one lockfile and installed tree, not of any one template:
71
+ a lockfile present and satisfying `package.json`, exactly one copy of React installed,
72
+ no `npm audit` finding at high or critical severity, and every shipped dependency's
73
+ license on an allowlist.
74
+
75
+ A run that selects zero templates to check is itself a failure —
76
+ [`no-templates`](rules/no-templates.md) — never a silent pass.
77
+
78
+ ## Output
79
+
80
+ Human-readable output prints one line per template (`✓ <key>`, or `✗ <key> — N
81
+ problem(s)` followed by each finding), then any workspace-level findings from the
82
+ dependency gates, then `check passed` if everything passed.
83
+
84
+ `--json` prints one report object instead, keyed per template so a single-template
85
+ consumer (the registry, ingesting one submission) and an all-templates consumer (your
86
+ own CI) parse the same shape:
87
+
88
+ ```ts
89
+ {
90
+ ok: boolean,
91
+ kitVersion: string,
92
+ templates: {
93
+ [templateKey: string]: { ok: boolean, diagnostics: Diagnostic[] },
94
+ },
95
+ // Dependency-gate findings — evaluated once, not per template.
96
+ workspace: { ok: boolean, diagnostics: Diagnostic[] },
97
+ }
98
+ ```
99
+
100
+ Each `Diagnostic` carries a `ruleId` (`template-kit/<id>`), the `template` it belongs to,
101
+ an optional `section` and `file`/`line`, a `message`, and a `fix`.
102
+
103
+ ## Exit code
104
+
105
+ `0` iff every selected template and the workspace pass. `1` otherwise — including when
106
+ the run itself can't proceed at all (an unknown template name, no `templates/` folder
107
+ in the workspace).
108
+
109
+ ## Thresholds are not configurable
110
+
111
+ Every byte budget, severity floor, and license allowlist `check` enforces is a platform
112
+ acceptance criterion, not a per-workspace preference — there is no config file or flag
113
+ that raises them. If a template has a legitimate reason to need an exception, ask the
114
+ platform team; a workaround that quietly disables a gate will simply fail again at
115
+ submission.
116
+
117
+ ## See also
118
+
119
+ - [Rules](rules/INDEX.md) — every id `check` (and the lint preset) can print, one page
120
+ each, Rule / Reason / Fix.
121
+ - [ESLint preset](eslint.md) — the one-file-at-a-time half of the same rule set, run
122
+ directly by your editor, not only through `check`.
123
+ - [Dev](dev.md) — the other workspace command: preview sections in a browser as you
124
+ author, before running `check` as the acceptance gate.