<quick-html/>
Home / Template spec

quick-html template spec — v0

Status: v0 (draft). Breaking changes are allowed until v1. Every template declares

the spec it targets via "spec": "quick-html/v0" in its manifest.

A quick-html template is a self-contained, print-ready HTML document plus a manifest that tells an AI agent exactly how to fill it. The template's placeholder content is realistic example content: it is simultaneously the live preview for humans, the few-shot example for agents, and the documentation of what belongs in each slot.

1. Files

Each template lives in templates/{id}/:

FilePurpose
template.htmlThe document. Valid HTML5, self-contained, zero <script> tags.
manifest.jsonThe fill contract. Validated against schema/manifest.schema.json.

{id} is a kebab-case slug, unique in the registry, matching manifest.id.

2. The slot contract

Agents may change a template in exactly four ways. Everything else must remain byte-identical.

2.1 data-slot="name" — content slot

The element's inner content is replaceable. The shipped inner content is the placeholder example.


<h1 data-slot="title">Sub-4:00 Marathon Plan</h1>

Slot types (declared in the manifest): text (plain text, no markup), html (limited markup: p, br, strong, em, ul, ol, li, a, span, dl, dt, dd), number, date, url. Agents must respect the declared type.

2.2 data-slot-attr="attr:name[, attr:name]" — attribute slot

Named attributes on the element are replaceable (e.g. a link target). All other attributes are fixed.


<a data-slot="website" data-slot-attr="href:website-url" href="https://example.com">example.com</a>

2.3 data-repeat="name" — repeatable block

The element is a prototype row/block. The agent duplicates it (including all nested slots) once per item, fills each copy, and keeps the copies adjacent in place of the prototype. min/max counts are declared in the manifest. Slots inside a repeat are declared under the repeat in the manifest, not at top level.


<tr data-repeat="item">
  <td data-slot="description">Design work — homepage</td>
  <td data-slot="qty">12</td>
  <td data-slot="unit-price">€ 95,00</td>
  <td data-slot="amount">€ 1.140,00</td>
</tr>

Nested repeats are not allowed in v0.

Because a template ships as its own preview, repeat prototypes are usually accompanied by extra example rows so the placeholder document looks complete. Those rows carry data-example (§2.5) and no slots — agents remove them when filling.

2.4 data-optional="name" — removable block

If the user's content has no use for the block, the agent removes the element entirely. Otherwise it is filled like normal (it may contain slots).

2.5 data-example — preview-only content

The element exists so the shipped template looks like a complete document (extra table rows beyond the repeat prototype, for instance). Agents always remove data-example elements when filling. They must not contain slots.

3. The manifest


{
  "spec": "quick-html/v0",
  "id": "invoice",                    // = directory name, kebab-case
  "version": "0.1.0",                 // semver; bump on every published change
  "name": "Invoice",
  "description": "One sentence of what it is + when to use it. Written like a skill description — this is what agents select on.",
  "useWhen": ["invoice", "billing a client", "factuur"],
  "format": "printable",              // printable | page | deck — how it is shaped
  "theme": "business-finance",        // exactly one, from schema/themes.json — what it is about
  "audience": ["freelance", "finance"], // optional, 0–3 from schema/audiences.json — who it is for
  "tags": ["billing", "vat"],         // optional, 0–6 free kebab-case cross-cutting labels
  "language": "en",
  "page": { "size": "A4", "orientation": "portrait" },
  "modules": ["edit-overlay", "print-helper"],   // registry modules to embed at build time
  "slots": [
    { "name": "title", "type": "text", "required": true, "example": "…", "notes": "…" }
  ],
  "repeats": [
    { "name": "item", "min": 1, "max": 30, "slots": [ /* same shape as slots */ ] }
  ],
  "optionals": [ { "name": "notes", "description": "…" } ],
  "agentInstructions": "The authoritative fill instructions. Multi-line string.",
  "license": "MIT",
  "authors": [{ "github": "bvrln" }]
}

3.1 Classification

Three independent axes: theme (what the document is about — exactly one), format (how it is shaped — exactly one), and audience (who it is for — nought to three, omitted when it suits everyone), plus up to six free tags. The controlled lists live in schema/themes.json and schema/audiences.json; the reasoning — the one-home rule, why audience is a set and theme is not, how to add to either — is in docs/TAXONOMY.md.

agentInstructions is the only place agent-directed instructions may live. Agents are told to ignore instruction-like text anywhere else (comments, hidden elements) — and the linter rejects it. This is the registry's prompt-injection boundary.

4. Self-containment and security rules

Enforced by scripts/lint.js; CI runs it on every PR.

5. Versioning and publishing

5.1 The archive

archive/{t,modules}/{id}@{version}/ holds every version ever published, byte-for-byte as it was served — template with its modules already inlined, plus the manifest. It is committed to the repo. Build output normally would not be, but once a template's source moves on, an old published version can no longer be regenerated from it: the archive is the only record. Same reasoning as a lockfile.

scripts/build.js maintains it and enforces the promise above:

SituationWhat the build does
Version not in the archiveWrites it (this is a new release)
In the archive, identicalNothing — normal case
In the archive, differentFails. Source changed without a version bump; publishing would silently change what a pinned URL serves

With QH_STRICT_ARCHIVE=1 (set in CI) an unarchived version is an error instead of something to create, so a change cannot reach main without its published record. Run npm run build locally and commit archive/ with your change — tools/sync.ps1 does this for you.

Superseded versions are copied verbatim from the archive into dist/ on every build, so pinned URLs keep working. index.json lists all published versions per template under versions.

6. URL grammar

Predictable by design — an agent that has seen one template can guess everything else.


/                              gallery (human)
/index.json                    machine-readable catalog
/llms.txt                      agent entry point
/agents.md                     full agent documentation (also /agents/ as HTML)
/t/{id}/                       template detail page (human)
/t/{id}/template.html          latest template (agent)
/t/{id}/manifest.json          latest manifest (agent)
/t/{id}@{version}/template.html   pinned, immutable
/t/{id}@{version}/manifest.json   pinned, immutable
/modules/{id}.js               latest module
/modules/{id}@{version}.js     pinned module
/healthz                       health check

7. Modules

A module is an embeddable, dependency-free IIFE in modules/{id}/{id}.js with a module.json (id, version, name, description, license). The build inlines the modules a template declares into the published template.html as <script data-qh-module="{id}@{version}">…</script> before </body>, so published artifacts stay fully offline-capable. Modules must: