# MIDL — full documentation
> Every canonical MIDL doc, concatenated. Source + per-doc pages: https://midl.site/docs
> Machine-readable surfaces: https://midl.site/llms.txt
================================================================================
# Agent Quickstart (https://midl.site/docs/quickstart)
================================================================================
# Build your first MIDL site on localhost — agent quickstart
A self-contained starter for an AI agent (or developer) to stand up a first **MIDL** website running on
`http://localhost:3000`, using the public `@wisepunk/*` packages. Every step has a command to run and a
way to verify it worked — follow them in order.
**End state:** a tiny Node project that renders a MIDL document to HTML and serves it on localhost, with
no framework and no auth.
> Want the full capability map + how to discover the component vocabulary programmatically? See the
> companion [AGENT-REFERENCE.md](AGENT-REFERENCE.md). You can also enumerate every component without docs:
> `import { describeVocabulary, EXAMPLE_DOCS } from '@wisepunk/core'`.
---
## 0. What MIDL is (read once)
- A **MIDL document** is plain JSON describing a page as semantic *frames* (components) — not HTML.
- A **renderer** + a **pack** turn that JSON into HTML. The pack decides the look; the document stays the same.
- Two render targets ship on public npm: **`@wisepunk/renderer-html`** (→ an HTML string, framework-free)
and **`@wisepunk/renderer-svelte`** (Svelte components). This guide uses `renderer-html`.
- A typed write-API client, **`@wisepunk/sdk`**, is optional (used in step 6 to pull a live page).
```js
renderMidl(doc, { pack, theme }) → { html, themeCss, validation }
```
---
## 1. Prerequisites
- **Node 18+** (for built-in `fetch`, used in step 6). Verify:
```sh
node -v # expect v18 or higher
```
---
## 2. Create the project
```sh
mkdir my-midl-site && cd my-midl-site
npm init -y
npm pkg set type=module # the code below is ESM
npm install @wisepunk/renderer-html @wisepunk/core
```
**Verify:** `ls node_modules/@wisepunk` lists `core` and `renderer-html`. (These are **public** packages — no token, no `.npmrc`.)
---
## 3. Write a MIDL document
Create **`src/home.mjs`**. A document is `{ midl, ns, frames }`; each frame has an `id`, a `type`
(namespaced like `ui:Hero`), and type-specific fields. A frame with `children` composes other frames by `id`.
```js
// src/home.mjs
export const home = {
midl: '0.1',
ns: { ui: 'https://midl.ai/ui#' },
frames: [
{ id: 'page', type: 'ui:Page', children: ['hero', 'cta'] },
{ id: 'hero', type: 'ui:Hero', title: 'My first MIDL site' },
{ id: 'cta', type: 'ui:Button', text: 'Get started', variant: 'default' }
]
};
```
---
## 4. Serve it on localhost
Create **`server.mjs`** — a zero-dependency Node HTTP server that renders the document on each request:
```js
// server.mjs
import { createServer } from 'node:http';
import { renderMidl } from '@wisepunk/renderer-html';
import { home } from './src/home.mjs';
const PORT = 3000;
const PACK = '@midl/pack-minimal-html'; // bare semantic tags, zero CSS needed
createServer((_req, res) => {
const { html, themeCss, validation } = renderMidl(home, { pack: PACK });
if (!validation.valid) console.warn('MIDL validation issues:', JSON.stringify(validation.errors));
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(
`
` +
`My MIDL site` +
`${html}`
);
}).listen(PORT, () => console.log(`MIDL site → http://localhost:${PORT}`));
```
Run it:
```sh
node server.mjs
```
**Verify** (in another terminal):
```sh
curl -s http://localhost:3000 | grep -o '.*'
# →
My first MIDL site
```
The Hero's `title` renders as a real `
` (the page's primary heading) — heading structure lives in the
document, so screen readers, crawlers, and agents all see it. If you see that line, your first MIDL site
is live on localhost. The server log should **not** print any
"validation issues."
---
## 5. Swap the look (packs)
The document never changes — change the **pack**. Set `PACK` in `server.mjs`:
- **`@midl/pack-minimal-html`** — plain semantic tags, no CSS. Works out of the box (used above).
- **`@midl/pack-tailwind-shadcn`** — Tailwind + shadcn class names + `themeCss` CSS variables. The HTML
will carry utility classes; to *see* it styled, your page needs Tailwind (e.g. the Tailwind Play CDN)
with the shadcn variables. For a first localhost run, stick with `minimal-html`.
> Pack ids are **literal strings**, not packages to install — they're built into `@wisepunk/core`.
---
## 6. (Optional) Pull a live page with the SDK
Render isn't only for hardcoded docs — you can fetch a stored page from a MIDL deployment and render it.
The public demo API serves the read endpoints without auth.
```sh
npm install @wisepunk/sdk
```
```js
// live.mjs
import { createClient } from '@wisepunk/sdk';
import { renderMidl } from '@wisepunk/renderer-html';
const midl = createClient({
baseUrl: 'https://midl-web.rene-dad.workers.dev',
apiKey: 'public-demo' // GET is public; the SDK just needs a non-empty key
});
const page = await midl.frames.get({ slug: 'home' }); // live document
const { html } = renderMidl(page.frame.content, { pack: '@midl/pack-minimal-html' });
console.log(html);
```
```sh
node live.mjs # prints the live home page rendered to HTML
```
To **author** pages (create/update/delete) you need a real API key; `midl.frames.create({ content, slug })`
returns `{ id, slug }`. See `docs/WRITE-API-MANUAL.md`.
---
## Reference
### The MIDL document
- `midl` — schema version (`'0.1'`).
- `ns` — namespace map; `ui` points at the UI component vocabulary.
- `frames` — array of components. Each: `{ id, type, ...fields }`. A `ui:Page` lists child `id`s in
`children`; children render in that order.
- Verified starter components: `ui:Page` (`children`), `ui:Hero` (`title` → `
`),
`ui:Heading` (`level` 1–6 + `text` → `
`–`
`), `ui:Text` (`text` → `
`), `ui:Button` (`text`,
`variant`). The authoritative component set + their allowed fields live in the pack's manifest — if a
field/type isn't allowed, `renderMidl` returns `validation.valid === false` with `validation.errors`
(an array of messages) describing the problem. **Always check `validation` rather than guessing.**
### `renderMidl(doc, options)` → result
- `options.pack` — pack id string (default registry pack if omitted).
- `options.theme` — optional token map (DESIGN.md tokens) → mapped to `result.themeCss` (`:root { … }`).
- `result.html` — the rendered HTML string.
- `result.themeCss` — `:root` CSS variables for the theme (empty string when no theme given).
- `result.validation` — `{ valid, errors }` from checking the doc against the pack manifest (`errors` is a string array).
### Machine-readable surfaces (for discovery)
Against a live deployment (e.g. `https://midl-web.rene-dad.workers.dev`):
- `GET /render/.midl.json` — the raw MIDL document for a page.
- `GET /render/.facts.json` — extracted facts (agent/RAG view).
- `GET /api/agents/openapi.json` and `/.well-known/agent.json` — the API + tool descriptions.
---
## Agent gotchas
- **ESM only:** set `"type": "module"` (`npm pkg set type=module`) or the `import`s fail.
- **Packs aren't npm packages:** don't `npm install @midl/pack-*` — pass the id string to `renderMidl`; it's bundled in `@wisepunk/core`.
- **`renderMidl` returns a string, not a server** — you serve `html` (+ ``) however you like.
- **`minimal-html` needs no CSS; `tailwind-shadcn` does** — use `minimal-html` for the first run to avoid an unstyled page.
- **Node 18+** for the SDK's `fetch` (step 6).
- **Trust `validation`** — if `validation.valid` is false, fix the document to match the manifest before assuming a render bug.
---
## Done check
1. `node server.mjs` logs `MIDL site → http://localhost:3000`.
2. `curl -s http://localhost:3000 | grep -q 'My first MIDL site'` exits 0.
3. No "validation issues" in the server log.
All three → you've built and served your first MIDL site. Next: read [AGENT-REFERENCE.md](AGENT-REFERENCE.md)
for the full vocabulary + the validate→repair→render loop, add more frames (try `ui:Heading`), switch to the
`tailwind-shadcn` pack with Tailwind, or pull live content with the SDK.
================================================================================
# Agent Reference (https://midl.site/docs/reference)
================================================================================
# MIDL agent reference
The capability map for an agent building with MIDL: what the system is, every surface you can use, and
the **validate → repair → render** loop that lets you author correctly without memorizing the spec. For a
hands-on first run, do [AGENT-QUICKSTART.md](AGENT-QUICKSTART.md) first; this is the companion reference.
> **Core principle: don't guess the spec — introspect and round-trip.** The vocabulary is machine-readable
> (`describeVocabulary()`), and every render/validate call returns structured feedback that names the fix.
---
## 1. The model in one minute
- A **MIDL document** is plain JSON: `{ midl: '0.1', ns: { ui: 'https://midl.ai/ui#' }, frames: [...] }`.
- A **frame** is a component: `{ id, type, ...fields, children? }`. `type` is namespaced (`ui:Hero`).
`children` is an array of **frame ids** (composition is by reference, not nesting).
- A **pack** turns the document into HTML. The document never changes; the pack decides the look.
- `@midl/pack-minimal-html` — bare semantic tags, zero CSS. Best for a first run.
- `@midl/pack-tailwind-shadcn` — Tailwind + shadcn classes + a `:root` theme via `themeCss`.
- Pack ids are **literal strings** built into `@wisepunk/core` — *not* npm packages to install.
- A **theme** (optional) is a token map mapped to `themeCss` (`:root { … }`), contrast-checked.
```js
import { renderMidl } from '@wisepunk/renderer-html';
const { html, themeCss, validation } = renderMidl(doc, { pack: '@midl/pack-minimal-html', theme });
```
---
## 2. Discover the vocabulary (instead of reading docs)
```js
import { describeVocabulary, EXAMPLE_DOCS } from '@wisepunk/core';
const vocab = describeVocabulary();
// vocab.components[] — one entry per ui:* type:
// { type, requiredFields, leaf, allowsChildren, container, acceptsText, textProp?,
// variants?, packs: { : { tag } }, example }
```
So for any component you get its **required fields**; whether it **accepts text** (`acceptsText` + which
`textProp` — e.g. `ui:Button` → `text`, `ui:Hero` → `title`); whether it **takes children** (`allowsChildren`
= the validator's real rule, `container` = a declared child container); its **variant axes** (e.g.
`ui:Button` → `variant`/`size`, `ui:Stack` → `direction`, `ui:Section` → `width`, `ui:Hero` → `size`); the
**tag each pack renders it as**; and a **minimal example**. `allowsChildren` and the validator now agree
(MID-100) — introspection never forbids a pattern the validator accepts.
`EXAMPLE_DOCS` is an **array** of `{ name, description, doc }` — full, valid documents to pattern-match
(`landing`, `contact-form`, `article`). Every `doc` is test-asserted valid against both packs, so they're
safe to copy and mutate. E.g. `EXAMPLE_DOCS.find(e => e.name === 'landing').doc`.
On a live deployment the same data is served (origin-rooted):
| Surface | What it gives you |
|---|---|
| `GET /llms.txt` | The discovery index (this, machine-readable) |
| `GET /api/agents/vocabulary.json` | `describeVocabulary()` output |
| `GET /api/agents/examples.json` | `EXAMPLE_DOCS` |
| `GET /api/agents/openapi.json` | OpenAPI 3.1 for the public API |
| `GET /.well-known/agent.json` | Site identity, tools, formats |
| `GET /render/.midl.json` | A page's raw MIDL document |
| `GET /render/.facts.json` | Extracted facts (RAG/agent view) |
---
## 3. The authoring loop: validate → repair → render
MIDL is designed so you author by round-tripping, not by getting it right blind.
1. **Write** a document from a `describeVocabulary()` entry or an `EXAMPLE_DOCS` doc.
2. **Validate** it. The result names every problem (missing required field, unknown type, bad child ref):
```js
import { validateMIDL, validateAgainstManifest, getPack } from '@wisepunk/core';
validateMIDL(doc); // structural: { valid, errors, warnings }
validateAgainstManifest(doc, getPack(packId).manifest); // + pack constraints: { valid, errors, warnings }
```
Or over HTTP: `POST /api/validate` with the document. Or just read `renderMidl(doc).validation`.
**Read `warnings`, not just `errors`:** a prop a component won't render (e.g. a `subtitle` on `ui:Hero`,
a hallucinated field) surfaces as a `warning` — it won't fail validation but it *won't render either* (MID-100).
3. **Repair** structural issues automatically instead of hand-fixing:
```js
import { repairAgainstManifest, getPack } from '@wisepunk/core';
const { doc: fixed, repairs } = repairAgainstManifest(doc, getPack(packId).manifest);
```
4. **Render** — `renderMidl(doc, { pack })` → HTML. `validation.valid === false`? Fix what `validation.errors`
say and re-render. **Trust the validator over your assumptions.**
---
## 4. Authoring & serving stored pages (the write API)
To create/update pages on a deployment, use `@wisepunk/sdk` with a Bearer API key:
```js
import { createClient } from '@wisepunk/sdk';
const midl = createClient({ baseUrl: 'https://your-deployment.workers.dev', apiKey: process.env.MIDL_API_KEY });
const { id, slug } = await midl.frames.create({ content: doc, slug: 'home' }); // validated server-side
const page = await midl.frames.get({ slug: 'home' }); // read it back (GET is public)
```
Reads (`list`, `get`) are public; writes (`create`, `update`, `delete`) need the key. The full HTTP contract
is `GET /api/agents/openapi.json`. See [WRITE-API-MANUAL.md](WRITE-API-MANUAL.md).
---
## 5. Theming
Pass `options.theme` (DESIGN.md tokens) to `renderMidl`, or let a document theme itself.
- **Token values** accept a plain string OR `{ value }` — `{ 'colors.primary': '#34d399' }` works (MID-102).
- **Self-theming documents** — a `ui:StyleToken` frame's `tokens` are read automatically when you don't pass
`options.theme`. `renderMidl(doc, { pack })` themes from the document itself.
- **Foregrounds are auto-derived to pass WCAG AA** against their own background — you supply background-side
colors; `--*-foreground` is computed (and contrast-checked). To override a derived foreground (e.g. make
`--muted-foreground` less bright), set the `*.text` key: `colors.muted.text`, `colors.card.text`,
`colors.primary.text`, etc.
- **Tailwind hosts** (for `@midl/pack-tailwind-shadcn`): don't hand-write the var mapping —
`import { tailwindThemeExtend } from '@wisepunk/core'` and spread it into `tailwind.config`'s `theme.extend`
so `bg-primary` / `text-muted-foreground` / the radius resolve to the vars `themeCss` sets.
---
## 6. Gotchas
- **Children are ids, not objects** — `children: ['hero', 'cta']`, and those frames exist in `frames`.
- **Packs aren't installable** — pass the id string to `renderMidl`; it's bundled in `@wisepunk/core`.
- **`minimal-html` needs no CSS; `tailwind-shadcn` does** — start with `minimal-html`.
- **Express structure in the document, not the host shell:** `ui:Heading` (`level` 1–6) / `ui:Hero` (`title`
→ `