Skip to main content
A Canvas block is a chart or custom visual on a Page: a sandboxed React component that reads Model values through named datasets. Four tools work with them:
  • inspect_canvas_library finds the components and templates a block can use.
  • edit_canvas_blocks creates a block or changes a saved one.
  • validate_code_blocks renders saved blocks in a browser and reports what it saw.
  • inspect_canvas_blocks reads saved blocks.
Read the build-visualizations skill through inspect_skills before you add a Canvas block; it owns design guidance. Page order and block removal belong to edit_pages, and editable model tables to edit_table_blocks.

Find components and templates

Call inspect_canvas_library with a list block whose query is a few search words:
  • An entry matches when any one word appears in its title, description, tags, aliases, or decision outcomes.
  • kind narrows the list to component or template. Without query, the list holds every entry of that kind.
Then read the entries you plan to use. One read takes up to 16 targets and resolves templates and components together, so a template and the components it needs come back in one call:
A read returns each entry’s instructions, its props skeleton, and its runtime requirements. A name that matches nothing, or more than one entry, comes back as an unresolved entry. Library entries are building guidance; they are not saved blocks and prove nothing about how a block renders.

Create a Canvas block

edit_canvas_blocks create requires page, title, and source:
  • page is the Page that receives the block, by name or as inspect_pages prints it. after_block places the block under another one; without it the block goes last.
  • title is the block’s name. Later updates and page edits name the block by it.
  • The receipt names the new block two ways. target is the block’s name-form selector, which the page and Canvas tools accept wherever they take a block as target. ref is the block’s pasteable reference, such as Pipeline by stage#a74, which they accept wherever a result’s block reference is accepted, such as reorder_blocks block_order. The reference keeps naming the block in later edit_pages calls even after a rename.
  • The receipt also carries dataset_evidence, one series and one row per dataset as the block receives them, and source_notes. A dataset still calculating when the result was due is listed with status: "unavailable" and error: this dataset’s calculation had not finished when this result was due, so it was stopped. The block is saved either way, and the other datasets keep their evidence.

What the source must be

source is one complete component, Block({ data, timeSettings, theme, state, action, emit }), run in a sandbox:
  • React is injected. Reach hooks through it, such as React.useState(...); a bare useState(...) is refused, and window.React is unavailable.
  • Imports, network access, and direct AgCharts are forbidden. Custom font families are refused.
  • print() prints only that block’s frame.
  • The injected components, their props, and the rest of the runtime contract are in the source field’s description in the tool schema, and each component’s props in inspect_canvas_library read.

Datasets

A dataset is a live, read-only table read that the block receives as data.<name>. A block holds up to 8. The block receives the read inverted: each row of the layout becomes a series, and each column becomes one entry of data.<name>.rows, one plotted point. A layout with columns: [] and window.granularity: "NO_GRANULARITY" declares timeless values: one row holding each Variable’s value. On update, each datasets entry adds a dataset or replaces the saved one with the same name, and the others stay. Leaving datasets out keeps them all; [] clears them all; remove_datasets removes the ones it names.

Values a dataset row carries

Each row carries each Variable’s own value under its series key: row.date is a display label, and periodStart, when present, is the ISO start of the period. A dataset that failed has empty rows and series and an error string. A comparison dataset also carries scenario.label, scenario.isCurrent, and scenario.lastClose, and timeSettings.lastClose is the current Scenario’s close date or null.

Colors and the theme

  • Colors and type from theme.tokens, theme.design, or --cb-* CSS variables follow the viewer’s light or dark mode.
  • A literal or named color, including one in a palette, keeps its value in both modes. The write saves it, and the receipt’s source_notes names the first one: “<color> on line <n> is the first fixed color in this source: it keeps this value in light and dark mode. Colors from theme.tokens, theme.design, or —cb-* CSS vars follow the viewer’s mode.”
  • A workspace brand can set the heading typeface, the accent color, and the corner shape. A block follows the brand through theme.tokens.fontHeading or --cb-font-heading, theme.tokens.accent or --cb-accent, and theme.tokens.radius or --cb-radius, in every workspace it is copied to.
  • source_notes also flags a component declared inside Block. React remounts such a component every time Block renders, so a focused input loses focus and its local state resets. Declare components at the top level of the source and pass values through props.

Change a saved block

edit_canvas_blocks update names the block with target, by name, ID, or as inspect_pages prints it. Fields you leave out keep their saved values, and the block keeps its identity and position.
  • source replaces the whole component.
  • source_patches changes part of it: a list of up to 50 exact old to new text replacements, applied in order. Each old must occur exactly once in the source as the earlier patches left it. The block saves only when every patch applies.
  • title renames the block; an empty string clears the title.
  • page, when given, confirms the block is still on that Page.

Check that a block renders

