mirror of
https://github.com/kepano/obsidian-skills.git
synced 2026-06-08 14:05:34 -07:00
feat: improve skill quality scores across 4 skills
Ran skills through tessl evaluation tooling and applied targeted improvements to structure, conciseness, and workflow clarity. | Skill | Before | After | Change | |-------------------|--------|-------|--------| | defuddle | 100% | 100% | -- | | json-canvas | 83% | 100% | +17% | | obsidian-bases | 83% | 100% | +17% | | obsidian-cli | 94% | 100% | +6% | | obsidian-markdown | 71% | 94% | +23% | Changes: - Add structured workflows with validation steps - Extract verbose reference tables to references/ files (progressive disclosure) - Remove standard Markdown content Claude already knows (obsidian-markdown) - Add troubleshooting section for common YAML/formula errors (obsidian-bases) - Restructure plugin dev section into numbered test cycle (obsidian-cli) - Condense redundant examples, keep one per pattern Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+96
-508
@@ -5,15 +5,9 @@ description: Create and edit JSON Canvas files (.canvas) with nodes, edges, grou
|
||||
|
||||
# JSON Canvas Skill
|
||||
|
||||
This skill enables skills-compatible agents 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:
|
||||
A canvas file (`.canvas`) contains two top-level arrays following the [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -25,37 +19,64 @@ A canvas file contains two top-level arrays:
|
||||
- `nodes` (optional): Array of node objects
|
||||
- `edges` (optional): Array of edge objects connecting nodes
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### 1. Create a New Canvas
|
||||
|
||||
1. Create a `.canvas` file with the base structure `{"nodes": [], "edges": []}`
|
||||
2. Generate unique 16-character hex IDs for each node (e.g., `"6f0ad84f44ce9c17"`)
|
||||
3. Add nodes with required fields: `id`, `type`, `x`, `y`, `width`, `height`
|
||||
4. Add edges referencing valid node IDs via `fromNode` and `toNode`
|
||||
5. **Validate**: Parse the JSON to confirm it is valid. Verify all `fromNode`/`toNode` values exist in the nodes array
|
||||
|
||||
### 2. Add a Node to an Existing Canvas
|
||||
|
||||
1. Read and parse the existing `.canvas` file
|
||||
2. Generate a unique ID that does not collide with existing node or edge IDs
|
||||
3. Choose position (`x`, `y`) that avoids overlapping existing nodes (leave 50-100px spacing)
|
||||
4. Append the new node object to the `nodes` array
|
||||
5. Optionally add edges connecting the new node to existing nodes
|
||||
6. **Validate**: Confirm all IDs are unique and all edge references resolve to existing nodes
|
||||
|
||||
### 3. Connect Two Nodes
|
||||
|
||||
1. Identify the source and target node IDs
|
||||
2. Generate a unique edge ID
|
||||
3. Set `fromNode` and `toNode` to the source and target IDs
|
||||
4. Optionally set `fromSide`/`toSide` (top, right, bottom, left) for anchor points
|
||||
5. Optionally set `label` for descriptive text on the edge
|
||||
6. Append the edge to the `edges` array
|
||||
7. **Validate**: Confirm both `fromNode` and `toNode` reference existing node IDs
|
||||
|
||||
### 4. Edit an Existing Canvas
|
||||
|
||||
1. Read and parse the `.canvas` file as JSON
|
||||
2. Locate the target node or edge by `id`
|
||||
3. Modify the desired attributes (text, position, color, etc.)
|
||||
4. Write the updated JSON back to the file
|
||||
5. **Validate**: Re-check all ID uniqueness and edge reference integrity after editing
|
||||
|
||||
## 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)
|
||||
Nodes are objects placed on the canvas. Array order determines z-index: first node = bottom layer, last node = top layer.
|
||||
|
||||
### 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` |
|
||||
| `id` | Yes | string | Unique 16-char hex identifier |
|
||||
| `type` | Yes | string | `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) |
|
||||
| `color` | No | canvasColor | Preset `"1"`-`"6"` or hex (e.g., `"#FF0000"`) |
|
||||
|
||||
### Text Nodes
|
||||
|
||||
Text nodes contain Markdown content.
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `text` | Yes | string | Plain text with Markdown syntax |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -69,27 +90,14 @@ Text nodes contain Markdown content.
|
||||
}
|
||||
```
|
||||
|
||||
#### Newline Escaping (Common Pitfall)
|
||||
|
||||
In JSON, newline characters inside strings **must** be represented as `\n`. Do **not** use the literal sequence `\\n` in a `.canvas` file—Obsidian will render it as the characters `\` and `n` instead of a line break.
|
||||
|
||||
Examples:
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "Line 1\nLine 2" }
|
||||
```
|
||||
|
||||
```json
|
||||
{ "type": "text", "text": "Line 1\\nLine 2" }
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `text` | Yes | string | Plain text with Markdown syntax |
|
||||
**Newline pitfall**: Use `\n` for line breaks in JSON strings. Do **not** use the literal `\\n` -- Obsidian renders that as the characters `\` and `n`.
|
||||
|
||||
### File Nodes
|
||||
|
||||
File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `file` | Yes | string | Path to file within the system |
|
||||
| `subpath` | No | string | Link to heading or block (starts with `#`) |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -103,27 +111,11 @@ File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "b2c3d4e5f6789012",
|
||||
"type": "file",
|
||||
"x": 500,
|
||||
"y": 400,
|
||||
"width": 400,
|
||||
"height": 300,
|
||||
"file": "Notes/Project Overview.md",
|
||||
"subpath": "#Implementation"
|
||||
}
|
||||
```
|
||||
### Link Nodes
|
||||
|
||||
| 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.
|
||||
| `url` | Yes | string | External URL |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -137,13 +129,15 @@ Link nodes display external URLs.
|
||||
}
|
||||
```
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `url` | Yes | string | External URL |
|
||||
|
||||
### Group Nodes
|
||||
|
||||
Group nodes are visual containers for organizing other nodes.
|
||||
Groups are visual containers for organizing other nodes. Position child nodes inside the group's bounds.
|
||||
|
||||
| Attribute | Required | Type | Description |
|
||||
|-----------|----------|------|-------------|
|
||||
| `label` | No | string | Text label for the group |
|
||||
| `background` | No | string | Path to background image |
|
||||
| `backgroundStyle` | No | string | `cover`, `ratio`, or `repeat` |
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -158,107 +152,37 @@ Group nodes are visual containers for organizing other nodes.
|
||||
}
|
||||
```
|
||||
|
||||
```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.
|
||||
Edges connect nodes via `fromNode` and `toNode` IDs.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "f67890123456789a",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"toNode": "a1b2c3d4e5f67890"
|
||||
}
|
||||
```
|
||||
| Attribute | Required | Type | Default | Description |
|
||||
|-----------|----------|------|---------|-------------|
|
||||
| `id` | Yes | string | - | Unique identifier |
|
||||
| `fromNode` | Yes | string | - | Source node ID |
|
||||
| `fromSide` | No | string | - | `top`, `right`, `bottom`, or `left` |
|
||||
| `fromEnd` | No | string | `none` | `none` or `arrow` |
|
||||
| `toNode` | Yes | string | - | Target node ID |
|
||||
| `toSide` | No | string | - | `top`, `right`, `bottom`, or `left` |
|
||||
| `toEnd` | No | string | `arrow` | `none` or `arrow` |
|
||||
| `color` | No | canvasColor | - | Line color |
|
||||
| `label` | No | string | - | Text label |
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "0123456789abcdef",
|
||||
"fromNode": "6f0ad84f44ce9c17",
|
||||
"fromSide": "right",
|
||||
"fromEnd": "none",
|
||||
"toNode": "b2c3d4e5f6789012",
|
||||
"toNode": "a1b2c3d4e5f67890",
|
||||
"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"
|
||||
}
|
||||
```
|
||||
The `canvasColor` type accepts either a hex string or a preset number:
|
||||
|
||||
| Preset | Color |
|
||||
|--------|-------|
|
||||
@@ -269,360 +193,23 @@ The `canvasColor` type can be specified in two ways:
|
||||
| `"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Preset color values are intentionally undefined -- applications use their own brand colors.
|
||||
|
||||
## ID Generation
|
||||
|
||||
Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
|
||||
Generate 16-character lowercase hexadecimal strings (64-bit random value):
|
||||
|
||||
```json
|
||||
"id": "6f0ad84f44ce9c17"
|
||||
"id": "a3b2c1d0e9f8g7h6"
|
||||
"id": "1234567890abcdef"
|
||||
```
|
||||
|
||||
This format is a 16-character lowercase hex string (64-bit random value).
|
||||
"6f0ad84f44ce9c17"
|
||||
"a3b2c1d0e9f8a7b6"
|
||||
```
|
||||
|
||||
## 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
|
||||
- `x` increases right, `y` increases down; position is the top-left corner
|
||||
- Space nodes 50-100px apart; leave 20-50px padding inside groups
|
||||
- Align to grid (multiples of 10 or 20) for cleaner layouts
|
||||
|
||||
| Node Type | Suggested Width | Suggested Height |
|
||||
|-----------|-----------------|------------------|
|
||||
@@ -631,24 +218,25 @@ This format is a 16-character lowercase hex string (64-bit random value).
|
||||
| Large text | 400-600 | 300-500 |
|
||||
| File preview | 300-500 | 200-400 |
|
||||
| Link preview | 250-400 | 100-200 |
|
||||
| Group | Varies | Varies |
|
||||
|
||||
### Spacing
|
||||
## Validation Checklist
|
||||
|
||||
- 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
|
||||
After creating or editing a canvas file, verify:
|
||||
|
||||
## Validation Rules
|
||||
1. All `id` values are unique across both nodes and edges
|
||||
2. Every `fromNode` and `toNode` references an existing node ID
|
||||
3. Required fields are present for each node type (`text` for text nodes, `file` for file nodes, `url` for link nodes)
|
||||
4. `type` is one of: `text`, `file`, `link`, `group`
|
||||
5. `fromSide`/`toSide` values are one of: `top`, `right`, `bottom`, `left`
|
||||
6. `fromEnd`/`toEnd` values are one of: `none`, `arrow`
|
||||
7. Color presets are `"1"` through `"6"` or valid hex (e.g., `"#FF0000"`)
|
||||
8. JSON is valid and parseable
|
||||
|
||||
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
|
||||
If validation fails, check for duplicate IDs, dangling edge references, or malformed JSON strings (especially unescaped newlines in text content).
|
||||
|
||||
## Complete Examples
|
||||
|
||||
See [references/EXAMPLES.md](references/EXAMPLES.md) for full canvas examples including mind maps, project boards, research canvases, and flowcharts.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
# JSON Canvas 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user