The canvas panel is Orion’s universal renderer. It has two conceptually distinct tracks driven by a single Zustand store. Once you understand the split, the rest follows.
This page covers the canvas surface itself. For TipTap document editing, see Frontend → TipTap. For how XState dispatches updates into the canvas, see Frontend → Streaming Machine.

The two tracks

TrackActivated byRendererWhat gets shown
Mode trackcanvasStore.openFile(path, mode)CanvasPanel switches on modeFile-backed surfaces (TipTap, PDF, code, spreadsheet, etc.)
Widget trackcanvasStore.setWidget(spec, title)WidgetCanvas via @json-render/react Renderer + orionRegistryAgent-generated JSON specs (charts, slides, image grids, custom layouts)
Mode and widget are mutually exclusive — calling setWidget() clears activeFile, and openFile() clears widgetSpec. ✓ VERIFIED at canvasStore.ts:547-559.

Canvas surface inventory

Four file-open paths converge in canvasStore.openFile

Media files (images, video, audio) always end up in the widget track. The detection point is canvasStore.openFile() at L281-342 — every entry path converges here for non-tool-call routes. Tool calls have their own media detection in the interceptor (lines 445-469) that fires before openFile() as a perf optimization. ✓ VERIFIED
GAP-C4: Drag-drop silently drops DIRECT_OPEN_EXTENSIONS. useCanvasDropZone.ts:49 — if the dropped file’s extension is in DIRECT_OPEN_EXTENSIONS (docx, doc, ppt, xls, ods, zip, dmg, etc.), the mode is null and the drop is silently ignored. No toast, no error.

How a tool call drives the canvas

The interceptor doesn’t receive raw IPC. It reads activityStream from the XState snapshot — by the time it sees a tool, the event has already been normalized and pushed into the activity stream by XState actions. ✓ VERIFIED at useCanvasToolInterceptor.ts:108.

WidgetSpec fix pipeline

Before any agent-generated spec reaches WidgetCanvas, it goes through a 7-step fix pipeline to handle hallucinations and legacy formats: Each step solves a real AI-output failure mode — see useCanvasToolInterceptor.ts:395-425 for the canonical order.
GAP-C2: ≤10 element threshold for inline-only is silent. Widgets with 10 or fewer elements render inline (in the chat) and do not open the canvas. If the user has scrolled away or switched sessions, the widget may be invisible with no fallback notification. The threshold is hardcoded with no documented rationale.

AI-WITH-YOU gate at the canvas

Orion’s product principle: AI may propose, stage, draft, and prepare. It may not silently decide meaning, commit structure, or ship the user’s work without an explicit user-owned seam. From .claude/rules/ui/ai-with-you-gate.md. Where the gate lives per canvas surface:
GAP-C1: ProvenanceCaption gate is advisory, not enforced.The rule says generated widgets (SlideDeck, MetricCard, Timeline, Chart) must include a ProvenanceCaption child to satisfy the AI-WITH-YOU provenance gate. The catalog’s description string enforces this in the system prompt (widget-catalog.ts:173-186) — meaning Claude’s prompt is told to include it.But there is no runtime validator that rejects a widget spec lacking ProvenanceCaption. An agent could emit a SlideDeck with zero provenance and it would render without error. Enforcement is convention-based — and convention-based enforcement breaks under model variance, Pi tool calls, and YAML specs. See Trust → GAP-C1.

Key files

The Claude/Pi tool → canvas bridge. ~990 LOC. Tool action dispatch table at L169-914. 7-step fix pipeline at L395-425. Small-widget threshold at L731.
Single Zustand store for all canvas state. openFile media-detection at L271-399. setWidget at L547-559.
EXTENSION_MAP (L20-75), getCanvasMode, getCanvasModeForFile. Maps extensions to canvas modes.
orionCatalog — 40+ Zod-typed components. catalog.prompt() auto-generates the system prompt for AI.
orionRegistry — wiring of catalog → React components in 8 groups (layout, media, text, data, form, ui, social, presentation).
Mode-based rendering container + auto-save logic. ~517 LOC.
@json-render/react Renderer wrapper + Proxy-based action catch-all.

Next

Streaming Machine

The XState v5 machine that drives the activity stream and dispatches into the canvas.

TipTap Editor

Extensions, the editor-ref bridge for set_content, debounced auto-save.