{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://flyanote.com/schemas/llm/v1.1.patch.json",
  "type": "object",
  "properties": {
    "ops": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/LlmOp"
      }
    }
  },
  "required": [
    "ops"
  ],
  "additionalProperties": false,
  "title": "Flyanote email patch",
  "description": "Edits to an email, applied in order. Its output expands to an email document at schema_version 1.1 (https://flyanote.com/schemas/email/v1.1.json).",
  "$defs": {
    "LlmOp": {
      "type": "object",
      "properties": {
        "op": {
          "type": "string",
          "enum": [
            "replace_lines",
            "set_block",
            "insert_block",
            "delete",
            "move",
            "set_meta",
            "insert_section"
          ],
          "description": "replace_lines: a text block’s lines. set_block: replaces a block, keeping its id and place. insert_block: adds a block. delete: removes a block or section. move: moves a block or section. set_meta: the subject and preheader. insert_section: adds an empty section, which insert_block ops fill."
        },
        "target": {
          "type": "string",
          "description": "replace_lines and set_block: the block’s id. delete and move: a block or section id. Otherwise \"\"."
        },
        "after": {
          "type": "string",
          "description": "insert_block: a block id inserts after that block; a column, grid item, or section id inserts at the start of it; \"\" appends at value.at. move: for a block, as insert_block’s; for a section, a section id, or \"\" for first. insert_section: a section id, or \"\" for first. Otherwise \"\"."
        },
        "subject": {
          "type": "string",
          "description": "set_meta: the new subject, or \"\" to keep it. Otherwise \"\"."
        },
        "preheader": {
          "type": "string",
          "description": "set_meta: the new preheader, or \"\" to keep it. Otherwise \"\"."
        },
        "clear_preheader": {
          "type": "boolean",
          "description": "set_meta: true removes the preheader; preheader must then be \"\". Otherwise false."
        },
        "lines": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/LlmLine"
          },
          "description": "replace_lines: the text block’s lines. Otherwise []."
        },
        "section": {
          "description": "insert_section: the new section. Otherwise a columns section with every field unset.",
          "$ref": "#/$defs/LlmSection"
        },
        "value": {
          "description": "set_block and insert_block: the block. Otherwise a divider with every field unset.",
          "$ref": "#/$defs/LlmBlock"
        }
      },
      "required": [
        "op",
        "target",
        "after",
        "subject",
        "preheader",
        "clear_preheader",
        "lines",
        "section",
        "value"
      ],
      "additionalProperties": false
    },
    "LlmLine": {
      "type": "object",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "p",
            "h1",
            "h2",
            "h3",
            "bullet",
            "number",
            "quote"
          ]
        },
        "level": {
          "type": "number",
          "enum": [
            1,
            2
          ],
          "description": "Bullet and number lines: 2 nests under the list line above."
        },
        "start": {
          "type": "integer",
          "description": "A top-level number line: begins a new list at this number. 0 continues."
        },
        "align": {
          "type": "string",
          "enum": [
            "kit",
            "left",
            "center",
            "right"
          ],
          "description": "p, h1 to h3, and quote lines. \"kit\" follows the block."
        },
        "spans": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/LlmSpan"
          }
        }
      },
      "required": [
        "kind",
        "level",
        "start",
        "align",
        "spans"
      ],
      "additionalProperties": false
    },
    "LlmSpan": {
      "type": "object",
      "properties": {
        "text": {
          "type": "string",
          "description": "Text. \"\\n\" is a line break. \"\" for a merge field alone."
        },
        "field": {
          "$ref": "#/$defs/LlmFieldPath"
        },
        "fallback": {
          "type": "string",
          "description": "Plain text shown when the field is empty, or \"\"."
        },
        "format": {
          "type": "string",
          "enum": [
            "",
            "date",
            "datetime",
            "number",
            "money"
          ],
          "description": "\"\" as the value is. money reads the value as a whole number of minor units: 1999 with USD shows $19.99."
        },
        "currency": {
          "type": "string",
          "description": "An ISO 4217 code or a path to one. Required when format is money; otherwise \"\"."
        },
        "marks": {
          "$ref": "#/$defs/LlmMarks"
        },
        "link": {
          "$ref": "#/$defs/LlmLink"
        },
        "color": {
          "$ref": "#/$defs/LlmColor"
        },
        "highlight": {
          "$ref": "#/$defs/LlmColor"
        },
        "size": {
          "$ref": "#/$defs/LlmSize"
        }
      },
      "required": [
        "text",
        "field",
        "fallback",
        "format",
        "currency",
        "marks",
        "link",
        "color",
        "highlight",
        "size"
      ],
      "additionalProperties": false,
      "description": "A run of text, a merge field, or text then a field."
    },
    "LlmFieldPath": {
      "type": "string",
      "description": "\"\" for plain text, or a data path, such as contact.first_name or data.order.total, shown after the text."
    },
    "LlmMarks": {
      "type": "string",
      "enum": [
        "",
        "bold",
        "italic",
        "strike",
        "sub",
        "sup",
        "underline",
        "bold italic",
        "bold strike",
        "bold sub",
        "bold sup",
        "bold underline",
        "italic strike",
        "italic sub",
        "italic sup",
        "italic underline",
        "strike sub",
        "strike sup",
        "strike underline",
        "sub underline",
        "sup underline",
        "bold italic strike",
        "bold italic sub",
        "bold italic sup",
        "bold italic underline",
        "bold strike sub",
        "bold strike sup",
        "bold strike underline",
        "bold sub underline",
        "bold sup underline",
        "italic strike sub",
        "italic strike sup",
        "italic strike underline",
        "italic sub underline",
        "italic sup underline",
        "strike sub underline",
        "strike sup underline",
        "bold italic strike sub",
        "bold italic strike sup",
        "bold italic strike underline",
        "bold italic sub underline",
        "bold italic sup underline",
        "bold strike sub underline",
        "bold strike sup underline",
        "italic strike sub underline",
        "italic strike sup underline",
        "bold italic strike sub underline",
        "bold italic strike sup underline"
      ],
      "description": "\"\" for plain text, or the marks joined by spaces in this order, such as \"bold italic\"."
    },
    "LlmLink": {
      "type": "string",
      "description": "\"\" for no link, or https://…, mailto:…, tel:…, kit:website, kit:links/<key>, kit:social/<network>, system:unsubscribe, system:preferences, system:view_online, or a merge tag such as {{ data.tracking_url }}."
    },
    "LlmColor": {
      "type": "string",
      "description": "\"kit\" for the kit's default, or a palette token such as brand.primary or text.muted. Use an exact color (#rrggbb) only when the person asked for one."
    },
    "LlmSize": {
      "type": "string",
      "description": "\"kit\" for the default size, a size token (size.small, size.body, size.h1 to size.h3), or whole pixels from 10px to 72px."
    },
    "LlmBackgroundImage": {
      "type": "string",
      "description": "One of the offered images, asset:<id>, which a hero requires. \"\" for none, on columns and grid only."
    },
    "LlmSection": {
      "type": "object",
      "properties": {
        "ref": {
          "type": "string",
          "description": "Names the section for blocks’ at.section: its id when it has one."
        },
        "kind": {
          "type": "string",
          "enum": [
            "columns",
            "hero",
            "grid"
          ],
          "description": "hero: one column over a background image. It needs background_image, one of the offered images, and 1 to 8 blocks at slot 0: text, image, button, divider, spacer, or logo. For a band of color with no image, use columns with layout 100 and a background."
        },
        "layout": {
          "type": "string",
          "enum": [
            "100",
            "50-50",
            "33-67",
            "67-33",
            "25-75",
            "75-25",
            "33-34-33",
            "25-50-25",
            "50-25-25",
            "25-25-50",
            "25-25-25-25",
            "grid-2",
            "grid-3",
            "grid-4"
          ],
          "description": "Column percents for columns, grid-N for a grid, 100 for a hero."
        },
        "label": {
          "type": "string",
          "description": "A short name for the editor, never shown in the email, or \"\"."
        },
        "background": {
          "$ref": "#/$defs/LlmColor"
        },
        "color": {
          "$ref": "#/$defs/LlmColor"
        },
        "background_image": {
          "$ref": "#/$defs/LlmBackgroundImage"
        },
        "slots": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The ids of its columns or grid items when it has them, else []."
        }
      },
      "required": [
        "ref",
        "kind",
        "layout",
        "label",
        "background",
        "color",
        "background_image",
        "slots"
      ],
      "additionalProperties": false
    },
    "LlmAt": {
      "type": "object",
      "properties": {
        "section": {
          "type": "string",
          "description": "The ref of the section the block goes in."
        },
        "slot": {
          "type": "integer",
          "description": "The column, counting from 0, or the grid item. A hero has one slot, 0."
        }
      },
      "required": [
        "section",
        "slot"
      ],
      "additionalProperties": false
    },
    "LlmImageSrc": {
      "type": "string",
      "description": "image: one of the offered images, asset:<id>. Otherwise \"\"."
    },
    "LlmBlock": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "text",
            "image",
            "button",
            "divider",
            "spacer",
            "social",
            "logo",
            "table",
            "footer"
          ],
          "description": "text: paragraphs, headings (h1 to h3), lists, and quotes. logo: the kit’s logo. table: a content table with plain-text cells. footer: the footer, in the last section; marketing mail always has one."
        },
        "id": {
          "type": "string",
          "description": "The block’s id when it already has one, else \"\"."
        },
        "at": {
          "$ref": "#/$defs/LlmAt"
        },
        "align": {
          "type": "string",
          "enum": [
            "kit",
            "left",
            "center",
            "right"
          ],
          "description": "text, image, button, social, and logo: its alignment. Otherwise \"kit\"."
        },
        "lines": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/LlmLine"
          },
          "description": "text: its lines. Otherwise []."
        },
        "src": {
          "$ref": "#/$defs/LlmImageSrc"
        },
        "alt": {
          "type": "string",
          "description": "image: what it shows, or \"\" when it is decorative. Otherwise \"\"."
        },
        "decorative": {
          "type": "boolean",
          "description": "image: true when it is decorative. Otherwise false."
        },
        "width": {
          "type": "integer",
          "description": "image and logo: pixels, or 0 for the image’s own width or the kit’s logo width. Otherwise 0."
        },
        "href": {
          "description": "image and button: its link. Otherwise \"\".",
          "$ref": "#/$defs/LlmLink"
        },
        "label": {
          "type": "string",
          "description": "button: its label. Otherwise \"\"."
        },
        "variant": {
          "type": "string",
          "enum": [
            "kit",
            "fill",
            "outline"
          ],
          "description": "button: its variant. Otherwise \"kit\"."
        },
        "button_width": {
          "type": "string",
          "enum": [
            "kit",
            "auto",
            "full"
          ],
          "description": "button: its width. Otherwise \"kit\"."
        },
        "background": {
          "description": "button: its background color. Otherwise \"kit\".",
          "$ref": "#/$defs/LlmColor"
        },
        "color": {
          "description": "button: its text color. Otherwise \"kit\".",
          "$ref": "#/$defs/LlmColor"
        },
        "height": {
          "type": "string",
          "enum": [
            "kit",
            "space.xxs",
            "space.xs",
            "space.sm",
            "space.md",
            "space.lg",
            "space.xl",
            "space.xxl"
          ],
          "description": "spacer: its height, a space token, with \"kit\" for space.md. Otherwise \"kit\"."
        },
        "networks": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "facebook",
              "instagram",
              "x",
              "threads",
              "bluesky",
              "linkedin",
              "youtube",
              "pinterest",
              "mastodon",
              "whatsapp",
              "website",
              "email"
            ]
          },
          "description": "social: the networks, each linking to the kit’s saved address for it. Otherwise []."
        },
        "caption": {
          "type": "string",
          "description": "table: its caption, or \"\". Otherwise \"\"."
        },
        "columns": {
          "type": "array",
          "items": {
            "type": "string",
            "enum": [
              "kit",
              "left",
              "center",
              "right"
            ]
          },
          "description": "table: one alignment per column, 1 to 4 columns. Otherwise []."
        },
        "header": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "table: one label per column. Otherwise []."
        },
        "rows": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "cells": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "One per column; \"\" for a blank. A merge field is written as {{ contact.first_name }}. Write an amount of money, held as whole minor units, as {{ data.order.total | format_money: \"USD\" }} (or a path to the currency), never with a currency sign before it; a date as {{ data.path | localize_date }}; a number as {{ data.path | format_number }}."
              },
              "role": {
                "type": "string",
                "enum": [
                  "body",
                  "total"
                ]
              }
            },
            "required": [
              "cells",
              "role"
            ],
            "additionalProperties": false
          },
          "description": "table: its rows. Otherwise []."
        },
        "note": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "footer: paragraphs added above the compliance lines, which are automatic. A merge field is written as {{ contact.first_name }}. Write an amount of money, held as whole minor units, as {{ data.order.total | format_money: \"USD\" }} (or a path to the currency), never with a currency sign before it; a date as {{ data.path | localize_date }}; a number as {{ data.path | format_number }}. Otherwise []."
        }
      },
      "required": [
        "type",
        "id",
        "at",
        "align",
        "lines",
        "src",
        "alt",
        "decorative",
        "width",
        "href",
        "label",
        "variant",
        "button_width",
        "background",
        "color",
        "height",
        "networks",
        "caption",
        "columns",
        "header",
        "rows",
        "note"
      ],
      "additionalProperties": false,
      "description": "One block. Each field says the types it applies to; every other field must be its unset value."
    }
  }
}
