Repository navigation
[0.3.0] - 2026-10-06
The Page Spec stays version: 1, and every spec that was valid in 0.2.0 stays
valid. These changes alter existing behaviour or output on purpose:
--theme(and thethemerender option) now replaces the spec's
theme.recipesas well as its tokens. Before, spec recipes applied
regardless.- Every
gallerythumbnail opens a full-size lightbox. There is no author
flag, so every page with a gallery emits new markup, CSS and runtime. - Tabs no longer fire
selectfor the initial tab when the page loads, so page
state keeps its declared initial values. - A remote video
posterthat the network policy blocks is avalidateerror
(POLICY_VIOLATIONat theposterpath), not a compile failure at$. - Pages with tabs or a carousel change markup and CSS so every panel and slide
reads without scripts and in print. catalogtext is grouped by category, andcatalog --jsonprints one block
per line with newcategoryandtagsfields.- The Claude Code and Codex plugin manifests link to https://render.agentkit.best, and the Codex plugin shows an icon, a logo and the brand colour in the plugin directory.
- The benchmarks are measured against AgentKit
9e322f928. The legacy baseline no longer counts the slide guide for document tasks (AgentKit loads it for--slidesonly), so its per-task mean is ~25.0k tokens, not ~33.7k.render-benchmark.mjsmeasures theak-renderskill route per task: the AgentKit shared HTML contract, the skill,catalog, anddescribe --jsonfor each block type the task's fixture compiles to. It fails when the AgentKit checkout is missing. Presentation context now drops 64–68% (previously reported as 87–88%). The typical-task estimate is 54% with full contracts and 66% withdescribe --compact, which counts the shared contract too (previously 65% and 75%).agent-token-cost.mjs --reuse-legacyreruns that estimate from recorded corpus measurements. The README and landing page use the new figures. - The landing page shows what the compiler saves an agent (measured and estimated token figures, kept apart), each component crop beside the fixture YAML that produced it, and a gallery of every demo page. The README gains a token cost section.
Added
- Rich composition: a tab panel, an accordion section, a carousel slide and a bento tile hold child blocks in
items[i].blocks(up to 12, or 6 in a bento tile). Any block may nest exceptpage,sectionandhero, and tabs may nest in tabs. An item'stext, and a bento tile'stitle, are optional when the item has blocks; an item with neither is an error at its path. - Nested slots with
acceptsandparentsrules. Diagnostics carry the nested JSON path, and nested blocks count toward the block limit. grid-itemplaces blocks on a 12-track grid withspan,tabletSpan,mobileSpan,rowSpan,startandalign.grid.columnstakes 1–12 orauto(withminItemWidth), grids takegapandalign, andstacktakesalign.- Semantic page layouts:
sidebar-layout(asidebarbeside the mainblocks, on eitherside),main-aside(anasidethat stays in view on wide screens) andrail-layout(a compactrail, a labelled<nav>when it holds only links or toolbars). - Datasets: top-level
datasets, anddataRef, inlinedataand a boundedtransform(filter, groupBy, sort, select, limit) on blocks that take data. Values format through one locale-free formatter (number,integer,compact,percent,currency,duration,bytes,date);percenttakes87for "87%". See docs/data-and-state.md. visibleWhenshows a block only while a state value matches (equals,notEquals,in,notIn,truthy,falsy). The compiler emits the initial view, so the page reads correctly without scripts and in print.- Native input controls
select,radio-group,checkbox,switch,text-input,number-inputanddate-input. Each has a visible label, writes one value tostate.<key>throughbind, and fireschangewith its value. filter-barfilters adata-table,kanbanorlog-viewerwith 1–8 controls, each naming a rowfieldand amatch. It adds an announced result count and a Reset button;selectandradio-groupcan list the target's values withoptionsFrom. Adata-tablethat a filter-bar targets renders no search box, row count or "no match" note of its own, so two filters never fight over the same rows; put free-text search in the bar as asearchchild.data-table: a typed, sortable table bound to a dataset. Column typestext,number,percent(aliaspercentage),currency,date,badge,link,progressandsparkline; search past 10 rows (unless a filter-bar targets the table), a sticky header past 12, labelled cards on phones, and the full table in authored order without scripts or in print.- Chart kinds
scatter,histogram,stacked-bar,stacked-bar-100,heatmap,waterfall,funnel,gaugeandtreemap. A chart can bind rows and read them through thex,y,series: { field }andvalueencodings, and takesmarkers,annotations,sortand a gaugetarget. A bound chart's fallback table comes from the bound rows. Charts that uselabelsandseriesrender the same markup as before. - Engineering widgets:
kanban,roadmap,test-results,log-viewer,api-endpointandschema-viewer. - Evidence widgets:
benchmark-comparison(computed delta, change and verdict;currencyis set once for the whole block, not per metric),metric-breakdown,annotated-imageandreferences(stable#ref-<id>anchors). - Product widgets:
pricing,feature-matrix,testimonial(two or more quotes form a grid),logo-cloud,peopleandcalendar(weeks start on Monday;weekStart: sundaychanges that). - Theme recipes restyle components through closed enums for cards, sections, tables, charts, hero, metrics, media and callouts. A preset, a preset file or the spec's
theme.recipesselects them. See docs/themes.md. - Four built-in presets:
data-console,executive-report,product-studioandresearch-notebook. They reuse the bundled faces. Every built-in preset is tested for WCAG 2.2 contrast in both schemes. - An inline spec theme accepts
theme.dark, dark-scheme token overrides. - Catalog discovery: every block has a category and tags.
ak-render catalog --category <c>lists one category,ak-render search-catalog <terms...>ranks block types by intent words, andak-render describe <type...> --compactreturns up to 12 contracts with one line per prop; a shape repeated within a contract readslike <field>and a repeated long enumenum as <field>. Row-filtering words rankfilter-barfirst. MCP gainssearch-catalog,catalog{category}anddescribe{types, compact}, on the stdio and the remote server; the remotesearch-catalogneeds no token. The library exportssearchCataloganddescribeMany. - Page recipes:
ak-render recipes [--json]lists ten starter specs (architecture-review,benchmark-report,case-study,dashboard,decision-memo,implementation-plan,incident-report,product-showcase,release-recap,research-report), andak-render recipe <name>prints one as YAML, or the full record with--json. An unknown name exits 1 and lists the allowed names; a missing name exits 2. Each recipe validates with no errors or warnings, compiles deterministically, stays within 3,000 bytes of YAML and loads nothing remote. MCP serves them asrecipesandrecipe{name}on the stdio and the remote server, and the library asrecipes(),recipe(name)and thePageRecipeandPageRecipeSummarytypes; an unknown name throwsSPEC_VALIDATION_ERRORat pathnamewithdetails.allowed. pnpm recipes:generateembeds the recipe YAML insrc/recipes/recipes.generated.ts, andpnpm recipes:check, part ofpnpm verify, fails when that module is stale.- The library exports
BLOCK_CATEGORIES,THEME_RECIPE_SPECS,VALUE_FORMATSand theMaterializedData,DataRowandDataScalartypes. - Remote MCP over Streamable HTTP at
https://render.agentkit.best/mcp, served by the cloud Worker. It lists the same tools asak-render mcp:catalog,search-catalog,describe,recipes,recipe,validate,renderandthemes. A remoterendertakes noout: it compiles through the same path asPOST /v1/render(byte-identical HTML), stores the page and returns the summary plusartifactUrlandexpiresAt, never the HTML. The artifact lives one hour, or becomes an expiring share withshare: true(needs the share scope).catalog,search-catalog,describe,recipes,recipeandthemesanswer without a token;validateandrenderneed an AgentKit bearer and count against the same per-subject rate limits as REST. - The MCP server is split into transport-independent modules under
src/mcp/: protocol vocabulary, one set of tool contracts, JSON-RPC routing, the stdio transport, and a Fetch-API Streamable HTTP transport the Worker imports.ak-render mcpanswers byte for byte as before. GET /v1/artifact/:idserves the short-lived artifact a remote MCP render stored.scripts/cloud-smoke.mjssmoke-tests a deployed renderer (MCP and REST, share lifecycle, exports) withBASE_URLand an optionalTOKEN; the manual cloud deploy workflow runs it after deploying.benchmarks/agent-token-cost.mjsestimates the tokens of a page task end to end: guidance read, output written and what returns into context, against a corpus of hand-written HTML passed with--legacy-html. Results are indocs/artifacts/agent-token-cost.md, with the corpus recorded by anonymous label.--legacy-from <artifact.json>refreshes the artifact from the recorded legacy rows without the private corpus, and a second typical-task row measures the compact workflow (describe <types...> --compact). Block types are read from the compiler IR.pnpm bench:spec(benchmarks/spec-compression.mjs) records, per composition fixture, the spec bytes, the bytes with everydataRefinlined, the HTML bytes, and the catalog and compact-describe bytes an agent reads, indocs/artifacts/spec-compression.{json,md}. The output is deterministic.- Six composition fixtures:
complex-dashboard,benchmark-report,research-report,product-case-study,interactive-data-explorerandincident-report. Each validates with no warnings and compiles to identical bytes three times.fixtures/rejected/validation/holds well-formed but wrong specs that must fail validation at a located path, optionally pinned by a first line# expect: CODE $.path. tests/browser/composition-matrix.spec.tschecks every fixture page for horizontal overflow at 320, 375, 768 and 1440 in light and dark, running animation under reduced motion, script-off readability, print, keyboard focus, network requests and critical axe violations.@axe-core/playwrightis a new devDependency.scripts/capture-demo-media.mjscaptures five more page shots and four component crops (KPI, terminal, checklist, file tree), and--only <names>recaptures a subset without the walkthrough video.- A pair of backticks in prose text renders as inline code, so
`ak-render catalog`in atext, a step, a list item, a description or a caption reads as a command. The text is escaped before the span is wrapped, nothing else is parsed as markup, and an unpaired backtick stays literal. Titles,codeblocks,terminallines and the chart summary are unchanged, anddescribeand the JSON Schema say which props do this.
Changed
catalogtext is grouped by category under## <category>headers, onetype — summaryline per block, without the kind column.catalog --jsonand the MCPcatalogaddcategoryandtagsto each entry and print one block and one action per line; the parsed value is otherwise unchanged. A single-typedescribe <type>is byte-identical.- The MCP
describeinput no longer requirestype: passtypeortypes, plus optionalcompact. Every local tool description is at most 200 characters. - URL props declare the network capability of the asset they load, so the CSP origins and the validation gate come from one schema walk at any depth.
- When a
oneOfvalue fails, diagnostics come from the option the author meant, chosen by JSON shape and then bytype. - A tab's
selectevent passes the tab id as its value, soset-valuewithout its ownvaluestores the selected tab. - Chart
labelsis optional in the schema; a chart without bound data still requires it, with the same message and path. - The cloud Worker is routed at
render.agentkit.best/mcpandrender.agentkit.best/v1/*, beside the site's Custom Domain on the same hostname. - The Claude Code and Codex plugin manifests link to https://render.agentkit.best, and the Codex plugin shows an icon, a logo and the brand colour in the plugin directory.
- The landing page shows what the compiler saves an agent (measured and estimated token figures, kept apart), each component crop beside the fixture YAML that produced it, and a gallery of every demo page. The README gains a token cost section.
Fixed
- Tab panels and carousel slides are readable without scripts and in print: every panel ships visible with its title until the runtime marks the container ready.
- Accordion sections print open, nested blocks included.
- Accordion and carousel styles apply only to their own sections and slides, so a nested block keeps its own look.
- The runtime escapes embedded state, so a value such as
</script>cannot close the script element. - An event stops at the nearest block, so it no longer reaches an enclosing block's bindings.
- A
set-valueaction withoutvaluetakes the event's value, for example a slider's. With no event value it does nothing; before, it setundefined. - The cloud Worker validates bearers against the canonical AgentKit endpoint,
GET https://agentkit.best/api/agentkit/entitlements, instead of an unreachable host, so authenticated render, share and export can succeed. It fails closed on a rejected token (401), an inactive account (403), and an upstream error, redirect, timeout or malformed body (502). An active app or kit entitlement grants therenderandsharescopes, which stay separate checks. - Screenshot and PDF export call the Browser Run binding's Quick Actions with the compiled HTML inline and caching off, and
wrangler.jsoncattaches theBROWSERbinding. - Remote MCP bounds its anonymous surface: a JSON-RPC batch carries at most 16 members, never
initialize, and is refused (400) under protocol revision 2025-06-18 or later, which removed batching.catalog,search-catalog,describe,recipes,recipeandthemesstay anonymous but count per client IP (anon-ip, 120/min). - A remote MCP request that presents a rejected bearer answers HTTP 401 with
WWW-Authenticate: Bearer, whatever the method, instead of a tool error; REST 401s carry the same challenge. A request without a bearer is still served, andvalidate/renderstill answer a tool error withUNAUTHENTICATED. - Cloud rate limits use the Workers Rate Limiting binding, one per route class, instead of KV counters that could fail on rapid writes; a limiter fault refuses the call with
RATE_LIMITED. An MCPrenderwithshare: truecounts againstshareonly, asPOST /v1/sharedoes, and a call refused by authorization consumes nothing. - The cloud Worker caches entitlements answers in memory under a SHA-256 digest of the bearer (60 s accepted, 10 s rejected), and limits uncached bearer checks per client IP (30/min) before contacting the entitlements endpoint.
- Remote MCP reports a storage or runtime fault as a generic
INTERNAL_ERRORtool error, and the REST routes answer such a fault with a generic 500, so no internal message reaches a caller. - Hosted shares and artifacts are served with
content-security-policy: sandbox …; frame-ancestors 'none': they run in an opaque origin and cannot be framed. - The expiry sweep pages through every listed object with the cursor, reads expiry from listing metadata instead of downloading bodies, and deletes in batches; R2 lifecycle rules (
apps/cloud/r2-lifecycle.json, applied on deploy) delete artifacts after 1 day and shares after the retention period plus a day. - A remote video
posterthat the network policy blocks now failsvalidatewith aPOLICY_VIOLATIONat the block'sposterpath, naming the reason. Before, validate passed and compile failed at$with "emitted a remote resource reference". A poster is gated by theimagescapability, an allowed remote poster's origin joins the CSP, and the renderer drops a blocked poster even for an IR that skipped validation. - Diagram adapter
<style>elements now receive the page style nonce, so styled adapter SVG renders as drawn instead of in default black. The policy stays nonce-only: inlinestyleattributes are removed from adapter output with a warning. Adapter CSS is scoped with@scopeto its own diagram canvas, so it cannot restyle the page, and it cannot load anything: escapes, character references,url()other than a fragment reference such asurl(#grad),image-set(), every at-rule except@media,@keyframesand@supports, and@keyframesthat redefine the page's own reject the output to the structured fallback. Comments and!importantare removed. Adapter markup that writes anonceor anydata-ak-*attribute, or anhref,xlink:href,src,srcset,poster,action,formaction,background,data,codebaseorpingthat is not a same-document#fragment, or SVG animation whoseattributeNameis one of those, is rejected too, and so is markup that is not a self-contained fragment: a stray or missing end tag, a raw-text element such asplaintextortextarea, an element that merges into the page document such asbodyormeta, an HTML element inside SVG, or a comment. Output from theak:diagramcompiler passes these checks. Adapter animation stops under reduced motion,motion-policy: noneand print.docs/diagram-adapter.mdlists what adapter CSS may use. - Checking adapter markup is linear in its size. An unterminated tag repeated to the 256 KB bound took minutes; it now takes milliseconds.
- A long unbroken token, such as a path or an env var list, no longer widens the page on a phone. Key-value values, list items, text and callouts break the token inside their own line, so the reported key-value page no longer scrolls sideways at 375px.
- A
diagram-panelshows adapter SVG at its natural size. A wide diagram scrolls sideways inside its panel, a named region that scrolls from the keyboard, instead of shrinking its labels until they are unreadable. In print it is scaled to the page width. - A
diagram-panelwhose adapter rendered no longer shows the structured description beside the drawing. The description stays in the document behind a closed "Text description" disclosure, which opens in print, with or without script. - A long
terminalsession no longer leaves its last lines blank in a capture. Lines now stagger 80ms apart instead of 340ms, lines past the twelfth share its delay, and the whole session is drawn about 1.4s after it arrives. - A wide
tableor adapter diagram now shows a soft shade on each edge that has more content beyond it, so a frame that scrolls sideways no longer looks clipped. A frame that fits shows none. - A
data-tablerow that a filter hides stays hidden at phone widths, where rows become cards. - Blocks inside or after a
visibleWhenview keep the normal block gap and the section-break gaps. - A thinned category axis always labels the last category, so a series never reads as ending early.
- Chart ticks finer than 0.01 keep the decimals the step needs instead of all printing the same value, and only floating-point noise is removed before a value is formatted.
- A histogram over huge, close values (such as 1e16 and 1e16 + 4) keeps finite, increasing bin edges: when clean steps cannot separate the values it draws one bin.
- The page outline skips a section behind
visibleWhen, so it never links to a hidden section. - Dataset field names
__proto__,constructorandprototypeare refused at validation, and alog-viewerlevel named like an object property gets the neutral tone. date-inputaccepts ISO dates in the years 0001–0099.