Self-host quickstart — render MIDL in your own app

MIDL is two halves you can use independently, in any Node/edge/browser project — no SvelteKit, no bundler required:

  • @wisepunk/renderer-html — turn a MIDL document into an HTML string (+ theme CSS).
  • @wisepunk/sdk — manage pages over the authenticated write API (create / update / delete / read).

Both are on public npm under the @wisepunk scope — no auth, no .npmrc:

npm install @wisepunk/renderer-html @wisepunk/core @wisepunk/sdk

1. Render a MIDL document (no server needed)

import { renderMidl } from '@wisepunk/renderer-html';

const doc = {
  midl: '0.1',
  ns: { ui: 'https://midl.ai/ui#' },
  frames: [
    { id: 'page', type: 'ui:Page', children: ['hero', 'cta'] },
    { id: 'hero', type: 'ui:Hero', title: 'Hello from my app' },
    { id: 'cta', type: 'ui:Button', text: 'Get started', variant: 'default' },
  ],
};

const { html, themeCss, validation } = renderMidl(doc, { pack: '@midl/pack-minimal-html' });
// → serve `html` (and `<style>${themeCss}</style>`) from your framework of choice.

Two packs ship today: @midl/pack-minimal-html (bare semantic tags, zero CSS) and @midl/pack-tailwind-shadcn (Tailwind + shadcn variables). validation.valid tells you whether the doc satisfied the pack's component manifests.

Wiring tailwind-shadcn (don't hand-write the var map)

The shadcn pack emits utility classes (bg-primary, text-muted-foreground, …) that resolve to the CSS vars themeCss sets. Import the mapping instead of writing it by hand:

// tailwind.config.js
import { tailwindThemeExtend } from '@wisepunk/core';
export default {
  content: ['./**/*.{html,js}'],
  theme: { extend: tailwindThemeExtend() }, // colors → var(--…), borderRadius → var(--radius)
};

Important — let Tailwind see the pack's classes. The renderer emits utility classes (flex-row, text-6xl, max-w-5xl, the heading scale, …) at runtime from the recipes inside @wisepunk/core, not from your own source — so Tailwind's content scanner won't find them and they'll be inert. Point it at the installed package. Tailwind v4 (CSS): add @source "../node_modules/@wisepunk/core/dist/**/*.js"; next to @import "tailwindcss";. Tailwind v3: add the path to content, e.g. content: ['./**/*.{html,js}', './node_modules/@wisepunk/core/dist/**/*.js']. (Or safelist them.)

Theme tokens accept a plain string or { value } ({ 'colors.primary': '#34d399' }). Foregrounds are auto-derived to pass WCAG AA against their background; override a derived one with its *.text key (e.g. colors.muted.text, colors.primary.text). A document can also theme itself — a ui:StyleToken frame's tokens are applied automatically when you don't pass options.theme.

2. Author + manage pages via the write API

Point the SDK at your MIDL deployment and use an API key (minted by an admin):

import { createClient } from '@wisepunk/sdk';

const midl = createClient({
  baseUrl: 'https://your-midl-deployment.workers.dev',
  apiKey: process.env.MIDL_API_KEY!,
});

const { id } = await midl.frames.create({ content: doc, slug: 'home' });
await midl.frames.update(id, { isActive: true });
const stored = await midl.frames.get({ slug: 'home' });

3. Put them together

A minimal Node handler that fetches a stored page and renders it:

import { createClient } from '@wisepunk/sdk';
import { renderMidl } from '@wisepunk/renderer-html';

const midl = createClient({ baseUrl: process.env.MIDL_URL!, apiKey: process.env.MIDL_API_KEY! });

export async function handler(slug: string): Promise<string> {
  const page = await midl.frames.get({ slug });
  const { html, themeCss } = renderMidl(page.frame.content, { pack: '@midl/pack-tailwind-shadcn' });
  return `<!doctype html><html><head><style>${themeCss}</style></head><body>${html}</body></html>`;
}

That's the whole loop — author with the SDK, render with the renderer — entirely outside this repo. The standalone consumer smoke test runs exactly this against the published tarballs in CI, so the quickstart can't silently rot.

Reference