Canvas artifacts
A canvas artifact is a designed, self-contained HTML page an agent publishes to
the canvas drawer: a run report, an audit briefing, a chart of usage over time.
Ask for one in chat (“make a report of this run”) and the agent authors the
page, publishes it with the canvas_publish tool, and the drawer opens on it.
Artifacts persist under <home>/artifacts/ as plain .html files, survive
server restarts, and update in place when the agent republishes the same name.
The pipeline has three parts:
- Design guidance. When
canvas_publishis available, the chat system prompt carries the authoring contract: theme through CSS custom properties (keelson’s dark values on:root, light overrides on:root[data-theme="light"]), system font stack, no external resources, and the keelson palette inlined as literal hex. Thecanvas_design_guidetool serves deeper references on demand: a ready class kit, the house voice for page copy, page types and layout, chart-form selection, the color system, mark anatomy, inline SVG diagram craft, the structured board view’s section catalog, and an anti-pattern catalog. - Computable validation. A page that carries categorical chart series
declares its palette on
<body data-palette-dark="…" data-palette-light="…">. Publishing validates color-vision-deficiency separation and surface contrast per theme and rejects hard failures with a per-check report, so the agent fixes the colors and retries in the same turn. Palettes are checked by computation, never by eyeball. - The sandboxed frame. Artifacts render through the same hardened
htmlcanvas kind ribs use: an opaque-origin iframe under a CSP that denies by default and re-allows only what a page needs to draw itself, inline script and style plusdata:andhttps:images and fonts. The load-bearing line isconnect-src 'none', which blocks script-initiated network egress (fetch, XHR, sendBeacon, WebSocket), so frame script cannot exfiltrate. The host stamps the app’s resolved theme onto the frame and pushes toggles live, so a token-styled page re-themes without a reload.
Pages can opt into transient UI state with keelson.saveState and
keelson.onRestore. Snapshot-backed pages retain it by key in bounded
browser-tab memory; inline text and direct artifact sources without a snapshot
key retain it only for the component’s lifetime. This is not artifact or draft
storage, and a page reload loses it. See the
HTML state bridge reference
for the API, lifecycle, and limits.
Publishing from chat
Section titled “Publishing from chat”Any provider with the tool registry sees canvas_publish. A typical exchange:
you> Chart last week's token usage by model and publish it as a page.agent> [canvas_design_guide: form] [canvas_design_guide: color]agent> [canvas_publish: "Token usage, June 29 to July 5"]The drawer opens on the published page. The tool result records the artifact
key (canvas:artifact:<slug>), and republishing with name: <slug> updates
that page in place rather than minting a sibling.
Publishing from a workflow
Section titled “Publishing from a workflow”The bundled design-artifact workflow is the reference shape: one prompt node
with allowed_tools: [canvas_publish, canvas_design_guide] that reads the
guide, authors the page, and publishes through the validated gate.
keelson workflow run design-artifact "a briefing on this week's merged PRs"Artifact quality tracks the authoring model. Pin provider: or model: in
the workflow (or set KEELSON_WORKFLOW_PROVIDER) to route artifact authoring
at your strongest option.
The design tokens
Section titled “The design tokens”Generated pages match the app because they inline the same palette the SPA
renders: @keelson/shared exports DESIGN_TOKENS (surfaces, ink, semantic
accents, the six chart-series slots, the five identity tones) for both themes,
and a drift guard keeps the module identical to the app stylesheet. The
sandboxed frame cannot read the app’s CSS custom properties across its origin
boundary, so inlining the values is what keeps an artifact on-palette. Rib
authors can import the same tokens and validateCategoricalPalette /
validateOrdinalRamp checks to build their own validated producers.
The kit and the house voice
Section titled “The kit and the house voice”Two guide sections do most of the work of making a page look and read like
keelson. The kit section is one stylesheet: the token block (which also paints
the page background, since the frame’s own ground is white) followed by ready
classes for the masthead, summary box, legend, stat tiles, tags, tables,
callouts, terminal output, and inline SVG diagrams. Tone classes are one
vocabulary across all of them: .good, .warn, .crit, and .info mark
state, and .id-1 to .id-5 mark who, so an actor keeps one color across every
figure and table on the page.
The voice section sets how the copy reads. A page leads with the state of the
work, uses keelson’s own terms (run, node, workflow, rib), points each claim at
the run id, node, file, or command that proves it, and writes “not measured”
instead of a zero when a check never ran. It speaks as the page, never as the
agent that wrote it.