A saved block compiles, but only a browser shows whether it renders.
  • A Canvas write that no browser rendered returns a render_unverified warning: The block is saved and compiles; whether it renders is proven only when validate_code_blocks opens it in a browser.
  • validate: true beside the write returns the browser’s result for the saved revision under validation instead. A render check that fails or is unavailable leaves the edit saved. The dataset readback and the render check share one answer deadline, about 10 seconds before the call’s 119-second limit, so the receipt arrives instead of a timeout. If validation has not finished when the result is due, validation.note reads The Canvas edit saved. Browser validation had not finished when this result was due, so it was stopped and the render was not observed. If the result is due before validation begins, it reads The Canvas edit saved. This result was due before browser validation began, so validation did not run and the render was not observed. In both cases validation.enabled is false, as it is whenever no browser render was observed, including when the check is switched off or unreachable; read validation.note to tell the causes apart. The receipt carries the render_unverified warning, and the saved block stays saved.
  • validate_code_blocks checks saved blocks on their own:

What validate_code_blocks reports

  • It renders up to 8 blocks per call in Chromium, with live calculated datasets, once per theme (light and dark). Each render uses the block’s saved state, Scenario, number formats, and Last close, in a frame 1200 px wide. Each result names the revision and source hash it rendered.
  • The result’s observed sentence states what the check saw, such as: “Each block was rendered once per theme, with its saved state, in a 1200 px wide frame. Errors, warnings, plotted chart values, guide anchors, and any visual review come from those renders. Clicks, typing, links, and guide launches did not run, so what they do is unobserved, and the layout at other widths is unseen.”
  • A block that rendered cleanly carries plotted: one entry per chart the render drew, with chart naming the chart by its title, when it has one, and its source position, such as Chart "Pipeline" at source L12, and series holding one entry per numeric value field of every visible series, with name, count, and the first, last, low, and high plotted points as {x, y}. The values are the ones the chart was given. The chart draws stacked and normalized series, waterfall running totals, and histogram bin counts from them, so the drawn shape of such a series differs from the list.
  • Errors are failures the render reported: runtime errors, widget errors, a stalled render, console errors, and failed element loads. Warnings may be intended: console warnings, blank or error-code cells, empty or zero-only data, missing series keys, hidden series, and content past the frame’s edge.
  • A clean render does not check that the values are financially correct.
  • guide_targets lists the copyable guide anchors visible in at least one theme, and guideAnchors keeps each theme’s visibility and ambiguity. guideAnchors lists at most the first 64 distinct anchor IDs per theme, in document order. When the render had more, guide_anchors_cut says how many, and guide_targets draws only on the listed ones.
  • visual_review returns one block in one theme as an image at the frame’s width.

When a render does not happen

Errors a person’s browser reported

When a person’s browser hits a runtime failure while rendering a saved block, it reports the error for that exact Scenario, block, and source revision. inspect_pages read then returns it as the block’s render_error, and a later successful render clears it. An MCP call has no browser of its own, so a block nobody has rendered has no such report. A missing render_error does not mean the block renders.

Read saved Canvas blocks

inspect_canvas_blocks takes target, a block name, ID, or reference, or a list of related blocks, and optionally page to constrain them. It returns each block’s exact saved source, dataset layouts and Scenario bindings, display state, position, revision, and references. All blocks are read at one snapshot, the latest unless as_of_point names an earlier one. It does not calculate datasets, and saved code is no proof of a successful render. A selection larger than one result is cut short:
  • Each run of 1,000 or more encoded characters in a source, such as base64 or hex, reads as [[N encoded characters not shown]].
  • A source still too large comes back in part. source_part gives the byte range it covers, and the next call passes next.source_offset as source_offset.
  • not_returned lists the selected blocks left out, with their sizes, and next holds the arguments that read the rest.
On a user click, emit({type: 'navigate', url}) opens a page or block link that a tool result returned, or another app address in the current workspace, through normal browser history. It keeps Scenario query parameters and #block-anchor-<block-id>. External addresses and public share views are not supported. The source field’s description covers the block’s other actions, such as saving presentation state and starting an Ari action.

Name blocks in page edits

edit_pages arranges and removes blocks, including Canvas blocks.
  • A block action names its Page with page.
  • add_text may omit page. It then uses the Page of an after_block reference it was given, else the Page in view, else the Scenario’s only Page. When none of these settles it, the call is refused: with several Pages, with no target page and up to 20 of them as candidates, and its hint gives the Scenario’s page count and how many are listed; with no Pages, with this scenario holds no pages.
  • update_text and delete_block may omit page; they then use the Page in view, and a returned block target finds its own Page.
  • reorder_blocks names the Page it arranges with page, or with target. block_order is the complete top-to-bottom order, with each visible block once, by name or by the block references other results print. place moves one block instead: block, after_block, and to_page to move it to another Page.
  • A delete_block aimed at a block that another saved change already deleted answers not_found and changes nothing. Its message names that change and, when the block went with its Page, the Page: Block <id> is already deleted in scenario "Main": saved change <n> deleted it together with its page "<name>". This call changed nothing. The result carries block_id, layer_id, and deleted_by_change_log_id.
  • delete_block refuses to delete objects that lose their last table occurrence while formulas, other blocks, settings, or hand-added items depend on them. force: true deletes them anyway, and those references break. Objects still shown on other tables are kept.
For paging through inspect_pages, see Pagination and truncation.