diff --git a/json-canvas/SKILL.md b/json-canvas/SKILL.md new file mode 100644 index 0000000..41fdbf3 --- /dev/null +++ b/json-canvas/SKILL.md @@ -0,0 +1,638 @@ +# JSON Canvas Skill + +This skill enables Claude Code to create and edit valid JSON Canvas files (`.canvas`) used in Obsidian and other applications. + +## Overview + +JSON Canvas is an open file format for infinite canvas data. Canvas files use the `.canvas` extension and contain valid JSON following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/). + +## File Structure + +A canvas file contains two top-level arrays: + +```json +{ + "nodes": [], + "edges": [] +} +``` + +- `nodes` (optional): Array of node objects +- `edges` (optional): Array of edge objects connecting nodes + +## Nodes + +Nodes are objects placed on the canvas. There are four node types: +- `text` - Text content with Markdown +- `file` - Reference to files/attachments +- `link` - External URL +- `group` - Visual container for other nodes + +### Z-Index Ordering + +Nodes are ordered by z-index in the array: +- First node = bottom layer (displayed below others) +- Last node = top layer (displayed above others) + +### Generic Node Attributes + +All nodes share these attributes: + +| Attribute | Required | Type | Description | +|-----------|----------|------|-------------| +| `id` | Yes | string | Unique identifier for the node | +| `type` | Yes | string | Node type: `text`, `file`, `link`, or `group` | +| `x` | Yes | integer | X position in pixels | +| `y` | Yes | integer | Y position in pixels | +| `width` | Yes | integer | Width in pixels | +| `height` | Yes | integer | Height in pixels | +| `color` | No | canvasColor | Node color (see Color section) | + +### Text Nodes + +Text nodes contain Markdown content. + +```json +{ + "id": "6f0ad84f44ce9c17", + "type": "text", + "x": 0, + "y": 0, + "width": 400, + "height": 200, + "text": "# Hello World\n\nThis is **Markdown** content." +} +``` + +| Attribute | Required | Type | Description | +|-----------|----------|------|-------------| +| `text` | Yes | string | Plain text with Markdown syntax | + +### File Nodes + +File nodes reference files or attachments (images, videos, PDFs, notes, etc.). + +```json +{ + "id": "a1b2c3d4e5f67890", + "type": "file", + "x": 500, + "y": 0, + "width": 400, + "height": 300, + "file": "Attachments/diagram.png" +} +``` + +```json +{ + "id": "b2c3d4e5f6789012", + "type": "file", + "x": 500, + "y": 400, + "width": 400, + "height": 300, + "file": "Notes/Project Overview.md", + "subpath": "#Implementation" +} +``` + +| Attribute | Required | Type | Description | +|-----------|----------|------|-------------| +| `file` | Yes | string | Path to file within the system | +| `subpath` | No | string | Link to heading or block (starts with `#`) | + +### Link Nodes + +Link nodes display external URLs. + +```json +{ + "id": "c3d4e5f678901234", + "type": "link", + "x": 1000, + "y": 0, + "width": 400, + "height": 200, + "url": "https://obsidian.md" +} +``` + +| Attribute | Required | Type | Description | +|-----------|----------|------|-------------| +| `url` | Yes | string | External URL | + +### Group Nodes + +Group nodes are visual containers for organizing other nodes. + +```json +{ + "id": "d4e5f6789012345a", + "type": "group", + "x": -50, + "y": -50, + "width": 1000, + "height": 600, + "label": "Project Overview", + "color": "4" +} +``` + +```json +{ + "id": "e5f67890123456ab", + "type": "group", + "x": 0, + "y": 700, + "width": 800, + "height": 500, + "label": "Resources", + "background": "Attachments/background.png", + "backgroundStyle": "cover" +} +``` + +| Attribute | Required | Type | Description | +|-----------|----------|------|-------------| +| `label` | No | string | Text label for the group | +| `background` | No | string | Path to background image | +| `backgroundStyle` | No | string | Background rendering style | + +#### Background Styles + +| Value | Description | +|-------|-------------| +| `cover` | Fills entire width and height of node | +| `ratio` | Maintains aspect ratio of background image | +| `repeat` | Repeats image as pattern in both directions | + +## Edges + +Edges are lines connecting nodes. + +```json +{ + "id": "f67890123456789a", + "fromNode": "6f0ad84f44ce9c17", + "toNode": "a1b2c3d4e5f67890" +} +``` + +```json +{ + "id": "0123456789abcdef", + "fromNode": "6f0ad84f44ce9c17", + "fromSide": "right", + "fromEnd": "none", + "toNode": "b2c3d4e5f6789012", + "toSide": "left", + "toEnd": "arrow", + "color": "1", + "label": "leads to" +} +``` + +| Attribute | Required | Type | Default | Description | +|-----------|----------|------|---------|-------------| +| `id` | Yes | string | - | Unique identifier for the edge | +| `fromNode` | Yes | string | - | Node ID where connection starts | +| `fromSide` | No | string | - | Side where edge starts | +| `fromEnd` | No | string | `none` | Shape at edge start | +| `toNode` | Yes | string | - | Node ID where connection ends | +| `toSide` | No | string | - | Side where edge ends | +| `toEnd` | No | string | `arrow` | Shape at edge end | +| `color` | No | canvasColor | - | Line color | +| `label` | No | string | - | Text label for the edge | + +### Side Values + +| Value | Description | +|-------|-------------| +| `top` | Top edge of node | +| `right` | Right edge of node | +| `bottom` | Bottom edge of node | +| `left` | Left edge of node | + +### End Shapes + +| Value | Description | +|-------|-------------| +| `none` | No endpoint shape | +| `arrow` | Arrow endpoint | + +## Colors + +The `canvasColor` type can be specified in two ways: + +### Hex Colors + +```json +{ + "color": "#FF0000" +} +``` + +### Preset Colors + +```json +{ + "color": "1" +} +``` + +| Preset | Color | +|--------|-------| +| `"1"` | Red | +| `"2"` | Orange | +| `"3"` | Yellow | +| `"4"` | Green | +| `"5"` | Cyan | +| `"6"` | Purple | + +Note: Specific color values for presets are intentionally undefined, allowing applications to use their own brand colors. + +## Complete Examples + +### Simple Canvas with Text and Connections + +```json +{ + "nodes": [ + { + "id": "8a9b0c1d2e3f4a5b", + "type": "text", + "x": 0, + "y": 0, + "width": 300, + "height": 150, + "text": "# Main Idea\n\nThis is the central concept." + }, + { + "id": "1a2b3c4d5e6f7a8b", + "type": "text", + "x": 400, + "y": -100, + "width": 250, + "height": 100, + "text": "## Supporting Point A\n\nDetails here." + }, + { + "id": "2b3c4d5e6f7a8b9c", + "type": "text", + "x": 400, + "y": 100, + "width": 250, + "height": 100, + "text": "## Supporting Point B\n\nMore details." + } + ], + "edges": [ + { + "id": "3c4d5e6f7a8b9c0d", + "fromNode": "8a9b0c1d2e3f4a5b", + "fromSide": "right", + "toNode": "1a2b3c4d5e6f7a8b", + "toSide": "left" + }, + { + "id": "4d5e6f7a8b9c0d1e", + "fromNode": "8a9b0c1d2e3f4a5b", + "fromSide": "right", + "toNode": "2b3c4d5e6f7a8b9c", + "toSide": "left" + } + ] +} +``` + +### Project Board with Groups + +```json +{ + "nodes": [ + { + "id": "5e6f7a8b9c0d1e2f", + "type": "group", + "x": 0, + "y": 0, + "width": 300, + "height": 500, + "label": "To Do", + "color": "1" + }, + { + "id": "6f7a8b9c0d1e2f3a", + "type": "group", + "x": 350, + "y": 0, + "width": 300, + "height": 500, + "label": "In Progress", + "color": "3" + }, + { + "id": "7a8b9c0d1e2f3a4b", + "type": "group", + "x": 700, + "y": 0, + "width": 300, + "height": 500, + "label": "Done", + "color": "4" + }, + { + "id": "8b9c0d1e2f3a4b5c", + "type": "text", + "x": 20, + "y": 50, + "width": 260, + "height": 80, + "text": "## Task 1\n\nImplement feature X" + }, + { + "id": "9c0d1e2f3a4b5c6d", + "type": "text", + "x": 370, + "y": 50, + "width": 260, + "height": 80, + "text": "## Task 2\n\nReview PR #123", + "color": "2" + }, + { + "id": "0d1e2f3a4b5c6d7e", + "type": "text", + "x": 720, + "y": 50, + "width": 260, + "height": 80, + "text": "## Task 3\n\n~~Setup CI/CD~~" + } + ], + "edges": [] +} +``` + +### Research Canvas with Files and Links + +```json +{ + "nodes": [ + { + "id": "1e2f3a4b5c6d7e8f", + "type": "text", + "x": 300, + "y": 200, + "width": 400, + "height": 200, + "text": "# Research Topic\n\n## Key Questions\n\n- How does X affect Y?\n- What are the implications?", + "color": "5" + }, + { + "id": "2f3a4b5c6d7e8f9a", + "type": "file", + "x": 0, + "y": 0, + "width": 250, + "height": 150, + "file": "Literature/Paper A.pdf" + }, + { + "id": "3a4b5c6d7e8f9a0b", + "type": "file", + "x": 0, + "y": 200, + "width": 250, + "height": 150, + "file": "Notes/Meeting Notes.md", + "subpath": "#Key Insights" + }, + { + "id": "4b5c6d7e8f9a0b1c", + "type": "link", + "x": 0, + "y": 400, + "width": 250, + "height": 100, + "url": "https://example.com/research" + }, + { + "id": "5c6d7e8f9a0b1c2d", + "type": "file", + "x": 750, + "y": 150, + "width": 300, + "height": 250, + "file": "Attachments/diagram.png" + } + ], + "edges": [ + { + "id": "6d7e8f9a0b1c2d3e", + "fromNode": "2f3a4b5c6d7e8f9a", + "fromSide": "right", + "toNode": "1e2f3a4b5c6d7e8f", + "toSide": "left", + "label": "supports" + }, + { + "id": "7e8f9a0b1c2d3e4f", + "fromNode": "3a4b5c6d7e8f9a0b", + "fromSide": "right", + "toNode": "1e2f3a4b5c6d7e8f", + "toSide": "left", + "label": "informs" + }, + { + "id": "8f9a0b1c2d3e4f5a", + "fromNode": "4b5c6d7e8f9a0b1c", + "fromSide": "right", + "toNode": "1e2f3a4b5c6d7e8f", + "toSide": "left", + "toEnd": "arrow", + "color": "6" + }, + { + "id": "9a0b1c2d3e4f5a6b", + "fromNode": "1e2f3a4b5c6d7e8f", + "fromSide": "right", + "toNode": "5c6d7e8f9a0b1c2d", + "toSide": "left", + "label": "visualized by" + } + ] +} +``` + +### Flowchart + +```json +{ + "nodes": [ + { + "id": "a0b1c2d3e4f5a6b7", + "type": "text", + "x": 200, + "y": 0, + "width": 150, + "height": 60, + "text": "**Start**", + "color": "4" + }, + { + "id": "b1c2d3e4f5a6b7c8", + "type": "text", + "x": 200, + "y": 100, + "width": 150, + "height": 60, + "text": "Step 1:\nGather data" + }, + { + "id": "c2d3e4f5a6b7c8d9", + "type": "text", + "x": 200, + "y": 200, + "width": 150, + "height": 80, + "text": "**Decision**\n\nIs data valid?", + "color": "3" + }, + { + "id": "d3e4f5a6b7c8d9e0", + "type": "text", + "x": 400, + "y": 200, + "width": 150, + "height": 60, + "text": "Process data" + }, + { + "id": "e4f5a6b7c8d9e0f1", + "type": "text", + "x": 0, + "y": 200, + "width": 150, + "height": 60, + "text": "Request new data", + "color": "1" + }, + { + "id": "f5a6b7c8d9e0f1a2", + "type": "text", + "x": 400, + "y": 320, + "width": 150, + "height": 60, + "text": "**End**", + "color": "4" + } + ], + "edges": [ + { + "id": "a6b7c8d9e0f1a2b3", + "fromNode": "a0b1c2d3e4f5a6b7", + "fromSide": "bottom", + "toNode": "b1c2d3e4f5a6b7c8", + "toSide": "top" + }, + { + "id": "b7c8d9e0f1a2b3c4", + "fromNode": "b1c2d3e4f5a6b7c8", + "fromSide": "bottom", + "toNode": "c2d3e4f5a6b7c8d9", + "toSide": "top" + }, + { + "id": "c8d9e0f1a2b3c4d5", + "fromNode": "c2d3e4f5a6b7c8d9", + "fromSide": "right", + "toNode": "d3e4f5a6b7c8d9e0", + "toSide": "left", + "label": "Yes", + "color": "4" + }, + { + "id": "d9e0f1a2b3c4d5e6", + "fromNode": "c2d3e4f5a6b7c8d9", + "fromSide": "left", + "toNode": "e4f5a6b7c8d9e0f1", + "toSide": "right", + "label": "No", + "color": "1" + }, + { + "id": "e0f1a2b3c4d5e6f7", + "fromNode": "e4f5a6b7c8d9e0f1", + "fromSide": "top", + "fromEnd": "none", + "toNode": "b1c2d3e4f5a6b7c8", + "toSide": "left", + "toEnd": "arrow" + }, + { + "id": "f1a2b3c4d5e6f7a8", + "fromNode": "d3e4f5a6b7c8d9e0", + "fromSide": "bottom", + "toNode": "f5a6b7c8d9e0f1a2", + "toSide": "top" + } + ] +} +``` + +## ID Generation + +Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs: + +```json +"id": "6f0ad84f44ce9c17" +"id": "a3b2c1d0e9f8g7h6" +"id": "1234567890abcdef" +``` + +This format is a 16-character lowercase hex string (64-bit random value). + +## Layout Guidelines + +### Positioning + +- Coordinates can be negative (canvas extends infinitely) +- `x` increases to the right +- `y` increases downward +- Position refers to top-left corner of node + +### Recommended Sizes + +| Node Type | Suggested Width | Suggested Height | +|-----------|-----------------|------------------| +| Small text | 200-300 | 80-150 | +| Medium text | 300-450 | 150-300 | +| Large text | 400-600 | 300-500 | +| File preview | 300-500 | 200-400 | +| Link preview | 250-400 | 100-200 | +| Group | Varies | Varies | + +### Spacing + +- Leave 20-50px padding inside groups +- Space nodes 50-100px apart for readability +- Align nodes to grid (multiples of 10 or 20) for cleaner layouts + +## Validation Rules + +1. All `id` values must be unique across nodes and edges +2. `fromNode` and `toNode` must reference existing node IDs +3. Required fields must be present for each node type +4. `type` must be one of: `text`, `file`, `link`, `group` +5. `backgroundStyle` must be one of: `cover`, `ratio`, `repeat` +6. `fromSide`, `toSide` must be one of: `top`, `right`, `bottom`, `left` +7. `fromEnd`, `toEnd` must be one of: `none`, `arrow` +8. Color presets must be `"1"` through `"6"` or valid hex color + +## References + +- [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/) +- [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas) +