{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://parlane.ai/spec/ui-document.schema.json",
  "title": "UI Document",
  "description": "A declarative, server-driven UI document: a single component tree drawn from the fixed native catalog (17 types). The client renders it with native components — never as downloaded code. Full-replace only (D13): a new document supersedes the old one; the renderer diffs by id. Structural limits (max depth 12, max 500 nodes) are enforced by the client/validator, NOT by this JSON Schema — see docs/06-integration-kit.md §5. Unknown component types are a soft error: the renderer draws a labeled placeholder rather than rejecting the document.",
  "type": "object",
  "additionalProperties": false,
  "required": ["version", "root"],
  "properties": {
    "version": {
      "type": "integer",
      "const": 1,
      "description": "UI document schema version. Currently always 1."
    },
    "id": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]+$",
      "description": "Optional dashboard id this document renders (matches a manifest dashboards[].id)."
    },
    "title": {
      "type": "string",
      "description": "Optional title for the rendered page; falls back to the manifest dashboard title."
    },
    "root": {
      "$ref": "#/$defs/component",
      "description": "The root component of the tree. Typically a stack or card."
    }
  },
  "$defs": {
    "action": {
      "type": "object",
      "additionalProperties": false,
      "required": ["tool"],
      "description": "Action envelope attached to an interactive component. Invoking it calls the named backend tool; the response is either a full replacement UI document or a bare ack (see action.schema.json).",
      "properties": {
        "tool": {
          "type": "string",
          "minLength": 1,
          "description": "Backend tool name to invoke (MCP tool call or REST POST /action tool)."
        },
        "params": {
          "type": "object",
          "description": "Static and bound parameters passed to the tool. Values from bound input/toggle/slider/select components (keyed by their `bind`) are merged in by the client at invocation time."
        },
        "confirm": {
          "description": "If present and truthy, the client shows a confirmation prompt before invoking. A string is used as the confirmation message; `true` uses a generic prompt.",
          "oneOf": [
            { "type": "boolean" },
            { "type": "string", "minLength": 1 }
          ]
        },
        "refresh": {
          "type": "string",
          "default": "self",
          "description": "What to refresh after the tool returns an ack (ignored when the response carries a full UI document). Reserved values: \"self\" = re-fetch the current dashboard; \"none\" = do nothing. Any other value is treated as a dashboard id to refresh."
        }
      }
    },
    "component": {
      "description": "A single UI component. Exactly one catalog type. Container types (stack, card) may carry `children`; leaf types may not.",
      "type": "object",
      "required": ["type"],
      "oneOf": [
        { "$ref": "#/$defs/stack" },
        { "$ref": "#/$defs/card" },
        { "$ref": "#/$defs/text" },
        { "$ref": "#/$defs/metric" },
        { "$ref": "#/$defs/chart" },
        { "$ref": "#/$defs/table" },
        { "$ref": "#/$defs/list" },
        { "$ref": "#/$defs/image" },
        { "$ref": "#/$defs/button" },
        { "$ref": "#/$defs/input" },
        { "$ref": "#/$defs/toggle" },
        { "$ref": "#/$defs/slider" },
        { "$ref": "#/$defs/select" },
        { "$ref": "#/$defs/progress" },
        { "$ref": "#/$defs/badge" },
        { "$ref": "#/$defs/divider" },
        { "$ref": "#/$defs/map" }
      ]
    },

    "stack": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type"],
      "description": "Layout container that arranges its children in a row or column.",
      "properties": {
        "type": { "const": "stack" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "children": {
          "type": "array",
          "items": { "$ref": "#/$defs/component" },
          "description": "Child components to lay out."
        },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "direction": {
              "type": "string",
              "enum": ["vertical", "horizontal"],
              "default": "vertical",
              "description": "Main-axis direction. Defaults to vertical."
            },
            "spacing": {
              "type": "number",
              "minimum": 0,
              "description": "Gap between children in points."
            },
            "align": {
              "type": "string",
              "enum": ["start", "center", "end", "stretch"],
              "description": "Cross-axis alignment of children."
            }
          }
        }
      }
    },

    "card": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type"],
      "description": "Titled surface that groups children with optional header and footer.",
      "properties": {
        "type": { "const": "card" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "children": {
          "type": "array",
          "items": { "$ref": "#/$defs/component" },
          "description": "Child components inside the card."
        },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "title": { "type": "string", "description": "Card header text." },
            "footer": { "type": "string", "description": "Card footer text." }
          }
        }
      }
    },

    "text": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A run of text in one of four styles.",
      "properties": {
        "type": { "const": "text" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["value"],
          "properties": {
            "value": { "type": "string", "description": "The text to display. May contain inline markdown." },
            "style": {
              "type": "string",
              "enum": ["title", "body", "caption", "mono"],
              "default": "body",
              "description": "Type style. Defaults to body."
            }
          }
        }
      }
    },

    "metric": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A single labeled figure with optional delta and sentiment.",
      "properties": {
        "type": { "const": "metric" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "value"],
          "properties": {
            "label": { "type": "string", "description": "Metric caption." },
            "value": {
              "type": ["string", "number"],
              "description": "The primary figure (pre-formatted string or raw number)."
            },
            "delta": {
              "type": ["string", "number"],
              "description": "Optional change indicator, e.g. \"+3%\" or -2."
            },
            "intent": {
              "type": "string",
              "enum": ["good", "bad", "neutral"],
              "default": "neutral",
              "description": "Sentiment tint for the value/delta. Defaults to neutral."
            }
          }
        }
      }
    },

    "chart": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A line, bar, or sparkline chart.",
      "properties": {
        "type": { "const": "chart" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["kind", "series"],
          "properties": {
            "kind": {
              "type": "string",
              "enum": ["line", "bar", "sparkline"],
              "description": "Chart type."
            },
            "series": {
              "type": "array",
              "minItems": 1,
              "description": "One or more data series.",
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["data"],
                "properties": {
                  "name": { "type": "string", "description": "Series label for the legend." },
                  "data": {
                    "type": "array",
                    "items": { "type": "number" },
                    "description": "Numeric data points, aligned to `labels` when present."
                  }
                }
              }
            },
            "labels": {
              "type": "array",
              "items": { "type": "string" },
              "description": "Optional x-axis labels aligned to each series' data points."
            }
          }
        }
      }
    },

    "table": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A tabular grid of string/number cells.",
      "properties": {
        "type": { "const": "table" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["columns", "rows"],
          "properties": {
            "columns": {
              "type": "array",
              "items": { "type": "string" },
              "description": "Column header labels."
            },
            "rows": {
              "type": "array",
              "description": "Rows; each row is an array of cells aligned to `columns`.",
              "items": {
                "type": "array",
                "items": { "type": ["string", "number", "boolean", "null"] }
              }
            },
            "maxHeight": {
              "type": "number",
              "minimum": 0,
              "description": "Optional max height in points before the table scrolls internally."
            }
          }
        }
      }
    },

    "list": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A vertical list of rows, each optionally tappable.",
      "properties": {
        "type": { "const": "list" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["items"],
          "properties": {
            "items": {
              "type": "array",
              "description": "List rows.",
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["title"],
                "properties": {
                  "title": { "type": "string", "description": "Primary row text." },
                  "subtitle": { "type": "string", "description": "Secondary row text." },
                  "icon": { "type": "string", "description": "Optional SF Symbol name or icon token." },
                  "action": { "$ref": "#/$defs/action" }
                }
              }
            }
          }
        }
      }
    },

    "image": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A remote image.",
      "properties": {
        "type": { "const": "image" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["url"],
          "properties": {
            "url": { "type": "string", "format": "uri", "description": "Absolute https image URL." },
            "aspect": {
              "type": "string",
              "pattern": "^[0-9]+:[0-9]+$",
              "description": "Optional aspect ratio as \"W:H\", e.g. \"16:9\". Defaults to the image's natural ratio."
            }
          }
        }
      }
    },

    "button": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props", "action"],
      "description": "A tappable button. Always requires an action.",
      "properties": {
        "type": { "const": "button" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["label"],
          "properties": {
            "label": { "type": "string", "description": "Button label." },
            "intent": {
              "type": "string",
              "enum": ["primary", "destructive", "plain"],
              "default": "primary",
              "description": "Visual weight/role. Defaults to primary."
            }
          }
        }
      }
    },

    "input": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A single-line text field bound to an action parameter.",
      "properties": {
        "type": { "const": "input" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "bind"],
          "properties": {
            "label": { "type": "string", "description": "Field label." },
            "value": { "type": "string", "description": "Initial value." },
            "bind": {
              "type": "string",
              "description": "Parameter name; the current value is merged into sibling/ancestor action params under this key at invocation time."
            },
            "placeholder": { "type": "string", "description": "Placeholder text when empty." },
            "keyboard": {
              "type": "string",
              "enum": ["default", "number", "email", "url", "phone"],
              "default": "default",
              "description": "Keyboard hint. Defaults to default."
            }
          }
        }
      }
    },

    "toggle": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A boolean switch bound to an action parameter.",
      "properties": {
        "type": { "const": "toggle" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "bind"],
          "properties": {
            "label": { "type": "string", "description": "Toggle label." },
            "value": { "type": "boolean", "default": false, "description": "Initial on/off state." },
            "bind": { "type": "string", "description": "Parameter name for the boolean value." }
          }
        }
      }
    },

    "slider": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A numeric slider bound to an action parameter.",
      "properties": {
        "type": { "const": "slider" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "bind"],
          "properties": {
            "label": { "type": "string", "description": "Slider label." },
            "value": { "type": "number", "description": "Initial value." },
            "bind": { "type": "string", "description": "Parameter name for the numeric value." },
            "min": { "type": "number", "default": 0, "description": "Minimum value. Defaults to 0." },
            "max": { "type": "number", "default": 1, "description": "Maximum value. Defaults to 1." },
            "step": { "type": "number", "exclusiveMinimum": 0, "description": "Step increment." }
          }
        }
      }
    },

    "select": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A single-choice picker bound to an action parameter.",
      "properties": {
        "type": { "const": "select" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["label", "bind", "options"],
          "properties": {
            "label": { "type": "string", "description": "Picker label." },
            "value": { "type": "string", "description": "Initially selected option value." },
            "bind": { "type": "string", "description": "Parameter name for the selected value." },
            "options": {
              "type": "array",
              "minItems": 1,
              "description": "Selectable options.",
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["label", "value"],
                "properties": {
                  "label": { "type": "string", "description": "Option display label." },
                  "value": { "type": "string", "description": "Option value passed to the tool." }
                }
              }
            }
          }
        }
      }
    },

    "progress": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A determinate progress bar.",
      "properties": {
        "type": { "const": "progress" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["value"],
          "properties": {
            "value": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "description": "Completion fraction from 0 to 1."
            },
            "label": { "type": "string", "description": "Optional caption." }
          }
        }
      }
    },

    "badge": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A small status pill.",
      "properties": {
        "type": { "const": "badge" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["value"],
          "properties": {
            "value": { "type": "string", "description": "Badge text." },
            "intent": {
              "type": "string",
              "enum": ["good", "bad", "neutral", "info"],
              "default": "neutral",
              "description": "Color tint. Defaults to neutral."
            }
          }
        }
      }
    },

    "divider": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type"],
      "description": "A horizontal rule. Takes no props.",
      "properties": {
        "type": { "const": "divider" },
        "id": { "$ref": "#/$defs/idField" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "description": "Divider has no props; an empty object is permitted."
        }
      }
    },

    "map": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "props"],
      "description": "A map centered on a coordinate with optional markers.",
      "properties": {
        "type": { "const": "map" },
        "id": { "$ref": "#/$defs/idField" },
        "action": { "$ref": "#/$defs/action" },
        "props": {
          "type": "object",
          "additionalProperties": false,
          "required": ["lat", "lng"],
          "properties": {
            "lat": { "type": "number", "minimum": -90, "maximum": 90, "description": "Center latitude." },
            "lng": { "type": "number", "minimum": -180, "maximum": 180, "description": "Center longitude." },
            "zoom": { "type": "number", "minimum": 0, "maximum": 22, "description": "Optional zoom level (0–22)." },
            "markers": {
              "type": "array",
              "description": "Optional pins.",
              "items": {
                "type": "object",
                "additionalProperties": false,
                "required": ["lat", "lng"],
                "properties": {
                  "lat": { "type": "number", "minimum": -90, "maximum": 90, "description": "Marker latitude." },
                  "lng": { "type": "number", "minimum": -180, "maximum": 180, "description": "Marker longitude." },
                  "label": { "type": "string", "description": "Optional marker label." }
                }
              }
            }
          }
        }
      }
    },

    "idField": {
      "type": "string",
      "pattern": "^[A-Za-z0-9_-]+$",
      "description": "Optional stable component id. Used by the renderer to diff on full-replace and as a refresh target."
    }
  }
}
