# 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: { <id>: { 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/<slug>.midl.json` | A page's raw MIDL document |
| `GET /render/<slug>.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`
  → `<h1>`, `size`) for outline; `ui:Label` (`text` + `for`) for inputs; `ui:Stack` (`direction: row|column`)
  / `ui:Section` (`width: content|full`) for layout; `ui:Button` with `href` renders an `<a>` (real CTA);
  `ui:Select` (`options: string[]`), `ui:Rating` (`value`/`max`), `ui:Accordion` (`summary` → `<details>`).
- **ESM + Node 18+** for the packages.
- **Don't assume — round-trip.** If `validation.valid` is false, the document is wrong, not the renderer; if
  `validation.warnings` is non-empty, a prop you set isn't being rendered.
