New files:
commands/canvas.md — slash command with full trigger table
skills/canvas/SKILL.md — complete skill: open, new, add image/text/pdf/note,
zone, list, from-banana operations
skills/canvas/references/canvas-spec.md — Obsidian canvas JSON spec:
coordinate system, all node types, color table,
image sizing by aspect ratio, auto-positioning
pseudocode, common mistakes, full example
Auto-positioning algorithm (bbox math):
- Finds rightmost node in target zone
- Places next node at rightmost_x + 40, same row y
- If overflow: wraps to new row (max_y_in_zone + 20)
- Falls back to below all content if no zone found
Banana integration:
- /canvas from banana: checks .recent-images.txt first, then
filesystem find -newer 10min, presents list for confirmation
- After any /banana run: suggests /canvas from banana
Updated:
skills/wiki/SKILL.md — added /canvas to routing table
skills/wiki/references/plugins.md — added Calendar + Thino with
pre-installed note + manual install fallback
README.md — /canvas in commands table + file structure
8.1 KiB
name, description, allowed-tools
| name | description | allowed-tools | ||||||
|---|---|---|---|---|---|---|---|---|
| canvas | Visual layer of the wiki. Add images, text cards, PDFs, and wiki pages to Obsidian canvas files. Auto-positions nodes inside zones using bbox math. Integrates with /banana skill for generated image capture. Triggers on: "/canvas", "canvas new", "canvas add image", "canvas add text", "canvas add pdf", "canvas add note", "canvas zone", "canvas list", "canvas from banana", "add to canvas", "put this on the canvas", "open canvas", "create canvas". |
|
canvas — Visual Reference Layer
The three knowledge capture layers:
/save→ text synthesis (wiki/questions/, wiki/concepts/)/autoresearch→ structured knowledge (wiki/sources/, wiki/concepts/)/canvas→ visual references (wiki/canvases/)
A canvas is a JSON file Obsidian renders as an infinite visual board. This skill reads and writes canvas JSON directly. Read references/canvas-spec.md for the full format reference before making any edits.
Default Canvas
wiki/canvases/main.canvas
If it does not exist, create it:
{
"nodes": [
{
"id": "title",
"type": "text",
"text": "# Visual Reference\n\nDrop images, PDFs, and notes here.",
"x": -400, "y": -300, "width": 400, "height": 120, "color": "6"
},
{
"id": "zone-default",
"type": "group",
"label": "General",
"x": -400, "y": -140, "width": 800, "height": 400, "color": "4"
}
],
"edges": []
}
Operations
open / status (/canvas with no args)
- Check if
wiki/canvases/main.canvasexists. - If yes: read it, count nodes by type, list all group node labels (zone names). Report: "Canvas has N nodes: X images, Y text cards, Z wiki pages. Zones: [list]"
- If no: create it with the starter structure above. Report: "Created main.canvas with a General zone."
- Tell user: "Open
wiki/canvases/main.canvasin Obsidian to view."
new (/canvas new [name])
- Slugify the name: lowercase, spaces → hyphens, strip special chars.
- Create
wiki/canvases/[slug].canvaswith the starter structure, title updated to# [Name]. - Add entry to
wiki/index.mdunder a "## Canvases" section (create the section if it doesn't exist). - Report: "Created wiki/canvases/[slug].canvas"
add image (/canvas add image [path or url])
Resolve the image:
- If URL (starts with
http): download withcurl -sL [url] -o _attachments/images/canvas/[filename]Derive filename from URL path, or useimg-[timestamp].jpgif unclear. - If local path outside vault:
cp [path] _attachments/images/canvas/ - If already vault-relative: use as-is.
Create _attachments/images/canvas/ if it doesn't exist.
Detect aspect ratio:
Use python3 -c "from PIL import Image; img=Image.open('[path]'); print(img.width, img.height)" or identify -format '%w %h' [path].
- 16:9 (width/height ≈ 1.7–2.0): width=420, height=236
- 1:1 (ratio ≈ 1.0): width=280, height=280
- 9:16 (ratio < 0.65): width=200, height=356
- Unknown: width=320, height=240
Position using auto-layout (see Auto-Positioning section below).
Append node to canvas JSON and write.
Report: "Added [filename] to [zone] zone at position ([x], [y])."
add text (/canvas add text [content])
Create a text node:
{
"id": "text-[timestamp]",
"type": "text",
"text": "[content]",
"x": [auto], "y": [auto],
"width": 300, "height": 120,
"color": "4"
}
Position using auto-layout. Write and report.
add pdf (/canvas add pdf [path])
Same as add image. Obsidian renders PDFs natively as file nodes.
- Copy to
_attachments/pdfs/canvas/if outside vault. - Fixed size: width=400, height=520.
- Report page count if you can determine it.
add note (/canvas add note [wiki-page])
- Search
wiki/for a file matching the page name (case-insensitive, partial match ok). - Use the vault-relative path as the
filefield. - Create a file node: width=300, height=100.
- Position using auto-layout.
{
"id": "note-[timestamp]",
"type": "file",
"file": "wiki/concepts/LLM Wiki Pattern.md",
"x": [auto], "y": [auto],
"width": 300, "height": 100
}
zone (/canvas zone [name] [color])
- Read canvas JSON.
- Find max_y:
max(node.y + node.height for all nodes) + 60. Use -140 if no nodes. - Create a group node:
{
"id": "zone-[slug]",
"type": "group",
"label": "[name]",
"x": -400,
"y": [max_y],
"width": 1000,
"height": 400,
"color": "[color or '3']"
}
Valid colors: "1"=red "2"=orange "3"=yellow "4"=green "5"=cyan "6"=purple
Write and report.
list (/canvas list)
glob wiki/canvases/*.canvas- For each canvas: read JSON, count nodes by type.
- Report:
wiki/canvases/main.canvas — 14 nodes (8 images, 3 text, 2 file, 1 group)
wiki/canvases/design-ideas.canvas — 42 nodes (30 images, 4 text, 8 groups)
from banana (/canvas from banana)
- Check
wiki/canvases/.recent-images.txtfirst (session log of newly written images). - If not found or empty:
find _attachments/images -name "*.png" -o -name "*.jpg" -newer [10min-ago-file]Create the reference file:python3 -c "import time; open('/tmp/ten-min-ago','w').close(); import os; os.utime('/tmp/ten-min-ago', (time.time()-600, time.time()-600))"Then:find _attachments/images -newer /tmp/ten-min-ago -name "*.png" -o -name "*.jpg" - If still none: show the 5 most recently modified images.
- Present list: "Found N recent images: [list]. Add to canvas? Which zone? (zone name / 'new [name]' / 'skip')"
- On confirmation: add each using the add image logic.
Auto-Positioning Algorithm
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 -400, max_y + 60
zx, zy = zone['x'], zone['y']
zw, zh = zone['width'], zone['height']
# Nodes inside this zone
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 zx + 20, zy + 20
rightmost_x = max(n['x'] + n.get('width', 0) for n in inside)
next_x = rightmost_x + 40
if next_x + new_w > zx + zw:
# New row
max_row_y = max(n['y'] + n.get('height', 0) for n in inside)
return zx + 20, max_row_y + 20
# Same row — match the top y of existing row
current_row_y = min(n['y'] for n in inside if n['x'] > zx + zw / 2) \
if any(n['x'] > zx + zw / 2 for n in inside) \
else zy + 20
return next_x, current_row_y
ID Generation
Read the canvas, collect all existing IDs. Never reuse one.
Safe ID pattern: [type]-[content-slug]-[unix-timestamp-last-4-digits]
Examples: img-cover-7823, text-note-1045, zone-branding-3391
Session Log (optional hook)
If wiki/canvases/.recent-images.txt exists, append any new image path written to _attachments/images/ during this session (one path per line, keep last 20).
/canvas from banana reads this file first, making it instant without filesystem search.
Banana Integration
After any /banana run in the same session, if the user says "add to canvas" or "put on canvas", treat it as /canvas from banana.
When /banana finishes generating images, suggest:
"Add generated images to canvas? Run
/canvas from banana"
Summary
- Read canvas-spec.md before editing any canvas JSON.
- Always read the canvas file before writing — parse existing nodes to avoid ID collisions and calculate auto-positions.
- Create
_attachments/images/canvas/for downloaded/copied images. - Update
wiki/index.mdwhen creating new canvases. - Report position and zone after every add operation.