@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/CHANGELOG.md CHANGED
@@ -1,5 +1,227 @@
1
1
  # @homepages/template-kit
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 94d6458: `template-kit dev`: a full-template published page at `/template/<t>`, scope-aware
8
+ `/api/screenshot` (section or whole template, with `width`), and a new `/api/inspect`
9
+ endpoint returning per-slot fill-state (filled / empty / missing + a render
10
+ cross-check), resolved props, and the null-corpus. Retires `/api/structure-summary`
11
+ (its outline is now `/api/inspect?format=text`).
12
+ - 47dbbc6: Section-folder static assets are now first-class. `check`'s `bundle-binary-asset` rule
13
+ flags only an _unreferenced_ binary in a section folder; an asset imported in a component
14
+ (`import crest from "./assets/crest.svg"` → `<Image src={crest} …>`) or `url()`-referenced
15
+ from a section stylesheet is allowed. A `declare module "*.svg"` (and the other supported
16
+ asset extensions) type shim ships so component imports of assets typecheck.
17
+ - 9a3e0e1: Adds a read-only canvas mirror to `template-kit dev`, at `/canvas/<template>`. It frames
18
+ the **whole template** — every section instance, stacked in one scroll container, exactly
19
+ as the deliverable renders — in an iframe and paints a hover/select overlay on top: click
20
+ an editable slot to select it (a gold ring), hover to preview it (a blue ring);
21
+ non-editable regions select their enclosing section instead. Selection is scoped per
22
+ section instance, so the same slot name in two sections resolves independently. The
23
+ overlay is purely visual — it never alters the rendered markup. A mode toggle switches to
24
+ a clean published render of the full template with no overlay, and the current view —
25
+ template, mode, and the selected instance/slot — is captured in the URL, so a canvas link
26
+ reloads and shares exactly what you see.
27
+
28
+ The `dev` server gains `GET /api/template-schema?template=<t>`, returning each section
29
+ instance's editability map (per slot: its type and whether it is user-editable) for the
30
+ whole template, so a tool can tell which slots are selectable without re-parsing the
31
+ section schemas. (The per-section `/api/render` response also still carries its own
32
+ `slotSchema` map.)
33
+
34
+ - 7f68570: Canvas mirror: exercise slots interactively. Selecting an editable slot opens a
35
+ side panel with a JSON editor for its value; applying a change re-renders that
36
+ section instance live. A per-instance fixture picker drives different content —
37
+ including the empty/missing states from the section's fixture corpus — without
38
+ editing fixture files.
39
+ - 5a8dcd4: Adds the `dev` command: `template-kit dev [template] [--port <n>]`. A local preview
40
+ server for a workspace's templates — it renders every section against its fixtures
41
+ server-side, with the template's theme and compiled Tailwind utilities, exactly the way
42
+ the platform does. A browser UI gives you a picker (template → section → fixture) and a
43
+ grid contact sheet of every fixture at each breakpoint, including empty and null states;
44
+ each section also has a standalone preview at `/sections/<template>/<section>/<fixture>`.
45
+ Editing a section reloads open previews; editing a theme hot-swaps its stylesheet.
46
+
47
+ Endpoints render a section without a browser, for an agent verifying its work from a
48
+ terminal: `/api/inspect` (a low-context per-slot fill-state view as JSON, with
49
+ `?format=text` for the rendered outline) and `/api/screenshot` (a full-page PNG). Like
50
+ `check`, `dev` runs your
51
+ toolchain — Vite and Tailwind resolved from your workspace, not shipped by the kit:
52
+
53
+ ```bash
54
+ npm i -D vite @vitejs/plugin-react tailwindcss @tailwindcss/cli
55
+ npm i -D @playwright/test # optional, for /api/screenshot
56
+ ```
57
+
58
+ - 13c3132: Add `template-kit pack <template>` — the per-template submission zip (template
59
+ source + workspace lockfile + a stamped `pack-manifest.json`), which refuses on a
60
+ failing `check`, an active link overlay, a missing lockfile, or local-protocol
61
+ deps. Add the internal `link`/`unlink`/`status` overlay for developing the kit
62
+ against a real consumer without publishing.
63
+ - bcf6fbb: `@homepages/template-kit/ssr` now also exports `wrapClientModuleSource` (the `"use
64
+ client"` → `IslandMarker` codemod), `findSectionIslands` (recursive `"use client"`
65
+ component discovery under a section directory), and `islandTransformEsbuildPlugin`
66
+ (an esbuild `onLoad` plugin that applies the same codemod across a build graph, keyed
67
+ `islandKey(sectionKey, <file-basename>)`). All three are the same primitives
68
+ `template-kit dev`'s Vite plugin already used internally, now exposed so a build-time
69
+ consumer — the publish renderer bundle's esbuild pass, or a native-import loader hook —
70
+ can wrap island modules identically without duplicating the codemod. `esbuild` stays a
71
+ devDependency of the kit; `islandTransformEsbuildPlugin` imports its `Plugin` type only.
72
+ - b76f822: Add the node-only `@homepages/template-kit/ssr` subpath: the shared section SSR helper (`loadSectionBundle`, `renderSectionHtml`) that renders a section + fixture to the exact HTML the publisher emits, plus island detection (`isClientComponentFile`, `hasClientDirective`) and key derivation (`islandKey`) for a section's `"use client"` components.
73
+
74
+ ## 0.3.0
75
+
76
+ ### Minor Changes
77
+
78
+ - bdb4dec: `zod` is now the kit's only dependency of any kind, besides its React peers. ESLint,
79
+ `@typescript-eslint/parser` and `esbuild` are no longer declared by the kit at all — a
80
+ linter and a bundler are development tools, and as dependencies they landed in the
81
+ **production** install of every consumer, including apps that only render a published
82
+ template and deploy with `npm ci --omit=dev`. For a consuming service that lints, the
83
+ production dependency graph drops by roughly 125 packages.
84
+
85
+ **Action required if you use the ESLint preset or the `check` command** — install the
86
+ toolchain they run, which they now resolve from your workspace rather than shipping
87
+ their own copy of:
88
+
89
+ ```bash
90
+ npm i -D eslint @typescript-eslint/parser # for @homepages/template-kit/eslint
91
+ npm i -D typescript esbuild # for template-kit check (with the two above)
92
+ ```
93
+
94
+ Nothing changes for code that only imports the kit's primitives, schema system or theme
95
+ tokens. The preset's rules and parser wiring are unchanged; `check`'s stages and rule ids
96
+ are unchanged — and it now reports a missing tool by name, with the install line, instead
97
+ of failing obscurely. As a side benefit, `check` runs against _your_ pinned TypeScript,
98
+ ESLint and esbuild.
99
+
100
+ - 49177ab: Adds the `check` command: `template-kit check [template] [--all] [--json]`. Runs a
101
+ template's authoring contract end to end — type errors, the lint preset, the schema and
102
+ manifest contracts, section-folder structure (contract files present, editor slot
103
+ markers, hand-CSS hygiene), a render-regression pass (determinism, broken images, and
104
+ other objective defects), and per-section bundle-size budgets — plus workspace-wide
105
+ dependency gates (lockfile health, a single copy of React, dependency licenses,
106
+ vulnerability severity) evaluated once per run. Every failure prints a stable rule id, a
107
+ file and line, and a one-line fix. Exits non-zero on any failure; pass `--json` for a
108
+ script-consumable report.
109
+ - 2a30f0b: `template-kit theme` generates your template's `theme.css` from its `theme.ts`.
110
+
111
+ `@theme` is a Tailwind directive — it only does anything when a real `.css` file carrying
112
+ it flows through your Tailwind entry. So the alias layer `compileThemeToCss` produces from
113
+ your `theme.ts` has to reach that entry as a file. Rather than call the compiler from your
114
+ own build script (and risk `theme.css` silently drifting from `theme.ts`), run:
115
+
116
+ ```
117
+ template-kit theme # the template in the current folder
118
+ template-kit theme <name> # a named template
119
+ template-kit theme --all # every template in the workspace
120
+ ```
121
+
122
+ It reads `templates/<key>/theme.ts` (which must `export const theme: TokenTheme`), validates
123
+ it, and writes `templates/<key>/theme.css` with a "generated — do not edit" header. Import
124
+ that file into your Tailwind entry after `@import "tailwindcss"` and the kit's `styles.css`:
125
+
126
+ ```css
127
+ @import "tailwindcss";
128
+ @import "@homepages/template-kit/styles.css";
129
+ @import "./theme.css"; /* generated by `template-kit theme` */
130
+ ```
131
+
132
+ Re-run the command whenever you change `theme.ts`; a token you add becomes a working Tailwind
133
+ utility with nothing else to edit. A missing `theme.ts`, a missing `theme` export, or a theme
134
+ that fails validation each fail with an actionable message.
135
+
136
+ - e15de58: Your template now owns its design tokens end to end — and names them itself.
137
+
138
+ `TokenTheme` no longer imposes a vocabulary. Instead of satisfying a fixed set of
139
+ semantic roles (`ink`, `surfaceAlt`, `primaryStrong`, …), you declare whatever tokens
140
+ your design actually has, under kebab-case names you choose, and `compileThemeToCss`
141
+ generates the Tailwind `@theme inline` alias layer from them. Declare `lagoon` and
142
+ `bg-lagoon` exists. Nothing is required, and Tailwind's own defaults (`text-2xl`,
143
+ `rounded-lg`, `red-500`) still work underneath, so a sparse theme is a working theme.
144
+
145
+ This also closes the hole that made the old "open palette" a fiction: a brand swatch
146
+ only became a usable utility once it was added to the kit's shared `styles.css`, so it
147
+ was never really yours. There is no shared file to edit any more.
148
+
149
+ **Upgrading — three changes:**
150
+
151
+ 1. **Rewrite `theme.ts`.** `colors` and `palette` merge into one open record, and keys
152
+ become kebab-case (`inkSoft` → `ink-soft`). `fontFaces` moves out of `fonts` to the
153
+ top level. Emission is verbatim: the key `ink-soft` becomes `--tr-color-ink-soft` and
154
+ the `text-ink-soft` utility.
155
+
156
+ One token yields exactly one utility, so two utility names mean two tokens. If you
157
+ relied on an alias that pointed two names at one value, declare both.
158
+
159
+ 2. **Add a `document` block.** The `body` defaults can no longer be assumed — there is no
160
+ `ink` to guess at — so name the tokens that dress the page:
161
+
162
+ ```ts
163
+ document: { font: "sans", color: "ink", background: "page", fontSize: "base" }
164
+ ```
165
+
166
+ Each reference is validated against your declared tokens: a typo fails at build
167
+ rather than rendering an unthemed page in silence.
168
+
169
+ 3. **Import your compiled theme into your Tailwind entry.** `@theme` is a Tailwind
170
+ directive and is inert outside the build graph, so the theme partial must be
171
+ `@import`ed by your entry, after the kit's `styles.css`:
172
+
173
+ ```css
174
+ @import "tailwindcss";
175
+ @import "@homepages/template-kit/styles.css";
176
+ @import "./theme.css"; /* compileThemeToCss output */
177
+ ```
178
+
179
+ `styles.css` and `base.css` keep their export paths — no import changes. `styles.css`
180
+ now carries only the breakpoint ladder, the `--spacing` base unit, and the motion
181
+ defaults.
182
+
183
+ The `Image` primitive no longer paints its frame with `bg-surface-alt` (a token your
184
+ theme may not declare). The fill lives on `.tr-image-frame` in `base.css`; retint it by
185
+ setting `--tr-image-frame-bg`.
186
+
187
+ - 57eddee: Migrate the schema system from Zod 3 to Zod 4.
188
+
189
+ `zod` is the kit's one runtime dependency, and it now tracks Zod 4 (`"zod": "^4"`).
190
+ Because the types you author section schemas against are Zod types, bumping Zod a
191
+ major is a breaking change for authors: your workspace resolves Zod 4 in its
192
+ lockfile, and the schema/slot types you see in editor tooltips are now Zod 4 types.
193
+
194
+ **Action required** — if your workspace declares `zod`, move it to `^4` so a single
195
+ Zod copy resolves across your workspace and the kit:
196
+
197
+ ```bash
198
+ npm i zod@^4
199
+ ```
200
+
201
+ Nothing else changes. The authoring API is untouched — the slot-type catalog, the
202
+ source/transform vocabulary, and `SectionProps<typeof schema>` inference are all
203
+ identical, and every section still materializes to a byte-identical serialized
204
+ schema. This is a dependency migration, invisible to an author apart from the Zod
205
+ version in the lockfile.
206
+
207
+ ### Patch Changes
208
+
209
+ - 6675c4c: `no-hex` no longer reports a `color-mix()` whose colour arguments are all theme-derived.
210
+ A mix over `var(--…)` references, `transparent`, or `currentColor` carries no palette of
211
+ its own — it composes a state colour (hover, active, pressed) out of the theme the
212
+ section was handed — so banning it contradicted the rule's own reason. A hard-coded
213
+ argument still reports: `color-mix(in srgb, var(--tr-color-primary) 80%, #fff)` and
214
+ `color-mix(…, white)` are violations, as is a `var()` fallback that bakes a colour
215
+ (`var(--x, #fff)`). The carve-out is `color-mix()` only — `rgb()`, `oklch()`, and the
216
+ rest take raw channel values and stay banned outright, so the "a section carries zero
217
+ palette" guarantee is unchanged.
218
+ - 1749cea: `no-hex` now ships as a **warning**, not an error. Centralized theme colours are
219
+ encouraged, not gated: a hard-coded colour still squiggles in the editor and the rule
220
+ still explains how to derive the colour from theme tokens, but it no longer fails
221
+ `template-kit check` locally or at ingest (both gate on errors only). Every other preset
222
+ rule stays an error — this is the one advisory rule. The `color-mix()` over theme-derived
223
+ arguments carve-out is unchanged; it just governs a warning now instead of an error.
224
+
3
225
  ## 0.2.0
