diff --git a/.markdownlint.json b/.markdownlint.json
new file mode 100644
index 00000000..871f42f3
--- /dev/null
+++ b/.markdownlint.json
@@ -0,0 +1,7 @@
+{
+ "MD013": false,
+ "MD040": false,
+ "MD041": false,
+ "MD028": false,
+ "MD033": false
+}
diff --git a/skills/json-canvas/SKILL.md b/skills/json-canvas/SKILL.md
index e0611fe7..85a68e2f 100644
--- a/skills/json-canvas/SKILL.md
+++ b/skills/json-canvas/SKILL.md
@@ -1,16 +1,26 @@
---
name: json-canvas
-description: Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating visual canvases, mind maps, flowcharts, or when the user mentions Canvas files in Obsidian.
+description: Create and edit JSON Canvas files (.canvas) with nodes, edges, groups, and connections. Use when working with .canvas files, creating or editing visual canvases, mind maps, flowcharts, diagrams, graphs, or network visualizations in Obsidian.
+allowed-tools: [Read, Write, Edit, Glob]
---
# JSON Canvas Skill
-This skill enables skills-compatible agents to create and edit valid JSON Canvas files (`.canvas`) used in Obsidian and other applications.
+This skill enables 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/).
+### Current Version
+
+JSON Canvas Specification: 1.0
+
+### Supported in
+
+- Obsidian (built-in Canvas app)
+- Other applications following the spec
+
## File Structure
A canvas file contains two top-level arrays:
@@ -22,621 +32,118 @@ A canvas file contains two top-level arrays:
}
```
-- `nodes` (optional): Array of node objects
+- `nodes` (optional): Array of node objects representing elements on the canvas
- `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 |
+1. **text** - Text content with Markdown
+2. **file** - Reference to files/attachments
+3. **link** - External URL
+4. **group** - Visual container for other nodes
-### File Nodes
+For detailed node specifications, see [Node Types](node-types.md).
-File nodes reference files or attachments (images, videos, PDFs, notes, etc.).
+### Quick Reference: Node Properties
-```json
-{
- "id": "a1b2c3d4e5f67890",
- "type": "file",
- "x": 500,
- "y": 0,
- "width": 400,
- "height": 300,
- "file": "Attachments/diagram.png"
-}
-```
+All nodes require:
-```json
-{
- "id": "b2c3d4e5f6789012",
- "type": "file",
- "x": 500,
- "y": 400,
- "width": 400,
- "height": 300,
- "file": "Notes/Project Overview.md",
- "subpath": "#Implementation"
-}
-```
+- `id` - Unique identifier (16-char hex string)
+- `type` - One of: `text`, `file`, `link`, `group`
+- `x`, `y` - Position in pixels
+- `width`, `height` - Dimensions in pixels
-| Attribute | Required | Type | Description |
-|-----------|----------|------|-------------|
-| `file` | Yes | string | Path to file within the system |
-| `subpath` | No | string | Link to heading or block (starts with `#`) |
+Optional:
-### Link Nodes
+- `color` - Preset ID (`"1"`-`"6"`) or hex color
-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 |
+See [Node Types](node-types.md) for type-specific attributes.
## 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"
-}
-```
+Edges are lines connecting nodes with optional labels and styling.
-| 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 |
+### Quick Reference: Edge Properties
-## Colors
+Required:
-The `canvasColor` type can be specified in two ways:
+- `id` - Unique identifier
+- `fromNode` - Source node ID
+- `toNode` - Target node ID
-### Hex Colors
+Optional:
-```json
-{
- "color": "#FF0000"
-}
-```
+- `fromSide`, `toSide` - Connection sides (`top`, `right`, `bottom`, `left`)
+- `fromEnd`, `toEnd` - Endpoint shapes (`none`, `arrow`)
+- `color` - Preset ID or hex color
+- `label` - Text label on edge
-### Preset Colors
+See [Edges](edges.md) for detailed specifications.
-```json
-{
- "color": "1"
-}
-```
+## Colors
-| Preset | Color |
-|--------|-------|
-| `"1"` | Red |
-| `"2"` | Orange |
-| `"3"` | Yellow |
-| `"4"` | Green |
-| `"5"` | Cyan |
-| `"6"` | Purple |
+Canvas elements support colors using presets or hex codes.
-Note: Specific color values for presets are intentionally undefined, allowing applications to use their own brand colors.
+**Preset Colors:**
-## Complete Examples
+- `"1"` = Red, `"2"` = Orange, `"3"` = Yellow
+- `"4"` = Green, `"5"` = Blue, `"6"` = Purple
-### Simple Canvas with Text and Connections
+**Hex Format:** `"#RRGGBB"` (e.g., `"#FF0000"`)
-```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"
- }
- ]
-}
-```
+See [Colors](colors.md) for complete reference and usage examples.
-### Project Board with Groups
+## Layout Recommendations
-```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": []
-}
-```
+### Node Sizing
-### Research Canvas with Files and Links
+| Type | Width | 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 |
-```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"
- }
- ]
-}
-```
+### Best Practices
-### Flowchart
+- Align nodes to 10-20px grid for cleaner layouts
+- Space nodes 50-100px apart for readability
+- Use groups with 20-50px padding to organize related nodes
+- Coordinates can be negative (canvas is infinite)
-```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"
- }
- ]
-}
-```
+For detailed validation rules and layout guidelines, see [Validation & Layout](validation.md).
## ID Generation
-Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
+Node and edge IDs should be 16-character lowercase hexadecimal strings (64-bit random):
-```json
-"id": "6f0ad84f44ce9c17"
-"id": "a3b2c1d0e9f8g7h6"
-"id": "1234567890abcdef"
+```
+"6f0ad84f44ce9c17"
+"a3b2c1d0e9f8g7h6"
+"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
+This format ensures compatibility with Obsidian and other canvas implementations.
-### Recommended Sizes
+## Examples
-| 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 |
+See [Examples](examples.md) for complete, runnable canvas files:
-### 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
+- **Research Mind Map** - Multi-document research canvas with supporting files
+- **Flowchart** - Decision flow with conditional branching
-## Validation Rules
+## Reference Guides
-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
+- [Node Types](node-types.md) - Text, file, link, and group node specifications
+- [Edges](edges.md) - Connection specifications and styling
+- [Colors](colors.md) - Color system (presets and hex)
+- [Validation & Layout](validation.md) - Validation rules and layout guidelines
+- [Examples](examples.md) - Real-world canvas examples
-## References
+## External Resources
-- [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/)
-- [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas)
+- [JSON Canvas Spec 1.0](https://jsoncanvas.org/spec/1.0/) - Official specification
+- [JSON Canvas GitHub](https://github.com/obsidianmd/jsoncanvas) - Project repository
diff --git a/skills/json-canvas/colors.md b/skills/json-canvas/colors.md
new file mode 100644
index 00000000..c24c5e3d
--- /dev/null
+++ b/skills/json-canvas/colors.md
@@ -0,0 +1,78 @@
+# JSON Canvas Colors
+
+Canvas elements support color customization using either hex colors or preset color IDs.
+
+## Hex Colors
+
+Use standard 6-digit hex color codes:
+
+```json
+{
+ "color": "#FF0000"
+}
+```
+
+Format: `#RRGGBB` where each pair is a hexadecimal value (00-FF).
+
+## Preset Colors
+
+Use color ID strings `"1"` through `"6"`:
+
+```json
+{
+ "color": "1"
+}
+```
+
+| ID | Color Name | Hex Code |
+| --- | ---------- | -------- |
+| `1` | Red | #EB3B3B |
+| `2` | Orange | #E8932F |
+| `3` | Yellow | #E8C547 |
+| `4` | Green | #5DA22F |
+| `5` | Blue | #4573D9 |
+| `6` | Purple | #A85EDF |
+
+## Usage
+
+Colors can be applied to:
+
+- **Nodes** - Set `color` property on any node
+- **Edges** - Set `color` property on edge to change line color
+- **Groups** - Set `color` property on group nodes for visual distinction
+
+### Example: Colored Node
+
+```json
+{
+ "id": "6f0ad84f44ce9c17",
+ "type": "text",
+ "x": 0,
+ "y": 0,
+ "width": 400,
+ "height": 200,
+ "text": "# Important",
+ "color": "1"
+}
+```
+
+### Example: Colored Edge
+
+```json
+{
+ "id": "f67890123456789a",
+ "fromNode": "node1",
+ "toNode": "node2",
+ "color": "4",
+ "label": "connects to"
+}
+```
+
+## Color Selection Tips
+
+- Use contrasting colors to distinguish node types or categories
+- Reserve red (#EB3B3B) for critical or error states
+- Use green (#5DA22F) for completed or positive states
+- Use yellow (#E8C547) for warnings or pending states
+- Use blue (#4573D9) for informational content
+- Mix hex colors with presets for visual consistency
diff --git a/skills/json-canvas/edges.md b/skills/json-canvas/edges.md
new file mode 100644
index 00000000..ce406aff
--- /dev/null
+++ b/skills/json-canvas/edges.md
@@ -0,0 +1,67 @@
+# JSON Canvas Edges
+
+Edges are lines connecting nodes.
+
+## Basic Edge
+
+```json
+{
+ "id": "f67890123456789a",
+ "fromNode": "6f0ad84f44ce9c17",
+ "toNode": "a1b2c3d4e5f67890"
+}
+```
+
+## Edge with Details
+
+```json
+{
+ "id": "0123456789abcdef",
+ "fromNode": "6f0ad84f44ce9c17",
+ "fromSide": "right",
+ "fromEnd": "none",
+ "toNode": "b2c3d4e5f6789012",
+ "toSide": "left",
+ "toEnd": "arrow",
+ "color": "1",
+ "label": "leads to"
+}
+```
+
+## Edge Attributes
+
+| 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
+
+Specify which side of a node an edge connects to:
+
+| Value | Description |
+| -------- | ------------------- |
+| `top` | Top edge of node |
+| `right` | Right edge of node |
+| `bottom` | Bottom edge of node |
+| `left` | Left edge of node |
+
+## End Shapes
+
+Specify what shape appears at the endpoints of an edge:
+
+| Value | Description |
+| ------- | ----------------- |
+| `none` | No endpoint shape |
+| `arrow` | Arrow endpoint |
+
+## Color Reference
+
+Edges support the same color system as nodes. See [Colors](colors.md) for hex and preset color options.
diff --git a/skills/json-canvas/examples.md b/skills/json-canvas/examples.md
new file mode 100644
index 00000000..6332e1aa
--- /dev/null
+++ b/skills/json-canvas/examples.md
@@ -0,0 +1,209 @@
+# JSON Canvas Examples
+
+## Research Mind Map
+
+```json
+{
+ "nodes": [
+ {
+ "id": "1e2f3a4b5c6d7e8f",
+ "type": "text",
+ "x": 350,
+ "y": 0,
+ "width": 250,
+ "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"
+ }
+ ]
+}
+```
diff --git a/skills/json-canvas/node-types.md b/skills/json-canvas/node-types.md
new file mode 100644
index 00000000..8b042138
--- /dev/null
+++ b/skills/json-canvas/node-types.md
@@ -0,0 +1,143 @@
+# JSON Canvas Node Types
+
+Nodes are objects placed on the canvas. There are four node types: `text`, `file`, `link`, and `group`.
+
+## 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 [Colors](colors.md)) |
+
+## 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 |
diff --git a/skills/json-canvas/validation.md b/skills/json-canvas/validation.md
new file mode 100644
index 00000000..a6d2fe57
--- /dev/null
+++ b/skills/json-canvas/validation.md
@@ -0,0 +1,50 @@
+# JSON Canvas Validation & Layout Guidelines
+
+## ID Generation
+
+Node and edge IDs must be unique strings. Obsidian generates 16-character hexadecimal IDs:
+
+```json
+"id": "6f0ad84f44ce9c17"
+"id": "a3b2c1d0e9f8g7h6"
+"id": "1234567890abcdef"
+```
+
+Format: 16-character lowercase hex string (64-bit random value). Tools should generate IDs in this format for compatibility.
+
+## 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 (see [Colors](colors.md))
+
+## 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
diff --git a/skills/obsidian-bases/SKILL.md b/skills/obsidian-bases/SKILL.md
index 6f82f112..4bbe9081 100644
--- a/skills/obsidian-bases/SKILL.md
+++ b/skills/obsidian-bases/SKILL.md
@@ -1,11 +1,12 @@
---
name: obsidian-bases
-description: Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database-like views of notes, or when the user mentions Bases, table views, card views, filters, or formulas in Obsidian.
+description: Create and edit Obsidian Bases (.base files) with views, filters, formulas, and summaries. Use when working with .base files, creating database or query views of notes, or when the user mentions Bases, table views, card views, list views, filters, formulas, or summaries in Obsidian.
+allowed-tools: [Read, Write, Edit, Glob]
---
# Obsidian Bases Skill
-This skill enables skills-compatible agents to create and edit valid Obsidian Bases (`.base` files) including views, filters, formulas, and all related configurations.
+This skill enables agents to create and edit valid Obsidian Bases (`.base` files) including views, filters, formulas, and all related configurations.
## Overview
@@ -15,602 +16,167 @@ Obsidian Bases are YAML-based files that define dynamic views of notes in an Obs
Base files use the `.base` extension and contain valid YAML. They can also be embedded in Markdown code blocks.
-## Complete Schema
+## Base Schema
+
+Complete structure of a base file:
```yaml
-# Global filters apply to ALL views in the base
+# Global filters apply to ALL views
filters:
- # Can be a single filter string
- # OR a recursive filter object with and/or/not
and: []
or: []
not: []
-# Define formula properties that can be used across all views
+# Define formula properties for all views
formulas:
- formula_name: 'expression'
+ formula_name: "expression"
-# Configure display names and settings for properties
+# Configure display names and settings
properties:
property_name:
displayName: "Display Name"
- formula.formula_name:
- displayName: "Formula Display Name"
- file.ext:
- displayName: "Extension"
# Define custom summary formulas
summaries:
- custom_summary_name: 'values.mean().round(3)'
+ custom_summary_name: "values.mean().round(3)"
# Define one or more views
views:
- type: table | cards | list | map
name: "View Name"
- limit: 10 # Optional: limit results
- groupBy: # Optional: group results
- property: property_name
- direction: ASC | DESC
- filters: # View-specific filters
- and: []
- order: # Properties to display in order
+ limit: 10 # Optional
+ order:
- file.name
- - property_name
- - formula.formula_name
- summaries: # Map properties to summary formulas
- property_name: Average
+ filters: {}
+ summaries: {}
```
-## Filter Syntax
+## Filters
-Filters narrow down results. They can be applied globally or per-view.
+Filters narrow down which items appear in views. They can be global (all views) or per-view.
-### Filter Structure
+**Basic syntax:**
```yaml
-# Single filter
-filters: 'status == "done"'
-
-# AND - all conditions must be true
+filters: 'status == "done"' # Single filter
filters:
- and:
+ and: # AND logic
- 'status == "done"'
- 'priority > 3'
-
-# OR - any condition can be true
-filters:
- or:
- - 'file.hasTag("book")'
- - 'file.hasTag("article")'
-
-# NOT - exclude matching items
-filters:
- not:
- - 'file.hasTag("archived")'
-
-# Nested filters
-filters:
- or:
- - file.hasTag("tag")
- - and:
- - file.hasTag("book")
- - file.hasLink("Textbook")
- - not:
- - file.hasTag("book")
- - file.inFolder("Required Reading")
```
-### Filter Operators
-
-| Operator | Description |
-|----------|-------------|
-| `==` | equals |
-| `!=` | not equal |
-| `>` | greater than |
-| `<` | less than |
-| `>=` | greater than or equal |
-| `<=` | less than or equal |
-| `&&` | logical and |
-| `\|\|` | logical or |
-| ! | logical not |
-
-## Properties
+Supported operators: `==`, `!=`, `>`, `<`, `>=`, `<=`, `&&`, `||`, `!`
-### Three Types of Properties
+For complete filter syntax and patterns, see [Filters](filters.md).
-1. **Note properties** - From frontmatter: `note.author` or just `author`
-2. **File properties** - File metadata: `file.name`, `file.mtime`, etc.
-3. **Formula properties** - Computed values: `formula.my_formula`
-
-### File Properties Reference
-
-| Property | Type | Description |
-|----------|------|-------------|
-| `file.name` | String | File name |
-| `file.basename` | String | File name without extension |
-| `file.path` | String | Full path to file |
-| `file.folder` | String | Parent folder path |
-| `file.ext` | String | File extension |
-| `file.size` | Number | File size in bytes |
-| `file.ctime` | Date | Created time |
-| `file.mtime` | Date | Modified time |
-| `file.tags` | List | All tags in file |
-| `file.links` | List | Internal links in file |
-| `file.backlinks` | List | Files linking to this file |
-| `file.embeds` | List | Embeds in the note |
-| `file.properties` | Object | All frontmatter properties |
+## Formulas
-### The `this` Keyword
+Formulas compute dynamic values from properties. Define them once and use across all views.
-- In main content area: refers to the base file itself
-- When embedded: refers to the embedding file
-- In sidebar: refers to the active file in main content
-
-## Formula Syntax
-
-Formulas compute values from properties. Defined in the `formulas` section.
+**Example:**
```yaml
formulas:
- # Simple arithmetic
- total: "price * quantity"
-
- # Conditional logic
- status_icon: 'if(done, "โ
", "โณ")'
-
- # String formatting
- formatted_price: 'if(price, price.toFixed(2) + " dollars")'
-
- # Date formatting
- created: 'file.ctime.format("YYYY-MM-DD")'
-
- # Complex expressions
- days_old: '((now() - file.ctime) / 86400000).round(0)'
-```
-
-## Functions Reference
-
-### Global Functions
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `date()` | `date(string): date` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
-| `duration()` | `duration(string): duration` | Parse duration string |
-| `now()` | `now(): date` | Current date and time |
-| `today()` | `today(): date` | Current date (time = 00:00:00) |
-| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
-| `min()` | `min(n1, n2, ...): number` | Smallest number |
-| `max()` | `max(n1, n2, ...): number` | Largest number |
-| `number()` | `number(any): number` | Convert to number |
-| `link()` | `link(path, display?): Link` | Create a link |
-| `list()` | `list(element): List` | Wrap in list if not already |
-| `file()` | `file(path): file` | Get file object |
-| `image()` | `image(path): image` | Create image for rendering |
-| `icon()` | `icon(name): icon` | Lucide icon by name |
-| `html()` | `html(string): html` | Render as HTML |
-| `escapeHTML()` | `escapeHTML(string): string` | Escape HTML characters |
-
-### Any Type Functions
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `isTruthy()` | `any.isTruthy(): boolean` | Coerce to boolean |
-| `isType()` | `any.isType(type): boolean` | Check type |
-| `toString()` | `any.toString(): string` | Convert to string |
-
-### Date Functions & Fields
-
-**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `date()` | `date.date(): date` | Remove time portion |
-| `format()` | `date.format(string): string` | Format with Moment.js pattern |
-| `time()` | `date.time(): string` | Get time as string |
-| `relative()` | `date.relative(): string` | Human-readable relative time |
-| `isEmpty()` | `date.isEmpty(): boolean` | Always false for dates |
-
-### Date Arithmetic
-
-```yaml
-# Duration units: y/year/years, M/month/months, d/day/days,
-# w/week/weeks, h/hour/hours, m/minute/minutes, s/second/seconds
-
-# Add/subtract durations
-"date + \"1M\"" # Add 1 month
-"date - \"2h\"" # Subtract 2 hours
-"now() + \"1 day\"" # Tomorrow
-"today() + \"7d\"" # A week from today
-
-# Subtract dates for millisecond difference
-"now() - file.ctime"
-
-# Complex duration arithmetic
-"now() + (duration('1d') * 2)"
+ priority_label: 'if(priority == 1, "๐ด High", "๐ข Low")'
+ days_due: 'if(due, ((date(due) - today()) / 86400000).round(0), "")'
```
-### String Functions
-
-**Field:** `string.length`
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `contains()` | `string.contains(value): boolean` | Check substring |
-| `containsAll()` | `string.containsAll(...values): boolean` | All substrings present |
-| `containsAny()` | `string.containsAny(...values): boolean` | Any substring present |
-| `startsWith()` | `string.startsWith(query): boolean` | Starts with query |
-| `endsWith()` | `string.endsWith(query): boolean` | Ends with query |
-| `isEmpty()` | `string.isEmpty(): boolean` | Empty or not present |
-| `lower()` | `string.lower(): string` | To lowercase |
-| `title()` | `string.title(): string` | To Title Case |
-| `trim()` | `string.trim(): string` | Remove whitespace |
-| `replace()` | `string.replace(pattern, replacement): string` | Replace pattern |
-| `repeat()` | `string.repeat(count): string` | Repeat string |
-| `reverse()` | `string.reverse(): string` | Reverse string |
-| `slice()` | `string.slice(start, end?): string` | Substring |
-| `split()` | `string.split(separator, n?): list` | Split to list |
-
-### Number Functions
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `abs()` | `number.abs(): number` | Absolute value |
-| `ceil()` | `number.ceil(): number` | Round up |
-| `floor()` | `number.floor(): number` | Round down |
-| `round()` | `number.round(digits?): number` | Round to digits |
-| `toFixed()` | `number.toFixed(precision): string` | Fixed-point notation |
-| `isEmpty()` | `number.isEmpty(): boolean` | Not present |
-
-### List Functions
-
-**Field:** `list.length`
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `contains()` | `list.contains(value): boolean` | Element exists |
-| `containsAll()` | `list.containsAll(...values): boolean` | All elements exist |
-| `containsAny()` | `list.containsAny(...values): boolean` | Any element exists |
-| `filter()` | `list.filter(expression): list` | Filter by condition (uses `value`, `index`) |
-| `map()` | `list.map(expression): list` | Transform elements (uses `value`, `index`) |
-| `reduce()` | `list.reduce(expression, initial): any` | Reduce to single value (uses `value`, `index`, `acc`) |
-| `flat()` | `list.flat(): list` | Flatten nested lists |
-| `join()` | `list.join(separator): string` | Join to string |
-| `reverse()` | `list.reverse(): list` | Reverse order |
-| `slice()` | `list.slice(start, end?): list` | Sublist |
-| `sort()` | `list.sort(): list` | Sort ascending |
-| `unique()` | `list.unique(): list` | Remove duplicates |
-| `isEmpty()` | `list.isEmpty(): boolean` | No elements |
-
-### File Functions
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `asLink()` | `file.asLink(display?): Link` | Convert to link |
-| `hasLink()` | `file.hasLink(otherFile): boolean` | Has link to file |
-| `hasTag()` | `file.hasTag(...tags): boolean` | Has any of the tags |
-| `hasProperty()` | `file.hasProperty(name): boolean` | Has property |
-| `inFolder()` | `file.inFolder(folder): boolean` | In folder or subfolder |
-
-### Link Functions
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `asFile()` | `link.asFile(): file` | Get file object |
-| `linksTo()` | `link.linksTo(file): boolean` | Links to file |
-
-### Object Functions
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `isEmpty()` | `object.isEmpty(): boolean` | No properties |
-| `keys()` | `object.keys(): list` | List of keys |
-| `values()` | `object.values(): list` | List of values |
-
-### Regular Expression Functions
-
-| Function | Signature | Description |
-|----------|-----------|-------------|
-| `matches()` | `regexp.matches(string): boolean` | Test if matches |
-
-## View Types
-
-### Table View
+Reference formulas in views using `formula.formula_name`.
-```yaml
-views:
- - type: table
- name: "My Table"
- order:
- - file.name
- - status
- - due_date
- summaries:
- price: Sum
- count: Average
-```
+For formula syntax and function reference, see [Formulas](formulas.md) and [Functions Reference](functions-reference.md).
-### Cards View
-
-```yaml
-views:
- - type: cards
- name: "Gallery"
- order:
- - file.name
- - cover_image
- - description
-```
-
-### List View
-
-```yaml
-views:
- - type: list
- name: "Simple List"
- order:
- - file.name
- - status
-```
-
-### Map View
-
-Requires latitude/longitude properties and the Maps community plugin.
-
-```yaml
-views:
- - type: map
- name: "Locations"
- # Map-specific settings for lat/lng properties
-```
+## Properties
-## Default Summary Formulas
-
-| Name | Input Type | Description |
-|------|------------|-------------|
-| `Average` | Number | Mathematical mean |
-| `Min` | Number | Smallest number |
-| `Max` | Number | Largest number |
-| `Sum` | Number | Sum of all numbers |
-| `Range` | Number | Max - Min |
-| `Median` | Number | Mathematical median |
-| `Stddev` | Number | Standard deviation |
-| `Earliest` | Date | Earliest date |
-| `Latest` | Date | Latest date |
-| `Range` | Date | Latest - Earliest |
-| `Checked` | Boolean | Count of true values |
-| `Unchecked` | Boolean | Count of false values |
-| `Empty` | Any | Count of empty values |
-| `Filled` | Any | Count of non-empty values |
-| `Unique` | Any | Count of unique values |
-
-## Complete Examples
-
-### Task Tracker Base
+Configure display names and settings for properties in views:
```yaml
-filters:
- and:
- - file.hasTag("task")
- - 'file.ext == "md"'
-
-formulas:
- days_until_due: 'if(due, ((date(due) - today()) / 86400000).round(0), "")'
- is_overdue: 'if(due, date(due) < today() && status != "done", false)'
- priority_label: 'if(priority == 1, "๐ด High", if(priority == 2, "๐ก Medium", "๐ข Low"))'
-
properties:
- status:
- displayName: Status
- formula.days_until_due:
- displayName: "Days Until Due"
+ author:
+ displayName: "Book Author"
formula.priority_label:
- displayName: Priority
-
-views:
- - type: table
- name: "Active Tasks"
- filters:
- and:
- - 'status != "done"'
- order:
- - file.name
- - status
- - formula.priority_label
- - due
- - formula.days_until_due
- groupBy:
- property: status
- direction: ASC
- summaries:
- formula.days_until_due: Average
-
- - type: table
- name: "Completed"
- filters:
- and:
- - 'status == "done"'
- order:
- - file.name
- - completed_date
+ displayName: "Priority"
```
-### Reading List Base
-
-```yaml
-filters:
- or:
- - file.hasTag("book")
- - file.hasTag("article")
-
-formulas:
- reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
- status_icon: 'if(status == "reading", "๐", if(status == "done", "โ
", "๐"))'
- year_read: 'if(finished_date, date(finished_date).year, "")'
+Three types of properties:
-properties:
- author:
- displayName: Author
- formula.status_icon:
- displayName: ""
- formula.reading_time:
- displayName: "Est. Time"
+- **Note properties** - From frontmatter: `author`, `status`, etc.
+- **File properties** - Built-in: `file.name`, `file.mtime`, `file.tags`, etc.
+- **Formula properties** - Computed: `formula.my_formula`
-views:
- - type: cards
- name: "Library"
- order:
- - cover
- - file.name
- - author
- - formula.status_icon
- filters:
- not:
- - 'status == "dropped"'
-
- - type: table
- name: "Reading List"
- filters:
- and:
- - 'status == "to-read"'
- order:
- - file.name
- - author
- - pages
- - formula.reading_time
-```
+## Views
-### Project Notes Base
+Display your data in multiple formats. Each view can have its own filters, ordering, and grouping.
-```yaml
-filters:
- and:
- - file.inFolder("Projects")
- - 'file.ext == "md"'
+**View types:**
-formulas:
- last_updated: 'file.mtime.relative()'
- link_count: 'file.links.length'
-
-summaries:
- avgLinks: 'values.filter(value.isType("number")).mean().round(1)'
+- `table` - Column-based display with sorting, grouping, summaries
+- `cards` - Visual card layout ideal for image-heavy content
+- `list` - Simple list view for quick scanning
+- `map` - Geographic visualization (requires location data)
-properties:
- formula.last_updated:
- displayName: "Updated"
- formula.link_count:
- displayName: "Links"
+For complete view documentation and options, see [Views](views.md).
-views:
- - type: table
- name: "All Projects"
- order:
- - file.name
- - status
- - formula.last_updated
- - formula.link_count
- summaries:
- formula.link_count: avgLinks
- groupBy:
- property: status
- direction: ASC
-
- - type: list
- name: "Quick List"
- order:
- - file.name
- - status
-```
+## Summaries
-### Daily Notes Index
+Add aggregation rows to table views:
```yaml
-filters:
- and:
- - file.inFolder("Daily Notes")
- - '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'
-
-formulas:
- word_estimate: '(file.size / 5).round(0)'
- day_of_week: 'date(file.basename).format("dddd")'
-
-properties:
- formula.day_of_week:
- displayName: "Day"
- formula.word_estimate:
- displayName: "~Words"
-
-views:
- - type: table
- name: "Recent Notes"
- limit: 30
- order:
- - file.name
- - formula.day_of_week
- - formula.word_estimate
- - file.mtime
+summaries:
+ priority: Average
+ completed_date: Latest
+ tasks_count: Sum
```
+Available aggregation functions: `Sum`, `Count`, `Average`, `Min`, `Max`, `Median`, `Stddev`, `Earliest`, `Latest`, `Range`, `Checked`, `Unchecked`, `Empty`, `Filled`, `Unique`
+
## Embedding Bases
-Embed in Markdown files:
+Embed a base file in Markdown notes:
```markdown
![[MyBase.base]]
-
-![[MyBase.base#View Name]]
+![[MyBase.base#View Name]] # Specific view
```
## YAML Quoting Rules
-- Use single quotes for formulas containing double quotes: `'if(done, "Yes", "No")'`
-- Use double quotes for simple strings: `"My View Name"`
+- Use **single quotes** for formulas containing double quotes: `'if(done, "Yes", "No")'`
+- Use **double quotes** for simple strings: `"My View Name"`
- Escape nested quotes properly in complex expressions
-## Common Patterns
+## Properties Reference
-### Filter by Tag
-```yaml
-filters:
- and:
- - file.hasTag("project")
-```
+### File Properties
-### Filter by Folder
-```yaml
-filters:
- and:
- - file.inFolder("Notes")
-```
+| Property | Type | Description |
+| ------------- | ------ | ------------------ |
+| `file.name` | String | File name |
+| `file.path` | String | Full path to file |
+| `file.folder` | String | Parent folder path |
+| `file.mtime` | Date | Modified time |
+| `file.ctime` | Date | Created time |
+| `file.tags` | List | All tags in file |
+| `file.links` | List | Internal links |
+| `file.size` | Number | File size in bytes |
-### Filter by Date Range
-```yaml
-filters:
- and:
- - 'file.mtime > now() - "7d"'
-```
+### Common Properties
-### Filter by Property Value
-```yaml
-filters:
- and:
- - 'status == "active"'
- - 'priority >= 3'
-```
+The `this` keyword refers to:
-### Combine Multiple Conditions
-```yaml
-filters:
- or:
- - and:
- - file.hasTag("important")
- - 'status != "done"'
- - and:
- - 'priority == 1'
- - 'due != ""'
-```
+- In main content area: the base file itself
+- When embedded: the embedding file
+- In sidebar: the active file in main content
+
+## Reference Guides
+
+- [Filters](filters.md) - Complete filter syntax and patterns
+- [Formulas](formulas.md) - Formula syntax and common examples
+- [Functions Reference](functions-reference.md) - All available functions and signatures
+- [Views](views.md) - View types and configuration options
+- [Examples](examples.md) - Real-world base file examples
-## References
+## External Resources
- [Bases Syntax](https://help.obsidian.md/bases/syntax)
- [Functions](https://help.obsidian.md/bases/functions)
diff --git a/skills/obsidian-bases/examples.md b/skills/obsidian-bases/examples.md
new file mode 100644
index 00000000..27176850
--- /dev/null
+++ b/skills/obsidian-bases/examples.md
@@ -0,0 +1,168 @@
+# Obsidian Bases: Examples
+
+Complete, runnable base file examples for common use cases.
+
+## Task Tracker Base
+
+```yaml
+filters:
+ and:
+ - file.hasTag("task")
+ - 'file.ext == "md"'
+
+formulas:
+ days_until_due: 'if(due, ((date(due) - today()) / 86400000).round(0), "")'
+ is_overdue: 'if(due, date(due) < today() && status != "done", false)'
+ priority_label: 'if(priority == 1, "๐ด High", if(priority == 2, "๐ก Medium", "๐ข Low"))'
+
+properties:
+ status:
+ displayName: Status
+ formula.days_until_due:
+ displayName: "Days Until Due"
+ formula.priority_label:
+ displayName: Priority
+
+views:
+ - type: table
+ name: "Active Tasks"
+ filters:
+ and:
+ - 'status != "done"'
+ order:
+ - file.name
+ - status
+ - formula.priority_label
+ - due
+ - formula.days_until_due
+ groupBy:
+ property: status
+ direction: ASC
+ summaries:
+ formula.days_until_due: Average
+
+ - type: table
+ name: "Completed"
+ filters:
+ and:
+ - 'status == "done"'
+ order:
+ - file.name
+ - completed_date
+```
+
+## Reading List Base
+
+```yaml
+filters:
+ or:
+ - file.hasTag("book")
+ - file.hasTag("article")
+
+formulas:
+ reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
+ status_icon: 'if(status == "reading", "๐", if(status == "done", "โ
", "๐"))'
+ year_read: 'if(finished_date, date(finished_date).year, "")'
+
+properties:
+ author:
+ displayName: Author
+ formula.status_icon:
+ displayName: ""
+ formula.reading_time:
+ displayName: "Est. Time"
+
+views:
+ - type: cards
+ name: "Library"
+ order:
+ - cover
+ - file.name
+ - author
+ - formula.status_icon
+ filters:
+ not:
+ - 'status == "dropped"'
+
+ - type: table
+ name: "Reading List"
+ filters:
+ and:
+ - 'status == "to-read"'
+ order:
+ - file.name
+ - author
+ - pages
+ - formula.reading_time
+```
+
+## Project Notes Base
+
+```yaml
+filters:
+ and:
+ - file.inFolder("Projects")
+ - 'file.ext == "md"'
+
+formulas:
+ last_updated: "file.mtime.relative()"
+ link_count: "file.links.length"
+
+summaries:
+ avgLinks: 'values.filter(value.isType("number")).mean().round(1)'
+
+properties:
+ formula.last_updated:
+ displayName: "Updated"
+ formula.link_count:
+ displayName: "Links"
+
+views:
+ - type: table
+ name: "All Projects"
+ order:
+ - file.name
+ - status
+ - formula.last_updated
+ - formula.link_count
+ summaries:
+ formula.link_count: avgLinks
+ groupBy:
+ property: status
+ direction: ASC
+
+ - type: list
+ name: "Quick List"
+ order:
+ - file.name
+ - status
+```
+
+## Daily Notes Index
+
+```yaml
+filters:
+ and:
+ - file.inFolder("Daily Notes")
+ - '/^\d{4}-\d{2}-\d{2}$/.matches(file.basename)'
+
+formulas:
+ word_estimate: "(file.size / 5).round(0)"
+ day_of_week: 'date(file.basename).format("dddd")'
+
+properties:
+ formula.day_of_week:
+ displayName: "Day"
+ formula.word_estimate:
+ displayName: "~Words"
+
+views:
+ - type: table
+ name: "Recent Notes"
+ limit: 30
+ order:
+ - file.name
+ - formula.day_of_week
+ - formula.word_estimate
+ - file.mtime
+```
diff --git a/skills/obsidian-bases/filters.md b/skills/obsidian-bases/filters.md
new file mode 100644
index 00000000..bb266cbe
--- /dev/null
+++ b/skills/obsidian-bases/filters.md
@@ -0,0 +1,115 @@
+# Obsidian Bases: Filters
+
+Filters narrow down results to show only matching items. They can be applied globally (to all views) or per-view.
+
+## Filter Structure
+
+### Single Filter String
+
+```yaml
+filters: 'status == "done"'
+```
+
+### AND - All Conditions Must Be True
+
+```yaml
+filters:
+ and:
+ - 'status == "done"'
+ - "priority > 3"
+```
+
+### OR - Any Condition Can Be True
+
+```yaml
+filters:
+ or:
+ - 'file.hasTag("book")'
+ - 'file.hasTag("article")'
+```
+
+### NOT - Exclude Matching Items
+
+```yaml
+filters:
+ not:
+ - 'file.hasTag("archived")'
+```
+
+### Nested Filters
+
+Combine conditions with nested logic:
+
+```yaml
+filters:
+ or:
+ - file.hasTag("tag")
+ - and:
+ - file.hasTag("book")
+ - file.hasLink("Textbook")
+ - not:
+ - file.hasTag("book")
+ - file.inFolder("Required Reading")
+```
+
+## Filter Operators
+
+| Operator | Description |
+| -------------- | --------------------- |
+| `==` | equals |
+| `!=` | not equal |
+| `>` | greater than |
+| `<` | less than |
+| `>=` | greater than or equal |
+| `<=` | less than or equal |
+| `&&` | logical and |
+| `\|\|` | logical or |
+| ! | logical not |
+
+## Common Filter Patterns
+
+### Filter by Tag
+
+```yaml
+filters:
+ and:
+ - file.hasTag("project")
+```
+
+### Filter by Folder
+
+```yaml
+filters:
+ and:
+ - file.inFolder("Notes")
+```
+
+### Filter by Date Range
+
+```yaml
+filters:
+ and:
+ - 'file.mtime > now() - "7d"'
+```
+
+### Filter by Property Value
+
+```yaml
+filters:
+ and:
+ - 'status == "active"'
+ - "priority >= 3"
+```
+
+### Combine Multiple Conditions
+
+```yaml
+filters:
+ or:
+ - and:
+ - file.hasTag("important")
+ - 'status != "done"'
+ - and:
+ - "priority == 1"
+ - 'due != ""'
+```
diff --git a/skills/obsidian-bases/formulas.md b/skills/obsidian-bases/formulas.md
new file mode 100644
index 00000000..079420d0
--- /dev/null
+++ b/skills/obsidian-bases/formulas.md
@@ -0,0 +1,122 @@
+# Obsidian Bases: Formulas
+
+Formulas compute dynamic values from properties and other data. Defined in the `formulas` section of a base file.
+
+## Formula Basics
+
+```yaml
+formulas:
+ # Simple arithmetic
+ total: "price * quantity"
+
+ # Conditional logic
+ status_icon: 'if(done, "โ
", "โณ")'
+
+ # String formatting
+ formatted_price: 'if(price, price.toFixed(2) + " dollars")'
+
+ # Date formatting
+ created: 'file.ctime.format("YYYY-MM-DD")'
+
+ # Complex expressions
+ days_old: "((now() - file.ctime) / 86400000).round(0)"
+```
+
+## YAML Quoting Rules
+
+- Use **single quotes** for formulas containing double quotes: `'if(done, "Yes", "No")'`
+- Use **double quotes** for simple strings: `"My View Name"`
+- Escape nested quotes properly in complex expressions
+
+## Formula Functions
+
+For a complete reference of available functions, see [Functions Reference](functions-reference.md).
+
+### Global Functions
+
+| Function | Description |
+| ------------- | --------------------------------------------------- |
+| `date()` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
+| `duration()` | Parse duration string |
+| `now()` | Current date and time |
+| `today()` | Current date (time = 00:00:00) |
+| `if()` | Conditional |
+| `min()/max()` | Smallest/largest number |
+| `number()` | Convert to number |
+| `link()` | Create a link |
+
+### Date Arithmetic
+
+Duration units: `y/year/years`, `M/month/months`, `d/day/days`, `w/week/weeks`, `h/hour/hours`, `m/minute/minutes`, `s/second/seconds`
+
+```yaml
+# Add/subtract durations
+"date + \"1M\"" # Add 1 month
+"date - \"2h\"" # Subtract 2 hours
+"now() + \"1 day\"" # Tomorrow
+"today() + \"7d\"" # A week from today
+
+# Subtract dates for millisecond difference
+"now() - file.ctime"
+
+# Complex duration arithmetic
+"now() + (duration('1d') * 2)"
+```
+
+## Formula Examples
+
+### Status Indicators
+
+```yaml
+priority_label: 'if(priority == 1, "๐ด High", if(priority == 2, "๐ก Medium", "๐ข Low"))'
+is_overdue: 'if(due, date(due) < today() && status != "done", false)'
+```
+
+### Date Calculations
+
+```yaml
+days_until_due: 'if(due, ((date(due) - today()) / 86400000).round(0), "")'
+days_old: "((now() - file.ctime) / 86400000).round(0)"
+day_of_week: 'date(file.basename).format("dddd")'
+```
+
+### String Operations
+
+```yaml
+formatted_date: 'file.ctime.format("MMM DD, YYYY")'
+reading_time: 'if(pages, (pages * 2).toString() + " min", "")'
+word_estimate: "(file.size / 5).round(0)"
+```
+
+### Counting and Stats
+
+```yaml
+link_count: "file.links.length"
+tag_count: "file.tags.length"
+has_content: "file.size > 100"
+```
+
+## Using Formulas in Views
+
+Reference formulas in view properties using the `formula.` prefix:
+
+```yaml
+views:
+ - type: table
+ name: "Active Tasks"
+ order:
+ - file.name
+ - status
+ - formula.priority_label # Reference defined formula
+ - due
+ - formula.days_until_due # Reference another formula
+```
+
+## Performance Tips
+
+- Keep formulas simple for faster calculation
+- Use conditional logic to avoid unnecessary computations
+- Cache repeated calculations by defining them once as formulas
+- Avoid deeply nested conditions (keep 2-3 levels max)
+
+For complete function signatures and advanced usage, see [Functions Reference](functions-reference.md).
diff --git a/skills/obsidian-bases/functions-reference.md b/skills/obsidian-bases/functions-reference.md
new file mode 100644
index 00000000..e95dde60
--- /dev/null
+++ b/skills/obsidian-bases/functions-reference.md
@@ -0,0 +1,150 @@
+# Obsidian Bases: Functions Reference
+
+Complete function signatures and usage for all available functions in Obsidian Bases formulas.
+
+## Global Functions
+
+| Function | Signature | Description |
+| -------------- | ----------------------------------------- | --------------------------------------------------- |
+| `date()` | `date(string): date` | Parse string to date. Format: `YYYY-MM-DD HH:mm:ss` |
+| `duration()` | `duration(string): duration` | Parse duration string |
+| `now()` | `now(): date` | Current date and time |
+| `today()` | `today(): date` | Current date (time = 00:00:00) |
+| `if()` | `if(condition, trueResult, falseResult?)` | Conditional |
+| `min()` | `min(n1, n2, ...): number` | Smallest number |
+| `max()` | `max(n1, n2, ...): number` | Largest number |
+| `number()` | `number(any): number` | Convert to number |
+| `link()` | `link(path, display?): Link` | Create a link |
+| `list()` | `list(element): List` | Wrap in list if not already |
+| `file()` | `file(path): file` | Get file object |
+| `image()` | `image(path): image` | Create image for rendering |
+| `icon()` | `icon(name): icon` | Lucide icon by name |
+| `html()` | `html(string): html` | Render as HTML |
+| `escapeHTML()` | `escapeHTML(string): string` | Escape HTML characters |
+
+## Any Type Functions
+
+Available on any value:
+
+| Function | Signature | Description |
+| ------------ | --------------------------- | ----------------- |
+| `isTruthy()` | `any.isTruthy(): boolean` | Coerce to boolean |
+| `isType()` | `any.isType(type): boolean` | Check type |
+| `toString()` | `any.toString(): string` | Convert to string |
+
+## Date Functions
+
+**Fields:** `date.year`, `date.month`, `date.day`, `date.hour`, `date.minute`, `date.second`, `date.millisecond`
+
+| Function | Signature | Description |
+| ------------ | ----------------------------- | ----------------------------- |
+| `date()` | `date.date(): date` | Remove time portion |
+| `format()` | `date.format(string): string` | Format with Moment.js pattern |
+| `time()` | `date.time(): string` | Get time as string |
+| `relative()` | `date.relative(): string` | Human-readable relative time |
+| `isEmpty()` | `date.isEmpty(): boolean` | Always false for dates |
+
+## String Functions
+
+**Field:** `string.length`
+
+| Function | Signature | Description |
+| --------------- | ---------------------------------------------- | ---------------------- |
+| `contains()` | `string.contains(value): boolean` | Check substring |
+| `containsAll()` | `string.containsAll(...values): boolean` | All substrings present |
+| `containsAny()` | `string.containsAny(...values): boolean` | Any substring present |
+| `startsWith()` | `string.startsWith(query): boolean` | Starts with query |
+| `endsWith()` | `string.endsWith(query): boolean` | Ends with query |
+| `isEmpty()` | `string.isEmpty(): boolean` | Empty or not present |
+| `lower()` | `string.lower(): string` | To lowercase |
+| `title()` | `string.title(): string` | To Title Case |
+| `trim()` | `string.trim(): string` | Remove whitespace |
+| `replace()` | `string.replace(pattern, replacement): string` | Replace pattern |
+| `repeat()` | `string.repeat(count): string` | Repeat string |
+| `reverse()` | `string.reverse(): string` | Reverse string |
+| `slice()` | `string.slice(start, end?): string` | Substring |
+| `split()` | `string.split(delimiter): list` | Split into list |
+| `join()` | `string.join(other): string` | Join list elements |
+
+## Number Functions
+
+| Function | Signature | Description |
+| ----------- | -------------------------------- | ------------------- |
+| `abs()` | `number.abs(): number` | Absolute value |
+| `ceil()` | `number.ceil(): number` | Round up |
+| `floor()` | `number.floor(): number` | Round down |
+| `round()` | `number.round(digits?): number` | Round to N decimals |
+| `sqrt()` | `number.sqrt(): number` | Square root |
+| `pow()` | `number.pow(exponent): number` | Raise to power |
+| `min()` | `number.min(other): number` | Smaller of two |
+| `max()` | `number.max(other): number` | Larger of two |
+| `clamp()` | `number.clamp(min, max): number` | Constrain to range |
+| `toFixed()` | `number.toFixed(digits): string` | Fixed decimals |
+
+## List Functions
+
+| Function | Signature | Description |
+| ------------ | ------------------------------- | ---------------------- |
+| `first()` | `list.first(): any` | First element |
+| `last()` | `list.last(): any` | Last element |
+| `at()` | `list.at(index): any` | Element at index |
+| `length` | `list.length: number` | Number of elements |
+| `reverse()` | `list.reverse(): list` | Reverse order |
+| `sort()` | `list.sort(): list` | Sort ascending |
+| `map()` | `list.map(fn): list` | Transform each element |
+| `filter()` | `list.filter(fn): list` | Keep matching elements |
+| `find()` | `list.find(fn): any` | First matching element |
+| `includes()` | `list.includes(value): boolean` | Contains value |
+| `isEmpty()` | `list.isEmpty(): boolean` | Empty or not present |
+| `join()` | `list.join(separator): string` | Join as string |
+| `concat()` | `list.concat(other): list` | Combine lists |
+| `flatten()` | `list.flatten(): list` | Flatten nested lists |
+| `unique()` | `list.unique(): list` | Remove duplicates |
+
+## List Aggregation Functions
+
+| Function | Parameter Type | Return Type | Description |
+| ----------- | -------------- | ----------- | ------------------------- |
+| `Sum` | Number | Number | Total of all values |
+| `Count` | Any | Number | Count of non-empty values |
+| `Average` | Number | Number | Mathematical average |
+| `Min` | Number | Number | Smallest value |
+| `Max` | Number | Number | Largest value |
+| `Median` | Number | Number | Mathematical median |
+| `Stddev` | Number | Number | Standard deviation |
+| `Earliest` | Date | Date | Earliest date |
+| `Latest` | Date | Date | Latest date |
+| `Range` | Date | Date | Latest - Earliest |
+| `Checked` | Boolean | Number | Count of true values |
+| `Unchecked` | Boolean | Number | Count of false values |
+| `Empty` | Any | Number | Count of empty values |
+| `Filled` | Any | Number | Count of non-empty values |
+| `Unique` | Any | Number | Count of unique values |
+
+## File Functions
+
+Functions available on `file` objects:
+
+| Function | Signature | Description |
+| ------------ | ------------------------------ | ------------------ |
+| `hasTag()` | `file.hasTag(tag): boolean` | Has specific tag |
+| `hasLink()` | `file.hasLink(link): boolean` | Has link to target |
+| `inFolder()` | `file.inFolder(path): boolean` | In specific folder |
+
+### File Properties
+
+| Property | Type | Description |
+| ----------------- | ------ | --------------------------- |
+| `file.name` | String | File name |
+| `file.basename` | String | File name without extension |
+| `file.path` | String | Full path to file |
+| `file.folder` | String | Parent folder path |
+| `file.ext` | String | File extension |
+| `file.size` | Number | File size in bytes |
+| `file.ctime` | Date | Created time |
+| `file.mtime` | Date | Modified time |
+| `file.tags` | List | All tags in file |
+| `file.links` | List | Internal links in file |
+| `file.backlinks` | List | Files linking to this file |
+| `file.embeds` | List | Embeds in the note |
+| `file.properties` | Object | All frontmatter properties |
diff --git a/skills/obsidian-bases/views.md b/skills/obsidian-bases/views.md
new file mode 100644
index 00000000..58ada678
--- /dev/null
+++ b/skills/obsidian-bases/views.md
@@ -0,0 +1,168 @@
+# Obsidian Bases: Views
+
+Views are different presentations of your data. Each base can contain multiple views of the same data with different filters, grouping, and display options.
+
+## View Types
+
+### Table View
+
+Displays data in a table format with columns and rows.
+
+```yaml
+views:
+ - type: table
+ name: "Active Tasks"
+ limit: 10 # Optional: limit results
+ order:
+ - file.name
+ - status
+ - priority
+ groupBy:
+ property: status
+ direction: ASC
+ filters:
+ and:
+ - 'status != "done"'
+ summaries:
+ priority: Average
+```
+
+**Table Features:**
+
+- Column ordering with `order`
+- Row grouping with `groupBy`
+- Row limits with `limit`
+- Summary rows with `summaries`
+- Sortable columns
+- Filterable by column values
+
+### Card View
+
+Displays each item as a visual card.
+
+```yaml
+views:
+ - type: cards
+ name: "Library"
+ order:
+ - cover
+ - file.name
+ - author
+ - status
+ filters:
+ not:
+ - 'status == "dropped"'
+```
+
+**Card Features:**
+
+- Visual preview of properties
+- Customizable field display order
+- Grouped layout
+- Good for image-heavy content
+
+### List View
+
+Simple list of matching items with key properties.
+
+```yaml
+views:
+ - type: list
+ name: "Quick List"
+ order:
+ - file.name
+ - status
+```
+
+**List Features:**
+
+- Compact display
+- Quick scanning
+- Title-focused
+- Ideal for large datasets
+
+### Map View
+
+Displays items with geographic coordinates (requires location properties).
+
+```yaml
+views:
+ - type: map
+ name: "Locations"
+ latitude: latitude_property
+ longitude: longitude_property
+```
+
+**Map Features:**
+
+- Geographic visualization
+- Clustering
+- Interactive pan/zoom
+- Requires location data in frontmatter
+
+## View Properties
+
+All views support these common properties:
+
+| Property | Required | Description |
+| ----------- | -------- | ----------------------------------------------- |
+| `type` | Yes | View type: `table`, `cards`, `list`, or `map` |
+| `name` | Yes | Display name for the view |
+| `order` | No | Array of properties to display in order |
+| `filters` | No | View-specific filters (in addition to global) |
+| `groupBy` | No | Group results by property (table views) |
+| `limit` | No | Maximum number of items to display |
+| `summaries` | No | Aggregation functions for columns (table views) |
+
+## View Ordering
+
+The `order` property controls which columns/fields display and in what sequence:
+
+```yaml
+order:
+ - file.name
+ - status
+ - formula.priority_label
+ - due
+ - formula.days_until_due
+```
+
+Use property names (from frontmatter), file properties (`file.name`, `file.mtime`), or formulas (`formula.formula_name`).
+
+## View Grouping
+
+Group results by a property using `groupBy`:
+
+```yaml
+groupBy:
+ property: status
+ direction: ASC
+```
+
+- `property`: Which property to group by
+- `direction`: `ASC` (ascending) or `DESC` (descending)
+
+## View Limits
+
+Limit the number of items displayed:
+
+```yaml
+limit: 50 # Show only first 50 items
+```
+
+Useful for views of large datasets or "recent items" views.
+
+## View Summaries
+
+Add aggregation rows to table views:
+
+```yaml
+summaries:
+ priority: Average
+ completed_date: Latest
+ tasks_count: Sum
+```
+
+Available aggregation functions: `Sum`, `Count`, `Average`, `Min`, `Max`, `Median`, `Stddev`, `Earliest`, `Latest`, `Range`, `Checked`, `Unchecked`, `Empty`, `Filled`, `Unique`
+
+See [Functions Reference](functions-reference.md) for details.
diff --git a/skills/obsidian-markdown/SKILL.md b/skills/obsidian-markdown/SKILL.md
index 2fb45c5c..c511493c 100644
--- a/skills/obsidian-markdown/SKILL.md
+++ b/skills/obsidian-markdown/SKILL.md
@@ -1,615 +1,260 @@
---
name: obsidian-markdown
-description: Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
+description: Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, linking notes, or when the user mentions wikilinks, internal links, backlinks, callouts, frontmatter, YAML, tags, embeds, or metadata.
+allowed-tools: [Read, Write, Edit, Glob]
---
# Obsidian Flavored Markdown Skill
-This skill enables skills-compatible agents to create and edit valid Obsidian Flavored Markdown, including all Obsidian-specific syntax extensions.
+This skill enables agents to create and edit valid Obsidian Flavored Markdown, including all Obsidian-specific syntax extensions.
## Overview
-Obsidian uses a combination of Markdown flavors:
-- [CommonMark](https://commonmark.org/)
-- [GitHub Flavored Markdown](https://github.github.com/gfm/)
-- [LaTeX](https://www.latex-project.org/) for math
-- Obsidian-specific extensions (wikilinks, callouts, embeds, etc.)
+Obsidian uses a combination of standard and extended Markdown flavors:
-## Basic Formatting
+- [CommonMark](https://commonmark.org/) - Standard Markdown
+- [GitHub Flavored Markdown](https://github.github.com/gfm/) - GFM extensions
+- [LaTeX](https://www.latex-project.org/) - Math notation
+- **Obsidian extensions** - Wikilinks, callouts, embeds, properties, and more
-### Paragraphs and Line Breaks
+## Basic Formatting
-```markdown
-This is a paragraph.
+### Text Styles
-This is another paragraph (blank line between creates separate paragraphs).
-
-For a line break within a paragraph, add two spaces at the end
-or use Shift+Enter.
-```
+| Style | Syntax | Example |
+| ------------- | ------------------------ | --------------- |
+| Bold | `**text**` or `__text__` | **Bold** |
+| Italic | `*text*` or `_text_` | _Italic_ |
+| Bold + Italic | `***text***` | **_Both_** |
+| Strikethrough | `~~text~~` | ~~Striked~~ |
+| Highlight | `==text==` | ==Highlighted== |
+| Inline code | `` `code` `` | `code` |
### Headings
```markdown
# Heading 1
+
## Heading 2
+
### Heading 3
+
#### Heading 4
+
##### Heading 5
+
###### Heading 6
```
-### Text Formatting
-
-| Style | Syntax | Example | Output |
-|-------|--------|---------|--------|
-| Bold | `**text**` or `__text__` | `**Bold**` | **Bold** |
-| Italic | `*text*` or `_text_` | `*Italic*` | *Italic* |
-| Bold + Italic | `***text***` | `***Both***` | ***Both*** |
-| Strikethrough | `~~text~~` | `~~Striked~~` | ~~Striked~~ |
-| Highlight | `==text==` | `==Highlighted==` | ==Highlighted== |
-| Inline code | `` `code` `` | `` `code` `` | `code` |
-
-### Escaping Formatting
+### Escaping
Use backslash to escape special characters:
+
```markdown
\*This won't be italic\*
\#This won't be a heading
1\. This won't be a list item
```
-Common characters to escape: `\*`, `\_`, `\#`, `` \` ``, `\|`, `\~`
-
-## Internal Links (Wikilinks)
+## Wikilinks
-### Basic Links
+Obsidian-specific links to other notes creating bidirectional relationships.
```markdown
[[Note Name]]
-[[Note Name.md]]
[[Note Name|Display Text]]
-```
-
-### Link to Headings
-
-```markdown
[[Note Name#Heading]]
-[[Note Name#Heading|Custom Text]]
-[[#Heading in same note]]
-[[##Search all headings in vault]]
-```
-
-### Link to Blocks
-
-```markdown
[[Note Name#^block-id]]
-[[Note Name#^block-id|Custom Text]]
-```
-
-Define a block ID by adding `^block-id` at the end of a paragraph:
-```markdown
-This is a paragraph that can be linked to. ^my-block-id
-```
-
-For lists and quotes, add the block ID on a separate line:
-```markdown
-> This is a quote
-> With multiple lines
-
-^quote-id
-```
-
-### Search Links
-
-```markdown
-[[##heading]] Search for headings containing "heading"
-[[^^block]] Search for blocks containing "block"
+[[#Heading in same note]]
```
-## Markdown-Style Links
+Wikilinks automatically appear in backlinks and the Graph view.
-```markdown
-[Display Text](Note%20Name.md)
-[Display Text](Note%20Name.md#Heading)
-[Display Text](https://example.com)
-[Note](obsidian://open?vault=VaultName&file=Note.md)
-```
-
-Note: Spaces must be URL-encoded as `%20` in Markdown links.
+See [Wikilinks](wikilinks.md) for complete documentation.
## Embeds
-### Embed Notes
+Include content from other files or external sources inline.
```markdown
![[Note Name]]
![[Note Name#Heading]]
-![[Note Name#^block-id]]
-```
-
-### Embed Images
-
-```markdown
-![[image.png]]
-![[image.png|640x480]] Width x Height
-![[image.png|300]] Width only (maintains aspect ratio)
-```
-
-### External Images
-
-```markdown
-
-
-```
-
-### Embed Audio
-
-```markdown
+![[image.png|300]]
![[audio.mp3]]
-![[audio.ogg]]
-```
-
-### Embed PDF
-
-```markdown
-![[document.pdf]]
![[document.pdf#page=3]]
-![[document.pdf#height=400]]
-```
-
-### Embed Lists
-
-```markdown
-![[Note#^list-id]]
```
-Where the list has been defined with a block ID:
-```markdown
-- Item 1
-- Item 2
-- Item 3
-
-^list-id
-```
+Embedded content updates automatically when the source changes.
-### Embed Search Results
-
-````markdown
-```query
-tag:#project status:done
-```
-````
+See [Embeds](embeds.md) for all embed types and options.
## Callouts
-### Basic Callout
+Styled blocks that highlight important information.
```markdown
> [!note]
-> This is a note callout.
+> This is a note.
-> [!info] Custom Title
-> This callout has a custom title.
+> [!warning] Custom Title
+> Important warning text.
-> [!tip] Title Only
+> [!tip]+ Expanded by default
+> Collapsible tip content.
```
-### Foldable Callouts
+Supported types: `note`, `abstract`, `info`, `todo`, `tip`, `success`, `question`, `warning`, `failure`, `danger`, `bug`, `example`, `quote`
-```markdown
-> [!faq]- Collapsed by default
-> This content is hidden until expanded.
+See [Callouts](callouts.md) for all types and custom callouts.
-> [!faq]+ Expanded by default
-> This content is visible but can be collapsed.
-```
+## Properties (Frontmatter)
-### Nested Callouts
+Add structured metadata at the start of a note:
-```markdown
-> [!question] Outer callout
-> > [!note] Inner callout
-> > Nested content
+```yaml
+---
+title: My Note
+date: 2024-01-15
+tags:
+ - project
+ - important
+status: active
+rating: 4.5
+due: 2024-02-01T14:30:00
+---
```
-### Supported Callout Types
-
-| Type | Aliases | Description |
-|------|---------|-------------|
-| `note` | - | Blue, pencil icon |
-| `abstract` | `summary`, `tldr` | Teal, clipboard icon |
-| `info` | - | Blue, info icon |
-| `todo` | - | Blue, checkbox icon |
-| `tip` | `hint`, `important` | Cyan, flame icon |
-| `success` | `check`, `done` | Green, checkmark icon |
-| `question` | `help`, `faq` | Yellow, question mark |
-| `warning` | `caution`, `attention` | Orange, warning icon |
-| `failure` | `fail`, `missing` | Red, X icon |
-| `danger` | `error` | Red, zap icon |
-| `bug` | - | Red, bug icon |
-| `example` | - | Purple, list icon |
-| `quote` | `cite` | Gray, quote icon |
-
-### Custom Callouts (CSS)
-
-```css
-.callout[data-callout="custom-type"] {
- --callout-color: 255, 0, 0;
- --callout-icon: lucide-alert-circle;
-}
-```
+Properties enable querying in Bases and appear in the Properties panel.
-## Lists
+## Tags
-### Unordered Lists
+Categorize notes with inline tags:
```markdown
-- Item 1
-- Item 2
- - Nested item
- - Another nested
-- Item 3
-
-* Also works with asterisks
-+ Or plus signs
+#project
+#nested/tag
+#tag-with-dashes
+#tag_with_underscores
```
-### Ordered Lists
+Tags can be:
-```markdown
-1. First item
-2. Second item
- 1. Nested numbered
- 2. Another nested
-3. Third item
-
-1) Alternative syntax
-2) With parentheses
-```
+- Inline in content
+- Defined in frontmatter
+- Organized hierarchically with forward slashes
+- Viewed in tag pane and graph
-### Task Lists
+See [Properties & Tags](properties-tags.md) for full reference.
-```markdown
-- [ ] Incomplete task
-- [x] Completed task
-- [ ] Task with sub-tasks
- - [ ] Subtask 1
- - [x] Subtask 2
-```
+## Markdown-Style Links
-## Quotes
+Standard Markdown links work alongside wikilinks:
```markdown
-> This is a blockquote.
-> It can span multiple lines.
->
-> And include multiple paragraphs.
->
-> > Nested quotes work too.
+[Display Text](Note%20Name.md)
+[Display Text](Note%20Name.md#Heading)
+[Display Text](https://example.com)
+[Note](obsidian://open?vault=VaultName&file=Note.md)
```
-## Code
+Note: Spaces must be URL-encoded as `%20`.
-### Inline Code
+## Lists and Checkboxes
```markdown
-Use `backticks` for inline code.
-Use double backticks for ``code with a ` backtick inside``.
-```
-
-### Code Blocks
-
-````markdown
-```
-Plain code block
-```
-
-```javascript
-// Syntax highlighted code block
-function hello() {
- console.log("Hello, world!");
-}
-```
-
-```python
-# Python example
-def greet(name):
- print(f"Hello, {name}!")
-```
-````
-
-### Nesting Code Blocks
-
-Use more backticks or tildes for the outer block:
-
-`````markdown
-````markdown
-Here's how to create a code block:
-```js
-console.log("Hello")
-```
-````
-`````
+- Unordered list
+ - Nested item
+ - Deeper nesting
-## Tables
+1. Ordered list
+2. Second item
+ 1. Nested ordered
-```markdown
-| Header 1 | Header 2 | Header 3 |
-|----------|----------|----------|
-| Cell 1 | Cell 2 | Cell 3 |
-| Cell 4 | Cell 5 | Cell 6 |
+- [ ] Unchecked task
+- [x] Completed task
+ - [x] Subtask done
+ - [ ] Subtask pending
```
-### Alignment
+## Blockquotes
```markdown
-| Left | Center | Right |
-|:---------|:--------:|---------:|
-| Left | Center | Right |
+> This is a blockquote
+> spanning multiple lines
+>
+> With multiple paragraphs
```
-### Using Pipes in Tables
+## Advanced Features
-Escape pipes with backslash:
-```markdown
-| Column 1 | Column 2 |
-|----------|----------|
-| [[Link\|Display]] | ![[Image\|100]] |
-```
+### Math
-## Math (LaTeX)
-
-### Inline Math
+Render LaTeX notation inline or in blocks:
```markdown
-This is inline math: $e^{i\pi} + 1 = 0$
-```
+Inline: $E = mc^2$
-### Block Math
+Display:
-```markdown
$$
-\begin{vmatrix}
-a & b \\
-c & d
-\end{vmatrix} = ad - bc
+\frac{1}{2}mv^2 = E_k
$$
```
-### Common Math Syntax
+### Diagrams
-```markdown
-$x^2$ Superscript
-$x_i$ Subscript
-$\frac{a}{b}$ Fraction
-$\sqrt{x}$ Square root
-$\sum_{i=1}^{n}$ Summation
-$\int_a^b$ Integral
-$\alpha, \beta$ Greek letters
-```
-
-## Diagrams (Mermaid)
+Create diagrams with Mermaid syntax:
````markdown
```mermaid
graph TD
A[Start] --> B{Decision}
- B -->|Yes| C[Do this]
- B -->|No| D[Do that]
- C --> E[End]
- D --> E
+ B -->|Yes| C[Path A]
+ B -->|No| D[Path B]
```
````
-### Sequence Diagrams
+### Footnotes
-````markdown
-```mermaid
-sequenceDiagram
- Alice->>Bob: Hello Bob
- Bob-->>Alice: Hi Alice
-```
-````
-
-### Linking in Diagrams
-
-````markdown
-```mermaid
-graph TD
- A[Biology]
- B[Chemistry]
- A --> B
- class A,B internal-link;
-```
-````
-
-## Footnotes
+Add citations and references:
```markdown
-This sentence has a footnote[^1].
-
-[^1]: This is the footnote content.
-
-You can also use named footnotes[^note].
-
-[^note]: Named footnotes still appear as numbers.
+This has a footnote[^1].
-Inline footnotes are also supported.^[This is an inline footnote.]
+[^1]: The footnote content appears at the bottom.
```
-## Comments
+### Comments
+
+Hide content from reading view:
```markdown
This is visible %%but this is hidden%% text.
%%
-This entire block is hidden.
-It won't appear in reading view.
+Entire block comment
+Not visible in reading view
%%
```
-## Horizontal Rules
+### Tables
```markdown
----
-***
-___
-- - -
-* * *
-```
-
-## Properties (Frontmatter)
-
-Properties use YAML frontmatter at the start of a note:
-
-```yaml
----
-title: My Note Title
-date: 2024-01-15
-tags:
- - project
- - important
-aliases:
- - My Note
- - Alternative Name
-cssclasses:
- - custom-class
-status: in-progress
-rating: 4.5
-completed: false
-due: 2024-02-01T14:30:00
----
+| Column 1 | Column 2 | Column 3 |
+| :------- | :------: | -------: |
+| Left | Center | Right |
```
-### Property Types
+## Reference Guides
-| Type | Example |
-|------|---------|
-| Text | `title: My Title` |
-| Number | `rating: 4.5` |
-| Checkbox | `completed: true` |
-| Date | `date: 2024-01-15` |
-| Date & Time | `due: 2024-01-15T14:30:00` |
-| List | `tags: [one, two]` or YAML list |
-| Links | `related: "[[Other Note]]"` |
-
-### Default Properties
-
-- `tags` - Note tags
-- `aliases` - Alternative names for the note
-- `cssclasses` - CSS classes applied to the note
-
-## Tags
-
-```markdown
-#tag
-#nested/tag
-#tag-with-dashes
-#tag_with_underscores
-
-In frontmatter:
----
-tags:
- - tag1
- - nested/tag2
----
-```
-
-Tags can contain:
-- Letters (any language)
-- Numbers (not as first character)
-- Underscores `_`
-- Hyphens `-`
-- Forward slashes `/` (for nesting)
-
-## HTML Content
-
-Obsidian supports HTML within Markdown:
-
-```markdown
-