bytespost-canvas
v0.4.5
Published
Local CLI + MCP server for the bytespost canvas — drive a running canvas tab from the terminal or any AI agent.
Maintainers
Readme
bytespost-canvas
Local CLI + MCP server for the bytespost canvas — drive a running canvas tab from your terminal or any AI agent (Claude Code, Cursor, etc.).
What it is
bytespost-canvas runs a tiny localhost daemon (HTTP + WebSocket hub) that bridges your terminal / AI agent and a canvas tab open in your browser. The first daemon-dependent canvas command starts it in the background automatically. Once the canvas tab is connected:
- The CLI sends commands (draw a rectangle, capture a screenshot, query the document, set selection, …).
- The daemon relays them over WebSocket to the canvas page.
- The page executes them in the live editor and streams back results (node IDs, PNG bytes, JSON document state).
It also exposes an MCP (Model Context Protocol) stdio server so AI agents that speak MCP can talk to your canvas with no glue code.
Install
# Global install — gives you the `canvas` binary
npm install -g bytespost-canvas
# Or run without installing
npx bytespost-canvas --helpThree binaries ship, all aliasing the same CLI: canvas, canvas-bridge, canvasd.
Quick start
# 1. Run a command. If needed, Canvas starts the daemon in the background.
canvas status --json
# 2. Open app.bytespost.com. The dashboard connects as a control-only tab, so
# project create/open works before an editor exists. A concrete Design,
# Slides, or 3D editor upgrades the same tab with canvas operations.
# 3. From any other terminal, run commands
canvas ls # list connected tabs (with node counts)
canvas watch # live dashboard of open canvases (r=refresh, q=quit)
canvas screenshot -o canvas.png # capture the active tab
canvas draw rect --x 100 --y 100 --w 200 --h 120 --fill "#7c3aed"
canvas document nodes --page active # list only the active page's nodes
canvas node move <id> --position front # bring a sibling to the top/front
canvas select set <id> # set selection
# Create and install Canvas Plugin v2 modules
canvas plugin create ./my-plugin --id @acme/my-plugin
canvas plugin install https://plugins.example.com/my-plugin.mjs --tab tab_abc123 --trust
# Discover and call tools registered by the current editor and its plugins
canvas tool list --json
canvas tool show building_create_wall --json
canvas tool call building_create_wall '{"startX":0,"startY":0,"endX":480,"endY":0}' --json
# Core capabilities that used to exist only inside the editor AI are now in
# the same catalog and therefore work through CLI, MCP, and WebMCP.
canvas tool call canvas_generate_grid '{"x":80,"y":80,"rows":8,"cols":12,"cell":"dot","gap":24}' --json
canvas tool call canvas_generate_isometric_stack '{"x":640,"y":180,"layers":[{"label":"UI","kind":"outline"},{"label":"Data","kind":"gradient","fill":"#7C3AED","fill2":"#38BDF8"}]}' --json
canvas tool call canvas_render_mermaid '{"text":"flowchart LR\nA[Observe]-->B[Act]-->C[Verify]","diagramId":"agent-loop"}' --json
canvas tool call canvas_create_connector '{"fromId":"node-a","toId":"node-b","routingType":"orthogonal","endArrow":"arrow"}' --json
canvas tool call canvas_apply_animation_preset '{"nodeId":"hero","preset":"slide-in-left","duration":0.7}' --json
# High-level composition: one semantic circle, not dozens of line segments
canvas draw circle --cx 320 --cy 240 --radius 90 --stroke "#111827"
canvas draw path --d "M0 0 C80 0 80 120 160 120" --stroke "#7c3aed"
canvas draw model --primitive torus --x 0 --y 1 --z 0 --size 2,2,.5
canvas draw model --file keyboard.glb --x 0 --y 0 --scale 1,1,1
cat 133-nodes.json | canvas draw - --json
canvas export glb -o scene.glb
canvas export svg -o active-page.svg
# Projects, pages, and exact micro-adjustments
canvas project create --name "Bike study" --type general-3d
canvas page create --name "Rear drivetrain"
canvas node transform <id> --by 12,0,0 --rotate 0,0,15
canvas camera preset hero
canvas lighting set studio
# Computer-use-style semantic loop
canvas observe --page active --json
canvas act '[{"type":"node.move","target":{"id":"heart"},"move":{"position":"front"}}]' --observe --json
canvas verify '[{"type":"order","front":{"id":"heart"},"back":{"id":"web"}}]' --json
canvas verify '[{"type":"layout","container":{"id":"artboard"},"allowOverlap":false,"minFontSize":12}]' --json
# Ask for the best AI prompt demo instead of guessing primitive commands
canvas helper "AI 系统架构图" --json
# Browse prompt demos, then give one complete prompt to an AI agent.
# These commands never inspect or change the canvas.
canvas demo list --json
canvas demo prompt architecture-ai-pipeline
canvas demo prompt architecture-ai-pipeline --tab tab_abc123 --target cli --json
canvas demo prompt architecture-ai-pipeline --target all --json
# Browse the extensible catalog (`morse` is a typo-tolerant alias)
canvas demo more --json
canvas demo morse --jsoncanvas start and canvas serve remain available when you intentionally want
the daemon in the foreground for logs or debugging. Set
CANVAS_BRIDGE_AUTO_SERVE=0 to disable automatic background startup.
Every daemon-backed CLI request and MCP tool checks the local daemon's package and protocol versions. An older lifecycle-capable daemon stops admitting new work, waits for outstanding operations, preserves unexpired operation receipts, and restarts from the invoking CLI installation. Commands resume only after the original tab IDs reconnect. A newer daemon is never downgraded by a stale CLI; update that CLI installation instead. Disabling auto-start still enforces version compatibility. Help, version, generation and offline headless commands do not start a daemon.
Pre-0.4.1 daemons cannot drain operations. Idle legacy daemons can be migrated
after verifying the owned local process; connected legacy tabs require a
one-time CANVAS_BRIDGE_LEGACY_RESTART=1 migration after preserving pending work.
No document reload is required. DAEMON_BUSY and DAEMON_RECONNECT_PENDING mean
the command was not dispatched; keep the editor open and resolve the reported
condition before retrying. A restart is not a durable backup of documents.
AI prompt demos
canvas demo is a read-only teaching surface, not a template executor. A demo
contains a user brief, agent boundaries, success criteria, recovery rules, and
a complete capabilities → observe → revision-guarded act → verify example.
The AI must inspect the actual document, adapt the recipe, execute the canvas
commands itself, and verify the resulting revision.
# Human-readable prompt with a complete, runnable shell example
canvas demo prompt architecture-ai-pipeline --tab tab_abc123
# Machine-readable prompt packet for an AI client
canvas demo prompt architecture-ai-pipeline --target all --json
# `show` is a read-only compatibility alias
canvas demo show architecture-ai-pipeline --jsonThe former canvas demo run command has been removed because it bypassed the
AI's observation and planning. Legacy invocations fail with
DEMO_EXECUTION_REMOVED, mutatedCanvas:false, and a demo prompt
replacement; they never contact the daemon, even with --dry-run.
Use canvas node move <id> --parent <frameId> --index <n> to reparent and
place a layer, or --root to return it to its current page root. Structural
parent/order changes are intentionally separate from node update, whose
shallow patch cannot replace id, type, or children.
node update accepts type-aware friendly aliases while remaining a
non-structural patch. For example, TEXT accepts fontFamily, fontStyle,
lineHeight, alignment and decoration fields; PATH accepts pathData; 2D
vectors accept fill, strokeColor, and strokeWidth; MODEL accepts
materialColor (or fill as a compatibility alias), metalness, roughness,
modelTranslation, modelRotation, and modelScale. Run
canvas node update --help for complete copyable examples.
Observe / act / verify
The primary agent interface is a canvas-specific state/action loop:
observereturns a content-derived document revision plus semantic nodes, stable IDs, parent/index/paint paths, effective visibility, world bounds, selection, and optional renderer PNG. Use--page activeto remove other-page noise and pass--since <revision>for a page-filtered diff.actaccepts a typed action array and an optional--expected-revision. All document actions are calculated against a private snapshot and committed once, so a bad selector, illegal parent, cycle, cross-page move, or stale revision leaves the live document unchanged.verifychecks postconditions such as existence, parent, containment, visibility, paint order, text/container overflow, overlap policy, and minimum font size against one document revision. Viewport and selection changes do not invalidate that revision.
Actions include create/update/transform/move/delete/duplicate/group/ungroup,
page lifecycle, selection, and viewport operations. Selectors can use a stable ID or a semantic query by
type, exact/partial name, parent, page, selection, and effective visibility.
Queries expect one node by default and reject ambiguity; opt into expect:
"many" explicitly.
canvas node update and the corresponding node.update action remain
non-structural property patches. Both accept the same documented, type-aware
allowlist and normalize it into exact IR fields. The
aggregate style, textStyle, and material objects are explicit advanced
escape hatches; arbitrary raw IR keys are not accepted. Unknown keys and
structural id, type, children, parentId, and pageId fields fail validation
without changing the document. Use node.move, node.group, and
node.ungroup for hierarchy and z-order.
The optional visual observation and screenshot never include DOM/Web overlays.
For 2D they use renderer-page export; for 3D canvas screenshot captures the
currently presented camera viewport.
canvas <command> --help prints command-specific flags. canvas --json returns stable machine-readable JSON for any command — pipe straight into jq.
Every mutation supports --dry-run; it returns the exact HTTP operation without
contacting the daemon or changing the canvas. Commands default to a 25 second
timeout and one retry with the same operation ID, so a late browser reply can
be retrieved without executing the mutation twice. Use --timeout-ms,
--retries, and --idempotency-key to override those bounds.
MCP mode (for Claude Code / Cursor / any MCP client)
canvas mcp # speaks MCP over stdioRegister it in your AI agent's MCP config. Sample for Claude Code:
// ~/.claude/config.json
{
"mcpServers": {
"canvas": {
"command": "canvas",
"args": ["mcp"]
}
}
}The agent sees the semantic control plane and typed helpers (canvas_observe,
canvas_act, canvas_verify, canvas_draw, canvas_document_get,
canvas_viewport_screenshot, canvas_export_glb, …) and can compose them autonomously.
It also sees canvas_tool_list / canvas_tool_call. Plugin tools declared by
the browser's protocol-neutral registry are dynamically projected into MCP by
their own names and original JSON Schemas; if more than one tab exposes the
same name, use canvas_tool_call with an explicit tab.
Commands
| Command | What it does |
|---|---|
| serve / start | Run the daemon (foreground). |
| mcp | Run an MCP stdio server. |
| status / health | Daemon + connected-tab health. |
| ls | List open canvas tabs (with node counts). |
| watch | Live TUI dashboard of open canvases; r refresh, q quit. |
| observe | Read revisioned semantic state and optional diff/renderer image. |
| act | Execute an atomic typed action batch with an optional revision guard. |
| verify | Check semantic postconditions without mutation. |
| capabilities | List canonical operations, actions, selectors, and limits. |
| tool list / show / call | Discover or invoke live core/plugin tools registered by the editor. |
| plugin create | Scaffold a self-contained Canvas Plugin v2 ESM project. |
| plugin install | Register and enable a reviewed ESM plugin URL in a selected Canvas tab; requires --trust. |
| helper [intent] | Recommend a category and stable read-only AI prompt demo. |
| demo list / prompt / show | Browse demos or generate complete CLI/MCP prompts; never operate the canvas directly. |
| demo more / demo morse / morse | Browse the extensible demo catalog beyond the featured eight. |
| tab info | Show tab metadata. |
| document get | Dump the full document IR. |
| document nodes | List node IDs, optionally filtered by --page active|<id>. |
| project create / open | Create any supported canvas type or open an existing project. |
| page list / create / use / rename / delete | Manage pages inside the active project. |
| draw | Atomically create nodes from inline JSON, a file, or stdin (-). |
| draw rect / frame / text / line / circle / ellipse / path / polygon / star / model | Semantic editable shapes, 3D primitives, or GLB/GLTF/FBX models. |
| export glb / svg | Export the live 3D scene or serialize editable 2D nodes through existing exporters. |
| node get / update / transform / move / delete | Read, micro-adjust, reparent, or remove a node. |
| fiber inspect | Name the real bundles, curves and control points inside one fiberBundle MODEL, paged. |
| select set / clear | Drive the selection. |
| viewport screenshot / screenshot | PNG bytes of the active tab. |
| viewport fit / zoom | Pan/zoom the camera. |
| scene / camera / lighting | Switch 2D/3D view and control 3D presentation. |
| science render | Compile scientific DSL into editable semantic nodes. |
| html write | Turn one HTML/CSS composition into editable canvas layers. |
| command | Advanced escape hatch for versioned bridge commands. |
| building | Building-3D plugin shortcuts (wall / slab / window / door / …). |
Editing inside a fiber-bundle MODEL
A fiber bundle is one node. Its curves and control points are addressable inside it — they are not child layers, and a network of 78,000 segments will never become 78,000 nodes. So the ids an edit uses have to be asked for:
# 1. what is in there, and which structure it is
canvas fiber inspect "$model" --tab "$tab" --json
# -> { structureKey: "1:3", counts: {...}, bundles: [{ bundleId: "cord:L:0", curveCount: 12, ... }] }
# 2. the curves of one bundle: point counts, parents and dependents
canvas fiber inspect "$model" --tab "$tab" --level curve --bundle cord:L:0 --limit 20 --json
# 3. the control points of one curve: real ids, rest positions, authored offsets
canvas fiber inspect "$model" --tab "$tab" --level point --curve 'cord:L:0#2' --offset 10 --limit 5 --json
# -> points: [{ pointId: "cord:L:0#2.12", position: [...], offset: null, animatedPosition: [...] }]
# 4. move one point, BOUND to the structure those ids came from
canvas node update "$model" --tab "$tab" --json --patch \
'{"fiberStructureKey":"1:3","fiberPointOffsets":{"cord:L:0#2.12":[20,0,0]}}'Three things worth knowing before you write an offset:
- Space.
position,offsetandboundsare all in the bundle's own object space, the spacefiberPointOffsetsare written in. The MODEL's own transform is on top of that; a world-space number would land somewhere else. - Rest vs. what you see.
positionis the edited REST pose, which is what an offset is applied to. WhenmotionAmount > 0the shader displaces it every frame, andanimatedPositionis where that same point is at the current clock. Neither is "wrong"; they answer different questions. fiberStructureKeyis the point of the whole flow. Ids are derived from the seed, socord:L:0#2.12names one strand of this network and a different strand of the next one. Pass the key you read back with the edit: if the seed changed in between, the whole patch is refused rather than applied to whatever now carries that number. Omitting it keeps the older unguarded behaviour, which is only safe for a caller holding the live node (the editor's own inspector).- A seed change and an internal-id write cannot travel together.
{"fiberSeed": 7, "fiberPointOffsets": {...}}is refused: the ids can only have been read from the network the seed is about to replace, and nobody can have read the new one yet. Change the seed, then read the ids again. A seed change may still carryfiberClearOverridesornullvalues — discarding what belonged to the old structure names no new id. statusis not decoration. A query answersok, orstalewhen the built bundle and the document name different structures (a reseed the renderer has not caught up with — read again; the CLI exits 1), orunavailablewhen no bundle is built for that node. Ids are never returned under a structure key they do not belong to.
fiberCurveColors, fiberPointOffsets and fiberClearOverrides go through the same
canvas node update mapper and the same editor history as every other patch, so Undo/Redo and
reopening behave the way they do everywhere else.
canvas fiber inspect is carried by the generic /v1/command tool-call envelope, invoking the
canonical canvas_fiber_inspect tool — the same one the page publishes to MCP, WebMCP and the
in-app assistant. It therefore needs no daemon restart: a running daemon relays it like any other
command. (The page does have to be new enough to publish the tool; if it is not, the call comes back
as an unknown tool rather than as an empty answer.) The daemon also exposes GET /v1/fiber/:nodeId
for callers that would rather use a route, but nothing in the CLI depends on it.
Verification and headless mode
verify exits with 0 for passed checks, 1 for failed checks, and 2
for invalid or incomplete evidence. HTTP success alone never passes verification.
canvas verify layout --tab <tab> --no-overlap --output audit.json
canvas verify layout --tab <tab> --no-overlap --screenshot --artifacts layout-001
canvas verify screenshot --tab <tab> --baseline approved.png --artifacts visual-001
canvas verify screenshot --image run/viewport.png --baseline approved.png --artifacts visual-002
canvas browser install --background --json
canvas browser status --json
canvas verify layout --headless --document document.json --no-overlap \
--app-dir apps/webapp/dist --artifacts artifacts/run-001
canvas headless run workflow.json --app-dir apps/webapp/dist --output artifacts/run-002Headless mode downloads an open-source Chromium Headless Shell separately
(about 93 MiB on the tested macOS ARM host). Use Node 24; installation requires
Node >=22.12. A compatible Canvas Web build containing headless.html is
required and is not bundled in the CLI package. No pre-opened browser or daemon
is needed with --app-dir.
Runs preserve the snapshot, revisioned checks, renderer diagnostics, resource
hashes, a bounded credential-redacted console.jsonl, and screenshot in a new
artifact directory. Offline actions may include deletion because they operate
only on the isolated snapshot; live destructive commands still require
--confirm. They do not use user cookies
or write cloud documents. Screenshot capture is not a visual-correctness verdict.
--require-rendered reports incomplete until render-consistency evidence exists.
Screenshot verification saves actual/baseline/difference PNGs and changed-pixel bounds. Without a baseline it saves a capture for review and exits 2; identical pixels do not prove the baseline itself is correct. Layout checks report recursive overlaps, text/container overflow and opaque rectangle occlusion with node IDs and covered model-box ratios. Transparent/clipped/masked content is not certified as opaque coverage. Live screenshot attachment rejects document/page changes and records that renderer exports exclude DOM overlays.
Layout reports default to a summary: complete issue counts by code/severity and
up to 20 examples per list. Use canvas verify layout --details --tab <tab>
(or the typed check details: "full") for all issues. Exit status uses the
complete audit, including errors outside the example window. This bounds the
reply; the audit still computes the complete report internally.
PATH bounds use the renderer's line/Bezier/arc geometry transformed into document
world coordinates, shared with node observation and fit bounds. Unknown or
malformed paths report unsupported coverage rather than a zero box at the origin.
Intersecting path envelopes alone are not proof of painted overlap. Conservative
stroke-envelope ambiguity also reports unsupported coverage. allowOverlap: true
skips the general overlap checks; connector attachment/crossing checks remain
separate. It does not certify overlapping artwork as visually correct.
Use headless capture <checks.json> --tab <tab> --output workflow.json to capture
a stable live snapshot. Synchronization is explicit with
headless sync <report.json> --tab <tab> --confirm: only passing, exact-ID
update/transform/move batches are replayed, guarded by the original revision and
verified again. External plugins and terminal connections are disabled in this
isolated execution surface.
See the workflow and evidence contract for canvas types, asset origins and regression-driven improvement.
Performance diagnostics
canvas perf is the Canvas CLI performance recorder, not the operating
system's perf utility. Use sample for a point-in-time snapshot, run for a
bounded recording, and suite for repeatable generated workloads. Every form
can write its complete JSON result with -o.
canvas perf sample --tab <tab> -o /tmp/canvas-sample.json
canvas perf run --tab <tab> --duration-ms 5000 -o /tmp/canvas-recording.json
canvas perf delete --tab <tab> -o /tmp/canvas-delete.json
canvas perf suite --tab <tab> --profile scenario -o /tmp/canvas-scenario.jsonperf delete is the shortest regression command for deletion flicker. It
creates a temporary editable neighborhood, deletes one vector, verifies nearby
nodes still exist, and rejects any whole-scene rebuild or empty-document frame.
Generated nodes are cleaned up unless --keep is supplied.
Editable 2.5D diagrams
canvas diagram isometric --example platform --output platform.json --json
canvas diagram isometric --example dataflow --output dataflow.json --json
canvas diagram isometric scene.json --output custom.json --jsonThe compiler projects 3D coordinates into native editable PATH/TEXT nodes inside
one FRAME. It supports exploded planes, tiled grids, boxes, wireframe hourglass
surfaces, native cubic arrows, polylines, polygons and screen-positioned labels. platform and
dataflow are bundled examples; no network, browser or daemon is required to
create them. Output paths must be new; --dry-run validates without writing.
This is orthographic 2.5D illustration, with explicit painter order, not live 3D
rotation, hidden-surface removal, or bound connector routing. Keep semantic
architecture connections in diagram apply (D2). A compatible headless Web build
can render these documents for visual review; geometry checks alone do not
establish reference-image fidelity.
See the scene schema and runnable capture example.
Configuration
- Daemon port:
CANVAS_BRIDGE_PORT(default7777). - All commands accept
--tab <id>when multiple canvas tabs are connected; with exactly one, it's optional.
Architecture (one sentence)
your CLI ──HTTP──> daemon ──WebSocket──> canvas tab in browser
│
└── MCP stdio (for AI agents)License
MIT
Repair detached diagram edges
canvas connector reconcile --tab <tab> --page <page> --json previews repairs without writing.
Inspect candidate IDs and before/after audits, then repeat with --confirm --revision <beforeRevision> to commit atomically.
The command requires a Web editor that advertises connector.reconcile; older editors reject it.
Only detached bound LINEs are re-routed through the canonical document-coordinate writer.
Healthy/manual/free paths are retained. Unresolved attachments, label occlusion and unrelated crossings
remain in the audit; this is not a rendered-visibility or clean-layout claim.
Headless workflows can use {type:"connector.reconcile",pageId:"page-1",dryRun:false} on an isolated document
and save the resulting document and screenshot before any explicit sync.
Progressive atomic batches and compact reads (local changes, not yet released)
act prints its operation ID immediately, then polls daemon progress automatically
on stderr. Stdout remains a single final JSON receipt. The shared control plane
prepares changes privately and yields between actions after approximately 8 ms
of preparation work. It commits the document once; it does not save partial chunks
or create a separate undo entry for each action. An edit, page switch, or selection
change while preparing rejects the batch before commit. A single large action,
connector refresh, revision hashing, and final observation can still exceed that
budget; this is not a hard frame-time guarantee.
canvas act actions.json --tab "$tab" --idempotency-key regroup-001 --json
canvas act status regroup-001 --tab "$tab" --json
canvas node get "$model" --tab "$tab" --fields id,transform,source --json
canvas node get "$model" --tab "$tab" --include-source --jsonStatus is daemon-local and scoped to the tab and operation ID. In-flight IDs remain
reserved. Completed outcomes are retained for five minutes and remain readable if
the tab disconnects; full results are retained within the byte budget below. outcome_unknown or OPERATION_UNKNOWN never proves a write
was not committed. Do not blindly retry with a new ID. Progress is informational;
complete means the control-plane operation finished, not that GPU presentation
or cloud persistence was verified. Unplanned daemon exits lose these records;
a lifecycle version switch preserves unexpired settled receipts.
Daemon 0.4.1 bounds retained operations to 1024 and concurrent
waiters per operation to 16. OPERATION_CAPACITY rejects a new command before
sending it; OPERATION_WAITERS_FULL rejects only the additional waiter. Query the
existing ID instead of issuing unknown writes under new IDs. Pending operations
are not silently evicted; completed records expire after five minutes. Waiter
timeouts release their timers and callbacks while preserving the operation ID.
Retained UTF-8 result/progress buffers share a 16 MiB budget; one result is limited
to 4 MiB and one progress update to 64 KiB. This bounds retained payload bytes,
not transient transport/parsing memory or total process RSS. Terminal outcomes
replace progress. Oversized progress is reported as progress: null with
progressOmitted: true; a later small update may replace it.
The original connected caller still receives the complete reply. If it cannot be
retained, status preserves completed/failed plus retention (byteLength,
sha256, retained=false, reason), and result is null. A retry with the same ID
returns RESULT_NOT_RETAINED without sending another command. Query status and
inspect document state before any new write; a missing result is not a failed
mutation. Expiry releases payload capacity. These records remain memory-only;
daemon restart recovery is not implemented.
The default relay deadline is 10 seconds; an explicit timeout remains clamped to
100–300000 ms. Only the socket that received a command may settle its receipt.
Normal node reads replace embedded data URIs with MIME/encoded-length descriptors,
including nested assets. Use --include-source explicitly to retrieve the complete
source; --fields selects existing top-level fields. The saved document is unchanged.
The browser projects before sending, and the daemon also projects old frontend
replies. This means an old frontend can still send large data over its WebSocket,
even though the CLI response is compact.
Imported GLB/FBX models no longer get a default uniform material. Explicit appearance
options still request a uniform replacement, which can remove per-mesh textures.
For an existing uniform override, restore the source materials without deleting its
settings with canvas node update "$model" --tab "$tab" --patch
'{"material":{"enabled":false}}'. Re-enable with enabled:true. This does not
retroactively migrate cloud documents. Primitive model defaults remain unchanged.
Cooperative work inside large actions
Creation is prepared in batches of 32 specifications; multi-target update,
transform, move, delete and duplicate actions also check the cooperative time
budget between nodes. Progress can include completedItems and totalItems
for the current action. Completed-action counts still describe the original
request, so a single action can report item progress while that count is zero.
All preparation stays private until the atomic commit. Yielding does not create
partial documents or extra undo entries. A concurrent document, page or selection
change aborts preparation with REVISION_CONFLICT. Individual geometry/engine
operations, connector refresh, revision/snapshot and commit remain synchronous:
the 8 ms checkpoint budget is a scheduling target, not a worst-frame guarantee.
Reproducible cloud reports
canvas report create --title "Problem" --description "Reproduction details" --demo demo.json --json submits an explicit reproduction bundle. canvas report list, canvas report get <id> --output <new-directory> and maintainer-only canvas report status <id> --status resolved complete the retrieval/triage loop. Use --dry-run before upload and a stable --id for retries. Requires a Canvas API bearer session via CANVAS_API_TOKEN or --token-file, not the local bridge token. No daemon/tab is required. See canvas report --help and the Reports contract. Deployment of the Reports API and D1 migration is a separate prerequisite.
Renderer diagnostics in headless workflows
The proposed requireRendererDiagnostics headless route is a local candidate pending Review; the B2 diagnostics route remains a local unreleased candidate pending review and actual target support.
Science documentation remains bounded: legacy v1 halo and v2 shared SourceObject/CurveFlow numeric paths are distinct; constant emissive beams and dynamic heads are limited profiles. Volume, arbitrary dynamic beam appearance, and 10k aggregate performance are not advertised. Repo-local scene document/package generation does not publish or authenticate an online document.