4
226
 
5
227
  ### Minor Changes
package/README.md CHANGED
@@ -1,21 +1,48 @@
1
1
  # @homepages/template-kit
2
2
 
3
3
  The authoring surface for HomePages marketing-section templates: schema system,
4
- contract primitives, theme tokens, and the `template-kit` CLI.
4
+ contract primitives, the theme system, and the `template-kit` CLI.
5
5
 
6
6
  **Status: 0.0.x.** The **schema system** (the section authoring contract and the closed
7
- vocabulary it validates against), the **contract primitives**, and the **theme tokens**
7
+ vocabulary it validates against), the **contract primitives**, and the **theme system**
8
8
  are complete — a section is authorable end to end against the kit. The `template-kit`
9
- CLI's commands are still being extracted.
9
+ CLI's `check`, `theme`, `dev`, and `pack` commands are implemented see
10
+ [Checking a workspace](#checking-a-workspace), [Generating a theme](#generating-a-theme),
11
+ and [Previewing a workspace](#previewing-a-workspace) below — and `new` / `new-section`
12
+ scaffold a template or a section into a workspace (see [docs/new.md](docs/new.md)).
13
+
14
+ ## Quick start
15
+
16
+ The fastest way to start is the workspace scaffolder — it creates a workspace with a
17
+ working starter template and the full toolchain already wired:
18
+
19
+ ```bash
20
+ npm create homepages-workspace <name>
21
+ cd <name>
22
+ npm install
23
+ npm run dev # preview the starter; the island hydrates
24
+ ```
25
+
26
+ Add more templates with `npx template-kit new <template>` and sections with
27
+ `npx template-kit new-section <template>/<section>` — see [docs/new.md](docs/new.md).
28
+ Prefer to wire a workspace by hand? See **Install** below.
10
29
 
11
30
  ## Install
12
31
 
13
32
  ```bash
14
33
  npm install @homepages/template-kit react react-dom
34
+ npm install -D typescript eslint @typescript-eslint/parser esbuild # the authoring toolchain
15
35
  ```
16
36
 
17
37
  React 19 is a **peer dependency** — the kit never bundles its own copy.
18
38
 
39
+ The kit has exactly **one runtime dependency** (`zod`). The authoring toolchain the
40
+ [`check` command](docs/check.md) and the [ESLint preset](docs/eslint.md) run on is
41
+ yours: both resolve TypeScript, ESLint, the parser and esbuild from your workspace, so
42
+ your pinned versions are the ones that judge your code. The kit does not depend on them
43
+ at all — which is why a linter never lands in the production install of an app that
44
+ merely renders a published template.
45
+
19
46
  ## Authoring a section
20
47
 
21
48
  A section is four files: `schema.ts` (slots + options), `fill-spec.ts` (how AI
@@ -64,7 +91,7 @@ Full contract: **[docs/primitives.md](docs/primitives.md)**.
64
91
  | `@homepages/template-kit` | Authoring API — schema system, primitives, marker contract, theme types, and the golden fixtures |
65
92
  | `@homepages/template-kit/island-runtime` | Browser loader that hydrates a page's islands |
66
93
  | `@homepages/template-kit/browser` | Browser helpers for published pages (site config, signed ingest) |
67
- | `@homepages/template-kit/styles.css` | Tailwind v4 theme tokens, the breakpoint ladder, kit source registration |
94
+ | `@homepages/template-kit/styles.css` | The global Tailwind preamble: the breakpoint ladder, the spacing base unit, the motion defaults, kit source registration |
68
95
  | `@homepages/template-kit/base.css` | Cascade-layer order, the three reset gaps Tailwind's preflight leaves, `.tr-section`, and the motion tokens |
69
96
  | `@homepages/template-kit/eslint` | ESLint flat-config preset |
70
97
  | `@homepages/template-kit/tsconfig` | tsconfig preset (`"extends"`) |
@@ -72,18 +99,21 @@ Full contract: **[docs/primitives.md](docs/primitives.md)**.
72
99
  The authoring API has **no subpath imports** — one specifier for everything you
73
100
  write a section against. The two CSS entries are the deliberate exception.
74
101
 
75
- `styles.css` does **not** import Tailwind. Import it *after* Tailwind in your entry:
102
+ `styles.css` does **not** import Tailwind. Import it *after* Tailwind in your entry, then
103
+ your generated theme after that:
76
104
 
77
105
  ```css
78
106
  @import "tailwindcss";
79
107
  @import "@homepages/template-kit/styles.css";
108
+ @import "./theme.css"; /* generated by `template-kit theme` from theme.ts */
80
109
  ```
81
110
 
82
- Two `@import "tailwindcss"` in one graph double-emits preflight. `base.css` is not part
83
- of that entry it loads as its own stylesheet on the page. `base.css` ships no reset
84
- of its own it depends on the preflight your entry's `@import "tailwindcss"` brings.
85
- Both must be on the page. Full contract:
86
- **[docs/theme-and-css.md](docs/theme-and-css.md)**.
111
+ Two `@import "tailwindcss"` in one graph double-emits preflight. Your generated theme
112
+ belongs in this entry and nowhere else: it carries an `@theme` block, a Tailwind directive
113
+ that is inert outside the build graph. `base.css` is not part of that entry it loads as
114
+ its own stylesheet on the page. `base.css` ships no reset of its own — it depends on the
115
+ preflight your entry's `@import "tailwindcss"` brings. Both must be on the page. Full
116
+ contract: **[docs/theme-and-css.md](docs/theme-and-css.md)**.
87
117
 
88
118
  ## Rules
89
119
 
@@ -93,6 +123,52 @@ The lint preset and the `template-kit check` CLI share one id namespace,
93
123
  Start at **[docs/rules/INDEX.md](docs/rules/INDEX.md)**; the full doc set is routed
94
124
  from **[docs/INDEX.md](docs/INDEX.md)**.
95
125
 
126
+ ## Generating a theme
127
+
128
+ ```bash
129
+ template-kit theme # infer the template from cwd, or the workspace's one template
130
+ template-kit theme --all # generate every template's theme.css
131
+ ```
132
+
133
+ `theme` compiles a template's `theme.ts` into `templates/<key>/theme.css` — the file your
134
+ Tailwind entry imports. The output is generated: don't hand-edit it, and re-run the
135
+ command whenever `theme.ts` changes. Full contract:
136
+ **[docs/theme-and-css.md](docs/theme-and-css.md#generating-themecss)**.
137
+
138
+ ## Previewing a workspace
139
+
140
+ ```bash
141
+ template-kit dev # serve every template in the workspace
142
+ template-kit dev <template> # preselect one template in the picker
143
+ template-kit dev --port 4000 # choose the port (default 5180)
144
+ ```
145
+
146
+ `dev` starts a local server that renders every section against its fixtures the same way
147
+ the platform does — with your template's theme and compiled Tailwind — and reloads as you
148
+ edit. A browser UI gives you a picker and a grid contact sheet across breakpoints; each
149
+ section also has a standalone page, and so does the whole template at
150
+ `/template/<template>`. Three endpoints let an agent verify a render from a terminal:
151
+ `/api/inspect` (per-slot fill-state as JSON), `/api/render` (the full structured render
152
+ payload), and `/api/screenshot` (a full-page PNG, section or whole template). Like
153
+ `check`, it runs **your** toolchain — Vite and Tailwind resolved from your workspace,
154
+ installed with `npm i -D vite @vitejs/plugin-react tailwindcss @tailwindcss/cli` — not
155
+ shipped by the kit. Full contract: **[docs/dev.md](docs/dev.md)**.
156
+
157
+ ## Checking a workspace
158
+
159
+ ```bash
160
+ template-kit check # infer the template from cwd, or the workspace's one template
161
+ template-kit check --all # check every template
162
+ template-kit check --json # machine-readable report
163
+ ```
164
+
165
+ `check` typechecks, lints, and validates a template's schema/fill-spec/fixtures/manifest
166
+ contracts, runs a determinism render probe, and enforces per-section byte budgets —
167
+ plus workspace-wide dependency gates (lockfile health, a single React, license and
168
+ vulnerability checks) evaluated once per run. It exits non-zero on any failure and is
169
+ the same gate the platform re-runs when you submit a template. Full contract:
170
+ **[docs/check.md](docs/check.md)**.
171
+
96
172
  ## Development
97
173
 
98
174
  ```bash
@@ -101,11 +177,13 @@ npm run check # typecheck + lint + build + island-size gate + test + publint
101
177
  ```
102
178
 
103
179
  `npm run verify:consumer` is the boundary gate: it packs the kit, installs the
104
- tarball into a scratch project, and asserts that types resolve; that a real section
180
+ tarball into scratch projects, and asserts that a service which pins the kit and
181
+ deploys with `npm ci --omit=dev` installs no ESLint or esbuild package at all, with
182
+ `dependencies` carrying nothing but `zod`; that types resolve; that a real section
105
183
  triple typechecks against the installed package, with a misspelled property fact or
106
- slot key still failing to compile; that Tailwind's cross-package `@source` scan
107
- reaches the shipped `dist`; and that only one React ends up in the tree. Run it
108
- before every release.
184
+ slot key still failing to compile; that the ESLint preset lints using the consumer's
185
+ own parser; that Tailwind's cross-package `@source` scan reaches the shipped `dist`;
186
+ and that only one React ends up in the tree. Run it before every release.
109
187
 
110
188
  The island loader (`@homepages/template-kit/island-runtime`) has its own gate,
111
189
  `npm run size:islands`: it must stay under 3072 B gzip and never import
@@ -131,9 +209,10 @@ token anywhere, and adding one would silently disable OIDC.
131
209
 
132
210
  3. **Merge the Version Packages PR.** That merge publishes to npm.
133
211
 
134
- Semver contract: the platform vocabulary (slot types, sources, transforms,
135
- tokens) is closed and versioned. A breaking change to it is a **major**; a new
136
- optional capability is a **minor**.
212
+ Semver contract: the platform vocabulary (slot types, sources, transforms) is
213
+ closed and versioned. A breaking change to it is a **major**; a new optional
214
+ capability is a **minor**. Design tokens are *not* part of that vocabulary —
215
+ your template names its own.
137
216
 
138
217
  ### Bumping the version by hand
139
218
 
@@ -0,0 +1,81 @@
1
+ //#region src/asset-modules.d.ts
2
+ declare module "*.svg" {
3
+ const src: string;
4
+ export default src;
5
+ }
6
+ declare module "*.png" {
7
+ const src: string;
8
+ export default src;
9
+ }
10
+ declare module "*.jpg" {
11
+ const src: string;
12
+ export default src;
13
+ }
14
+ declare module "*.jpeg" {
15
+ const src: string;
16
+ export default src;
17
+ }
18
+ declare module "*.gif" {
19
+ const src: string;
20
+ export default src;
21
+ }
22
+ declare module "*.webp" {
23
+ const src: string;
24
+ export default src;
25
+ }
26
+ declare module "*.avif" {
27
+ const src: string;
28
+ export default src;
29
+ }
30
+ declare module "*.ico" {
31
+ const src: string;
32
+ export default src;
33
+ }
34
+ declare module "*.woff" {
35
+ const src: string;
36
+ export default src;
37
+ }
38
+ declare module "*.woff2" {
39
+ const src: string;
40
+ export default src;
41
+ }
42
+ declare module "*.ttf" {
43
+ const src: string;
44
+ export default src;
45
+ }
46
+ declare module "*.otf" {
47
+ const src: string;
48
+ export default src;
49
+ }
50
+ declare module "*.eot" {
51
+ const src: string;
52
+ export default src;
53
+ }
54
+ declare module "*.mp4" {
55
+ const src: string;
56
+ export default src;
57
+ }
58
+ declare module "*.webm" {
59
+ const src: string;
60
+ export default src;
61
+ }
62
+ declare module "*.mov" {
63
+ const src: string;
64
+ export default src;
65
+ }
66
+ declare module "*.mp3" {
67
+ const src: string;
68
+ export default src;
69
+ }
70
+ declare module "*.wav" {
71
+ const src: string;
72
+ export default src;
73
+ }
74
+ declare module "*.pdf" {
75
+ const src: string;
76
+ export default src;
77
+ }
78
+ declare module "*.zip" {
79
+ const src: string;
80
+ export default src;
81
+ }
package/dist/base.css CHANGED
@@ -9,6 +9,7 @@
9
9
  * - The cascade-layer order declaration above — the one place it is stated
10
10
  * - The three reset gaps Tailwind's preflight leaves (below)
11
11
  * - The section box model (.tr-section, emitted by the <Section> primitive)
12
+ * - The <Image> frame's placeholder fill (.tr-image-frame)
12
13
  * - Universal tokens: motion
13
14
  *
14
15
  * REQUIRES A TAILWIND ENTRY ON THE PAGE. Your entry's `@import "tailwindcss"` brings
@@ -54,6 +55,14 @@
54
55
  margin: 0;
55
56
  }
56
57
 
58
+ /* The <Image> frame's placeholder fill, showing while an image is absent or loading.
59
+ * It is a plain class rather than a `bg-*` utility on the primitive because a template
60
+ * names its own tokens — there is no colour token this primitive may assume exists.
61
+ * Retint it by setting --tr-image-frame-bg in your theme's layout tokens. */
62
+ .tr-image-frame {
63
+ background-color: var(--tr-image-frame-bg, oklch(0.97 0 0));
64
+ }
65
+
57
66
  /* ----- Universal tokens ------------------------------------------------- */
58
67
 
59
68
  :root {
@@ -0,0 +1,41 @@
1
+ //#region src/cli/check/config.ts
2
+ const CHECK_CONFIG = {
3
+ size: {
4
+ /** Gzipped bytes of a section's renderer bundle, React excluded. */
5
+ rendererBundleGzip: 50 * 1024,
6
+ /** Gzipped bytes of a section's stylesheet, including CSS pulled in from packages. */
7
+ sectionCssGzip: 50 * 1024,
8
+ /** Gzipped bytes of a section's largest island (browser) bundle, React excluded. */
9
+ islandBundleGzip: 50 * 1024,
10
+ /** Raw bytes of a section's static assets (svg/images/fonts). RAW, not gzip — images/fonts are already compressed. */
11
+ assets: 250 * 1024
12
+ },
13
+ audit: {
14
+ /** A vulnerability at or above this severity fails the check. */
15
+ failOn: ["high", "critical"] },
16
+ /**
17
+ * A package whose license is not on this list fails the check. A missing
18
+ * license field, or a bespoke "SEE LICENSE IN …", is a denial — not a pass.
19
+ */
20
+ licenseAllowlist: [
21
+ "MIT",
22
+ "ISC",
23
+ "Apache-2.0",
24
+ "BSD-2-Clause",
25
+ "BSD-3-Clause",
26
+ "0BSD",
27
+ "Unlicense",
28
+ "CC0-1.0"
29
+ ],
30
+ /**
31
+ * The license gate walks the *shipped* dependency graph of every third-party
32
+ * package a template pulls in — this is the first-party exception to that.
33
+ * The kit itself is `UNLICENSED` by design (it is not published for reuse
34
+ * outside this platform) and every workspace depends on it, so without this
35
+ * exemption the gate would deny the kit on a stock, correct workspace.
36
+ */
37
+ licenseExemptions: ["@homepages/template-kit"]
38
+ };
39
+
40
+ //#endregion
41
+ export { CHECK_CONFIG };