---
name: canvas
description: Create and drive shared Snowiespace 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 `snowiespace` MCP server (tools create_canvas, await_updates, ...).
---

## End-to-end encrypted Snowiespace service

Use the one-line installation to connect Snowiespace to your MCP host:

```sh
curl -fsSL https://snowiespace.com/bootstrap | sh
```

```powershell
irm https://snowiespace.com/bootstrap.ps1 | iex
```

Canvas creation is public. The adapter preserves the normal Snowiespace tools and keeps canvas encryption keys out of the relay.
Share only the separate human 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. Trusted endpoint clients
validate every write from both sides against your schema before encryption.

## End-to-end encryption

Snowiespace's local MCP adapter encrypts canvas envelopes and events before they
reach the hosted relay. The human browser keeps the same content key in the
fragment of the returned share URL and encrypts its edits before upload. The
relay, SQLite database, backups, and WebSocket transport contain ciphertext;
the service has no content key. The MCP host/AI provider and human browser are
trusted endpoints and necessarily see plaintext. Always share the COMPLETE
returned URL, including its `#sb1...` fragment.

## Installation

Canvas creation on `snowiespace.com` is public and requires no account or
invitation. Use the one-line installation to connect Snowiespace to your MCP
host:

```sh
curl -fsSL https://snowiespace.com/bootstrap | sh
```

```powershell
irm https://snowiespace.com/bootstrap.ps1 | iex
```

It writes a portable MCP configuration to `~/.snowiespace/mcp.json` and
registers the server in `~/.codex/config.toml`. Restart the MCP host after
installation.

## 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.
   Add catalog-defined `surfaceProperties.theme` in the same call when the
   canvas needs a deliberate visual identity.
   If the human gave you a complete Snowiespace agent address such as
   `https://snowiespace.com/@maya/product#sba1...`, pass it as `board` in this
   same call. A successful result includes `deliveredTo`; the human will see it
   in that board's workspace. Otherwise, **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
     encrypted 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.
- A2UI v1.0 RPC: set `wantResponse: true` (and optionally `responsePath`) on
  an event, then answer its `actionId` with `respond_to_action`. Use
  `call_client_function` only for the allowlisted read-only client functions
  and await `function_response` or `rpc_error`.
- A2UI v1.0 themes live in `surfaceProperties.theme`, not component props.
  Supported tokens are `colorScheme`, semantic colors (`primaryColor`,
  `accentColor`, `positiveColor`, `negativeColor`, `backgroundColor`,
  `surfaceColor`, `textColor`, `mutedTextColor`, `borderColor`), `fontFamily`,
  `density`, and `radius`. Colors must be six-digit hex values.
- `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 Snowiespace'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.
- `INVITE_REQUIRED` / `CREATOR_ACCESS_REVOKED` → this relay has enabled optional
  restricted creation; use the public Snowiespace relay or contact its operator.
- `CREATOR_QUOTA_EXCEEDED` → reuse or delete an existing canvas; do not retry by
  creating near-duplicates.
- 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.
