{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/ods-app.schema.json",
  "title": "ODS Application Schema",
  "description": "Defines the structure for a dynamic application rendered from a JSON definition.",
  "type": "object",
  "properties": {
    "appName": {
      "description": "The display name of the application.",
      "type": "string"
    },
    "startPage": {
      "description": "The ID of the page to load when the app starts. Can be a string (same page for everyone) or an object mapping roles to page IDs. When an object, 'default' is used for roles not listed.",
      "oneOf": [
        { "type": "string" },
        {
          "type": "object",
          "properties": {
            "default": {
              "description": "Fallback start page for roles not explicitly listed.",
              "type": "string"
            },
            "admin": { "type": "string" },
            "user": { "type": "string" },
            "guest": { "type": "string" }
          },
          "required": ["default"],
          "additionalProperties": { "type": "string" }
        }
      ]
    },
    "logo": {
      "description": "URL to the app logo displayed in sidebar and login screen. Recommended: horizontal aspect ratio (e.g., 200x50). Supports PNG, SVG, JPG.",
      "type": "string",
      "format": "uri"
    },
    "favicon": {
      "description": "URL to a small icon for the browser tab (web) or app context (desktop). Recommended: 32x32 or 64x64 PNG.",
      "type": "string",
      "format": "uri"
    },
    "appIcon": {
      "description": "Optional emoji or icon identifier shown alongside the app name (e.g., '📊'). Renderer-specific in how it's displayed.",
      "type": "string"
    },
    "theme": {
      "description": "Optional. Visual theme + customizations. A base theme picks colors/typography from the catalog; `overrides` adjusts any token (color, font, etc.) on top of it. When omitted, uses the 'indigo' theme. See ADR-0002 for the design rationale.",
      "type": "object",
      "properties": {
        "base": {
          "description": "Name of a theme from the catalog (e.g., 'corporate', 'nord', 'dracula'). See Themes/catalog.json for all options. Defaults to 'indigo'.",
          "type": "string",
          "default": "indigo"
        },
        "mode": {
          "description": "Color mode: 'light', 'dark', or 'system' (follows OS preference). Each theme has both light and dark variants.",
          "type": "string",
          "enum": ["light", "dark", "system"],
          "default": "system"
        },
        "headerStyle": {
          "description": "Visual style of the top app bar. 'solid' fills with primary color, 'light' uses neutral background, 'transparent' removes the background.",
          "type": "string",
          "enum": ["solid", "light", "transparent"],
          "default": "light"
        },
        "overrides": {
          "description": "Per-token overrides on top of the base theme. Keys are camelCase token names (e.g., 'primary', 'base100', 'radiusBox', 'fontSans', 'fontSerif', 'fontMono'). Values are oklch color strings, CSS lengths, or font family names. Override as many tokens as needed.",
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      }
    },
    "auth": {
      "description": "Optional. Configures multi-user authentication and role-based access control. When omitted or multiUser is false, the app runs in single-user mode with no login or roles.",
      "type": "object",
      "properties": {
        "multiUser": {
          "description": "When true, enables login, user management, and role-based access control. Defaults to false.",
          "type": "boolean",
          "default": false
        },
        "multiUserOnly": {
          "description": "When true, the app refuses to run without admin setup \u00e2\u20ac\u201d the framework shows a setup wizard. Defaults to false.",
          "type": "boolean",
          "default": false
        },
        "roles": {
          "description": "Custom roles beyond the three built-in defaults (guest, user, admin). The built-in roles always exist implicitly.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "defaultRole": {
          "description": "The role automatically assigned to newly registered users. Defaults to 'user'.",
          "type": "string",
          "default": "user"
        },
        "selfRegistration": {
          "description": "When true, the login screen shows a 'Sign Up' option allowing new users to create their own accounts. Supported in web frameworks; local/desktop frameworks may ignore or warn.",
          "type": "boolean",
          "default": false
        }
      }
    },
    "menu": {
      "description": "Defines the primary navigation menu.",
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "label": {
            "type": "string"
          },
          "mapsTo": {
            "type": "string"
          },
          "roles": {
            "$ref": "#/$defs/rolesArray"
          }
        },
        "required": [
          "label",
          "mapsTo"
        ]
      }
    },
    "pages": {
      "description": "A dictionary of all pages available in the application.",
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/page"
      }
    },
    "dataSources": {
      "description": "A dictionary of all data sources used by the application. Each key is a unique identifier referenced by components and actions.",
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/dataSource"
      }
    },
    "help": {
      "description": "Optional. Provides help and guidance content for the application.",
      "$ref": "#/$defs/helpObject"
    },
    "tour": {
      "description": "Optional. Defines a guided tour shown on first launch to introduce the user to the app.",
      "type": "array",
      "items": {
        "$ref": "#/$defs/tourStep"
      }
    },
    "settings": {
      "description": "Optional. Defines user-configurable app settings with default values. Each key is the setting's programmatic name.",
      "type": "object",
      "additionalProperties": {
        "$ref": "#/$defs/appSetting"
      }
    }
  },
  "required": [
    "appName",
    "startPage",
    "pages"
  ],
  "$defs": {
    "helpObject": {
      "title": "Help Object",
      "description": "Provides help content for the application. Includes an overview and optional per-page help.",
      "type": "object",
      "properties": {
        "overview": {
          "description": "A general description of what the app does and how to use it.",
          "type": "string"
        },
        "pages": {
          "description": "Per-page help text, keyed by page ID.",
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        }
      },
      "required": [
        "overview"
      ]
    },
    "tourStep": {
      "title": "Tour Step",
      "description": "A single step in the guided tour.",
      "type": "object",
      "properties": {
        "title": {
          "description": "The title of this tour step.",
          "type": "string"
        },
        "content": {
          "description": "The explanatory text for this tour step.",
          "type": "string"
        },
        "page": {
          "description": "Optional. The page ID to navigate to for this step, so the user sees the relevant page.",
          "type": "string"
        }
      },
      "required": [
        "title",
        "content"
      ]
    },
    "dataSource": {
      "title": "DataSource Object",
      "description": "Defines a data endpoint. Use 'local://<tableName>' for framework-managed local storage, or a full URL (https://) for external REST APIs.",
      "type": "object",
      "properties": {
        "url": {
          "description": "The data endpoint. Use 'local://<tableName>' for local storage managed by the framework, or a full URL for an external API.",
          "type": "string"
        },
        "method": {
          "description": "The access method. For local:// sources, GET reads all rows, POST inserts a new row, PUT updates an existing row, and DELETE removes a row. For external URLs, this is the HTTP method.",
          "type": "string",
          "enum": [
            "GET",
            "POST",
            "PUT",
            "DELETE"
          ]
        },
        "fields": {
          "description": "Optional. Explicitly defines the columns/fields for a local:// data source. If omitted for a local:// source, the schema is auto-inferred from the first form that submits to it.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/fieldDefinition"
          }
        },
        "seedData": {
          "description": "Optional. An array of objects to pre-populate a local:// data source when the table is first created. Each object's keys must match the field names.",
          "type": "array",
          "items": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "ownership": {
          "$ref": "#/$defs/ownership"
        }
      },
      "required": [
        "url",
        "method"
      ]
    },
    "fieldDefinition": {
      "title": "Field Definition",
      "description": "Defines a single field/column in a data source or form.",
      "type": "object",
      "properties": {
        "name": {
          "description": "The programmatic name of the field, used as the column name in storage.",
          "type": "string"
        },
        "type": {
          "description": "The data type of the field. Used for input widget selection and display formatting.",
          "type": "string",
          "enum": [
            "text",
            "email",
            "number",
            "date",
            "datetime",
            "multiline",
            "select",
            "checkbox",
            "hidden",
            "user"
          ]
        },
        "options": {
          "description": "An array of string values the user can choose from, rendered as a dropdown. Required when type is 'select' unless optionsFrom is provided.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "optionsFrom": {
          "description": "Optional. Dynamically populates dropdown options from a GET data source. When provided on a 'select' field, the framework queries the referenced data source and uses the specified column as dropdown values. Takes priority over static 'options' if both are present.",
          "type": "object",
          "properties": {
            "dataSource": {
              "description": "The ID of a GET data source to fetch options from.",
              "type": "string"
            },
            "valueField": {
              "description": "The field/column name whose values become the dropdown options.",
              "type": "string"
            },
            "filter": {
              "description": "Optional. Filters the data source rows before extracting options, creating dependent dropdowns. 'field' is the column in the data source to filter on, 'fromField' is the name of a sibling form field whose current value provides the filter criteria.",
              "type": "object",
              "properties": {
                "field": {
                  "description": "The column name in the data source to filter on.",
                  "type": "string"
                },
                "fromField": {
                  "description": "The name of a sibling form field whose current value is used as the filter value.",
                  "type": "string"
                }
              },
              "required": [
                "field",
                "fromField"
              ]
            }
          },
          "required": [
            "dataSource",
            "valueField"
          ]
        },
        "label": {
          "description": "Optional. A human-readable label for the field.",
          "type": "string"
        },
        "required": {
          "description": "Optional. When true, the field must have a non-empty value before the form can be submitted. Defaults to false.",
          "type": "boolean",
          "default": false
        },
        "placeholder": {
          "description": "Optional. Example or hint text shown inside the field when it is empty. Disappears once the user starts typing.",
          "type": "string"
        },
        "default": {
          "description": "Optional. A default value to pre-fill the field with when the form is first displayed. The user can change it. Supports magic values: 'NOW' (current datetime), 'CURRENTDATE' (current date), relative offsets like '+7d' or '+1m' for date/datetime fields, and CURRENT_USER dot-notation for logged-in user properties: 'CURRENT_USER.NAME' (display name), 'CURRENT_USER.EMAIL' (email, web frameworks only), 'CURRENT_USER.USERNAME'. Bare 'CURRENT_USER' is a legacy alias for CURRENT_USER.NAME.",
          "type": "string"
        },
        "formula": {
          "description": "Optional. A formula that computes this field's value from other fields at render time. Reference fields with {fieldName}. Number-type fields use math expressions (e.g., \"{qty} * {price}\"), text-type fields use string interpolation (e.g., \"{firstName} {lastName}\"). Computed fields are read-only and not stored in the database.",
          "type": "string"
        },
        "readOnly": {
          "description": "Optional. When true, the field is displayed but not editable by the user. Defaults to false.",
          "type": "boolean",
          "default": false
        },
        "displayVariant": {
          "description": "Optional. Controls how a read-only field is rendered visually. Only meaningful when readOnly is true.",
          "type": "string",
          "enum": [
            "heading",
            "body",
            "caption",
            "subtitle"
          ]
        },
        "optionLabels": {
          "description": "Optional. An array of human-friendly labels corresponding positionally to the 'options' array. When present, the dropdown displays these labels but stores the original option values.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "currency": {
          "description": "Optional. When true, the field value is formatted with the currency symbol defined in app settings. Applies to number fields.",
          "type": "boolean",
          "default": false
        },
        "visibleWhen": {
          "description": "Optional. Makes this field conditionally visible based on another field's value. The field is hidden (and its value excluded from submission) until the condition is met.",
          "type": "object",
          "properties": {
            "field": {
              "description": "The name of another field in the same form to watch.",
              "type": "string"
            },
            "equals": {
              "description": "The value the watched field must equal for this field to be visible.",
              "type": "string"
            }
          },
          "required": [
            "field",
            "equals"
          ]
        },
        "validation": {
          "description": "Optional. Validation constraints beyond 'required'. The framework shows inline error text when the value fails validation. Validation runs on submit, not on every keystroke.",
          "type": "object",
          "properties": {
            "min": {
              "description": "Minimum numeric value (inclusive). Only applies to 'number' fields.",
              "type": "number"
            },
            "max": {
              "description": "Maximum numeric value (inclusive). Only applies to 'number' fields.",
              "type": "number"
            },
            "minLength": {
              "description": "Minimum string length (inclusive). Applies to text, email, and multiline fields.",
              "type": "integer"
            },
            "pattern": {
              "description": "A regular expression the value must match. Use for custom formats like phone numbers or postal codes.",
              "type": "string"
            },
            "message": {
              "description": "Optional. A custom error message shown when validation fails. If omitted, the framework generates a default message.",
              "type": "string"
            }
          }
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "name",
        "type"
      ]
    },
    "page": {
      "title": "Page Object",
      "type": "object",
      "properties": {
        "component": {
          "const": "page"
        },
        "title": {
          "description": "The title displayed for the page.",
          "type": "string"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        },
        "content": {
          "description": "An array of components to render on the page.",
          "type": "array",
          "items": {
            "oneOf": [
              {
                "$ref": "#/$defs/textComponent"
              },
              {
                "$ref": "#/$defs/listComponent"
              },
              {
                "$ref": "#/$defs/kanbanComponent"
              },
              {
                "$ref": "#/$defs/formComponent"
              },
              {
                "$ref": "#/$defs/buttonComponent"
              },
              {
                "$ref": "#/$defs/chartComponent"
              },
              {
                "$ref": "#/$defs/summaryComponent"
              },
              {
                "$ref": "#/$defs/tabsComponent"
              },
              {
                "$ref": "#/$defs/detailComponent"
              }
            ]
          }
        }
      },
      "required": [
        "component",
        "title",
        "content"
      ]
    },
    "textComponent": {
      "title": "Text Component",
      "type": "object",
      "properties": {
        "component": {
          "const": "text"
        },
        "content": {
          "description": "The text content to display. May include aggregate expressions using {FUNC(dataSourceId, field)} syntax where FUNC is one of SUM, COUNT, AVG, MIN, MAX, or PCT. For example: 'Total: {SUM(expenses, amount)}' or 'Completed: {PCT(tasks, status, done)}%'. These are evaluated at render time against the referenced data source.",
          "type": "string"
        },
        "format": {
          "description": "Optional. The text format. 'plain' (default) renders as simple text, 'markdown' renders basic Markdown syntax (headings, bold, italic, lists, links).",
          "type": "string",
          "enum": [
            "plain",
            "markdown"
          ],
          "default": "plain"
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy. Uses {fieldName} references and supports ==, != comparisons.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "content"
      ]
    },
    "listComponent": {
      "title": "List Component",
      "type": "object",
      "properties": {
        "component": {
          "const": "list"
        },
        "dataSource": {
          "description": "The ID of the dataSource to read data from.",
          "type": "string"
        },
        "columns": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "header": {
                "type": "string"
              },
              "field": {
                "type": "string"
              },
              "sortable": {
                "description": "Optional. When true, the column header becomes tappable to sort the list by this field. Defaults to false.",
                "type": "boolean",
                "default": false
              },
              "filterable": {
                "description": "Optional. When true, a filter dropdown appears above the list for this column, letting the user filter rows by the column's distinct values. Defaults to false.",
                "type": "boolean",
                "default": false
              },
              "displayMap": {
                "description": "Optional. Maps raw stored values to human-friendly display labels (e.g., {\"true\": \"Yes\", \"false\": \"No\"}). The list shows the mapped label instead of the raw value.",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "colorMap": {
                "description": "Optional. Maps raw field values to color names for per-cell coloring. Colors: red, green, blue, orange, grey, purple, teal, amber.",
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "currency": {
                "description": "Optional. When true, formats the column value with the currency symbol from app settings.",
                "type": "boolean",
                "default": false
              },
              "toggle": {
                "description": "Optional. Renders this column as an inline checkbox toggle. Tapping the checkbox flips the field value between 'true' and 'false' and fires an update to the specified data source. Ideal for simple checklists and to-do items.",
                "type": "object",
                "properties": {
                  "dataSource": {
                    "description": "The ID of a PUT data source to update when the toggle is tapped.",
                    "type": "string"
                  },
                  "matchField": {
                    "description": "Optional. The field used to identify the row for the update. Defaults to '_id'.",
                    "type": "string",
                    "default": "_id"
                  }
                },
                "required": [
                  "dataSource"
                ]
              },
              "roles": {
                "$ref": "#/$defs/rolesArray"
              }
            },
            "required": [
              "header",
              "field"
            ]
          }
        },
        "searchable": {
          "description": "Optional. When true, a search bar appears above the list. Filters rows where any displayed column contains the search text (case-insensitive). Defaults to false.",
          "type": "boolean",
          "default": false
        },
        "defaultSort": {
          "description": "Optional. Sets the initial sort order when the list first renders. Users can still change the sort by tapping sortable column headers.",
          "type": "object",
          "properties": {
            "field": {
              "description": "The field name to sort by.",
              "type": "string"
            },
            "direction": {
              "description": "The sort direction. 'asc' for ascending (A-Z, oldest first), 'desc' for descending (Z-A, newest first).",
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          "required": [
            "field"
          ]
        },
        "displayAs": {
          "description": "Optional. How to render the list data. 'table' (default) renders a DataTable, 'cards' renders a scrollable grid of cards showing each row's column values.",
          "type": "string",
          "enum": [
            "table",
            "cards"
          ],
          "default": "table"
        },
        "rowColorField": {
          "description": "Optional. The field name to evaluate for row-level background coloring. Used with rowColorMap.",
          "type": "string"
        },
        "rowColorMap": {
          "description": "Optional. Maps field values to background color names for entire rows. Requires rowColorField. Colors: red, green, blue, orange, grey, purple, teal, amber.",
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "onRowTap": {
          "description": "Optional. Defines what happens when a user taps a row. Typically navigates to a detail or edit page, optionally pre-filling a form with the tapped row's data.",
          "type": "object",
          "properties": {
            "action": {
              "description": "The action to perform on row tap (e.g., 'navigate').",
              "type": "string"
            },
            "target": {
              "description": "The page ID to navigate to when a row is tapped.",
              "type": "string"
            },
            "populateForm": {
              "description": "Optional. The form ID on the target page to pre-fill with the tapped row's data.",
              "type": "string"
            }
          },
          "required": [
            "action",
            "target"
          ]
        },
        "rowActions": {
          "description": "Optional. An array of actions rendered as buttons in each row. Allows inline operations like marking a task done without navigating to a separate page.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/rowAction"
          }
        },
        "summary": {
          "description": "Optional. An array of aggregation rules to display as a summary row at the bottom of the list. Supported functions: sum, avg, count, min, max.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "column": {
                "description": "The field name to aggregate.",
                "type": "string"
              },
              "function": {
                "description": "The aggregation function to apply.",
                "type": "string",
                "enum": [
                  "sum",
                  "avg",
                  "count",
                  "min",
                  "max"
                ]
              },
              "label": {
                "description": "Optional. A custom label for the summary value (e.g., 'Total Spent'). If omitted, the framework generates one from the function name.",
                "type": "string"
              }
            },
            "required": [
              "column",
              "function"
            ]
          }
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "dataSource",
        "columns"
      ]
    },
    "kanbanComponent": {
      "title": "Kanban Component",
      "description": "Renders data as a kanban board with draggable cards. Columns are defined by a select-type field whose options become the board columns. Drag-and-drop moves cards between columns by updating the status field automatically.",
      "type": "object",
      "properties": {
        "component": {
          "const": "kanban"
        },
        "dataSource": {
          "description": "The ID of the GET dataSource to read data from.",
          "type": "string"
        },
        "statusField": {
          "description": "The field name whose select options define the kanban columns. Must be a select-type field with an options array. Drag-and-drop updates this field automatically.",
          "type": "string"
        },
        "titleField": {
          "description": "Optional. The field name to display as the card title (rendered larger/bolder). Defaults to the first entry in cardFields.",
          "type": "string"
        },
        "cardFields": {
          "description": "Array of field names to display on each card. The first field is the card title unless titleField is specified.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "rowActions": {
          "description": "Optional. Action buttons displayed on each card.",
          "type": "array",
          "items": {
            "$ref": "#/$defs/rowAction"
          }
        },
        "defaultSort": {
          "description": "Optional. Default sort order for cards within each column.",
          "type": "object",
          "properties": {
            "field": {
              "type": "string"
            },
            "direction": {
              "type": "string",
              "enum": ["asc", "desc"],
              "default": "asc"
            }
          },
          "required": ["field"]
        },
        "searchable": {
          "description": "Optional. When true, a search bar appears above the board. Defaults to false.",
          "type": "boolean",
          "default": false
        },
        "visible": {
          "description": "Optional. Expression-based visibility.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "dataSource",
        "statusField",
        "cardFields"
      ]
    },
    "formComponent": {
      "title": "Form Component",
      "type": "object",
      "properties": {
        "component": {
          "const": "form"
        },
        "id": {
          "description": "A unique identifier for the form.",
          "type": "string"
        },
        "fields": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/fieldDefinition"
          }
        },
        "recordSource": {
          "description": "Optional. The ID of a GET data source. When set, the form is populated from a record cursor rather than manual entry. Used with record navigation actions (firstRecord, nextRecord, previousRecord, lastRecord).",
          "type": "string"
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "id",
        "fields"
      ]
    },
    "buttonComponent": {
      "title": "Button Component",
      "type": "object",
      "properties": {
        "component": {
          "const": "button"
        },
        "label": {
          "type": "string"
        },
        "onClick": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/action"
          }
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "label",
        "onClick"
      ]
    },
    "chartComponent": {
      "title": "Chart Component",
      "description": "Renders a data visualization chart from a data source. Supports bar, line, and pie chart types.",
      "type": "object",
      "properties": {
        "component": {
          "const": "chart"
        },
        "dataSource": {
          "description": "The ID of the GET dataSource to read data from.",
          "type": "string"
        },
        "chartType": {
          "description": "The type of chart to render.",
          "type": "string",
          "enum": [
            "bar",
            "line",
            "pie"
          ]
        },
        "labelField": {
          "description": "The field to use for category labels (X axis or pie slice names).",
          "type": "string"
        },
        "valueField": {
          "description": "The field to use for numeric values (Y axis height or pie slice size). For 'count' aggregate, can be the same as labelField.",
          "type": "string"
        },
        "aggregate": {
          "description": "Optional. How to aggregate values per label group. 'count' counts rows, 'sum' totals the valueField (default), 'avg' averages the valueField. When labelField equals valueField, defaults to 'count'.",
          "type": "string",
          "enum": [
            "count",
            "sum",
            "avg"
          ],
          "default": "sum"
        },
        "title": {
          "description": "Optional. A title displayed above the chart.",
          "type": "string"
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "dataSource",
        "chartType",
        "labelField",
        "valueField"
      ]
    },
    "summaryComponent": {
      "title": "Summary Component",
      "description": "Renders a styled card showing a label, a large aggregate value, and an optional icon. Ideal for dashboard KPI displays.",
      "type": "object",
      "properties": {
        "component": {
          "const": "summary"
        },
        "label": {
          "description": "The label text shown above the value (e.g., 'Total Spent', 'Active Users').",
          "type": "string"
        },
        "value": {
          "description": "The value expression. Supports aggregate syntax like {SUM(expenses, amount)}, {COUNT(tasks)}, or static text.",
          "type": "string"
        },
        "icon": {
          "description": "Optional. Material icon name (e.g., 'attach_money', 'trending_up', 'people', 'check_circle').",
          "type": "string"
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "label",
        "value"
      ]
    },
    "tabsComponent": {
      "title": "Tabs Component",
      "description": "Renders a tabbed layout within a page. Each tab has its own content array of components.",
      "type": "object",
      "properties": {
        "component": {
          "const": "tabs"
        },
        "tabs": {
          "description": "An array of tab definitions, each with a label and content array.",
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "label": {
                "description": "The tab's label shown in the tab bar.",
                "type": "string"
              },
              "content": {
                "description": "An array of components to render inside this tab.",
                "type": "array",
                "items": {
                  "oneOf": [
                    {
                      "$ref": "#/$defs/textComponent"
                    },
                    {
                      "$ref": "#/$defs/listComponent"
                    },
                    {
                      "$ref": "#/$defs/kanbanComponent"
                    },
                    {
                      "$ref": "#/$defs/formComponent"
                    },
                    {
                      "$ref": "#/$defs/buttonComponent"
                    },
                    {
                      "$ref": "#/$defs/chartComponent"
                    },
                    {
                      "$ref": "#/$defs/summaryComponent"
                    },
                    {
                      "$ref": "#/$defs/detailComponent"
                    }
                  ]
                }
              }
            },
            "required": [
              "label",
              "content"
            ]
          }
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "tabs"
      ]
    },
    "detailComponent": {
      "title": "Detail Component",
      "description": "Renders a read-only detail view showing field values from a data source record or form state. Ideal for 'view record' pages.",
      "type": "object",
      "properties": {
        "component": {
          "const": "detail"
        },
        "dataSource": {
          "description": "The ID of the GET data source to read from.",
          "type": "string"
        },
        "fields": {
          "description": "Optional. An array of field names to display, in order. If omitted, all columns from the record are shown.",
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "labels": {
          "description": "Optional. Maps field names to display labels. If a field is not in this map, the field name is used as the label.",
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "fromForm": {
          "description": "Optional. The form ID whose current state provides the record to display. Used with the populateForm flow.",
          "type": "string"
        },
        "visible": {
          "description": "Optional. An expression evaluated against form state. Component is shown only when the expression is truthy.",
          "type": "string"
        },
        "visibleWhen": {
          "$ref": "#/$defs/visibleWhen"
        },
        "styleHint": {
          "$ref": "#/$defs/styleHint"
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "component",
        "dataSource"
      ]
    },
    "visibleWhen": {
      "title": "Visibility Condition",
      "description": "Controls component visibility based on form field values or data source row counts.",
      "type": "object",
      "properties": {
        "field": {
          "description": "The form field name to watch.",
          "type": "string"
        },
        "form": {
          "description": "The form ID containing the field. If omitted, checks all forms.",
          "type": "string"
        },
        "equals": {
          "description": "The value the field must equal for the component to be visible.",
          "type": "string"
        },
        "notEquals": {
          "description": "The value the field must NOT equal for the component to be visible.",
          "type": "string"
        },
        "source": {
          "description": "A data source ID to check row count against.",
          "type": "string"
        },
        "countEquals": {
          "description": "Show when data source row count equals this value.",
          "type": "integer"
        },
        "countMin": {
          "description": "Show when data source row count is >= this value.",
          "type": "integer"
        },
        "countMax": {
          "description": "Show when data source row count is <= this value.",
          "type": "integer"
        }
      }
    },
    "action": {
      "title": "Action Object",
      "description": "Defines an action to be performed: navigate (go to a page), submit (insert a new row), update (modify an existing row), showMessage (display a snackbar), or record navigation (firstRecord, nextRecord, previousRecord, lastRecord) for cursor-based form browsing.",
      "type": "object",
      "oneOf": [
        {
          "properties": {
            "action": {
              "const": "navigate"
            },
            "target": {
              "description": "The ID of the page to navigate to.",
              "type": "string"
            },
            "populateForm": {
              "description": "Optional. The form ID on the target page to pre-fill with row data from the originating list. When present, the tapped row's data is used to populate the named form.",
              "type": "string"
            },
            "withData": {
              "description": "An optional object of data to pass to the target page.",
              "type": "object",
              "additionalProperties": true
            },
            "confirm": {
              "description": "Optional. When provided, the framework shows a confirmation dialog with this text before executing the action. The user must confirm to proceed.",
              "type": "string"
            }
          },
          "required": [
            "action",
            "target"
          ]
        },
        {
          "properties": {
            "action": {
              "const": "submit"
            },
            "dataSource": {
              "description": "The ID of the dataSource to submit to.",
              "type": "string"
            },
            "target": {
              "description": "The ID of the form to submit.",
              "type": "string"
            },
            "computedFields": {
              "description": "Optional. An array of fields whose values are computed at submit time from expressions referencing form fields. These are added to the submitted data.",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "field": {
                    "description": "The field name to set in the submitted data.",
                    "type": "string"
                  },
                  "expression": {
                    "description": "An expression using {fieldName} references and math operators to compute the value.",
                    "type": "string"
                  }
                },
                "required": [
                  "field",
                  "expression"
                ]
              }
            },
            "confirm": {
              "description": "Optional. When provided, the framework shows a confirmation dialog with this text before executing the action. The user must confirm to proceed.",
              "type": "string"
            },
            "preserveFields": {
              "description": "Optional. An array of field names to preserve after the form is cleared on successful submit. Enables 'Add & Add Another' flows where contextual fields (e.g., the selected list) are kept while other fields reset.",
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "required": [
            "action",
            "dataSource",
            "target"
          ]
        },
        {
          "properties": {
            "action": {
              "const": "update"
            },
            "dataSource": {
              "description": "The ID of the PUT dataSource to update.",
              "type": "string"
            },
            "target": {
              "description": "The ID of the form whose values provide the update data.",
              "type": "string"
            },
            "matchField": {
              "description": "The field name used to find the row to update. The row where this field matches the form value is updated.",
              "type": "string"
            },
            "computedFields": {
              "description": "Optional. An array of fields whose values are computed at update time from expressions referencing form fields. These are merged into the update data.",
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "field": {
                    "description": "The field name to set in the updated data.",
                    "type": "string"
                  },
                  "expression": {
                    "description": "An expression using {fieldName} references and math operators to compute the value.",
                    "type": "string"
                  }
                },
                "required": [
                  "field",
                  "expression"
                ]
              }
            },
            "confirm": {
              "description": "Optional. When provided, the framework shows a confirmation dialog with this text before executing the action. The user must confirm to proceed.",
              "type": "string"
            }
          },
          "required": [
            "action",
            "dataSource",
            "target",
            "matchField"
          ]
        },
        {
          "properties": {
            "action": {
              "const": "showMessage"
            },
            "message": {
              "description": "The text to display in a snackbar notification.",
              "type": "string"
            }
          },
          "required": [
            "action",
            "message"
          ]
        },
        {
          "properties": {
            "action": {
              "const": "firstRecord"
            },
            "target": {
              "description": "The form ID whose record cursor should move to the first record.",
              "type": "string"
            },
            "filter": {
              "description": "Optional. A filter object to constrain which records the cursor navigates through.",
              "type": "object",
              "additionalProperties": true
            },
            "onEnd": {
              "description": "Optional. A nested action to execute when the cursor is already at the first record.",
              "$ref": "#/$defs/action"
            }
          },
          "required": [
            "action",
            "target"
          ]
        },
        {
          "properties": {
            "action": {
              "const": "nextRecord"
            },
            "target": {
              "description": "The form ID whose record cursor should advance to the next record.",
              "type": "string"
            },
            "filter": {
              "description": "Optional. A filter object to constrain which records the cursor navigates through.",
              "type": "object",
              "additionalProperties": true
            },
            "onEnd": {
              "description": "Optional. A nested action to execute when the cursor has reached the last record and cannot advance further.",
              "$ref": "#/$defs/action"
            }
          },
          "required": [
            "action",
            "target"
          ]
        },
        {
          "properties": {
            "action": {
              "const": "previousRecord"
            },
            "target": {
              "description": "The form ID whose record cursor should move to the previous record.",
              "type": "string"
            },
            "filter": {
              "description": "Optional. A filter object to constrain which records the cursor navigates through.",
              "type": "object",
              "additionalProperties": true
            },
            "onEnd": {
              "description": "Optional. A nested action to execute when the cursor is already at the first record and cannot move back further.",
              "$ref": "#/$defs/action"
            }
          },
          "required": [
            "action",
            "target"
          ]
        },
        {
          "properties": {
            "action": {
              "const": "lastRecord"
            },
            "target": {
              "description": "The form ID whose record cursor should move to the last record.",
              "type": "string"
            },
            "filter": {
              "description": "Optional. A filter object to constrain which records the cursor navigates through.",
              "type": "object",
              "additionalProperties": true
            },
            "onEnd": {
              "description": "Optional. A nested action to execute when the cursor is already at the last record.",
              "$ref": "#/$defs/action"
            }
          },
          "required": [
            "action",
            "target"
          ]
        }
      ]
    },
    "rowAction": {
      "title": "Row Action",
      "description": "An action button rendered in each row of a list. Uses the row's own data to identify which record to act on. Supports 'update' (set new values) and 'delete' (remove the row).",
      "type": "object",
      "properties": {
        "label": {
          "description": "The text shown on the action button in each row.",
          "type": "string"
        },
        "action": {
          "description": "The action type: 'update' sets new values on the row, 'delete' removes the row (with confirmation), 'copyRows' copies the parent row and linked child rows to create a duplicate.",
          "type": "string",
          "enum": [
            "update",
            "delete",
            "copyRows"
          ]
        },
        "dataSource": {
          "description": "The ID of the dataSource (PUT for update, DELETE for delete) to perform the action.",
          "type": "string"
        },
        "matchField": {
          "description": "The field name used to identify the row. The value is taken from the row's own data.",
          "type": "string"
        },
        "values": {
          "description": "A map of field names to values to set when the action is triggered. Required for 'update' actions, not needed for 'delete'.",
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "confirm": {
          "description": "Optional. When provided, the framework shows a confirmation dialog with this text before executing the row action. Overrides the default delete confirmation for delete actions.",
          "type": "string"
        },
        "sourceDataSource": {
          "description": "For copyRows: the GET data source to read child rows from.",
          "type": "string"
        },
        "targetDataSource": {
          "description": "For copyRows: the POST data source to write copied child rows to.",
          "type": "string"
        },
        "parentDataSource": {
          "description": "For copyRows: the POST data source to create the new parent record.",
          "type": "string"
        },
        "linkField": {
          "description": "For copyRows: the field on child rows that links them to the parent's name.",
          "type": "string"
        },
        "nameField": {
          "description": "For copyRows: the parent field used as the name. The copy gets '(copy)' appended.",
          "type": "string"
        },
        "resetValues": {
          "description": "For copyRows: fields to reset on copied children (e.g., {\"done\": \"false\"}).",
          "type": "object",
          "additionalProperties": {
            "type": "string"
          }
        },
        "hideWhen": {
          "description": "Optional. Hides the row action button when the specified field in the row matches the given value. Useful for conditionally showing actions (e.g., hide 'Complete' when status is already 'done').",
          "type": "object",
          "properties": {
            "field": {
              "description": "The field name in the row data to evaluate.",
              "type": "string"
            },
            "equals": {
              "description": "The value that, when matched, causes the button to be hidden.",
              "type": "string"
            },
            "notEquals": {
              "description": "The value that, when NOT matched, causes the button to be hidden. Use when a button should only appear for one specific state (e.g., show 'Start' only when status is 'To Do').",
              "type": "string"
            }
          },
          "required": [
            "field"
          ]
        },
        "roles": {
          "$ref": "#/$defs/rolesArray"
        }
      },
      "required": [
        "label",
        "action"
      ]
    },
    "rolesArray": {
      "title": "Roles Array",
      "description": "Optional. Restricts visibility to users with one of the listed roles. When absent or empty, visible to everyone. Admin always has access regardless.",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "ownership": {
      "title": "Ownership (Row-Level Security)",
      "description": "Optional. When enabled, the framework auto-injects the current user's ID on insert and filters queries to only return rows owned by the current user.",
      "type": "object",
      "properties": {
        "enabled": {
          "description": "When true, enables row-level ownership filtering. Defaults to false.",
          "type": "boolean",
          "default": false
        },
        "ownerField": {
          "description": "The column name that stores the owner's user ID. Defaults to '_owner'.",
          "type": "string",
          "default": "_owner"
        },
        "adminOverride": {
          "description": "When true (default), admin users bypass ownership filters and see all rows.",
          "type": "boolean",
          "default": true
        }
      }
    },
    "styleHint": {
      "title": "StyleHint Object",
      "description": "An optional object providing abstract styling guidance. Frameworks should interpret known hints and gracefully ignore unknown ones. Known hints: 'variant' (text: heading/subheading/body/caption; button: filled/outlined/text/tonal), 'emphasis' (button: primary/secondary/danger), 'color' (semantic: primary/secondary/tertiary/success/warning/error/info, or named: green/red/blue/orange/purple/teal/pink/amber/indigo/grey), 'icon' (Material icon name for buttons and summaries), 'align' (left/center/right for text and buttons), 'size' (compact/default/large for buttons and summaries), 'density' (compact/default/comfortable for lists), 'elevation' (0-3 for summaries).",
      "type": "object",
      "additionalProperties": true,
      "properties": {
        "variant": {
          "type": "string",
          "description": "Text: heading/subheading/body/caption. Button: filled/outlined/text/tonal."
        },
        "emphasis": {
          "type": "string",
          "description": "Button color: primary/secondary/danger."
        },
        "color": {
          "type": "string",
          "description": "Accent color: primary/success/warning/error/info, or named: green/red/blue/orange/purple/teal/pink/amber/indigo/grey."
        },
        "icon": {
          "type": "string",
          "description": "Material icon name (e.g., 'add', 'check', 'star', 'shopping_cart')."
        },
        "align": {
          "type": "string",
          "description": "Content alignment: left/center/right."
        },
        "size": {
          "type": "string",
          "description": "Component size: compact/default/large."
        },
        "density": {
          "type": "string",
          "description": "List row density: compact/default/comfortable."
        },
        "elevation": {
          "type": "integer",
          "description": "Card shadow depth: 0, 1, 2, or 3."
        }
      }
    },
    "appSetting": {
      "title": "App Setting",
      "description": "A single user-configurable setting. Uses the same type vocabulary as form fields.",
      "type": "object",
      "properties": {
        "label": {
          "description": "Human-readable label shown in the settings UI.",
          "type": "string"
        },
        "type": {
          "description": "The setting type. Determines the input widget used.",
          "type": "string",
          "enum": [
            "text",
            "number",
            "select",
            "checkbox"
          ]
        },
        "default": {
          "description": "The default value used when the user hasn't changed the setting. Checkbox defaults should be 'true' or 'false'.",
          "type": "string"
        },
        "options": {
          "description": "Required when type is 'select'. The list of allowed values.",
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      },
      "required": [
        "label",
        "type",
        "default"
      ]
    }
  }
}
