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/siteinstalled, 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.
- Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
- Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
- Write the record for the page I describe. Keep every string inside its bound.
- Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
- 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 charactersimport { 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.
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.
- Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
- Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
- Write the record for the page I describe. Keep every string inside its bound.
- Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
- 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:
10 records parseWith one plan named Every berth, all year, 21 characters:
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// 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.
// 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`);- Read https://next.mdxui.dev/llms.txt and https://next.mdxui.dev/docs/agents/write-a-page.md.
- Fetch https://next.mdxui.dev/schema.json and use it as the structured output schema for one page record.
- Write the record for the page I describe. Keep every string inside its bound.
- Parse it with PageSchema.safeParse. When it refuses, shorten the content at the path it names, never the bound.
- Render it with renderPage and save the HTML file.
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}`];
});
}// 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`);