@homepages/template-kit 0.3.0 → 0.4.1
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 +84 -0
- package/README.md +39 -4
- package/dist/asset-modules.d.ts +81 -0
- package/dist/cli/check/config.js +5 -1
- package/dist/cli/check/css.js +27 -13
- package/dist/cli/check/loader.js +4 -0
- package/dist/cli/check/section-assets.js +58 -0
- package/dist/cli/check/stages/size.js +124 -8
- package/dist/cli/check/stages/tree.js +26 -27
- package/dist/cli/dev/build-css.js +91 -0
- package/dist/cli/dev/catalog.js +35 -0
- package/dist/cli/dev/compose-template.js +38 -0
- package/dist/cli/dev/content-override.js +58 -0
- package/dist/cli/dev/discover.js +35 -0
- package/dist/cli/dev/fill-state.js +75 -0
- package/dist/cli/dev/index.js +32 -0
- package/dist/cli/dev/inspect.js +38 -0
- package/dist/cli/dev/island-bootstrap.js +38 -0
- package/dist/cli/dev/island-map.js +35 -0
- package/dist/cli/dev/island-transform.js +31 -0
- package/dist/cli/dev/manifest-instances.js +31 -0
- package/dist/cli/dev/render-section.js +24 -0
- package/dist/cli/dev/screenshot-target.js +24 -0
- package/dist/cli/dev/section-page.js +54 -0
- package/dist/cli/dev/server.js +480 -0
- package/dist/cli/dev/slot-schema.js +12 -0
- package/dist/cli/dev/structure-summary.js +118 -0
- package/dist/cli/dev/tailwind.js +32 -0
- package/dist/cli/dev/vite-server.js +33 -0
- package/dist/cli/dev/workspace.js +63 -0
- package/dist/cli/link/index.js +74 -0
- package/dist/cli/link/overlay.js +59 -0
- package/dist/cli/link/watch.js +25 -0
- package/dist/cli/new/emit.js +48 -0
- package/dist/cli/new/index.js +69 -0
- package/dist/cli/new/scaffold/section/_Renderer.tsx +20 -0
- package/dist/cli/new/scaffold/section/_fill-spec.ts +18 -0
- package/dist/cli/new/scaffold/section/_fixtures.ts +13 -0
- package/dist/cli/new/scaffold/section/_schema.ts +24 -0
- package/dist/cli/new/scaffold/template/_manifest.json +9 -0
- package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +45 -0
- package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +40 -0
- package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +14 -0
- package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +29 -0
- package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +41 -0
- package/dist/cli/new/scaffold/template/sections/hero/schema.ts +60 -0
- package/dist/cli/new/scaffold/template/theme.css +24 -0
- package/dist/cli/new/scaffold/template/theme.ts +27 -0
- package/dist/cli/new/scaffold-assets.js +20 -0
- package/dist/cli/pack/collect.js +44 -0
- package/dist/cli/pack/guards.js +57 -0
- package/dist/cli/pack/index.js +66 -0
- package/dist/cli/pack/manifest.js +15 -0
- package/dist/cli/pack/zip.js +86 -0
- package/dist/cli.js +80 -14
- package/dist/dev-client/assets/index-DfEfrp6P.css +1 -0
- package/dist/dev-client/assets/index-bpMP0b0W.js +50 -0
- package/dist/dev-client/index.html +13 -0
- package/dist/eslint/is-client-file.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/islands/detect.d.ts +16 -0
- package/dist/islands/detect.js +22 -0
- package/dist/islands/discover.d.ts +5 -0
- package/dist/islands/discover.js +19 -0
- package/dist/islands/esbuild-plugin.d.ts +13 -0
- package/dist/islands/esbuild-plugin.js +39 -0
- package/dist/islands/wrap.d.ts +12 -0
- package/dist/islands/wrap.js +48 -0
- package/dist/package.js +1 -1
- package/dist/ssr.d.ts +31 -0
- package/dist/ssr.js +46 -0
- package/docs/INDEX.md +9 -1
- package/docs/assets.md +128 -0
- package/docs/check.md +6 -3
- package/docs/dev.md +187 -0
- package/docs/islands.md +5 -2
- package/docs/llms.txt +41 -3
- package/docs/new.md +27 -0
- package/docs/overview.md +57 -0
- package/docs/pack.md +46 -0
- package/docs/primitives.md +5 -6
- package/docs/quickstart.md +102 -0
- package/docs/recipes/INDEX.md +27 -0
- package/docs/recipes/bind-property-fact.md +58 -0
- package/docs/recipes/collection-slot.md +100 -0
- package/docs/recipes/fill-spec-decision.md +66 -0
- package/docs/recipes/fixture-states.md +68 -0
- package/docs/recipes/image-slot-crop.md +98 -0
- package/docs/recipes/interactive-island.md +100 -0
- package/docs/recipes/organize-section-folder.md +72 -0
- package/docs/recipes/prepare-submission.md +55 -0
- package/docs/recipes/second-template.md +49 -0
- package/docs/recipes/select-slot.md +59 -0
- package/docs/recipes/static-asset.md +103 -0
- package/docs/recipes/third-party-package.md +90 -0
- package/docs/rules/INDEX.md +4 -2
- package/docs/rules/bundle-binary-asset.md +33 -52
- package/docs/rules/bundle-incomplete.md +2 -2
- package/docs/rules/css-reason.md +3 -3
- package/docs/rules/no-bare-css-import.md +8 -8
- package/docs/rules/no-css-import-from-render-path.md +7 -8
- package/docs/rules/size-assets.md +88 -0
- package/docs/rules/size-island-bundle.md +123 -0
- package/docs/rules/size-section-css.md +19 -14
- package/docs/schema-system.md +28 -28
- package/docs/theme-and-css.md +2 -2
- package/docs/vocabulary.md +98 -0
- package/package.json +12 -3
- package/tsconfig.json +1 -1
package/docs/dev.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The `template-kit dev` command — the local preview server, what it serves, its URLs, and the agent-facing render endpoints.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, theme-and-css.md, schema-system.md, check.md, islands.md]
|
|
5
|
+
updated: 2026-07-16
|
|
6
|
+
---
|
|
7
|
+
# template-kit dev
|
|
8
|
+
|
|
9
|
+
`template-kit dev` is the local preview server for a workspace's templates. It
|
|
10
|
+
renders every section against its fixtures the same way the platform does —
|
|
11
|
+
server-side, with your template's theme and compiled Tailwind utilities — so you
|
|
12
|
+
see real output while you author, and reload as you edit.
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
template-kit dev # serve every template in the workspace
|
|
18
|
+
template-kit dev <template> # preselect one template in the UI
|
|
19
|
+
template-kit dev --port 4000 # choose the port (default 5180)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Run it from the workspace root or from inside a template's own folder — running
|
|
23
|
+
from inside a template preselects it on the home launcher. The server serves every
|
|
24
|
+
template it finds under `templates/`, regardless of where you launched it; the
|
|
25
|
+
command runs until you interrupt it (Ctrl-C).
|
|
26
|
+
|
|
27
|
+
### What it needs installed
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm i -D vite @vitejs/plugin-react tailwindcss @tailwindcss/cli
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Like [`check`](check.md), `dev` runs **your** toolchain, resolved from your
|
|
34
|
+
workspace — Vite renders and reloads your sections, and Tailwind compiles your
|
|
35
|
+
theme's utilities. None of these are dependencies of the kit; they resolve from
|
|
36
|
+
your install, so the versions you pinned are the ones that run. A missing one is
|
|
37
|
+
reported by name with the line to install it.
|
|
38
|
+
|
|
39
|
+
Screenshots (below) additionally need Playwright, which is optional:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm i -D @playwright/test && npx playwright install chromium
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## What you get
|
|
46
|
+
|
|
47
|
+
Open the server's URL and you land on the **home launcher** (`/`) — a template
|
|
48
|
+
selector plus two tool cards, each a self-contained view you open in its own
|
|
49
|
+
page and return from by clicking the `template-kit dev` wordmark:
|
|
50
|
+
|
|
51
|
+
- **Full template viewer** — opens `/template/<template>`, the whole page as it
|
|
52
|
+
publishes.
|
|
53
|
+
- **Canvas mirror** — a section and fixture selector, opening
|
|
54
|
+
`/canvas/<template>/<section>/<fixture>`.
|
|
55
|
+
|
|
56
|
+
The home launcher is the only place navigation happens; both tools are
|
|
57
|
+
near-chromeless and URL-keyed, so a link to either reloads and shares exactly
|
|
58
|
+
the view you're looking at.
|
|
59
|
+
|
|
60
|
+
### Live reload
|
|
61
|
+
|
|
62
|
+
The server watches your template files. Editing a section reloads any open
|
|
63
|
+
preview of it; editing a theme recompiles its Tailwind utilities and hot-swaps
|
|
64
|
+
the stylesheet without a full reload.
|
|
65
|
+
|
|
66
|
+
### Interactivity (islands)
|
|
67
|
+
|
|
68
|
+
A section's `"use client"` components hydrate in the preview. The server renders
|
|
69
|
+
the section — including each client component's initial HTML — then the browser
|
|
70
|
+
loads React and hydrates only those components, so they become interactive while
|
|
71
|
+
the rest of the page stays static. A section with no `"use client"` component
|
|
72
|
+
loads no React at all. Editing a client component reloads the preview, re-rendered
|
|
73
|
+
and re-hydrated. See [Islands](islands.md) for the client-component contract.
|
|
74
|
+
|
|
75
|
+
### Overriding content
|
|
76
|
+
|
|
77
|
+
A preview page accepts an `overrides` query parameter — base64-encoded JSON,
|
|
78
|
+
deep-merged onto the fixture's resolved props — so you can probe a section against
|
|
79
|
+
content the fixtures don't carry without editing `fixtures.ts`. The decoded JSON
|
|
80
|
+
shape mirrors the props themselves:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{ "slots": {}, "options": {}, "nav": { "selfAnchor": "", "anchors": {} }, "variants": {} }
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
All four top-level keys are optional. Merging follows one rule at every level: a
|
|
87
|
+
plain object merges key by key with the fixture's value; anything else — a scalar,
|
|
88
|
+
an array, or `null` — replaces the fixture's value outright. A key the override
|
|
89
|
+
omits keeps the fixture's value; a key set explicitly to `null` replaces it with
|
|
90
|
+
`null`, so you can probe an empty/missing state a fixture doesn't otherwise cover.
|
|
91
|
+
The encoded payload is capped at 16 KB.
|
|
92
|
+
|
|
93
|
+
## Views: the full template, and the section render behind the canvas
|
|
94
|
+
|
|
95
|
+
The whole template renders at `/template/<template>` — every section in
|
|
96
|
+
`manifest.json` order, at each section's base fixture (with that instance's manifest
|
|
97
|
+
options), with the template theme and page shell applied and islands hydrating, as
|
|
98
|
+
the published site stacks it. Open it from the home launcher's **Full template
|
|
99
|
+
viewer** card, or point a browser tool at the URL directly. (Cross-section
|
|
100
|
+
`reconcile` rules are a publish-time behavior and are not applied here.)
|
|
101
|
+
|
|
102
|
+
Every section also has a standalone render at
|
|
103
|
+
`/sections/<template>/<section>/<fixture>` — the document the `/api/screenshot`
|
|
104
|
+
endpoint targets for a single section. It is not a navigation destination in its
|
|
105
|
+
own right.
|
|
106
|
+
|
|
107
|
+
## Canvas mirror
|
|
108
|
+
|
|
109
|
+
`/canvas/<template>` — open it from the home launcher's **Canvas mirror** card,
|
|
110
|
+
or directly — mirrors the whole template in an iframe: every section instance
|
|
111
|
+
from `manifest.json`, stacked in one scroll container, exactly as the deliverable
|
|
112
|
+
renders. A paint-only overlay sits on top of it, scoped per section instance —
|
|
113
|
+
the same slot name in two different sections selects independently. Click a slot
|
|
114
|
+
to select it (a gold ring); hover to preview one (a blue ring). Only slots that
|
|
115
|
+
instance's schema marks `editable_by_user: true` select individually — clicking
|
|
116
|
+
anywhere else in a section selects that section instance as a whole. The overlay
|
|
117
|
+
never changes the rendered markup. A mode toggle switches between this canvas
|
|
118
|
+
view and a published view — the clean full-template render, the same page with
|
|
119
|
+
the overlay off. A `template-kit dev` wordmark in the toolbar links back to the
|
|
120
|
+
home launcher (`/`) — the canvas mirror is otherwise chromeless.
|
|
121
|
+
|
|
122
|
+
The current template, mode, and selection are encoded in the URL as
|
|
123
|
+
`/canvas/<template>?mode=&sel=`, where `sel` names a section instance
|
|
124
|
+
(`<instanceId>`), a slot within it (`<instanceId>~<slotId>`), or one item of
|
|
125
|
+
a repeating slot (`<instanceId>~<slotId>~<itemIndex>`) — so a canvas link
|
|
126
|
+
reloads and shares exactly the view you're looking at.
|
|
127
|
+
|
|
128
|
+
Selecting a slot (not the section instance as a whole) docks an **edit panel**
|
|
129
|
+
beside the iframe. One JSON editor handles every slot type — you edit the
|
|
130
|
+
slot's value in its own JSON shape rather than switching between per-type
|
|
131
|
+
widgets — alongside a **fill-state badge** (filled / empty / missing) for the
|
|
132
|
+
selected slot and a per-instance **fixture picker**. The picker lists that
|
|
133
|
+
section's whole fixture corpus, including its synthetic empty-content
|
|
134
|
+
fixtures, so you can drive a slot into an empty or missing state without
|
|
135
|
+
touching `fixtures.ts`. Applying a JSON edit, switching the fixture, or
|
|
136
|
+
resetting the instance re-renders that section instance alone, the same way
|
|
137
|
+
it renders in the stack, and swaps its HTML into the iframe in place — no
|
|
138
|
+
page reload, no other instance disturbed — then re-anchors the selection ring
|
|
139
|
+
to the refreshed markup. The kit dev server is the harness's only backend, so
|
|
140
|
+
this loop never touches fixture files, a database, or another service. Edit
|
|
141
|
+
state lives only in the tab: nothing is written to disk, and reloading the
|
|
142
|
+
page loses it. A re-rendered instance's `"use client"` islands stay static —
|
|
143
|
+
they do not re-hydrate — until you rebuild; the panel notes this so it isn't
|
|
144
|
+
mistaken for a bug.
|
|
145
|
+
|
|
146
|
+
## Agent-facing endpoints
|
|
147
|
+
|
|
148
|
+
These render a section — or the whole template — without a browser, for an agent
|
|
149
|
+
verifying its work from a terminal.
|
|
150
|
+
|
|
151
|
+
- `/api/inspect` — the low-context semantic view of a section×fixture as JSON: each
|
|
152
|
+
declared slot with its `type`, `required`, `fillState` (`filled` / `empty` /
|
|
153
|
+
`missing`), whether it actually `rendered`, a compact `value` preview, and its
|
|
154
|
+
`schema.ts:<line>`; plus resolved `options` / `variants` and `nullCorpus` (the
|
|
155
|
+
synthetic empty-state fixtures, one per optional slot, you can walk to review every
|
|
156
|
+
missing-content branch). An `empty`/`missing` slot that still shows `rendered: true`
|
|
157
|
+
is a broken empty shell. Takes `template`, `section`, `fixture`, the same base64
|
|
158
|
+
`overrides` parameter as a preview page, and `format=text` to add the indented
|
|
159
|
+
structure outline.
|
|
160
|
+
- `/api/render` — the full structured render payload for one section×fixture
|
|
161
|
+
(`html`, `resolvedProps`, `slotTree`, `islandMap`, `slotSchema` — that
|
|
162
|
+
section's slot types and editability — and `slotFills`, the same per-slot
|
|
163
|
+
fill state `/api/inspect` reports); GET takes `overrides`, POST takes an
|
|
164
|
+
unencoded JSON body. POST also accepts a manifest `instanceId` in place of
|
|
165
|
+
`section`+`fixture`: the server resolves that instance's section, its
|
|
166
|
+
manifest options, and its default fixture the same way the whole-template
|
|
167
|
+
render does, so re-rendering one instance matches how it renders in the
|
|
168
|
+
stack. This is what the canvas mirror's edit panel calls to apply a slot
|
|
169
|
+
edit or switch a fixture.
|
|
170
|
+
- `/api/template-schema?template=<t>` — the editability map for the whole
|
|
171
|
+
template: every manifest section instance with its per-slot `type` and
|
|
172
|
+
whether it's user-editable. The canvas mirror's overlay gates selection on
|
|
173
|
+
this endpoint, not on `/api/render`'s per-section `slotSchema`.
|
|
174
|
+
- `/api/screenshot` — a full-page PNG. Give it `template` + `section` + `fixture` for a
|
|
175
|
+
section, or `template` alone for the whole template. `width=<px>` sets the viewport
|
|
176
|
+
(default 1280). Requires Playwright (`npm i -D @playwright/test && npx playwright
|
|
177
|
+
install chromium`); without it the endpoint answers with the install line.
|
|
178
|
+
|
|
179
|
+
`/api/catalog.json` returns the full **template × section × fixture** matrix.
|
|
180
|
+
|
|
181
|
+
## See also
|
|
182
|
+
|
|
183
|
+
- [Theme and CSS](theme-and-css.md) — the theme the preview compiles, and the
|
|
184
|
+
`template-kit theme` command that generates it.
|
|
185
|
+
- [Schema system](schema-system.md) — the fixtures the preview renders and the
|
|
186
|
+
props the `overrides` parameter merges onto.
|
|
187
|
+
- [Check](check.md) — the acceptance gate to run once a section previews cleanly.
|
package/docs/islands.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
purpose: How interactivity works in a template — client components ("islands"), their props contract, and their editor options.
|
|
3
3
|
status: living
|
|
4
|
-
related: [INDEX.md, eslint.md]
|
|
5
|
-
updated: 2026-07-
|
|
4
|
+
related: [INDEX.md, eslint.md, dev.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Islands: interactivity in a template
|
|
@@ -17,6 +17,9 @@ If a page contains no islands, it loads no React and no island code — every pa
|
|
|
17
17
|
still ships the small island loader (a few KB, gzipped), but that loader only ever
|
|
18
18
|
reaches React through a dynamic import it takes when an island is actually on the page.
|
|
19
19
|
|
|
20
|
+
[`template-kit dev`](dev.md) hydrates a section's islands in its preview, so you can
|
|
21
|
+
exercise one as you author it.
|
|
22
|
+
|
|
20
23
|
## Writing one
|
|
21
24
|
|
|
22
25
|
```tsx
|
package/docs/llms.txt
CHANGED
|
@@ -6,16 +6,30 @@
|
|
|
6
6
|
|
|
7
7
|
Status: 0.0.x. The schema system, the contract primitives, and the theme system are
|
|
8
8
|
complete — a section is authorable end to end against the kit. The `template-kit`
|
|
9
|
-
CLI's `check` and `
|
|
10
|
-
[Theme and CSS](theme-and-css.md#generating-themecss)
|
|
11
|
-
|
|
9
|
+
CLI's `check`, `theme`, `dev`, `pack`, `new`, and `new-section` commands are
|
|
10
|
+
implemented — see [Check](check.md), [Theme and CSS](theme-and-css.md#generating-themecss),
|
|
11
|
+
[Dev](dev.md), [Pack](pack.md), and [Adding templates](new.md); its remaining
|
|
12
|
+
commands arrive with the surfaces they drive.
|
|
12
13
|
|
|
13
14
|
Everything an author writes a section against is imported from the package root —
|
|
14
15
|
`@homepages/template-kit`, no subpath imports. The two CSS entries
|
|
15
16
|
(`./styles.css`, `./base.css`) are the deliberate exception.
|
|
16
17
|
|
|
18
|
+
## Start here
|
|
19
|
+
|
|
20
|
+
To build a section, start at [Quickstart](quickstart.md), then the task
|
|
21
|
+
[Recipes](recipes/INDEX.md) ("how do I X" → the recipe). For the mental model,
|
|
22
|
+
read [Overview](overview.md). For the closed sets — slot types, `direct()`
|
|
23
|
+
sources, `derived()` transforms — see [Vocabulary](vocabulary.md). When a lint
|
|
24
|
+
or `check` prints a rule id, resolve it at `rules/<id>.md`.
|
|
25
|
+
|
|
17
26
|
## Docs
|
|
18
27
|
|
|
28
|
+
- [Quickstart](quickstart.md): the cold-start tutorial — create a workspace,
|
|
29
|
+
preview the starter's `hero` section, read its four files, make one edit,
|
|
30
|
+
and check it. Owns the author-loop anchor every recipe ends with.
|
|
31
|
+
- [Overview](overview.md): the one-page mental model — workspace → templates →
|
|
32
|
+
sections, the four reserved files, and the server-vs-client mapping.
|
|
19
33
|
- [Schema system](schema-system.md): the section authoring contract. A section is
|
|
20
34
|
four files — `schema.ts` (slots + options + `meta.groups`), `fill-spec.ts` (how AI
|
|
21
35
|
fills them), `fixtures.ts` (preview content, built on the kit's golden property
|
|
@@ -23,6 +37,9 @@ Everything an author writes a section against is imported from the package root
|
|
|
23
37
|
`SectionProps<typeof schema>`). Covers the `as const satisfies` requirement, binding
|
|
24
38
|
slots to property facts with `direct()`/`derived()` over a closed vocabulary, and
|
|
25
39
|
fact-driven variants.
|
|
40
|
+
- [Vocabulary](vocabulary.md): the closed authoring vocabulary — the 9 slot
|
|
41
|
+
types, the `direct()` source fields, and the `derived()` transforms, with
|
|
42
|
+
selection semantics (which one to pick).
|
|
26
43
|
- [Contract primitives](primitives.md): the only five components the kit ships —
|
|
27
44
|
`Section` (full-bleed section root), `Slot` (`data-slot-id`), `SlotItem` (per-item
|
|
28
45
|
handle in an `object_list` / `image_collection`), `SlotGroup` (`data-slot-group`),
|
|
@@ -41,6 +58,10 @@ Everything an author writes a section against is imported from the package root
|
|
|
41
58
|
rendered inside the section.
|
|
42
59
|
- [README](../README.md): install, exports, and the release flow.
|
|
43
60
|
- [Islands](islands.md): interactivity — client components, the serializable-props contract, editor options, and the browser loader.
|
|
61
|
+
- [Static assets](assets.md): shipping a logo, texture, icon, or brand font with a
|
|
62
|
+
template — where an asset file lives (template level, or a section folder if the
|
|
63
|
+
section references it), referencing it from a section's CSS with `url()`/`@font-face`
|
|
64
|
+
or importing it into a component, and the asset-vs-content-image distinction.
|
|
44
65
|
- [ESLint preset](eslint.md): `@homepages/template-kit/eslint`, the flat-config preset —
|
|
45
66
|
a two-line config with no parser wiring, over an ESLint + `@typescript-eslint/parser`
|
|
46
67
|
install of your own (the kit depends on neither — both resolve from your workspace).
|
|
@@ -49,7 +70,24 @@ Everything an author writes a section against is imported from the package root
|
|
|
49
70
|
TypeScript, ESLint and esbuild, resolved from your workspace), its six per-template
|
|
50
71
|
stages, the once-per-invocation dependency gates, `--all`/`--json`, the report shape,
|
|
51
72
|
and the exit code. Thresholds are fixed platform acceptance criteria, not configurable.
|
|
73
|
+
- [Dev](dev.md): the `template-kit dev` CLI command — the local preview server. Its
|
|
74
|
+
home launcher with a full template viewer (`/template/<template>`) and a canvas
|
|
75
|
+
mirror (`/canvas/<template>/<section>/<fixture>`), live reload, the Vite + Tailwind
|
|
76
|
+
toolchain it resolves from your workspace, and the `inspect` / `render` / `screenshot`
|
|
77
|
+
endpoints an agent uses to verify a render from a terminal.
|
|
78
|
+
- [Pack](pack.md): the `template-kit pack <template>` CLI command — builds a template's
|
|
79
|
+
submission zip (the template's source, the workspace lockfile, and a stamped
|
|
80
|
+
`pack-manifest.json`), and the conditions it refuses to pack under: a failing
|
|
81
|
+
`check`, a missing lockfile, or a local `file:`/`link:` dependency.
|
|
82
|
+
- [Adding templates](new.md): the `template-kit new <template>` and
|
|
83
|
+
`template-kit new-section <template>/<section>` CLI commands — scaffolding a new
|
|
84
|
+
template with a working starter section, or adding a bare section (the four
|
|
85
|
+
reserved contract files, no island) to an existing template. Both refuse to
|
|
86
|
+
overwrite an existing folder.
|
|
52
87
|
- [Rules](rules/INDEX.md): every rule-id a lint or `check` failure can print, under the
|
|
53
88
|
`template-kit/<id>` namespace, one page each at `docs/rules/<id>.md` — Rule / Reason /
|
|
54
89
|
Fix / Before→After. **The loop:** see an id in a failure → read `docs/rules/<id>.md` →
|
|
55
90
|
fix. Nothing else to search for; the id is the filename.
|
|
91
|
+
- [Recipes](recipes/INDEX.md): task recipes routed by "how do I…" — each one a minimal,
|
|
92
|
+
complete delta from the starter's `hero` section (never a from-scratch file), ending in
|
|
93
|
+
the [author loop](quickstart.md#the-author-loop).
|
package/docs/new.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: Add a template or a section to a workspace with template-kit new / new-section.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, dev.md, check.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Adding templates and sections
|
|
9
|
+
|
|
10
|
+
A workspace starts with one template (`templates/starter/`). Add more without
|
|
11
|
+
duplicating any shared context — templates are plain folders, and the kit,
|
|
12
|
+
configs, and docs stay at the workspace root.
|
|
13
|
+
|
|
14
|
+
## `template-kit new <template>`
|
|
15
|
+
|
|
16
|
+
Creates `templates/<template>/` with a working starter section (a text slot, an
|
|
17
|
+
image slot, a fact-bound slot, an option, fixtures, a fill spec, a nested
|
|
18
|
+
`components/` folder, and a `"use client"` island), plus `theme.ts`/`theme.css`
|
|
19
|
+
and a `manifest.json`. It passes `check` as-is; edit it into your template.
|
|
20
|
+
|
|
21
|
+
## `template-kit new-section <template>/<section>`
|
|
22
|
+
|
|
23
|
+
Adds `templates/<template>/sections/<section>/` — the four reserved contract
|
|
24
|
+
files (`Renderer.tsx`, `schema.ts`, `fill-spec.ts`, `fixtures.ts`), no island.
|
|
25
|
+
Reference the new section by name in the template's `manifest.json`.
|
|
26
|
+
|
|
27
|
+
Both refuse to overwrite an existing folder.
|
package/docs/overview.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The mental model in one page — workspace, templates, sections, and the server-vs-client mapping.
|
|
3
|
+
status: living
|
|
4
|
+
related: [schema-system.md, islands.md, rules/server-vs-client.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
|
+
---
|
|
7
|
+
# Overview
|
|
8
|
+
|
|
9
|
+
## Workspace → templates → sections
|
|
10
|
+
|
|
11
|
+
A **workspace** is the container: one kit version, one shared config. A
|
|
12
|
+
**template** is the authored, submitted unit — a folder of sections plus a
|
|
13
|
+
manifest and theme. A **section** is four reserved files in an otherwise
|
|
14
|
+
free-form folder:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
templates/<template>/sections/<section>/
|
|
18
|
+
Renderer.tsx
|
|
19
|
+
schema.ts
|
|
20
|
+
fill-spec.ts
|
|
21
|
+
fixtures.ts
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Everything else in that folder — extra components, an island, helpers — is
|
|
25
|
+
yours. See [Adding templates and sections](new.md).
|
|
26
|
+
|
|
27
|
+
## The four files
|
|
28
|
+
|
|
29
|
+
| File | Declares | Reference |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `schema.ts` | The slots and options the section has | [schema-system.md](schema-system.md#schemats--what-the-section-contains) |
|
|
32
|
+
| `fill-spec.ts` | How AI fills those slots | [schema-system.md](schema-system.md#fill-spects--how-ai-fills-the-slots) |
|
|
33
|
+
| `fixtures.ts` | Sample content for local preview | [schema-system.md](schema-system.md#fixturests--content-for-local-preview) |
|
|
34
|
+
| `Renderer.tsx` | The markup | [schema-system.md](schema-system.md#renderertsx--props-derived-never-hand-written) |
|
|
35
|
+
|
|
36
|
+
## Slots, options, fill, fixtures
|
|
37
|
+
|
|
38
|
+
A **slot** is editable content; an **option** is a behavior switch — see
|
|
39
|
+
[the schema system](schema-system.md#schemats--what-the-section-contains) for
|
|
40
|
+
the distinction and [the closed vocabulary](vocabulary.md) for the types.
|
|
41
|
+
**Fill** is how AI assigns slot values via `fill-spec.ts`'s decisions, covered
|
|
42
|
+
in [schema-system.md](schema-system.md#fill-spects--how-ai-fills-the-slots).
|
|
43
|
+
**Fixtures** are the sample content a section previews against, built on the
|
|
44
|
+
kit's golden property dataset — see
|
|
45
|
+
[schema-system.md](schema-system.md#fixturests--content-for-local-preview).
|
|
46
|
+
|
|
47
|
+
## Server vs client — your Next.js instinct, mapped
|
|
48
|
+
|
|
49
|
+
A `Renderer.tsx` is a server component: a pure function of props, ships no
|
|
50
|
+
JS. Interactivity goes in a `"use client"` island, hydrated in the browser.
|
|
51
|
+
The directive is **per file**. See [server vs client](rules/server-vs-client.md)
|
|
52
|
+
for the precise rule and [Islands](islands.md) for how to write one.
|
|
53
|
+
|
|
54
|
+
## The author loop
|
|
55
|
+
|
|
56
|
+
For the write → preview → check cycle, see
|
|
57
|
+
[the author loop](quickstart.md#the-author-loop).
|
package/docs/pack.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: The `template-kit pack` command — what it produces, what it refuses, and why.
|
|
3
|
+
status: living
|
|
4
|
+
related: [INDEX.md, check.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
|
+
---
|
|
7
|
+
# template-kit pack
|
|
8
|
+
|
|
9
|
+
Build a template's submission artifact: a zip of the template's source plus the
|
|
10
|
+
workspace lockfile, validated and stamped.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
template-kit pack <template>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Run it from the workspace root, or from inside a template folder (the template is
|
|
19
|
+
inferred). In a workspace with several templates you must name one — the submission
|
|
20
|
+
unit is a single template.
|
|
21
|
+
|
|
22
|
+
## What it produces
|
|
23
|
+
|
|
24
|
+
`<template>.zip` at the workspace root, containing:
|
|
25
|
+
|
|
26
|
+
- `pack-manifest.json` — the exact kit version the source was validated against, a
|
|
27
|
+
content hash of the template tree, the pack timestamp, and a `check` flag
|
|
28
|
+
recording that validation passed.
|
|
29
|
+
- `package.json` and `package-lock.json` from the workspace root, so the build is
|
|
30
|
+
reproducible with `npm ci`.
|
|
31
|
+
- `templates/<template>/` — the template's source only (sections, manifest, theme,
|
|
32
|
+
README). No `node_modules`, no build output, no other template.
|
|
33
|
+
|
|
34
|
+
## What it refuses
|
|
35
|
+
|
|
36
|
+
`pack` will not produce a zip when:
|
|
37
|
+
|
|
38
|
+
- `check <template>` is not green — fix the reported problems first.
|
|
39
|
+
- the workspace has no `package-lock.json` — run `npm install` and commit it.
|
|
40
|
+
- a dependency uses a local `file:`/`link:` protocol — depend on published versions.
|
|
41
|
+
|
|
42
|
+
Fix the reported cause and run it again.
|
|
43
|
+
|
|
44
|
+
## See also
|
|
45
|
+
|
|
46
|
+
- [Check](check.md) — the gate `pack` runs before it will produce a zip.
|
package/docs/primitives.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
purpose: The five contract primitives a section renderer must use, and the marker attributes they emit.
|
|
3
3
|
status: living
|
|
4
4
|
related: [schema-system.md, theme-and-css.md, eslint.md]
|
|
5
|
-
updated: 2026-07-
|
|
5
|
+
updated: 2026-07-15
|
|
6
6
|
---
|
|
7
7
|
# Contract primitives
|
|
8
8
|
|
|
@@ -106,8 +106,8 @@ whole — no per-item selection, no per-item image cropping, no per-item live su
|
|
|
106
106
|
`index` **must** be the item's zero-based position in the slot's array as rendered. The
|
|
107
107
|
editor writes edits back by that index, so an index that doesn't match its position edits
|
|
108
108
|
the wrong row. `index` is an array position, not a schema key, so unlike `Slot`'s and
|
|
109
|
-
`SlotGroup`'s `id` it is exempt from the
|
|
110
|
-
lint rule.
|
|
109
|
+
`SlotGroup`'s `id` it is exempt from the
|
|
110
|
+
[`template-kit/slot-marker-literal`](rules/slot-marker-literal.md) lint rule.
|
|
111
111
|
|
|
112
112
|
## `SlotGroup` — slots edited as one unit
|
|
113
113
|
|
|
@@ -206,7 +206,6 @@ magic string.
|
|
|
206
206
|
| `ATTR_SLOT_ITEM` | `data-slot-item` | one item of a collection slot |
|
|
207
207
|
| `ATTR_SLOT_GROUP` | `data-slot-group` | the container wrapping a group's members |
|
|
208
208
|
| `ATTR_SECTION_INSTANCE_ID` | `data-section-instance-id` | the section root — **written by the platform**, not by a primitive |
|
|
209
|
-
| `ATTR_TR_ENHANCE` | `data-tr-enhance` | opt-in hook telling an enhancer to attach to this element |
|
|
210
209
|
|
|
211
210
|
Changing what an attribute means is a platform contract break, so these names are stable
|
|
212
211
|
across a major version of the kit.
|
|
@@ -215,5 +214,5 @@ across a major version of the kit.
|
|
|
215
214
|
|
|
216
215
|
Renderers are pure functions of props: no fetches, no globals, no `useEffect` for data,
|
|
217
216
|
no `Date.now()` / `Math.random()`. They are rendered to static markup by the publisher
|
|
218
|
-
and re-mounted constantly by the editor. Interactivity that needs the browser
|
|
219
|
-
|
|
217
|
+
and re-mounted constantly by the editor. Interactivity that needs the browser moves into
|
|
218
|
+
a `"use client"` island (see [Islands](islands.md)), never into render-time side effects.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: Zero to a rendered, checked section in under an hour, on the scaffolded starter.
|
|
3
|
+
status: living
|
|
4
|
+
related: [overview.md, dev.md, check.md, schema-system.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
|
+
---
|
|
7
|
+
# Quickstart
|
|
8
|
+
|
|
9
|
+
This walks the scaffolded starter template end to end: create a workspace, see
|
|
10
|
+
its one section render, read how it's built, make one edit, and check it. It
|
|
11
|
+
does not re-teach the authoring contract — see [Overview](overview.md) for the
|
|
12
|
+
mental model and [Schema system](schema-system.md) for the four files in
|
|
13
|
+
depth.
|
|
14
|
+
|
|
15
|
+
## Create your workspace
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm create homepages-workspace@latest my-workspace
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This stamps a workspace with one template, `starter`, and one section in it,
|
|
22
|
+
`hero` — a working section built on the real contract, not a stub. See
|
|
23
|
+
[Overview](overview.md) for what a workspace, template, and section are.
|
|
24
|
+
|
|
25
|
+
## See it render
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd my-workspace
|
|
29
|
+
npm run dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This opens the **home launcher** in your browser: from there the **canvas
|
|
33
|
+
mirror** renders the template with `starter`'s `hero` in it, and the
|
|
34
|
+
full-template viewer shows the whole page. See [Dev](dev.md) for the launcher,
|
|
35
|
+
the canvas mirror, and live reload.
|
|
36
|
+
|
|
37
|
+
If you're an agent (or otherwise browser-less), hit the inspect endpoint
|
|
38
|
+
instead — it returns each slot's fill state and a text outline of the render:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl 'http://localhost:<port>/api/inspect?template=starter§ion=hero&fixture=typical&format=text'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`typical` is the starter hero's baseline fixture; it also has `left` and
|
|
45
|
+
`long-blurb` fixtures exercising an option and a content-length branch. See
|
|
46
|
+
[Dev](dev.md#agent-facing-endpoints) for `/api/inspect` and the other
|
|
47
|
+
agent-facing endpoints.
|
|
48
|
+
|
|
49
|
+
## Read the worked example
|
|
50
|
+
|
|
51
|
+
Before changing anything, read the four files that make up the starter's
|
|
52
|
+
`hero` section — `templates/starter/sections/hero/`:
|
|
53
|
+
|
|
54
|
+
- `schema.ts` — the slots (`hero_image`, `headline`, `blurb`) and the `align`
|
|
55
|
+
option, each comment explaining why it's declared the way it is.
|
|
56
|
+
- `fill-spec.ts` — how AI fills `hero_image` and `blurb`; `headline` needs no
|
|
57
|
+
decision because it's fact-bound.
|
|
58
|
+
- `fixtures.ts` — the `typical` baseline plus the `left`/`long-blurb` states.
|
|
59
|
+
- `Renderer.tsx` — the markup, including the one interactive part: `blurb`
|
|
60
|
+
wrapped in a `"use client"` island.
|
|
61
|
+
|
|
62
|
+
Each file is annotated in place. For the concepts behind them, see
|
|
63
|
+
[Schema system](schema-system.md).
|
|
64
|
+
|
|
65
|
+
## Make one edit
|
|
66
|
+
|
|
67
|
+
Open `templates/starter/sections/hero/fixtures.ts` and change the `headline`
|
|
68
|
+
value under `base`:
|
|
69
|
+
|
|
70
|
+
```diff
|
|
71
|
+
- headline: "101 Queensway, Jamaica Plain, MA 02130",
|
|
72
|
+
+ headline: "12 Elm Street, Jamaica Plain, MA 02130",
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Save it. If `npm run dev` is running, any open preview of the `hero` section
|
|
76
|
+
reloads with the new headline; re-running the `/api/inspect` call above also
|
|
77
|
+
shows the new value.
|
|
78
|
+
|
|
79
|
+
## Check it
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run check
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
✓ starter
|
|
87
|
+
check passed
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A green `check` is the acceptance gate — see [Check](check.md) for what its
|
|
91
|
+
six stages verify and what a failure looks like.
|
|
92
|
+
|
|
93
|
+
## The author loop
|
|
94
|
+
|
|
95
|
+
Every section, and every change to one, runs the same three-step loop:
|
|
96
|
+
|
|
97
|
+
1. **Edit** a section's files.
|
|
98
|
+
2. **Preview** the result — `npm run dev` in a browser, or `/api/inspect` from
|
|
99
|
+
a terminal.
|
|
100
|
+
3. **Check** it — `npm run check` — until it's green.
|
|
101
|
+
|
|
102
|
+
Every recipe in this corpus ends by running this loop.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: Task router — how do I… → the recipe.
|
|
3
|
+
status: living
|
|
4
|
+
related: []
|
|
5
|
+
updated: 2026-07-16
|
|
6
|
+
---
|
|
7
|
+
# Recipes
|
|
8
|
+
|
|
9
|
+
Every recipe is a **delta from the starter's `hero` section** — never a
|
|
10
|
+
from-scratch file — ending in the same [author loop](../quickstart.md#the-author-loop)
|
|
11
|
+
as everything else in this corpus: edit, preview, check. Find your task below and
|
|
12
|
+
copy the shapes from its recipe, not from memory.
|
|
13
|
+
|
|
14
|
+
| How do I… | Recipe |
|
|
15
|
+
|---|---|
|
|
16
|
+
| add an image slot the editor can crop | [`image-slot-crop.md`](image-slot-crop.md) |
|
|
17
|
+
| fill a slot from a property fact without AI | [`bind-property-fact.md`](bind-property-fact.md) |
|
|
18
|
+
| give the user a closed set of choices | [`select-slot.md`](select-slot.md) |
|
|
19
|
+
| add a repeating list the user edits per item (unit table, cards, gallery) | [`collection-slot.md`](collection-slot.md) |
|
|
20
|
+
| have AI write a slot's content | [`fill-spec-decision.md`](fill-spec-decision.md) |
|
|
21
|
+
| preview a section's structural edge cases / states | [`fixture-states.md`](fixture-states.md) |
|
|
22
|
+
| make part of a section interactive (an island) | [`interactive-island.md`](interactive-island.md) |
|
|
23
|
+
| organize a section's files beyond the four reserved | [`organize-section-folder.md`](organize-section-folder.md) |
|
|
24
|
+
| ship a static asset (image/svg/font) with a section | [`static-asset.md`](static-asset.md) |
|
|
25
|
+
| use a third-party npm package in a section | [`third-party-package.md`](third-party-package.md) |
|
|
26
|
+
| add another template to the workspace | [`second-template.md`](second-template.md) |
|
|
27
|
+
| package a template for submission | [`prepare-submission.md`](prepare-submission.md) |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
purpose: Fill a slot from a known property fact, no AI.
|
|
3
|
+
status: living
|
|
4
|
+
related: [../vocabulary.md, ../schema-system.md, ../rules/schema-invalid.md]
|
|
5
|
+
updated: 2026-07-15
|
|
6
|
+
---
|
|
7
|
+
# Bind a slot to a property fact
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
Fill a slot from a known property fact, no AI.
|
|
12
|
+
|
|
13
|
+
## The delta
|
|
14
|
+
|
|
15
|
+
Give the slot a `source: direct("<field>")` instead of a `produced_by` — the
|
|
16
|
+
same pattern the starter's `headline` already uses
|
|
17
|
+
(`source: direct("address")`). Model it on an AI-produced slot you want to
|
|
18
|
+
make fact-bound instead: swap its `produced_by` for `source`, and drop the
|
|
19
|
+
matching decision from `fill-spec.ts`.
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// schema.ts — was produced_by: "blurb_text"
|
|
23
|
+
blurb: {
|
|
24
|
+
type: "text",
|
|
25
|
+
size: "long",
|
|
26
|
+
editable_by_user: true,
|
|
27
|
+
required: true,
|
|
28
|
+
source: direct("lot_size"),
|
|
29
|
+
display_name: "Blurb",
|
|
30
|
+
},
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then delete the now-unused `blurb_text` decision from `fill-spec.ts`'s
|
|
34
|
+
`decisions` array — a `direct()`-bound slot needs none; the fact is baked in
|
|
35
|
+
at fill time, and the [`schema-invalid`](../rules/schema-invalid.md) check
|
|
36
|
+
rejects a slot that declares both `source` and `produced_by`.
|
|
37
|
+
|
|
38
|
+
The same pattern works for any slot type — pick a field whose runtime shape
|
|
39
|
+
matches. For a `number` slot, `source: direct("beds")`; for `text`,
|
|
40
|
+
`source: direct("address")` or any other text-shaped field in
|
|
41
|
+
[the closed vocabulary](../vocabulary.md#direct-sources).
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
None — this is a pure content edit. No CLI command beyond the author loop below.
|
|
46
|
+
|
|
47
|
+
## Verify
|
|
48
|
+
|
|
49
|
+
Verify with [the author loop](../quickstart.md#the-author-loop).
|
|
50
|
+
|
|
51
|
+
## See also
|
|
52
|
+
|
|
53
|
+
- [The closed vocabulary](../vocabulary.md#direct-sources) — every `direct()` field,
|
|
54
|
+
grouped by what it resolves against.
|
|
55
|
+
- [The schema system](../schema-system.md#binding-a-slot-to-a-property-fact) — the
|
|
56
|
+
`direct()`/`derived()` contract and why a bad name is a compile error.
|
|
57
|
+
- [`schema-invalid`](../rules/schema-invalid.md) — fires if a slot declares both
|
|
58
|
+
`source` and `produced_by`, or neither.
|