Skip to content
HomePagesHomePages template kit
Menu

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.

Terminal window
template-kit dev # serve every template in the workspace
template-kit dev <template> # preselect one template in the UI
template-kit dev --port 4000 # choose the port (default 5180)
template-kit dev --diagnostics-cmd "<cmd>" # show lint/validate violations in the preview

Run 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).

Terminal window
npm i -D @homepages/template-cli # provides the `template-kit` command
npm i -D vite @vitejs/plugin-react tailwindcss @tailwindcss/cli
npm i -D @homepages/dev-media # sample photos/plans/videos for the media library

Like 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:

Terminal window
npm i -D @playwright/test && npx playwright install chromium

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.

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.

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.

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-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=&section= 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/<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.

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 its type, required, fillState (filled / empty / missing), whether it actually rendered, a compact value preview, and its schema.ts:<line>; plus resolved options / variants and nullCorpus (the synthetic empty-state fixtures, one per optional slot, you can walk to review every missing-content branch). An empty/missing slot that still shows rendered: true is a broken empty shell. Takes template, section, fixture, the same base64 overrides parameter as a preview page, and format=text to 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_order and groups — and slotFills, the same per-slot fill state /api/inspect reports); GET takes overrides, POST takes an unencoded JSON body. POST also accepts a manifest instanceId in place of section+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 same sectionSchema for 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 it template + section + fixture for a section, or template alone 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 an id, kind, a preview image (a video’s is its poster), and a src under /__dev-media/ serving the actual bytes. It answers even without @homepages/dev-media installed; without it every src 404s.

/api/catalog.json returns the full template × section × fixture matrix.

  • Theme and CSS — the theme the preview compiles, and the template-kit theme command that generates it.
  • Schema system — the fixtures the preview renders and the props the overrides parameter merges onto.
  • Check — the acceptance gate to run once a section previews cleanly.