Alpha: Ships on npm at 7.0.0-alpha.07.0.0-alpha.0

Have your agent write a page

Prompt, record, safeParse, fix at the path, render; then the same check on every push, so no refused record ships.

You describe the page; the agent writes the record, parses it, fixes each refusal at its path and renders it. Then one script keeps every record parsing on every push.

Prerequisites

  • @mdxui/site installed, as in the Quickstart.
  • A coding agent that can fetch an address and run a script.

Hand it the prompt and the schema

The prompt names what to read and the rule. /schema.json makes a good structured output schema: the model sees every bound as maxLength, minItems and maxItems while it writes. It cannot see every rule, so the next step still parses.

prompt
Write a page from a description
  1. Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
  2. Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
  3. Write the record for the page I describe. Keep every string inside its bound.
  4. Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
  5. Render it with renderPage and save the HTML file.

Parse what it wrote

PageSchema.safeParse accepts the record or returns its issues, each a path and a message. Nothing renders until it accepts. A hero title of 71 characters returns:

sections.1.block.title: Too big: expected string to have <=64 characters
refusal-url.ts
import { PageSchema } from '@mdxui/site';

// Each refusal, then the address of its fix: the block's type, then the path after `block`, list indexes dropped.
export function refusalUrls(page: any): string[] {
  const result = PageSchema.safeParse(page);
  if (result.success) return [];
  return result.error.issues.flatMap(({ path, message }) => {
    const keys = (p: PropertyKey[]) => p.filter((s) => typeof s === 'string').join('.');
    const anchor = path[0] !== 'sections' ? `page.${keys(path)}` : path[2] === 'shell' ? `shell.${keys(path.slice(3))}` : [page.sections[path[1] as number]?.block?.type, keys(path.slice(3))].filter(Boolean).join('.');
    return [`${path.join('.')}: ${message}`, `https://next.mdxui.dev/docs/reference/refusals#${anchor}`];
  });
}

Fix it at the path

refusalUrls turns each issue into the address of its rule on Refusals. The agent reads the rule, shortens the content at that path and parses again. It never raises a bound or drops the section to make the error go away.

refusal-url.ts
import { PageSchema } from '@mdxui/site';

// Each refusal, then the address of its fix: the block's type, then the path after `block`, list indexes dropped.
export function refusalUrls(page: any): string[] {
  const result = PageSchema.safeParse(page);
  if (result.success) return [];
  return result.error.issues.flatMap(({ path, message }) => {
    const keys = (p: PropertyKey[]) => p.filter((s) => typeof s === 'string').join('.');
    const anchor = path[0] !== 'sections' ? `page.${keys(path)}` : path[2] === 'shell' ? `shell.${keys(path.slice(3))}` : [page.sections[path[1] as number]?.block?.type, keys(path.slice(3))].filter(Boolean).join('.');
    return [`${path.join('.')}: ${message}`, `https://next.mdxui.dev/docs/reference/refusals#${anchor}`];
  });
}

Render it

Once the record parses, renderPage(record) returns the page, and renderPage(record, { theme }) the same page under another Theme. The agent hands back the record and the HTML; you review a JSON diff of words.

prompt
Write a page from a description
  1. Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
  2. Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
  3. Write the record for the page I describe. Keep every string inside its bound.
  4. Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
  5. Render it with renderPage and save the HTML file.

Check on every push

Run node check-records.ts in CI. It parses every record in pages/ and fails the run on any refusal, with the address of its fix. On records that parse:

node check-records.ts
10 records parse

With one plan named Every berth, all year, 21 characters:

node check-records.ts
pages/pricing.json: sections.1.block.tiers.0.name: Too big: expected string to have <=16 characters
fix: https://next.mdxui.dev/docs/reference/refusals#pricing.tiers.name
check-records.ts
// node check-records.ts: every record in pages/ parses, or each refusal prints with the address of its fix.
let refused = 0;
const files = (await readdir('pages', { recursive: true })).filter((f) => f.endsWith('.json')).sort();
for (const file of files) {
  const lines = refusalUrls(JSON.parse(await readFile(`pages/${file}`, 'utf8')));
  if (lines.length) refused++;
  for (let i = 0; i < lines.length; i += 2) console.error(`pages/${file}: ${lines[i]}\n  fix: ${lines[i + 1]}`);
}
if (refused) process.exitCode = 1;
else console.log(`${files.length} records parse`);

Result

Every record the agent writes parses before it renders, and none that is refused reaches a deploy.

check-records.ts
// node check-records.ts: every record in pages/ parses, or each refusal prints with the address of its fix.
let refused = 0;
const files = (await readdir('pages', { recursive: true })).filter((f) => f.endsWith('.json')).sort();
for (const file of files) {
  const lines = refusalUrls(JSON.parse(await readFile(`pages/${file}`, 'utf8')));
  if (lines.length) refused++;
  for (let i = 0; i < lines.length; i += 2) console.error(`pages/${file}: ${lines[i]}\n  fix: ${lines[i + 1]}`);
}
if (refused) process.exitCode = 1;
else console.log(`${files.length} records parse`);
Write a page from a description
  1. Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
  2. Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
  3. Write the record for the page I describe. Keep every string inside its bound.
  4. Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
  5. Render it with renderPage and save the HTML file.

Next steps

On this page