set_canvas
Replace a canvas dashboard's full document. Validates the new canvas and returns field-level errors if invalid, so you can fix and resubmit.
Instructions
Replace a canvas dashboard's document. Returns a compact {ok,id,rev,elements} ack (not the full document), or an error with field-level "details" (HTTP 422) if the document is invalid, so you can correct it and retry. Pass base_rev (the rev from get_canvas) to be warned with HTTP 409 if the page changed under you. For a one-field change prefer update_element / patch_canvas. After setting, call render_preview() (or render_report()) to check the result.
A canvas document is JSON: { "w": int, "h": int, # artboard size in px (match the target panel) "theme": str, "style": str, # appearance ids from list_widgets().appearance "font": str, "bg": str, # optional font id and background colour override "els": [ , ... ] # painted in list order: first = back, last = front } Elements may sit partly off the panel (it clips at the edge). Each element has a unique "id" and a box "x","y","w","h" (px, top-left origin; x/y may be negative), plus optional "opacity" (0-100) and "rotate" (degrees). By "kind":
widget: {"kind":"widget","widget":"","fragment":"full","options":{...}} from list_widgets(); "fragment" from that widget's fragments (or "full"); "options" per get_widget_options().
text: {"kind":"text","text":"...","color":"<css or var(--accent-1)>","size":<px, 0=auto>,"align":"left|center|right"}
rect: {"kind":"rect","color":"...","fill":true,"stroke":,"radius":}
ellipse: {"kind":"ellipse","color":"...","fill":true,"stroke":}
line: {"kind":"line","color":"...","stroke":}
icon: {"kind":"icon","icon":"","color":"...","weight":"thin|light|regular|bold|fill|duotone"}
data: {"kind":"data","source":"","options":{...},"field":"", "display":"text|number|line|bar|sparkline","format":"","unit":"","precision":0, "label":"","color":"...","size":<px, 0=auto>,"align":"..."} Binds a widget's data field to a scalable value or graph. "source" is a widget key from list_widgets(); configure it via "options" (get_widget_options). Use probe_widget_data(source, options) to see the real data shape before choosing "field". "format" (text/number only) is a date pattern ("HH:mm","MMM d","ddd HH:mm"), "relative", or a number pattern ("0.0").
html: {"kind":"html","html":"…","css":"div{…}"} A mini widget from static HTML + CSS in a sandboxed iframe (no scripts, no network).
svg: {"kind":"svg","html":"<svg …>…","css":""} -- raw SVG, scaled to fill the box.
LIVE BINDINGS ("bind" on ANY element -- makes a SHAPE reflect data): Data elements auto-update, but shapes (rect/ellipse/icon/line/text) are static geometry. Add "bind": [ , ... ] to drive a shape's props from data each render (in lockstep with data elements, no polling). A binding is: {"source":"","options":{...},"field":"","transform":"","params":{...}} transforms: position -- scalar to a coordinate: {"axis":"x"|"y","in":[lo,hi],"out":[p0,p1],"center":} lo/hi may be numbers OR other field paths (e.g. "sun.riseMin"). length -- scalar to a size: {"dim":"w"|"h","in":[lo,hi],"out":[minPx,maxPx],"anchorMax":?} pick -- integer field indexes arrays: {"set":{"x":[...],"color":[...],...},"center":?} color -- scalar to a colour by ascending thresholds: {"stops":[[max,"#hex"],...],"else":"#hex"} gradient -- scalar interpolated smoothly along colour stops: {"stops":[[value,"#hex"],...]} (quantised to the panel palette on e-ink; a value-driven gradient, not animation) icon -- code/string to a Phosphor glyph: {"table":{"":"ph-name"},"default":"ph-name"} Several bindings combine (e.g. bind x by position AND colour by threshold). A binding that can't resolve its value is skipped, so the element keeps its authored props.
FIELD PATHS ("field" on data elements): dotted -- "current.temp" array index -- "hourly.0.temp" (or "hourly[0].temp") pluck (for charts) -- "series.*.total" (or "series[].total") maps .total over every item of the array "series", yielding an array of numbers. Charts (line/bar/sparkline) need a field that resolves to an array of numbers -- use pluck to get one from an array-of-objects. probe_widget_data() returns a "fields" list of the bindable paths (with sample values), so you don't have to reverse-engineer the shape.
EDITING WITHOUT RESENDING EVERYTHING: update_element / delete_element / patch_canvas change one thing without re-sending the whole document (cheaper, fewer errors on a big canvas). add_element appends one.
AVOID CLOBBERING A CONCURRENT EDIT: get_canvas() returns a "rev". Pass it as base_rev to a write; if the page changed since (someone edited it in the UI, or another agent), the write returns HTTP 409 with the current rev, so you re-read instead of overwriting.
LAY OUT BY INTENT, NOT PIXELS: arrange(box, layout, count) returns aligned child boxes (grid/row/column) to spread across your elements -- no hand-computed x/y. measure_text() tells you how wide text renders so a box fits its content (prevents clipping). render_report() reads back what actually rendered (per-element value, overflow flags, live-vs-sample, colours) so you verify without eyeballing the PNG.
MATCH THE HARDWARE: list_devices() reports each panel's colour capability (color_mode, the renderable palette as hex, and a "mono" flag). Design within that palette so colours don't quantise away on the panel.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| canvas | Yes | ||
| page_id | Yes | ||
| base_rev | No |