---
name: canvas
description: Create and drive shared Snowieboard canvases to collaborate with a human — structured asks (forms), status dashboards, review/approve panels. Use when you need human input, review, or approval mid-task, or want to show live progress. Requires the `snowieboard` MCP server (tools create_canvas, await_updates, ...).
---

## Public Snowieboard service

Connect an MCP client named `snowieboard` to `https://snowieboard.com/mcp`.
The `canvasId` returned by `create_canvas` is a private agent capability; never put it in a human-facing message.
Share only the separate `url` returned by `create_canvas`.

# Working a shared canvas

A canvas is a live web page the human opens in their browser. You author it as
declarative A2UI components bound to a JSON data document, guarded by a JSON
Schema you also author. Humans edit widgets and press buttons; you observe via
`await_updates` and react by patching the data and UI. The server validates
every write from both sides against your schema.

## Storage security

Hosted Snowieboard encrypts persisted canvas envelopes and events at rest,
including database backups. This does not change the MCP workflow or canvas
URLs. It is not end-to-end encryption: the Snowieboard server processes canvas
content in plaintext for validation, WebSocket delivery, and sketch rendering,
so do not send secrets that the service operator must never be able to access.

## The loop (always this shape)

1. Design the **data document first**, then the schema, then bind UI to it.
2. `create_canvas` — title + `ui.components` + `dataSchema` + `data` in ONE call.
   **Tell the human the returned `url`.** Remember the returned `rev`.
3. `await_updates({canvasId, sinceRev})` — blocks up to ~55s.
   - `{timedOut: true}` → just call it again with the same `sinceRev`. That is
     the normal poll loop, not a failure. Keep polling while you expect input.
   - `{resyncRequired: true}` → your sinceRev predates the server's event
     history (busy canvas or restart). Call `get_canvas`, act on current state,
     continue from the returned `rev`.
   - It wakes on the FIRST matching event. Humans edit fields as they go, so if
     you only care about a completed form, pass
     `eventTypes: ["user_action"]` and treat button presses as "done"
     (the button's `context` carries the bound field values, resolved).
   - Update `sinceRev` to the returned `rev` before the next call.
4. React: read `events` (`data_patched` = edits, `user_action` = button
   presses) plus the returned `data`. Patch the canvas so the human SEES you
   acted (banner, status text, remove the buttons you consumed). Loop or finish.

## Authoring A2UI (rules that keep you out of trouble)

- Components are FLAT objects: `{"id": "title", "component": "Text", "text": "hi"}`.
  Props sit at the top level next to `id` and `component`. Never nest a
  component inside another — containers reference children **by id**:
  `{"id": "root", "component": "Column", "children": ["title", "form"]}`.
- The surface renders only when a component with **id `"root"`** exists.
- Bind any dynamic prop to the data document with `{"path": "/json/pointer"}`.
  Input components (TextField, CheckBox, ChoicePicker, Slider, DateTimeInput)
  write the human's input back to their bound `value` path.
- Buttons: `"action": {"event": {"name": "approve", "context": {"notes": {"path": "/notes"}}}}`.
  Name actions like intents (`approve`, `submit_notes`). Bound context values
  arrive RESOLVED in the `user_action` event.
- `ChoicePicker.value` binds to a **string array** (even single-select) —
  schema it as `{"type": "array", "items": {"enum": [...]}, "maxItems": 1}`.
- `Text.text` supports simple markdown (bold, lists — no links/images/HTML).
- Removing a component from its parent's `children` hides it; also `{"remove": id}`
  via update_canvas_ui to drop it entirely. When you remove, update the parent too.
- Component catalog (19): Text, Image, Icon, Video, AudioPlayer, Row, Column,
  List, Card, Tabs, Modal, Divider, Button, TextField, CheckBox, ChoicePicker,
  Slider, DateTimeInput, and Snowieboard's **SketchPad** (a drawing board — YOU can
  draw on it, and so can the human). Exact props: `references/a2ui-authoring.md`.
- **Drawing**: bind `SketchPad.value` to a path (e.g. `/sketch`); draw by
  patching `{kind: rect|ellipse|line|polyline|stroke|text}` shapes into
  `/sketch/shapes` (coordinate space 800×500). Emit CLEAN primitives — the
  renderer crayon-ifies them. Human strokes arrive as `stroke` shapes with ids
  starting `h-`. Give important shapes a `label` ("the roof") so you can talk
  about them. Full guide: `references/a2ui-authoring.md` § SketchPad.
- **LOOK at the board**: `render_sketch({canvasId})` returns a PNG of the
  SketchPad scene — use it before interpreting human strokes (an image is far
  easier to read than stroke point arrays). The text part summarizes shape
  count, human strokes, and labels.
- Do NOT call `submit_canvas_input` — it exists for the embedded canvas app to
  submit the HUMAN's edits with correct attribution.
- Full worked payloads for the three idioms (structured ask, status dashboard,
  review/approve): `references/recipes.md`.

## When a tool returns an error

Errors are structured JSON: `{code, message, details: [{path, problem, allowed?}], hint, currentRev?}`.

- `SCHEMA_VIOLATION` / `INVALID_COMPONENT` → fix exactly the listed `details[].path`s.
  Do NOT retry unchanged.
- `REV_CONFLICT` → call `get_canvas`, re-read `data`/`rev`, rebase your change,
  retry once with the new `baseRev`.
- `SCHEMA_COMPILE_ERROR` → your JSON Schema itself is malformed.
- Changing the schema while data no longer fits? Pass a migrating `dataPatch`
  in the SAME `update_canvas_schema` call (it is atomic).

## Etiquette

- One canvas per task. Update it in place; do not spawn near-duplicates.
- After acting on human input, ALWAYS reflect it back on the canvas
  (confirmation banner, updated status) — silent consumption feels broken.
- Remove or disable buttons you have already consumed.
- Use `baseRev` on mutations when you reasoned over a snapshot for a while.
- `delete_canvas` when the task is fully done — after telling the human.
