template-kit dev
The `template-kit dev` command — the local preview server, what it serves, its URLs, and the agent-facing render endpoints.
template-kit dev is the local preview server for a workspace’s templates. It
renders every section against its fixtures the same way the platform does —
server-side, with your template’s theme and compiled Tailwind utilities — so you
see real output while you author, and reload as you edit.
template-kit dev # serve every template in the workspacetemplate-kit dev <template> # preselect one template in the UItemplate-kit dev --port 4000 # choose the port (default 5180)template-kit dev --diagnostics-cmd "<cmd>" # show lint/validate violations in the previewRun it from the workspace root or from inside a template’s own folder — running
from inside a template preselects it on the home launcher. The server serves every
template it finds under templates/, regardless of where you launched it; the
command runs until you interrupt it (Ctrl-C).
What it needs installed
Section titled “What it needs installed”npm i -D @homepages/template-cli # provides the `template-kit` commandnpm i -D vite @vitejs/plugin-react tailwindcss @tailwindcss/clinpm i -D @homepages/dev-media # sample photos/plans/videos for the media libraryLike check, dev runs your toolchain, resolved from your
workspace — Vite renders and reloads your sections, and Tailwind compiles your
theme’s utilities. None of these are dependencies of the kit; they resolve from
your install, so the versions you pinned are the ones that run. A missing one is
reported by name with the line to install it. @homepages/dev-media is optional —
the server still runs without it, but every media library preview 404s.
Screenshots (below) additionally need Playwright, which is optional:
npm i -D @playwright/test && npx playwright install chromiumWhat you get
Section titled “What you get”Open the server’s URL and you land on the home launcher (/) — a template
selector plus two tool cards, each a self-contained view you open in its own
page and return from by clicking the template-kit dev wordmark:
- Full template viewer — opens
/template/<template>, the whole page as it publishes. - Canvas mirror — a section and fixture selector, opening
/canvas/<template>/<section>/<fixture>.
The home launcher is the only place navigation happens; both tools are near-chromeless and URL-keyed, so a link to either reloads and shares exactly the view you’re looking at.
Live reload
Section titled “Live reload”The server watches your template files. Editing a section reloads any open preview of it; editing a theme recompiles its Tailwind utilities and hot-swaps the stylesheet without a full reload.
Interactivity (islands)
Section titled “Interactivity (islands)”A section’s "use client" components hydrate in the preview. The server renders
the section — including each client component’s initial HTML — then the browser
loads React and hydrates only those components, so they become interactive while
the rest of the page stays static. A section with no "use client" component
loads no React at all. Editing a client component reloads the preview, re-rendered
and re-hydrated. See Islands for the client-component contract.
Overriding content
Section titled “Overriding content”A preview page accepts an overrides query parameter — base64-encoded JSON,
deep-merged onto the fixture’s resolved props — so you can probe a section against
content the fixtures don’t carry without editing fixtures.ts. The decoded JSON
shape mirrors the props themselves:
{ "slots": {}, "options": {}, "nav": { "selfAnchor": "", "anchors": {} }, "variants": {} }All four top-level keys are optional. Merging follows one rule at every level: a
plain object merges key by key with the fixture’s value; anything else — a scalar,
an array, or null — replaces the fixture’s value outright. A key the override
omits keeps the fixture’s value; a key set explicitly to null replaces it with
null, so you can probe an empty/missing state a fixture doesn’t otherwise cover.
The encoded payload is capped at 16 KB.
Diagnostics
Section titled “Diagnostics”--diagnostics-cmd "<cmd>" wires an external command into the section preview
as a live violations banner. It’s unset by default — nothing changes unless you
configure it.
When set, opening /sections/<template>/<section>/<fixture> runs
<cmd> --template <t> --section <s> (the command string is split on
whitespace — no shell, no quoting — and --template/--section are always
appended as separate arguments). The command must print JSON to stdout:
{ "violations": [{ "rule": "some-rule", "message": "what's wrong", "file": "schema.ts", "line": 12 }] }file and line are optional. A non-zero exit or unparseable stdout surfaces
as an error in the banner rather than crashing the server. If any violations
come back, the preview shows a dismissible banner at the top of the page; it
re-fetches (and reappears, if still violated) on every file-triggered reload,
the same way the rest of the preview already goes live on a change.
GET /api/diagnostics?template=§ion= is the underlying endpoint, if you
want to query it directly: {available: false} when no command is configured,
{available: true, violations: [...]} on success, {available: true, error}
when the command failed or its output didn’t parse.
Views: the full template, and the section render behind the canvas
Section titled “Views: the full template, and the section render behind the canvas”The whole template renders at /template/<template> — every section in
manifest.json order, at each section’s base fixture (with that instance’s manifest
options), with the template theme and page shell applied and islands hydrating, as
the published site stacks it. Open it from the home launcher’s Full template
viewer card, or point a browser tool at the URL directly. (Cross-section
reconcile rules are a publish-time behavior and are not applied here.)
Every section also has a standalone render at
/sections/<template>/<section>/<fixture> — the document the /api/screenshot
endpoint targets for a single section. It is not a navigation destination in its
own right.
Canvas mirror
Section titled “Canvas mirror”/canvas/<template> — open it from the home launcher’s Canvas mirror card,
or directly — renders the template inside the platform’s own editor canvas
frame: a rounded page card that auto-scales to fit the available width, on the
same dotted canvas surface and with the same selection chrome the platform’s
content editor uses. Each manifest section instance loads independently and is
injected into the page in manifest order as it arrives, so instances can appear
progressively rather than all at once; one instance failing to render shows an
error placeholder in its own spot without blocking the rest. A paint-only
overlay sits on top of the page, scoped per section instance —
the same slot name in two different sections selects independently. Click a slot
to select it (a gold ring); hover to preview one (a blue ring). Only slots that
instance’s schema marks editable_by_user: true select individually — clicking
anywhere else in a section selects that section instance as a whole. The overlay
never changes the rendered markup. The canvas mirror is fully chromeless — no
toolbar, no back-link to the home launcher; navigate back with the browser’s
own back button or a bookmarked / URL. To see the clean published render
instead, open the Full template viewer card from the home launcher.
The current template and selection are encoded in the URL as
/canvas/<template>?sel=, where sel names a section instance
(<instanceId>), a slot within it (<instanceId>~<slotId>), or one item of
a repeating slot (<instanceId>~<slotId>~<itemIndex>) — so a canvas link
reloads and shares exactly the view you’re looking at.
A floating edit panel, a fixed-width card anchored over the canvas’s right
edge, is always open and shows one section instance at a time: whichever
instance is currently selected, or — before any selection, or after Escape
clears the ring — the last instance shown, defaulting to the template’s first
manifest instance on initial load. It shows that instance’s
editable_by_user: true slots as the platform’s own accordion cards, in the
platform’s card order and grouping — a meta.groups group collapses its members
into one card, every other slot gets its own — and each slot inside a card gets
the real typed editor for its type: the same combobox, bullet-list builder, price
field or POI picker a user gets. Selecting a slot opens its card
and scrolls it into view. The four media slot types (image,
image_collection, video, video_collection) get the same real photo/video
library and crop editors a user gets, listing the workspace’s dev media pack;
picking a library item and cropping it both work, with cropping producing a
real cropped preview rather than the uncropped original. Two things the
playground cannot do: uploading a new file (there is no upload backend, so the
picker offers no Upload tile) and assigning a floor plan to a unit (a
property-editing act — the playground has no property, so that picker opens an
inert dialog explaining why instead of doing nothing). Above the cards sits a per-instance fixture picker: it lists that
section’s whole fixture corpus, including its synthetic empty-content fixtures,
so you can drive a slot into an empty or missing state without touching
fixtures.ts. Applying an edit,
switching the fixture, or resetting the instance re-renders that section
instance alone, the same way it renders in the stack, and swaps its HTML into
the iframe in place — no page reload, no other instance disturbed — then
re-anchors the selection ring to the refreshed markup, and re-hydrates that
instance’s islands. The kit dev server is the harness’s only backend, so this
loop never touches fixture files, a database, or another service. Edit state
lives only in the tab: nothing is written to disk, and reloading the page
loses it.
An island whose component declares editor.live: true (see Islands)
hydrates in the canvas the same way it does on a published page, but behind a
click-through shield until it’s selected: the first click on a live island
selects its owning slot or section instance instead of reaching the
component, dropping the shield; a second click reaches the component itself.
Pressing Escape clears the select ring and re-arms every shield; it does not
close the edit panel, which keeps showing the instance it was last showing.
An island with no editor export, or editor.live: false, stays static
markup in the canvas — same as a published page’s non-live islands. Some
islands own no slot of their own (e.g. a slider or map sitting beside a
<Slot> rather than wrapped by one); their shield only ever drops under a
section instance selection. The edit panel always includes a read-only
Islands list for its current instance, showing every top-level island in
that section instance, whether it’s live or static, its shield placement, and
whether it’s currently interactive or shielded.
Agent-facing endpoints
Section titled “Agent-facing endpoints”These render a section — or the whole template — without a browser, for an agent verifying its work from a terminal.
/api/inspect— the low-context semantic view of a section×fixture as JSON: each declared slot with itstype,required,fillState(filled/empty/missing), whether it actuallyrendered, a compactvaluepreview, and itsschema.ts:<line>; plus resolvedoptions/variantsandnullCorpus(the synthetic empty-state fixtures, one per optional slot, you can walk to review every missing-content branch). Anempty/missingslot that still showsrendered: trueis a broken empty shell. Takestemplate,section,fixture, the same base64overridesparameter as a preview page, andformat=textto add the indented structure outline./api/render— the full structured render payload for one section×fixture (html,resolvedProps,slotTree,islandMap,sectionSchema— that section’s schema as the platform serializes it: the full per-slot schema,options,slot_orderandgroups— andslotFills, the same per-slot fill state/api/inspectreports); GET takesoverrides, POST takes an unencoded JSON body. POST also accepts a manifestinstanceIdin place ofsection+fixture: the server resolves that instance’s section, its manifest options, and its default fixture the same way the whole-template render does, so re-rendering one instance matches how it renders in the stack. This is what the canvas mirror’s edit panel calls to apply a slot edit or switch a fixture./api/template-schema?template=<t>— the samesectionSchemafor the whole template: every manifest section instance with its section schema, which is both the overlay’s editability map and the sidebar’s card model. The canvas mirror builds both from this endpoint, not from/api/render’s per-section copy, so neither depends on which sections have rendered yet./api/screenshot— a full-page PNG. Give ittemplate+section+fixturefor a section, ortemplatealone for the whole template.width=<px>sets the viewport (default 1280). Requires Playwright (npm i -D @playwright/test && npx playwright install chromium); without it the endpoint answers with the install line./api/media-library.json— the sample media library’s listing: every photo, floor plan, library asset, and video, each with anid,kind, a preview image (a video’s is its poster), and asrcunder/__dev-media/serving the actual bytes. It answers even without@homepages/dev-mediainstalled; without it everysrc404s.
/api/catalog.json returns the full template × section × fixture matrix.
See also
Section titled “See also”- Theme and CSS — the theme the preview compiles, and the
template-kit themecommand that generates it. - Schema system — the fixtures the preview renders and the
props the
overridesparameter merges onto. - Check — the acceptance gate to run once a section previews cleanly.