{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/cnvs-app/json-schema/cnvs-app-board-snapshot-schema.json",
  "title": "BoardSnapshot",
  "description": "Full board state as returned by `/json/{id}` and the MCP `get_board` tool. Same shape as the `cnvs://board/{id}/state.json` MCP resource.",
  "x-generated": "2026-11-04",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/cnvs-app-openapi.yml#/components/schemas/BoardSnapshot",
  "type": "object",
  "properties": {
    "boardId": {
      "type": "string"
    },
    "mode": {
      "type": "string",
      "enum": [
        "draw",
        "todo"
      ],
      "description": "Board mode. `draw` (default) = infinite-canvas whiteboard; `todo` = kanban task board. Switchable whenever the board is empty of real content (drawings + tasks); empty/seeded columns don't count, so a cleared board can switch again."
    },
    "texts": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/TextNode"
      }
    },
    "lines": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/StrokeLine"
      }
    },
    "images": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/ImageNode"
      }
    },
    "columns": {
      "type": "array",
      "description": "Kanban columns (present / non-empty on `todo`-mode boards).",
      "items": {
        "$ref": "#/$defs/Column"
      }
    },
    "tasks": {
      "type": "array",
      "description": "Kanban tasks (present / non-empty on `todo`-mode boards).",
      "items": {
        "$ref": "#/$defs/Task"
      }
    }
  },
  "required": [
    "boardId",
    "texts",
    "lines",
    "images"
  ],
  "$defs": {
    "Column": {
      "type": "object",
      "description": "A kanban column on a `todo`-mode board.",
      "properties": {
        "id": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "sort": {
          "type": "number",
          "description": "Left-to-right order of the column."
        },
        "lane": {
          "type": "integer",
          "default": 0,
          "description": "Row index for multi-lane layout. Defaults to 0 (top row)."
        },
        "color": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "red",
            "blue",
            "green",
            null
          ],
          "description": "Column title color — one of `red` / `blue` / `green`, or null for the default."
        },
        "author": {
          "type": [
            "string",
            "null"
          ]
        },
        "last_updated": {
          "type": "string",
          "description": "UTC timestamp."
        }
      },
      "required": [
        "id",
        "title"
      ]
    },
    "ImageNode": {
      "type": "object",
      "description": "Image metadata. Full `dataUrl` is NOT included in this shape — fetch `/api/boards/{id}` if you need raw pixels.",
      "properties": {
        "id": {
          "type": "string"
        },
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        },
        "width": {
          "type": "number"
        },
        "height": {
          "type": "number"
        },
        "thumbDataUrl": {
          "type": [
            "string",
            "null"
          ],
          "description": "Tiny preview embedded in SVG previews (≤8 kB)."
        },
        "author": {
          "type": [
            "string",
            "null"
          ]
        },
        "last_updated": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "x",
        "y",
        "width",
        "height"
      ]
    },
    "StrokeLine": {
      "type": "object",
      "description": "A freehand stroke — an ordered list of world-coordinate points.",
      "properties": {
        "id": {
          "type": "string"
        },
        "points": {
          "type": "array",
          "description": "Array of `[x, y]` pairs in board world coordinates.",
          "items": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "minItems": 2,
            "maxItems": 2
          }
        },
        "color": {
          "type": [
            "string",
            "null"
          ]
        },
        "author": {
          "type": [
            "string",
            "null"
          ]
        },
        "last_updated": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "points"
      ]
    },
    "Task": {
      "type": "object",
      "description": "A kanban task (card) on a `todo`-mode board.",
      "properties": {
        "id": {
          "type": "string"
        },
        "column_id": {
          "type": "string",
          "description": "Column the card currently lives in."
        },
        "name": {
          "type": "string",
          "description": "Card title."
        },
        "content": {
          "type": [
            "string",
            "null"
          ],
          "description": "Opaque JSON string blob, e.g. `{\"description\":...}`. Treat as a blob — do not parse application meaning out of it. Max 20 000 chars."
        },
        "due_date": {
          "type": [
            "string",
            "null"
          ],
          "description": "ISO 8601 due date, or null."
        },
        "priority": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "H",
            "M",
            "L",
            null
          ],
          "description": "High / Medium / Low, or null."
        },
        "assignee": {
          "type": [
            "string",
            "null"
          ]
        },
        "done": {
          "type": "boolean",
          "description": "Whether the task is completed (checked off). Defaults to false."
        },
        "sort": {
          "type": "number",
          "description": "Order within the column."
        },
        "author": {
          "type": [
            "string",
            "null"
          ]
        },
        "last_updated": {
          "type": "string",
          "description": "UTC timestamp."
        }
      },
      "required": [
        "id",
        "column_id",
        "name"
      ]
    },
    "TextNode": {
      "type": "object",
      "description": "A draggable, editable text block. Content supports cnvs markup (Markdown-ish) and Mermaid diagrams. When a node contains Mermaid, its entire content is one ```mermaid fenced block (one diagram per node).",
      "properties": {
        "id": {
          "type": "string"
        },
        "x": {
          "type": "number",
          "description": "World x (+x right)."
        },
        "y": {
          "type": "number",
          "description": "World y (+y DOWN, standard SVG)."
        },
        "content": {
          "type": "string",
          "description": "Raw text content (up to 100 000 chars). May include Markdown, Mermaid, emoji."
        },
        "color": {
          "type": [
            "string",
            "null"
          ],
          "description": "Resolved ink value stored on the node — the palette hex for a named color (e.g. `red` → `#ff3b30`) or the CSS variable `var(--text-color)` for theme-aware ink. Writes accept only the color NAMES, never raw hex."
        },
        "width": {
          "type": [
            "number",
            "null"
          ],
          "description": "Explicit wrapping width in px, or null for auto."
        },
        "postit": {
          "type": "integer",
          "enum": [
            0,
            1
          ],
          "description": "1 renders as a yellow sticky note."
        },
        "kind": {
          "type": "string",
          "enum": [
            "text",
            "link"
          ],
          "description": "`text` is a free-form markdown/Mermaid node; `link` is a URL capsule rendered as a clickable pill. Both live in the same table — `kind` lets machine clients distinguish them without guessing from content."
        },
        "author": {
          "type": [
            "string",
            "null"
          ],
          "description": "Author tag: `user:<uuid>` for browser edits, `ai:<label>` for MCP edits. IMMUTABLE after creation — subsequent moves / edits by other collaborators do NOT change this value."
        },
        "last_updated": {
          "type": "string",
          "description": "UTC timestamp."
        }
      },
      "required": [
        "id",
        "x",
        "y",
        "content"
      ]
    }
  }
}