canvas: AI-Orchestrated Visual Production
Claude acts as Creative Director for Obsidian Canvas. Describe what you want and get a fully populated, professionally laid-out .canvas file.
Context Detection
Before any operation, determine the canvas directory:
- If
wiki/canvases/ exists in the current directory or a parent: use it (claude-obsidian vault mode).
- Media goes to
_attachments/images/canvas/
- Otherwise: use
.canvases/ in the current working directory (standalone mode).
- Media goes to
.canvases/assets/
- Create the directory if it doesn't exist.
Default canvas: [canvas_dir]/main.canvas
Command Routing
| Command |
Sub-skill |
Description |
/canvas (no args) |
(inline) |
Status: list canvases, node counts, zones |
/canvas create [name] |
canvas-create |
Create blank or templated canvas |
/canvas create [name] from [template] |
canvas-create |
Create from archetype |
/canvas add [type] [content] |
canvas-populate |
Add node (image/text/pdf/note/link/mermaid/svg/gif/banana) |
/canvas zone [name] [color] |
canvas-populate |
Add group node |
/canvas connect [from] [to] [label] |
canvas-populate |
Add edge between nodes |
/canvas from banana |
canvas-populate |
Import recent AI-generated images |
/canvas layout [algorithm] |
canvas-layout |
Re-layout (auto/grid/dagre/radial/force/linear) |
/canvas present [topic] |
canvas-present |
Build presentation canvas (1200x675 slides) |
/canvas present from [notes] |
canvas-present |
Presentation from existing content |
/canvas generate [description] |
canvas-generate |
AI-orchestrated full canvas generation |
/canvas template list |
canvas-template |
Browse 12 archetypes |
/canvas template use [name] |
canvas-template |
Instantiate a template |
/canvas export [format] [path] |
canvas-export |
Export to PNG/SVG/PDF |
/canvas list |
(inline) |
List all canvases with stats |
Status / List (Inline Operations)
/canvas (no args)
- Detect canvas directory (vault or standalone).
- Find default canvas (
main.canvas).
- If exists: read JSON, count nodes by type, list zone labels.
Report: "Canvas has N nodes: X images, Y text, Z files. Zones: [list]"
- If not exists: report "No canvas found. Run
/canvas create [name] to start."
/canvas list
- Glob
[canvas_dir]/*.canvas.
- For each: read JSON, count nodes by type.
- Report table:
main.canvas 14 nodes (8 images, 3 text, 2 file, 1 group)
design-ideas.canvas 42 nodes (30 images, 4 text, 8 groups)
Key References
Read these references before performing canvas operations:
references/canvas-spec.md — JSON Canvas 1.0 format, coordinate system, node types, edges, colors, sizing
references/performance-guide.md — Node limits, GIF lag, SVG gotchas, 20px grid snapping
Additional references:
references/layout-algorithms.md — 6 layout algorithms (canvas-layout)
references/template-catalog.md — 12 archetypes (canvas-template)
references/presentation-spec.md — Advanced Canvas slides (canvas-present)
references/mermaid-patterns.md — Mermaid in text nodes (canvas-populate, canvas-generate)
references/media-guide.md — Image/GIF/SVG integration (canvas-generate, canvas-populate)
Auto-Positioning Algorithm
Used by canvas-populate to place new nodes. Read references/canvas-spec.md for the full coordinate system.
def next_position(canvas_nodes, target_zone_label, new_w, new_h):
# Find zone group node
zone = next((n for n in canvas_nodes
if n.get('type') == 'group'
and n.get('label') == target_zone_label), None)
if zone is None:
# No zone: place below all content
max_y = max((n['y'] + n.get('height', 0) for n in canvas_nodes), default=-140)
return snap_grid(-400, max_y + 60)
zx, zy = zone['x'], zone['y']
zw, zh = zone['width'], zone['height']
# Nodes inside this zone (exclude groups)
inside = [n for n in canvas_nodes
if n.get('type') != 'group'
and zx <= n['x'] < zx + zw
and zy <= n['y'] < zy + zh]
if not inside:
return snap_grid(zx + 20, zy + 20)
# Find the bottom-most row: nodes whose bottom edge is closest to the zone bottom
max_bottom = max(n['y'] + n.get('height', 0) for n in inside)
# Nodes on the last row: those whose top y is within one row-height of the bottom
last_row = [n for n in inside if n['y'] + n.get('height', 0) >= max_bottom - 20]
if not last_row:
last_row = inside # fallback
rightmost_x = max(n['x'] + n.get('width', 0) for n in last_row)
next_x = rightmost_x + 40
if next_x + new_w > zx + zw:
# Overflow: new row below the current last row
return snap_grid(zx + 20, max_bottom + 20)
# Same row: align to top of the LAST row (not all nodes)
current_row_y = min(n['y'] for n in last_row)
return snap_grid(next_x, current_row_y)
def snap_grid(x, y, grid=20):
return (round(x / grid) * grid, round(y / grid) * grid)
ID Generation
Read the canvas JSON first. Collect all existing IDs. Never reuse one.
Pattern: [type]-[content-slug]-[full-unix-timestamp]
Use the full 10-digit Unix timestamp to avoid collisions in batch operations.
Examples: img-cover-1744032823, text-note-1744032845, zone-branding-1744032901
If a collision is detected (ID already exists), append -2, -3, etc.
Canvas JSON Structure
The minimal valid canvas:
{
"nodes": [],
"edges": []
}
Z-index rule: First node in the array renders at the bottom (background). Last node renders on top (foreground). Groups MUST come before their contained nodes so content renders in front of zone backgrounds.
Grid snapping: All x, y, width, height values should be multiples of 20.
Node limit: Warn the user if a canvas exceeds 100 nodes. Error if it exceeds 200.
Quality Standards (MANDATORY)
Every canvas produced by any sub-skill MUST pass these checks before reporting success. These are not optional — they are the definition of "done."
Content Quality
- NO placeholder text in any node. Replace ALL of these:
- "Describe this step" → write a real step description relevant to the title
- "YYYY-MM-DD" → use today's date or a realistic date
- "Value: 0, Target: 100" → use realistic example values
- "Content goes here" → write actual content matching the slide topic
- "Define this entity" → write a real definition
- "What happened" → write a real event description
- Every text node must contain real, useful content that a user can immediately understand
- Template instantiation is STEP 1 — writing real content into the nodes is STEP 2 (never skip it)
Layout Quality
- Minimum 80px horizontal gap between adjacent content nodes
- Minimum 60px vertical gap between adjacent content nodes
- Mind-map canvases must have radial layout (run
canvas layout radial after instantiation)
- Knowledge-graph canvases must have force layout (run
canvas layout force after instantiation)
- Flowchart canvases should have dagre layout applied (run
canvas layout dagre for proper hierarchy)
- No overlapping nodes — run
canvas_validate.py to confirm
Structural Quality
- Groups (zones) appear BEFORE content nodes in the array (z-index)
- All coordinates are multiples of 20 (grid snapping)
- Node count under 120 (warn at 100, error at 200)
- All file paths are vault-relative (no absolute paths)
- Edge IDs are unique, node IDs are unique
Before Reporting Success
- Run
python3 scripts/canvas_validate.py <path> — must return valid: true with 0 errors
- Visually scan the generated JSON — are there any "Describe this" or "YYYY-MM-DD" strings remaining?
- If the canvas has groups, verify content nodes are inside their designated zones (center-point check)
- If the archetype needs a specific layout (mind-map→radial, kg→force), verify it was applied
Integration with Other Skills
banana (AI image generation):
/canvas add banana [prompt] delegates to the banana skill, then adds the result as a file node.
/canvas from banana reads .recent-images.txt or finds images modified in the last 10 minutes.
- If banana is not installed, report gracefully: "Install the banana skill for AI image generation."
svg (diagram/chart/icon generation):
/canvas add svg [description] delegates to the svg skill, then adds the SVG as a file node.
- SVGs render as
<img> in Obsidian — no interactivity. Must include viewBox for proper scaling.
claude-gif-* (GIF generation/editing):
/canvas add gif [description] delegates to the gif skill, then adds as a file node.
- Performance warning: limit to 3 GIFs per canvas, cap dimensions at 480px width.
Mermaid (native in text nodes):
- Mermaid code blocks render natively in Obsidian text nodes. No external skill needed.
- Wrap in triple-backtick mermaid code fence inside a text node.
1---2name: canvas3description: AI-orchestrated visual production for Obsidian Canvas. Create presentations, flowcharts, mood boards, knowledge graphs, galleries, storyboards, timelines, dashboards, and more with intelligent layout and AI-generated content. Claude acts as Creative Director — dispatching sub-agents for image generation, SVG diagrams, GIF creation, and spatial layout. Supports 12 template archetypes, 6 layout algorithms, and Advanced Canvas presentation mode. Triggers on: /canvas, create canvas, build canvas, make a presentation, visual board, mood board, flowchart canvas, storyboard, canvas from template, lay out canvas, export canvas, canvas layout, canvas generate, add to canvas, put this on the canvas, open canvas, canvas present, canvas template.4---56# canvas: AI-Orchestrated Visual Production78Claude acts as Creative Director for Obsidian Canvas. Describe what you want and get a fully populated, professionally laid-out `.canvas` file.910---1112## Context Detection1314Before any operation, determine the canvas directory:15161. If `wiki/canvases/` exists in the current directory or a parent: use it (claude-obsidian vault mode).17 - Media goes to `_attachments/images/canvas/`182. Otherwise: use `.canvases/` in the current working directory (standalone mode).19 - Media goes to `.canvases/assets/`203. Create the directory if it doesn't exist.2122**Default canvas**: `[canvas_dir]/main.canvas`2324---2526## Command Routing2728| Command | Sub-skill | Description |29|---------|-----------|-------------|30| `/canvas` (no args) | (inline) | Status: list canvases, node counts, zones |31| `/canvas create [name]` | canvas-create | Create blank or templated canvas |32| `/canvas create [name] from [template]` | canvas-create | Create from archetype |33| `/canvas add [type] [content]` | canvas-populate | Add node (image/text/pdf/note/link/mermaid/svg/gif/banana) |34| `/canvas zone [name] [color]` | canvas-populate | Add group node |35| `/canvas connect [from] [to] [label]` | canvas-populate | Add edge between nodes |36| `/canvas from banana` | canvas-populate | Import recent AI-generated images |37| `/canvas layout [algorithm]` | canvas-layout | Re-layout (auto/grid/dagre/radial/force/linear) |38| `/canvas present [topic]` | canvas-present | Build presentation canvas (1200x675 slides) |39| `/canvas present from [notes]` | canvas-present | Presentation from existing content |40| `/canvas generate [description]` | canvas-generate | AI-orchestrated full canvas generation |41| `/canvas template list` | canvas-template | Browse 12 archetypes |42| `/canvas template use [name]` | canvas-template | Instantiate a template |43| `/canvas export [format] [path]` | canvas-export | Export to PNG/SVG/PDF |44| `/canvas list` | (inline) | List all canvases with stats |4546---4748## Status / List (Inline Operations)4950### `/canvas` (no args)51521. Detect canvas directory (vault or standalone).532. Find default canvas (`main.canvas`).543. If exists: read JSON, count nodes by type, list zone labels.55 Report: "Canvas has N nodes: X images, Y text, Z files. Zones: [list]"564. If not exists: report "No canvas found. Run `/canvas create [name]` to start."5758### `/canvas list`59601. Glob `[canvas_dir]/*.canvas`.612. For each: read JSON, count nodes by type.623. Report table:6364```65main.canvas 14 nodes (8 images, 3 text, 2 file, 1 group)66design-ideas.canvas 42 nodes (30 images, 4 text, 8 groups)67```6869---7071## Key References7273Read these references before performing canvas operations:7475- `references/canvas-spec.md` — JSON Canvas 1.0 format, coordinate system, node types, edges, colors, sizing76- `references/performance-guide.md` — Node limits, GIF lag, SVG gotchas, 20px grid snapping7778Additional references:79- `references/layout-algorithms.md` — 6 layout algorithms (canvas-layout)80- `references/template-catalog.md` — 12 archetypes (canvas-template)8182- `references/presentation-spec.md` — Advanced Canvas slides (canvas-present)8384- `references/mermaid-patterns.md` — Mermaid in text nodes (canvas-populate, canvas-generate)85- `references/media-guide.md` — Image/GIF/SVG integration (canvas-generate, canvas-populate)8687---8889## Auto-Positioning Algorithm9091Used by canvas-populate to place new nodes. Read `references/canvas-spec.md` for the full coordinate system.9293```python94def next_position(canvas_nodes, target_zone_label, new_w, new_h):95 # Find zone group node96 zone = next((n for n in canvas_nodes97 if n.get('type') == 'group'98 and n.get('label') == target_zone_label), None)99100 if zone is None:101 # No zone: place below all content102 max_y = max((n['y'] + n.get('height', 0) for n in canvas_nodes), default=-140)103 return snap_grid(-400, max_y + 60)104105 zx, zy = zone['x'], zone['y']106 zw, zh = zone['width'], zone['height']107108 # Nodes inside this zone (exclude groups)109 inside = [n for n in canvas_nodes110 if n.get('type') != 'group'111 and zx <= n['x'] < zx + zw112 and zy <= n['y'] < zy + zh]113114 if not inside:115 return snap_grid(zx + 20, zy + 20)116117 # Find the bottom-most row: nodes whose bottom edge is closest to the zone bottom118 max_bottom = max(n['y'] + n.get('height', 0) for n in inside)119 # Nodes on the last row: those whose top y is within one row-height of the bottom120 last_row = [n for n in inside if n['y'] + n.get('height', 0) >= max_bottom - 20]121 if not last_row:122 last_row = inside # fallback123124 rightmost_x = max(n['x'] + n.get('width', 0) for n in last_row)125 next_x = rightmost_x + 40126127 if next_x + new_w > zx + zw:128 # Overflow: new row below the current last row129 return snap_grid(zx + 20, max_bottom + 20)130131 # Same row: align to top of the LAST row (not all nodes)132 current_row_y = min(n['y'] for n in last_row)133 return snap_grid(next_x, current_row_y)134135def snap_grid(x, y, grid=20):136 return (round(x / grid) * grid, round(y / grid) * grid)137```138139---140141## ID Generation142143Read the canvas JSON first. Collect all existing IDs. Never reuse one.144145**Pattern**: `[type]-[content-slug]-[full-unix-timestamp]`146147Use the full 10-digit Unix timestamp to avoid collisions in batch operations.148149Examples: `img-cover-1744032823`, `text-note-1744032845`, `zone-branding-1744032901`150151If a collision is detected (ID already exists), append `-2`, `-3`, etc.152153---154155## Canvas JSON Structure156157The minimal valid canvas:158159```json160{161 "nodes": [],162 "edges": []163}164```165166**Z-index rule**: First node in the array renders at the bottom (background). Last node renders on top (foreground). Groups MUST come before their contained nodes so content renders in front of zone backgrounds.167168**Grid snapping**: All x, y, width, height values should be multiples of 20.169170**Node limit**: Warn the user if a canvas exceeds 100 nodes. Error if it exceeds 200.171172---173174## Quality Standards (MANDATORY)175176Every canvas produced by any sub-skill MUST pass these checks before reporting success. These are not optional — they are the definition of "done."177178### Content Quality179- **NO placeholder text** in any node. Replace ALL of these:180 - "Describe this step" → write a real step description relevant to the title181 - "YYYY-MM-DD" → use today's date or a realistic date182 - "Value: 0, Target: 100" → use realistic example values183 - "Content goes here" → write actual content matching the slide topic184 - "Define this entity" → write a real definition185 - "What happened" → write a real event description186- Every text node must contain **real, useful content** that a user can immediately understand187- Template instantiation is STEP 1 — writing real content into the nodes is STEP 2 (never skip it)188189### Layout Quality190- **Minimum 80px horizontal gap** between adjacent content nodes191- **Minimum 60px vertical gap** between adjacent content nodes192- **Mind-map canvases** must have radial layout (run `canvas layout radial` after instantiation)193- **Knowledge-graph canvases** must have force layout (run `canvas layout force` after instantiation)194- **Flowchart canvases** should have dagre layout applied (run `canvas layout dagre` for proper hierarchy)195- **No overlapping nodes** — run `canvas_validate.py` to confirm196197### Structural Quality198- Groups (zones) appear BEFORE content nodes in the array (z-index)199- All coordinates are multiples of 20 (grid snapping)200- Node count under 120 (warn at 100, error at 200)201- All file paths are vault-relative (no absolute paths)202- Edge IDs are unique, node IDs are unique203204### Before Reporting Success2051. Run `python3 scripts/canvas_validate.py <path>` — must return `valid: true` with 0 errors2062. Visually scan the generated JSON — are there any "Describe this" or "YYYY-MM-DD" strings remaining?2073. If the canvas has groups, verify content nodes are inside their designated zones (center-point check)2084. If the archetype needs a specific layout (mind-map→radial, kg→force), verify it was applied209210---211212## Integration with Other Skills213214**banana** (AI image generation):215- `/canvas add banana [prompt]` delegates to the banana skill, then adds the result as a file node.216- `/canvas from banana` reads `.recent-images.txt` or finds images modified in the last 10 minutes.217- If banana is not installed, report gracefully: "Install the banana skill for AI image generation."218219**svg** (diagram/chart/icon generation):220- `/canvas add svg [description]` delegates to the svg skill, then adds the SVG as a file node.221- SVGs render as `<img>` in Obsidian — no interactivity. Must include `viewBox` for proper scaling.222223**claude-gif-*** (GIF generation/editing):224- `/canvas add gif [description]` delegates to the gif skill, then adds as a file node.225- Performance warning: limit to 3 GIFs per canvas, cap dimensions at 480px width.226227**Mermaid** (native in text nodes):228- Mermaid code blocks render natively in Obsidian text nodes. No external skill needed.229- Wrap in triple-backtick mermaid code fence inside a text node.