Canvas

When canvases appear

A canvas is the rendered surface for any output that is not a chat reply. Tables, kanbans, decks, score cards, code diffs, document drafts. Two paths put a canvas on screen: a chat reply that emits a canvas spec, or a technique fired from the picker. Both end up rendering the same block types through the same renderer.

Updated today

Overview

At a glance
Renderer
src/components/canvas/CanvasBlock.tsx
Block types
30+ kinds in the catalogue
Entry points
Chat reply with canvas spec, or Techniques picker
Spec format
JSON: { type, data }
Persistence
Attached to the parent message in messages.content

Canvas exists because some outputs do not belong in a chat bubble. A side-by-side comparison reads better as a table. A pipeline reads better as a kanban. A long-form deliverable reads better as a structured document. The chat surface still owns the conversation; the canvas owns whatever artefact the conversation produced.

Two paths into a canvas

Both paths produce the same spec shape and hit the same renderer.

PathTriggerOutcome
From chatA skill emits a canvas spec inside its replyRenders inline under the reply, with a maximise affordance to open it full screen
From TechniquesUser opens the Techniques picker from the chat composerPicks a workflow, fills in any required inputs, gets a canvas back

From chat: the canvas button

A dedicated button next to the chat composer opens the Techniques picker as a side panel.

Inside the composer toolbar, a small grid icon labelled Techniques opens the technique picker without leaving the conversation. The picker shows the curated workflows grouped by category. Filling a technique attaches the resulting canvas to the next chat reply. The button is the primary discoverability surface for workflows the user has not seen yet; first-time users find Techniques by clicking it.

Tip
A skill can also choose to render a canvas as part of its reply without the user opening the picker. The chat handler detects a canvas spec in the skill's output, persists it on the message, and the chat surface renders it inline.

From the technique picker

The technique picker is the entry point to the full catalogue.

Techniques are reusable workflows built by the team. Each one ships with a curated input form, a deterministic spec output, and a preview. The user picks one, fills the form, and gets a canvas back attached to a new chat reply. The full catalogue lives at /techniques on the marketing site; the in-chat picker is a compact version that links straight into the same library.

Note
The marketing site's Techniques page is the canonical browser for prebuilt workflows. The chat picker mirrors the same catalogue.

Canvas lifecycle

From spec to rendered, persisted, and exportable.

  1. 01
    Spec emitted
    A skill or a technique produces a JSON object shaped as { type, data, meta }. The type names a block kind; the data is whatever that block needs.
  2. 02
    Persisted on the message
    The chat handler stores the spec on the parent message row so a refresh, a resume, or a shared session renders the same canvas.
  3. 03
    Rendered inline
    The chat surface reads the spec, looks up the matching block component, and renders it in the reply. A maximise affordance opens it in a full-screen viewer.
  4. 04
    Editable (some blocks)
    A subset of blocks accept inline edits (kanban move, table cell, structured doc paragraph). Edits write back to the same spec.
  5. 05
    Exportable
    Every block supports PNG and PDF exports through the canvas exporter. A subset can be pushed straight into Brain as a memory.
  6. 06
    Shareable
    A canvas can be published as a read-only public view at /s/[slug] with the canvas resource type. The snapshot is taken at share time.

Anatomy of a canvas

One JSON object. Three top-level fields.

canvas-spec.jsonjson
1{
2 "type": "comparison_table",
3 "data": {
4 "rows": [
5 { "name": "Stripe", "price": "2.9% + 30c", "fit": "high" },
6 { "name": "Adyen", "price": "varies", "fit": "med" },
7 { "name": "Checkout","price": "1.0% + 10c", "fit": "high" }
8 ],
9 "columns": ["name", "price", "fit"]
10 },
11 "meta": {
12 "title": "Payment provider comparison",
13 "skill_id": "competitive-analysis",
14 "created_at": "2026-05-11T18:42:00Z"
15 }
16}

type selects the block component. data is whatever shape that component expects. meta is the surrounding metadata used for the title, the exporter, and the activity feed. The renderer's job is to look up the component for the type and render it against the data.

Backend in 60 seconds

How a canvas reaches the screen.

src/components/canvas/CanvasBlock.tsxtsx
1// Block registry maps each type to the component that renders it.
2const REGISTRY: Record<string, React.ComponentType<{ data: unknown }>> = {
3 comparison_table: ComparisonTable,
4 kanban: KanbanBoard,
5 score_card: ScoreCard,
6 code_diff: CodeDiff,
7 structured_doc: StructuredDoc,
8 // ...30+ entries
9}
10
11export function CanvasBlock({ type, data }: { type: string; data: unknown }) {
12 const Component = REGISTRY[type] ?? UnknownBlock
13 return <Component data={data} />
14}
src/app/api/chat/v2/route.tsts
1// Inside the chat handler, after a skill produces its reply
2const canvases = extractCanvasSpecs(reply.content)
3for (const spec of canvases) {
4 await supabase.from("messages").update({
5 canvas: spec,
6 }).eq("id", message.id)
7}

The handler scans the reply for canvas markers (a fenced JSON block tagged canvas), parses each into a spec, and stores it on the message row. The chat surface reads the same column when rendering and hands the spec to CanvasBlock. The registry resolves the type to a component and the component takes it from there.

File map

src/components/canvas
CanvasBlock.tsxregistry + dispatch
ComparisonTable.tsx
KanbanBoard.tsx
ScoreCard.tsx
CodeDiff.tsx
StructuredDoc.tsx
CanvasExportPicker.tsxPNG/PDF/Brain
src/app/api/chat
v2/route.tsextracts canvas specs from skill output
src/app
techniques/page.tsxthe public technique picker
canvas-demo/data.tsblock type definitions and demos

Glossary

Canvas
A rendered surface for any output that is not a chat reply. Driven by a JSON spec with a type and a data payload.
Block
One renderable kind of canvas. The block registry maps a type string to a React component.
Technique
A curated workflow that produces a canvas. Lives in the Techniques catalogue and is invocable from the picker in the chat composer.
Picker
The compact technique browser that opens from the canvas button on the chat composer.
Spec
The JSON object the canvas renderer consumes. type + data + meta.
Inline render
Showing the canvas in the same reply that produced it. The default. A maximise control opens a full-screen viewer.