{
  "$comment": "Generated at build time from the Toolbench source. The same data as the site's Reference pages.",
  "contract": 9,
  "runtime": "0.15.0",
  "inputs": {
    "common": [
      {
        "name": "id",
        "type": "string",
        "required": true,
        "since": 1,
        "notes": "The key this input's value has in `run(values)`. Unique within the tool."
      },
      {
        "name": "label",
        "type": "string",
        "required": true,
        "since": 1,
        "notes": "The visible label, which is also the control's accessible name."
      },
      {
        "name": "description",
        "type": "string",
        "required": false,
        "since": 1,
        "notes": "The hint a form shows and a screen reader reads. Not a placeholder: those vanish on focus. Takes light markup: a blank line starts a paragraph, a line starting \"- \" is a list item, `code`, **strong**, and {good|text} for the tones good, warn, bad, muted and accent. Anything else is text."
      },
      {
        "name": "details",
        "type": "string",
        "required": false,
        "since": 8,
        "notes": "The rest of the help, behind a native disclosure under the description: every case a format covers, per-vendor rules, the reasons. Takes the same markup as `description`. It is not part of the control's accessible description, so a screen reader announces only the short hint on focus."
      },
      {
        "name": "unit",
        "type": "string",
        "required": false,
        "since": 1,
        "notes": "Shown after the control: \"ms\", \"req/s\", \"bytes\". A number without one is a riddle."
      },
      {
        "name": "primary",
        "type": "boolean",
        "required": false,
        "since": 1,
        "notes": "Show this input on a compact card, where there is only room for what matters. Mark as many as the question genuinely needs: a card asking \"how many servers\" is useless with only one of a rate and a duration. Mark none and a card shows the first input. Every input is shown on the full tool regardless."
      },
      {
        "name": "dir",
        "type": "\"ltr\" | \"auto\"",
        "required": false,
        "since": 1,
        "notes": "Force text direction. Bytes, code and identifiers are left-to-right even inside right-to-left prose, and a runtime cannot know which is which, so the tool says."
      },
      {
        "name": "share",
        "type": "boolean",
        "required": false,
        "default": "true, and false on a secret",
        "since": 8,
        "notes": "Whether a host may put this input's value in a link it builds from `host.values`, and read it back from one. `false` for a credential or pasted private material: a URL ends up in browser history, server logs, chat previews and referrer headers. A privacy hint for hosts, not access control: the runtime builds no links and does not enforce it, and `host.values` still writes the input."
      },
      {
        "name": "role",
        "type": "string",
        "required": false,
        "since": 8,
        "notes": "A name for this input, rendered as `data-role` on its `.tb-field-row`, so a host stylesheet can style \"the credential\" or \"the advanced ones\" without naming each id. The runtime gives it no meaning, as with a result part's `role`."
      }
    ],
    "types": [
      {
        "type": "text",
        "value": "string",
        "attributes": [
          {
            "name": "default",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "The value the form opens with."
          },
          {
            "name": "maxLength",
            "type": "number",
            "required": false,
            "since": 1,
            "notes": "The most characters the control accepts."
          }
        ]
      },
      {
        "type": "textarea",
        "value": "string",
        "attributes": [
          {
            "name": "default",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "The value the form opens with."
          },
          {
            "name": "rows",
            "type": "number",
            "required": false,
            "default": "3",
            "since": 1,
            "notes": "Visible lines of text."
          },
          {
            "name": "maxLength",
            "type": "number",
            "required": false,
            "since": 1,
            "notes": "The most characters the control accepts."
          },
          {
            "name": "lang",
            "type": "string",
            "required": false,
            "since": 6,
            "notes": "The language of what the reader types, passed to the host's `highlight(source, lang)` hook, the same one `code` results use, which paints it behind the textarea. A hint: with no hook, or a hook that cannot paint it, the textarea is the plain one."
          },
          {
            "name": "grammar",
            "type": "TextareaGrammar",
            "required": false,
            "since": 7,
            "notes": "The tool's own notation for `lang`, so a textarea is painted where no host highlighter knows the language: a state-machine syntax, a packet filter. Requires `lang`. A host hook that paints `lang` still wins."
          }
        ]
      },
      {
        "type": "number",
        "value": "number, clamped to `min..max`",
        "attributes": [
          {
            "name": "default",
            "type": "number",
            "required": true,
            "since": 1,
            "notes": "The value the form opens with, within `min..max`."
          },
          {
            "name": "min",
            "type": "number",
            "required": true,
            "since": 1,
            "notes": "The lowest value. Required: the only guard against a runaway value. A value outside `min..max` is refused before `run`, and a host's write is clamped."
          },
          {
            "name": "max",
            "type": "number",
            "required": true,
            "since": 1,
            "notes": "The highest value, above `min`."
          },
          {
            "name": "step",
            "type": "number",
            "required": false,
            "since": 1,
            "notes": "The increment for the arrow keys and the slider. Any value when omitted."
          },
          {
            "name": "control",
            "type": "\"slider\"",
            "required": false,
            "since": 4,
            "notes": "`slider` asks for a range control beside the number box, for a tool read by sweeping a value rather than by typing one. A hint, not a type: a runtime that does not know it renders the box alone."
          }
        ]
      },
      {
        "type": "select",
        "value": "string, one of the option values",
        "attributes": [
          {
            "name": "default",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "The option value the form opens with."
          },
          {
            "name": "options",
            "type": "SelectOption[]",
            "required": true,
            "since": 1,
            "notes": "Two or more choices, with unique values."
          },
          {
            "name": "control",
            "type": "\"segmented\"",
            "required": false,
            "default": "the dropdown",
            "since": 4,
            "notes": "`segmented` draws the options as a row of segments instead of a dropdown, for two to five short choices a reader should see at once. They are native radio buttons in a fieldset, so the arrow keys, the single tab stop and the announcement are the browser's, and the chosen one is a solid accent fill. When a segment would not fit its label, measured as the form resizes, the row becomes stacked radios. A hint, not a type: `run` still receives an option value, presets work as they do on a dropdown, and a runtime that does not know it draws the dropdown."
          }
        ]
      },
      {
        "type": "toggle",
        "value": "boolean",
        "attributes": [
          {
            "name": "default",
            "type": "boolean",
            "required": true,
            "since": 1,
            "notes": "Whether the form opens with it on."
          }
        ]
      },
      {
        "type": "date",
        "value": "string, `YYYY-MM-DD`, `HH:MM` or `YYYY-MM-DDTHH:MM`, within `min..max`",
        "attributes": [
          {
            "name": "mode",
            "type": "\"date\" | \"time\" | \"datetime\"",
            "required": false,
            "default": "\"date\"",
            "since": 9,
            "notes": "What the reader picks, in the browser's own picker: a day, a time of day, or both. `run` gets `YYYY-MM-DD`, `HH:MM` or `YYYY-MM-DDTHH:MM`, local time, with no zone."
          },
          {
            "name": "default",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "The value the form opens with, written in the mode's format, within `min..max`."
          },
          {
            "name": "min",
            "type": "string",
            "required": false,
            "since": 1,
            "notes": "The earliest value, in the mode's format. A value before it is refused before `run`, with a message naming the input, so `run` can trust the range."
          },
          {
            "name": "max",
            "type": "string",
            "required": false,
            "since": 1,
            "notes": "The latest value, in the mode's format, not before `min`."
          }
        ]
      },
      {
        "type": "color",
        "value": "string, a lowercase `#rrggbb`",
        "attributes": [
          {
            "name": "default",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "The colour the form opens with, as `run` gets every colour: a lowercase `#rrggbb`, with no alpha and no shorthand. The reader types it in a hex box, the labelled control, or picks it from the system's picker in the swatch beside it."
          }
        ]
      },
      {
        "type": "secret",
        "value": "string, empty until typed, at least `minLength` characters",
        "attributes": [
          {
            "name": "minLength",
            "type": "number",
            "required": false,
            "since": 9,
            "notes": "The fewest characters `run` accepts. A shorter value is refused before `run`, naming the input."
          },
          {
            "name": "maxLength",
            "type": "number",
            "required": false,
            "since": 1,
            "notes": "The most characters the control accepts, not below `minLength`."
          }
        ]
      },
      {
        "type": "multiselect",
        "value": "string[], the chosen option values in the options' order, `min` to `max` of them",
        "attributes": [
          {
            "name": "default",
            "type": "string[]",
            "required": true,
            "since": 1,
            "notes": "The option values the form opens with chosen, each once, between `min` and `max` of them. `[]` for none."
          },
          {
            "name": "options",
            "type": "SelectOption[]",
            "required": true,
            "since": 1,
            "notes": "Two or more choices, with unique values. An option here takes no `sets`: several presets at once have no single meaning."
          },
          {
            "name": "min",
            "type": "number",
            "required": false,
            "default": "0",
            "since": 1,
            "notes": "The fewest a reader may choose. `1` is what makes \"pick at least one\" a rule."
          },
          {
            "name": "max",
            "type": "number",
            "required": false,
            "default": "the number of options",
            "since": 1,
            "notes": "The most a reader may choose. At it, the rest are marked unavailable, with the reason beside them."
          },
          {
            "name": "control",
            "type": "\"checkboxes\" | \"chips\"",
            "required": false,
            "default": "checkboxes up to 6 options, chips above",
            "since": 4,
            "notes": "How the choices are drawn: `checkboxes`, a native checkbox per option, or `chips`, a row of toggle buttons that wraps, for a long list of short values. A hint, like a number's `slider`."
          }
        ]
      },
      {
        "type": "rows",
        "value": "Row[], one object per row keyed by column id, `minRows` to `maxRows` of them, even for one column",
        "attributes": [
          {
            "name": "columns",
            "type": "RowColumn[]",
            "required": true,
            "since": 9,
            "notes": "One to four columns, each a text, number, select or toggle with that type's keys and no `default`."
          },
          {
            "name": "maxRows",
            "type": "number",
            "required": true,
            "since": 9,
            "notes": "The most rows a reader may add, at most 50. Required, as a number's `max` is: the guard against a runaway list, and a runaway link. At it, Add stays focusable with the reason beside it."
          },
          {
            "name": "minRows",
            "type": "number",
            "required": false,
            "default": "0",
            "since": 9,
            "notes": "The fewest rows Run accepts."
          },
          {
            "name": "default",
            "type": "Row[]",
            "required": true,
            "since": 1,
            "notes": "The rows the form opens with, each with a value for every column. `[]` for none."
          }
        ]
      }
    ]
  },
  "results": {
    "common": [
      {
        "name": "role",
        "type": "string",
        "required": false,
        "since": 5,
        "notes": "What this part is to the reader, `\"verdict\"` or `\"stats\"` say, rendered as `data-role` on the part's root element for host CSS and tests to find it by name rather than by position."
      },
      {
        "name": "id",
        "type": "string",
        "required": false,
        "since": 5,
        "notes": "A name for this one part, unique within the result, rendered as `data-part`, so a page can link to it: `host.reveal(id)` opens it if it is collapsed, scrolls to it and moves focus there. Where `role` says what kind of part this is, `id` says which one."
      }
    ],
    "kinds": [
      {
        "kind": "fields",
        "since": 1,
        "use": "Label and value pairs, optionally grouped",
        "attributes": [
          {
            "name": "fields",
            "type": "Field[]",
            "required": true,
            "since": 1,
            "notes": "Shown in order as a list of labels and values, grouped by `group`. A card shows the first `cardFields`."
          }
        ],
        "types": {
          "Field": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 1,
              "notes": "What the value is: the term the value is listed under."
            },
            {
              "name": "value",
              "type": "string",
              "required": true,
              "since": 1,
              "notes": "The value, shown as the tool formatted it."
            },
            {
              "name": "note",
              "type": "string",
              "required": false,
              "since": 1,
              "notes": "A qualifier shown beside the value: a unit, a caveat, \"(dynamic)\"."
            },
            {
              "name": "group",
              "type": "string",
              "required": false,
              "since": 1,
              "notes": "Fields sharing a group are rendered together under its name."
            },
            {
              "name": "tone",
              "type": "TextTone",
              "required": false,
              "since": 1,
              "notes": "Draws attention without inventing a severity scale, or `muted` to let a routine value recede."
            },
            {
              "name": "role",
              "type": "string",
              "required": false,
              "since": 5,
              "notes": "A name for this one field, rendered as `data-role`, so a host stylesheet can find \"the verdict\" without counting rows. The runtime gives it no meaning."
            },
            {
              "name": "mono",
              "type": "boolean",
              "required": false,
              "default": "true",
              "since": 8,
              "notes": "Set the value, and its note, in the monospace face, for an address, a hex string or a header the reader compares character by character. `false` for a sentence or a plain number with a unit, which read like terminal output in it. Rendered as `.tb-mono` on the `<dd>`, the class a table cell uses."
            }
          ]
        }
      },
      {
        "kind": "text",
        "since": 1,
        "use": "Prose, or preformatted text",
        "attributes": [
          {
            "name": "text",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "Shown as a paragraph, or preformatted when `mono` is set."
          },
          {
            "name": "mono",
            "type": "boolean",
            "required": false,
            "default": "false",
            "since": 1,
            "notes": "Monospace and preformatted, keeping spaces and line breaks."
          }
        ],
        "types": {}
      },
      {
        "kind": "code",
        "since": 1,
        "use": "Source with a language, painted by the host's highlighter",
        "attributes": [
          {
            "name": "lang",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "Passed to the host's `highlight(source, lang)` hook and set as `data-lang`. A single token also becomes the `language-<lang>` class."
          },
          {
            "name": "source",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "Shown as is, and what Copy copies even when a highlighter adds line numbers."
          }
        ],
        "types": {}
      },
      {
        "kind": "table",
        "since": 1,
        "use": "Rows and typed columns, optionally linked to a chart",
        "attributes": [
          {
            "name": "columns",
            "type": "Column[]",
            "required": true,
            "since": 1,
            "notes": "The header row. Each row's cells line up with these in order."
          },
          {
            "name": "rows",
            "type": "Cell[][]",
            "required": true,
            "since": 1,
            "notes": "One array of cells per row, in column order."
          },
          {
            "name": "caption",
            "type": "string",
            "required": false,
            "since": 1,
            "notes": "Shown above the table, except on a card."
          },
          {
            "name": "link",
            "type": "{ chart: string; keys: (number | string | null)[] }",
            "required": false,
            "since": 4,
            "notes": "Link these rows to a chart in the same result: `keys[i]` is the chart x that row i describes, `null` for a row with none. Pointing at the chart highlights the matching rows, and pointing at a row moves the chart's readout to it."
          }
        ],
        "types": {
          "Column": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 1,
              "notes": "The header text, also set on each cell as `data-label` so host CSS can name a value on a narrow screen."
            },
            {
              "name": "align",
              "type": "\"start\" | \"end\"",
              "required": false,
              "default": "\"start\"",
              "since": 1,
              "notes": "Alignment of the header and the column's cells. Without it, a cell given as a number aligns to the end and every other cell to the start."
            },
            {
              "name": "mono",
              "type": "boolean",
              "required": false,
              "since": 1,
              "notes": "Monospace, for anything the reader will compare digit by digit."
            },
            {
              "name": "wrap",
              "type": "\"prose\" | \"none\"",
              "required": false,
              "since": 8,
              "notes": "How the column's text wraps, rendered as `data-wrap` on its header and every cell, so a narrow layout knows which columns are sentences: `prose` may wrap and wants a reading width, `none` is a short token (a hash, a date, a status word) kept on one line. A cell's own `wrap` overrides it. Absent makes no claim, and no attribute is written."
            },
            {
              "name": "rowHeader",
              "type": "boolean",
              "required": false,
              "default": "false",
              "since": 8,
              "notes": "The column names each row: its cells render as `<th scope=\"row\">` instead of `<td>`, so a screen reader announces the row's name with every cell, and on a narrow tool the column stays in view while the rest scrolls. Opt-in, because a host selector such as `td:first-child` stops matching those cells."
            }
          ]
        }
      },
      {
        "kind": "series",
        "since": 1,
        "use": "Line, area, bar, scatter, box and other x/y charts",
        "attributes": [
          {
            "name": "chart",
            "type": "Chart",
            "required": true,
            "since": 1,
            "notes": "The chart: its axes, its series and how it answers a pointer."
          }
        ],
        "types": {
          "Chart": [
            {
              "name": "xLabel",
              "type": "string",
              "required": true,
              "since": 1,
              "notes": "The x axis title, with `xUnit` in parentheses. Also heads the data table's x column."
            },
            {
              "name": "yLabel",
              "type": "string",
              "required": true,
              "since": 1,
              "notes": "The left y axis title, with `yUnit` in parentheses."
            },
            {
              "name": "x",
              "type": "(number | string)[]",
              "required": true,
              "since": 1,
              "notes": "One position per value. Numbers for a quantity; text for named things, \"Mesh\", \"SFU\", \"p99\", which are drawn as evenly spaced categories in the order given. If any value is text, all are read as text."
            },
            {
              "name": "series",
              "type": "Series[]",
              "required": true,
              "since": 1,
              "notes": "Every line, bar or area, drawn in order, each in the next of six series colours. A legend is shown when there is more than one."
            },
            {
              "name": "xUnit",
              "type": "string",
              "required": false,
              "since": 1,
              "notes": "Shown after `xLabel` in parentheses, on the axis and in the data table."
            },
            {
              "name": "yUnit",
              "type": "string",
              "required": false,
              "since": 1,
              "notes": "Shown after `yLabel` in parentheses."
            },
            {
              "name": "annotations",
              "type": "{ x: number | string; label: string }[]",
              "required": false,
              "since": 1,
              "notes": "A vertical marker with a name: \"the knee\", \"capacity\", \"p99\"."
            },
            {
              "name": "thresholds",
              "type": "{ y: number; label: string; axis?: \"left\" | \"right\"; tone?: Tone }[]",
              "required": false,
              "since": 4,
              "notes": "A labelled horizontal rule at a y value: a budget, an SLO, a limit. Not a series, so it adds no legend entry, no data-table column and no readout row, and the y scale widens to show it. `tone` lets a limit read as one."
            },
            {
              "name": "yLabelRight",
              "type": "string",
              "required": false,
              "since": 1,
              "notes": "Second axis label, required if any series sets `axis: \"right\"`."
            },
            {
              "name": "readout",
              "type": "Readout",
              "required": false,
              "since": 4,
              "notes": "How the chart answers a pointer or keyboard focus. Default: a crosshair and every series at the nearest x."
            },
            {
              "name": "id",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "A name a `table` in the same result can link to with `link.chart`. Unique within a result."
            },
            {
              "name": "orientation",
              "type": "\"vertical\" | \"horizontal\"",
              "required": false,
              "since": 4,
              "notes": "`horizontal` draws bars growing rightward, one row per x, for long category names and budgets read left to right. Bar series only: other shapes are not drawn on a horizontal chart, and the data table's caption says so."
            },
            {
              "name": "mirror",
              "type": "boolean",
              "required": false,
              "since": 4,
              "notes": "On a horizontal bar chart: series the tool gives as negative values are drawn to the left, and every label (ticks, readout, data table) shows the magnitude. For back-to-back comparisons and pyramids, where both sides are quantities and neither is negative."
            },
            {
              "name": "yScale",
              "type": "\"linear\" | \"log\"",
              "required": false,
              "since": 4,
              "notes": "`log` draws an axis in powers of ten, for values spanning orders of magnitude: latency distributions, loss from 0.01% to 10%. A log axis cannot show zero or a negative, so those points are left out and the data table's caption says so; bars are not drawn on a log y axis, because a bar's length means nothing there."
            },
            {
              "name": "xScale",
              "type": "\"linear\" | \"log\"",
              "required": false,
              "default": "\"linear\"",
              "since": 4,
              "notes": "`log` draws the x axis in powers of ten, as `yScale` does for y. Ignored when `x` is text."
            }
          ],
          "Series": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 1,
              "notes": "The series' name in the legend, the readout and the data table."
            },
            {
              "name": "points",
              "type": "(number | null)[]",
              "required": true,
              "since": 1,
              "notes": "One value per x. A `null` is a gap, not a zero, and the distinction matters on a chart."
            },
            {
              "name": "unit",
              "type": "string",
              "required": false,
              "since": 1,
              "notes": "Shown after each value in the readout, and after `label` in the legend and the data table. The first right-axis series' unit also goes beside `yLabelRight`."
            },
            {
              "name": "shape",
              "type": "\"line\" | \"area\" | \"bar\" | \"points\" | \"step\" | \"band\" | \"box\"",
              "required": false,
              "since": 1,
              "notes": "`points` draws a marker at each value and no line between them: for an x of separate items (frames, requests, runs) where nothing exists between two of them, so a joining line would invent a slope. `step` holds each value until the next x, then rises or falls straight to the next: for state sampled on events (a buffer's depth, a configured rate), where a sloped line invents a gradual change."
            },
            {
              "name": "boxes",
              "type": "({ low: number; q1: number; median: number; q3: number; high: number; outliers?: number[] } | null)[]",
              "required": false,
              "since": 4,
              "notes": "The summary each `box` draws, one per x: whiskers from `low` to `high`, a box from `q1` to `q3`, a rule at `median`, and `outliers` as points. The tool computes it, so what the whiskers mean (1.5 IQR, min to max, p5 to p95) is the tool's and belongs in the chart's labels. Set `points` to the medians, which is what the readout, links and scale read where they read one value."
            },
            {
              "name": "lower",
              "type": "(number | null)[]",
              "required": false,
              "since": 4,
              "notes": "The lower edge of a `band`, one per x, with `points` as the upper edge: a spread such as p5 to p95, or a min-to-max envelope, filled between the two under every other series. A `null` in either edge breaks the band."
            },
            {
              "name": "markers",
              "type": "boolean",
              "required": false,
              "since": 4,
              "notes": "Draw a marker at each value of a `line` or `area`, so every real point is visible and a series under an identical one can still be found. Each series gets its own marker shape."
            },
            {
              "name": "stack",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "Bar series sharing a `stack` id are stacked at each x, in series order, bottom to top, so a reader sees each part and the total. Positive and negative values stack away from zero separately. Bar series without one are grouped beside the stack as before."
            },
            {
              "name": "x",
              "type": "number[]",
              "required": false,
              "since": 4,
              "notes": "This series' own x values, one per point, instead of the chart's shared `x`: for measurements that do not share x positions, a scatter of (delay, cut-off) pairs say. The x scale covers every series. Usually with `shape: \"points\"`."
            },
            {
              "name": "sizes",
              "type": "(number | null)[]",
              "required": false,
              "since": 4,
              "notes": "A third measure per point, drawn as the AREA of a bubble on a `points` series. By area, not radius, so a value twice as large looks twice as large. `sizeLabel` names it in the key and the readout: \"Calls\"."
            },
            {
              "name": "sizeLabel",
              "type": "string",
              "required": false,
              "default": "\"Size\"",
              "since": 4,
              "notes": "The name of `sizes` in the key, the readout and the data table."
            },
            {
              "name": "axis",
              "type": "\"left\" | \"right\"",
              "required": false,
              "default": "\"left\"",
              "since": 1,
              "notes": "`right` draws the series against a second y scale on the right, titled by the chart's `yLabelRight`."
            },
            {
              "name": "notes",
              "type": "(string | null)[]",
              "required": false,
              "since": 4,
              "notes": "What the readout says for this series at each point, replacing the formatted value there: \"250 ms, 60 ms late\". `null` keeps the default for that point. Same length as `points`."
            }
          ],
          "Readout": [
            {
              "name": "mode",
              "type": "\"x\" | \"point\" | \"none\"",
              "required": false,
              "since": 1,
              "notes": "`x`: every series at the nearest x (default). `point`: only the nearest point. `none`: no readout."
            },
            {
              "name": "crosshair",
              "type": "\"x\" | \"y\" | \"both\" | \"none\"",
              "required": false,
              "since": 1,
              "notes": "Which crosshair lines to draw. Default `x`."
            },
            {
              "name": "titles",
              "type": "(string | null)[]",
              "required": false,
              "since": 1,
              "notes": "A heading per x, replacing \"<xLabel> <x>\": \"Frame 6 (lost)\". `null` keeps the default. Same length as `x`."
            },
            {
              "name": "highlightTable",
              "type": "boolean",
              "required": false,
              "since": 1,
              "notes": "Highlight the matching row of this chart's own data table as the readout moves."
            }
          ]
        }
      },
      {
        "kind": "heatmap",
        "since": 4,
        "use": "A value over two dimensions",
        "attributes": [
          {
            "name": "heatmap",
            "type": "Heatmap",
            "required": true,
            "since": 4,
            "notes": "The grid of values and its row and column labels."
          }
        ],
        "types": {
          "Heatmap": [
            {
              "name": "xLabel",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "The title under the columns."
            },
            {
              "name": "yLabel",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "The title above the row labels."
            },
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "What a cell's value is, for the key and the readout: \"p95 latency\"."
            },
            {
              "name": "unit",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "Shown after each value in the readout, and after `label` in the key and the data table's caption."
            },
            {
              "name": "x",
              "type": "(number | string)[]",
              "required": true,
              "since": 4,
              "notes": "The column labels, left to right."
            },
            {
              "name": "y",
              "type": "(number | string)[]",
              "required": true,
              "since": 4,
              "notes": "The row labels, top to bottom."
            },
            {
              "name": "values",
              "type": "(number | null)[][]",
              "required": true,
              "since": 4,
              "notes": "`values[row][column]`. A `null` or missing cell has no value and is drawn hatched."
            },
            {
              "name": "scale",
              "type": "\"sequential\" | \"diverging\"",
              "required": false,
              "since": 4,
              "notes": "`sequential` (default) is one hue, light to dark: how much. `diverging` is two hues either side of `midpoint`."
            },
            {
              "name": "midpoint",
              "type": "number",
              "required": false,
              "since": 4,
              "notes": "The neutral value of a diverging scale. Default 0."
            }
          ]
        }
      },
      {
        "kind": "pie",
        "since": 4,
        "use": "Parts of a whole, or a donut with its total",
        "attributes": [
          {
            "name": "pie",
            "type": "Pie",
            "required": true,
            "since": 4,
            "notes": "The whole and its slices."
          }
        ],
        "types": {
          "Pie": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "What the whole is: \"Traffic by codec\"."
            },
            {
              "name": "unit",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "Shown after each value in the readout and in the data table's Value column."
            },
            {
              "name": "slices",
              "type": "{ label: string; value: number; tone?: Tone; explode?: boolean }[]",
              "required": true,
              "since": 4,
              "notes": "`explode` pulls a slice out a little to point at it. A slice pulled out looks bigger than it is, so use it for the one part the surrounding text is about, not for decoration."
            },
            {
              "name": "donut",
              "type": "boolean",
              "required": false,
              "since": 4,
              "notes": "A ring rather than a disc, with `total` in its centre."
            },
            {
              "name": "total",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "The donut's centre text: \"48 kb/s\"."
            }
          ]
        }
      },
      {
        "kind": "radar",
        "since": 4,
        "use": "A profile across several measures",
        "attributes": [
          {
            "name": "radar",
            "type": "Radar",
            "required": true,
            "since": 4,
            "notes": "The axes and the series drawn across them."
          }
        ],
        "types": {
          "Radar": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "What the chart is: its accessible name and the data table's caption."
            },
            {
              "name": "unit",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "Shown after each value in the readout, and after each series' name in the legend and the data table."
            },
            {
              "name": "axes",
              "type": "{ label: string; max?: number }[]",
              "required": true,
              "since": 4,
              "notes": "One spoke per axis, clockwise from 12 o'clock. An axis `max` gives that spoke its own scale; otherwise every spoke shares one, rounded up from the largest value to 1, 2 or 5 times a power of ten. Values are clamped between zero and the scale's top."
            },
            {
              "name": "series",
              "type": "{ label: string; values: (number | null)[] }[]",
              "required": true,
              "since": 4,
              "notes": "One value per axis; a `null` breaks the shape at that spoke rather than drawing it at the centre."
            }
          ]
        }
      },
      {
        "kind": "treemap",
        "since": 4,
        "use": "A hierarchy sized by value",
        "attributes": [
          {
            "name": "treemap",
            "type": "Treemap",
            "required": true,
            "since": 4,
            "notes": "The hierarchy, from its root."
          }
        ],
        "types": {
          "Treemap": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "What the whole is: its accessible name and the data table's caption."
            },
            {
              "name": "unit",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "Shown after each value on a rectangle, in the readout and in the data table."
            },
            {
              "name": "root",
              "type": "TreemapNode",
              "required": true,
              "since": 4,
              "notes": "The whole. Not drawn itself: its children are the top level, each in its own series colour."
            }
          ],
          "TreemapNode": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "Drawn on the rectangle where it fits, cut at 18 characters, and always in the readout and the data table."
            },
            {
              "name": "value",
              "type": "number",
              "required": false,
              "since": 4,
              "notes": "A leaf's size. A negative counts as zero, and a node of zero is not drawn. On a branch the children's sum is drawn instead, and the data table's caption says the two differed."
            },
            {
              "name": "children",
              "type": "TreemapNode[]",
              "required": false,
              "since": 4,
              "notes": "Drawn inside this node's rectangle, largest first. Past the third level they fold into their parent."
            },
            {
              "name": "tone",
              "type": "Tone",
              "required": false,
              "since": 4,
              "notes": "Replaces the colour this node's top-level group gives it. `normal` keeps the group's colour."
            }
          ]
        }
      },
      {
        "kind": "venn",
        "since": 4,
        "use": "How two or three sets overlap",
        "attributes": [
          {
            "name": "venn",
            "type": "Venn",
            "required": true,
            "since": 4,
            "notes": "The sets and their overlaps."
          }
        ],
        "types": {
          "Venn": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 4,
              "notes": "What the diagram is: its accessible name and the data table's caption."
            },
            {
              "name": "unit",
              "type": "string",
              "required": false,
              "since": 4,
              "notes": "Shown after each count in the readout and in the data table's Count column."
            },
            {
              "name": "sets",
              "type": "{ label: string; size: number }[]",
              "required": true,
              "since": 4,
              "notes": "Two or three sets, each `size` above zero and counting every member, including those in an overlap. Any other number of sets draws a message instead."
            },
            {
              "name": "overlaps",
              "type": "{ sets: number[]; size: number }[]",
              "required": true,
              "since": 4,
              "notes": "`sets` are indices into `sets`: [0, 1], or [0, 1, 2] for the middle of three."
            }
          ]
        }
      },
      {
        "kind": "graph",
        "since": 7,
        "use": "Nodes and edges, laid out by the runtime",
        "attributes": [
          {
            "name": "graph",
            "type": "Graph",
            "required": true,
            "since": 7,
            "notes": "The nodes, edges, groups and lanes, and how to lay them out."
          }
        ],
        "types": {
          "Graph": [
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "What the graph is: \"Order lifecycle\". The figure's accessible name."
            },
            {
              "name": "layout",
              "type": "\"layered\" | \"tree\" | \"radial\" | \"force\" | \"manual\"",
              "required": false,
              "since": 7,
              "notes": "Default `layered`: ranks along `direction`, for anything with a flow. `tree` and `radial` for a hierarchy, `force` for a network with no direction, `manual` to place every node at its `x` and `y`."
            },
            {
              "name": "direction",
              "type": "\"LR\" | \"RL\" | \"TB\" | \"BT\"",
              "required": false,
              "since": 7,
              "notes": "Default `LR` for layered, `TB` for tree."
            },
            {
              "name": "routing",
              "type": "\"orthogonal\" | \"spline\" | \"straight\" | \"polyline\"",
              "required": false,
              "since": 7,
              "notes": "Default `orthogonal` for layered and tree, `straight` for force and manual, `spline` for radial. An edge a straight line cannot draw without crossing a node is drawn as a polyline instead."
            },
            {
              "name": "directed",
              "type": "boolean",
              "required": false,
              "since": 7,
              "notes": "Default true."
            },
            {
              "name": "merge",
              "type": "\"none\" | \"source\" | \"target\"",
              "required": false,
              "since": 7,
              "notes": "Default `none`: no two edges ever share a stretch of line. `source` lets the edges leaving one node share a trunk, the bus of an org chart; `target` the edges arriving at one."
            },
            {
              "name": "seed",
              "type": "number",
              "required": false,
              "since": 7,
              "notes": "Force layout only. Default 1. The same seed gives the same picture everywhere."
            },
            {
              "name": "spacing",
              "type": "\"compact\" | \"normal\" | \"loose\"",
              "required": false,
              "since": 7,
              "notes": "Default `normal`."
            },
            {
              "name": "nodes",
              "type": "GraphNode[]",
              "required": true,
              "since": 7,
              "notes": "Every node, ids unique. At least one, or the graph is listed instead of drawn."
            },
            {
              "name": "edges",
              "type": "GraphEdge[]",
              "required": true,
              "since": 7,
              "notes": "Every edge, each end naming a node. All are also listed in a table under the picture."
            },
            {
              "name": "groups",
              "type": "GraphGroup[]",
              "required": false,
              "since": 7,
              "notes": "Boxes drawn around their members in any layout. A node joins one with `group`."
            },
            {
              "name": "lanes",
              "type": "GraphLane[]",
              "required": false,
              "since": 7,
              "notes": "Swimlanes, in order. Layered layouts only. A node joins one with `lane`."
            },
            {
              "name": "highlight",
              "type": "string[]",
              "required": false,
              "since": 7,
              "notes": "A path to emphasise, as node ids in order."
            },
            {
              "name": "limits",
              "type": "{ nodes?: number; edges?: number }",
              "required": false,
              "since": 7,
              "notes": "The tool's own limits. Past either, the runtime shows the edge table and says why instead of drawing. No default: without one, any size is drawn."
            },
            {
              "name": "interaction",
              "type": "{ /** Whether a reader can collapse and open groups. Default true. */ collapse?: boolean; /** What pointing at or focusing a node emphasises. Default `neighbours`. */ focus?: \"neighbours\" | \"none\"; /** Whether picking two nodes highlights the shortest path between them. Default true. */ paths?: boolean; }",
              "required": false,
              "since": 7,
              "notes": "What a reader can do with the picture. Every part is on by default."
            }
          ],
          "GraphNode": [
            {
              "name": "id",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "Unique within the graph. What edges, `highlight` and the edge table name."
            },
            {
              "name": "label",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Default: the id."
            },
            {
              "name": "note",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "A second, muted line under the label."
            },
            {
              "name": "shape",
              "type": "GraphShape",
              "required": false,
              "since": 7,
              "notes": "Default `rounded`."
            },
            {
              "name": "tone",
              "type": "Tone",
              "required": false,
              "since": 7,
              "notes": "Status: the border and tint of the tone. Wins over `color`."
            },
            {
              "name": "color",
              "type": "GraphColor",
              "required": false,
              "since": 7,
              "notes": "Category: a series colour or a named token."
            },
            {
              "name": "badge",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "A few characters over the top-right corner: a count, a version."
            },
            {
              "name": "rows",
              "type": "GraphRow[]",
              "required": false,
              "since": 7,
              "notes": "The rows of a `record` node."
            },
            {
              "name": "group",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "The innermost group this node belongs to. Groups nest through `parent`."
            },
            {
              "name": "lane",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Layered layouts only: the swimlane."
            },
            {
              "name": "rank",
              "type": "number",
              "required": false,
              "since": 7,
              "notes": "Layered layouts only: pin this node to a rank, 0 being the first."
            },
            {
              "name": "x",
              "type": "number",
              "required": false,
              "since": 7,
              "notes": "`manual` layout: the centre. `force` layout: pins the node here."
            },
            {
              "name": "y",
              "type": "number",
              "required": false,
              "since": 7,
              "notes": "`manual` layout: the centre's y, in the same units as `x`; a missing one counts as 0. `force` layout: pins the node only with `x` set too."
            },
            {
              "name": "role",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Rendered as `data-role`, for host stylesheets. The runtime gives it no meaning."
            }
          ],
          "GraphEdge": [
            {
              "name": "from",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "The id of the node the edge leaves. Must name a node, or the graph is listed instead of drawn."
            },
            {
              "name": "to",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "The id of the node the edge arrives at. Must name a node; the same as `from` draws a loop."
            },
            {
              "name": "fromPort",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "A row id on the `from` node, a `record`: the edge attaches at that row. Must name one of its rows."
            },
            {
              "name": "toPort",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "A row id on the `to` node, a `record`: the edge attaches at that row. Must name one of its rows."
            },
            {
              "name": "fromSide",
              "type": "GraphSide",
              "required": false,
              "since": 7,
              "notes": "The side of the `from` node the edge must use. Default: the side facing the other end."
            },
            {
              "name": "toSide",
              "type": "GraphSide",
              "required": false,
              "since": 7,
              "notes": "The side of the `to` node the edge must use. Default: the side facing the other end."
            },
            {
              "name": "label",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Drawn on the line at `labelAt`, and shown in the edge table and the keyboard readout."
            },
            {
              "name": "labelAt",
              "type": "\"start\" | \"middle\" | \"end\"",
              "required": false,
              "since": 7,
              "notes": "Where along the edge the label sits. Default `middle`."
            },
            {
              "name": "line",
              "type": "\"solid\" | \"dashed\" | \"dotted\"",
              "required": false,
              "since": 7,
              "notes": "Default `solid`."
            },
            {
              "name": "width",
              "type": "\"thin\" | \"normal\" | \"thick\"",
              "required": false,
              "since": 7,
              "notes": "Default `normal`."
            },
            {
              "name": "tone",
              "type": "Tone",
              "required": false,
              "since": 7,
              "notes": "Status: the line in the tone's colour. Wins over `color`, except `normal`."
            },
            {
              "name": "color",
              "type": "GraphColor",
              "required": false,
              "since": 7,
              "notes": "Category: a series colour or a named token for the line. An unknown value draws the default colour."
            },
            {
              "name": "head",
              "type": "GraphMarker",
              "required": false,
              "since": 7,
              "notes": "At `to`. Default `arrow` when the graph is directed, else `none`."
            },
            {
              "name": "tail",
              "type": "GraphMarker",
              "required": false,
              "since": 7,
              "notes": "At `from`. Default `none`."
            },
            {
              "name": "role",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Rendered as `data-role`, for host stylesheets. The runtime gives it no meaning."
            }
          ],
          "GraphGroup": [
            {
              "name": "id",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "Unique among groups and nodes, because a collapsed group is drawn as a node with this id. What a node's `group` names."
            },
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "The title at the group's top-left corner, which collapses the group when pressed. Also the collapsed node's label."
            },
            {
              "name": "parent",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "The enclosing group. Groups nest to any depth; a cycle of parents is refused."
            },
            {
              "name": "collapsed",
              "type": "boolean",
              "required": false,
              "since": 7,
              "notes": "Drawn as one node standing for its members until a reader opens it. Default false."
            },
            {
              "name": "color",
              "type": "GraphColor",
              "required": false,
              "since": 7,
              "notes": "The box's colour, and the colour of the node standing for the group when collapsed. Default: the plain border."
            },
            {
              "name": "role",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Rendered as `data-role` on the box and on the collapsed node, for host stylesheets. The runtime gives it no meaning."
            }
          ],
          "GraphLane": [
            {
              "name": "id",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "Unique among lanes. What a node's `lane` names."
            },
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "The title at the start of the band."
            },
            {
              "name": "role",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Rendered as `data-role` on the band, for host stylesheets. The runtime gives it no meaning."
            }
          ],
          "GraphRow": [
            {
              "name": "id",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "Unique within the node; what `fromPort` and `toPort` name."
            },
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 7,
              "notes": "The row's text, after `tag` and before `detail`."
            },
            {
              "name": "detail",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "Right-aligned and muted: a column type, a value."
            },
            {
              "name": "tag",
              "type": "string",
              "required": false,
              "since": 7,
              "notes": "A short tag before the label, such as PK or FK."
            }
          ]
        }
      },
      {
        "kind": "group",
        "since": 1,
        "use": "Several results in one answer",
        "attributes": [
          {
            "name": "parts",
            "type": "Output[]",
            "required": true,
            "since": 1,
            "notes": "Rendered in order, each by its own kind, and may nest. A host's `cardParts` caps how many are shown."
          }
        ],
        "types": {}
      },
      {
        "kind": "bytes",
        "since": 2,
        "use": "Hex dump with offsets, a printable gutter and named ranges",
        "attributes": [
          {
            "name": "bytes",
            "type": "number[]",
            "required": true,
            "since": 2,
            "notes": "One number per byte, 0 to 255, drawn sixteen to a row. A card shows the first four rows."
          },
          {
            "name": "offset",
            "type": "number",
            "required": false,
            "default": "0",
            "since": 2,
            "notes": "What the first byte is labelled in the offset column and the legend."
          },
          {
            "name": "highlight",
            "type": "ByteRange[]",
            "required": false,
            "since": 2,
            "notes": "Named ranges, each coloured over its bytes and listed in a legend. Where two overlap, the later one wins."
          },
          {
            "name": "caption",
            "type": "string",
            "required": false,
            "since": 2,
            "notes": "Shown above the grid, on a card too."
          }
        ],
        "types": {
          "ByteRange": [
            {
              "name": "at",
              "type": "number",
              "required": true,
              "since": 2,
              "notes": "Offset from the start of `bytes`, not from `offset`."
            },
            {
              "name": "len",
              "type": "number",
              "required": true,
              "since": 2,
              "notes": "How many bytes, from `at`. Bytes past the end of `bytes` are not marked."
            },
            {
              "name": "label",
              "type": "string",
              "required": true,
              "since": 2,
              "notes": "Named in the legend with its length and offset, and the hover title of each of its bytes."
            },
            {
              "name": "tone",
              "type": "Tone",
              "required": false,
              "since": 2,
              "notes": "Default: the next of six series colours, counted over ranges without a tone, so neighbouring ranges differ."
            }
          ]
        }
      },
      {
        "kind": "error",
        "since": 1,
        "use": "The input was wrong, and the tool says which one",
        "attributes": [
          {
            "name": "message",
            "type": "string",
            "required": true,
            "since": 1,
            "notes": "Shown in full, never truncated, and announced. Also read as the description of the input named by `input`."
          },
          {
            "name": "input",
            "type": "string",
            "required": false,
            "since": 1,
            "notes": "The id of the input at fault: that control is marked `aria-invalid` and points its description at the message."
          },
          {
            "name": "at",
            "type": "number",
            "required": false,
            "since": 1,
            "notes": "Where in that input's value the problem starts, shown after the message. The tools here give a character offset."
          },
          {
            "name": "len",
            "type": "number",
            "required": false,
            "since": 1,
            "notes": "How far the problem runs from `at`, shown as a range after the message."
          },
          {
            "name": "row",
            "type": "number",
            "required": false,
            "since": 9,
            "notes": "For a `rows` input, the index of the row at fault, 0 for the first: the message is shown under that row, named \"Limits, row 2: ...\", and its cells are marked invalid."
          }
        ],
        "types": {}
      }
    ]
  },
  "manifest": [
    {
      "name": "sdk",
      "type": "number",
      "required": true,
      "since": 1,
      "notes": "The contract version this tool was written against."
    },
    {
      "name": "id",
      "type": "string",
      "required": true,
      "since": 1,
      "notes": "Stable, lowercase, hyphenated. Also the directory name and the URL segment."
    },
    {
      "name": "name",
      "type": "string",
      "required": true,
      "since": 1,
      "notes": "The tool's title, shown as its heading."
    },
    {
      "name": "blurb",
      "type": "string",
      "required": true,
      "since": 1,
      "notes": "One sentence. Shown on cards and used as a page description."
    },
    {
      "name": "version",
      "type": "string",
      "required": true,
      "since": 1,
      "notes": "The tool's own version, independent of `sdk`."
    },
    {
      "name": "capabilities",
      "type": "Capability[]",
      "required": true,
      "since": 1,
      "notes": "What the tool is allowed to do. At least one; `pure` is the only capability today."
    },
    {
      "name": "inputs",
      "type": "InputSpec[]",
      "required": true,
      "since": 1,
      "notes": "The form, one control per entry, in order. At least one."
    },
    {
      "name": "kinds",
      "type": "OutputKind[]",
      "required": true,
      "since": 1,
      "notes": "Every kind `run` can return, so a host can refuse a tool it cannot draw."
    },
    {
      "name": "runtime",
      "type": "{ /** Entry file, relative to the tool's directory. */ entry: string; /** * `main` is the default and costs nothing. `worker` is for a tool whose running time depends * on its input. It is the only mode where a timeout can actually stop anything. */ thread?: \"main\" | \"worker\"; }",
      "required": true,
      "since": 1,
      "notes": "How the runtime loads and runs the tool."
    },
    {
      "name": "status",
      "type": "\"live\" | \"deprecated\" | \"retired\"",
      "required": false,
      "since": 1,
      "notes": "Lifecycle. Default `live`. `deprecated` still runs, with a visible marker a host can style, and is refused as a live card. `retired` does not run: the element explains and renders `links`, so a bookmarked URL is not a 404."
    },
    {
      "name": "card",
      "type": "\"live\" | \"info\" | \"none\"",
      "required": false,
      "since": 1,
      "notes": "How the tool appears in a compact slot: runnable, a summary that links out, or not at all."
    },
    {
      "name": "cardFields",
      "type": "number",
      "required": false,
      "since": 1,
      "notes": "How many fields a card shows before it stops. Errors are never truncated."
    },
    {
      "name": "autoRun",
      "type": "boolean",
      "required": false,
      "since": 1,
      "notes": "Re-run as the reader types, instead of waiting for the Run button. Off by default, and that default is deliberate. A tool that runs on every keystroke does work nobody asked for, makes the Run button look broken (the answer is already there), and takes the decision away from the reader. Opt in only for a tool that is genuinely instant and reads better live, a unit converter say. Anything with a worker or a heavy loop should leave it alone."
    },
    {
      "name": "reset",
      "type": "boolean",
      "required": false,
      "default": "true",
      "since": 8,
      "notes": "`false` hides the runtime's Reset control, which puts every input back to the values the tool opened with: for a tool with one input, where retyping it is the reset."
    },
    {
      "name": "timeoutMs",
      "type": "number",
      "required": false,
      "since": 1,
      "notes": "Worker mode only. A tool that declares this on the main thread is rejected: nothing there can be stopped."
    },
    {
      "name": "help",
      "type": "string",
      "required": false,
      "since": 1,
      "notes": "Markdown file, relative to the tool's directory, rendered as the tool's help."
    },
    {
      "name": "tags",
      "type": "string[]",
      "required": false,
      "since": 1,
      "notes": "Free-form labels a host can filter or group tools by."
    },
    {
      "name": "links",
      "type": "{ /** The link text. */ label: string; /** An `http`, `https` or `mailto` URL, or a relative path. Anything else is refused. */ href: string; }[]",
      "required": false,
      "since": 1,
      "notes": "Anything the tool wants to point at: a spec it implements, a post explaining it, a reference decoder. Deliberately generic: a host site's own content relations are the host's business."
    },
    {
      "name": "samples",
      "type": "Sample[]",
      "required": false,
      "since": 3,
      "notes": "Example inputs, rendered as a row of buttons under the form. Clicking one fills the form. It is an input change like any other, so the result goes stale and waits for Run, or re-runs if the tool set `autoRun`. A compact card does not show samples at all, because it has room for one input and a Run button, and a row of buttons there would crowd out the result the card exists to show."
    }
  ],
  "host": {
    "options": [
      {
        "name": "source",
        "type": "ToolSource",
        "required": true,
        "notes": "Where tools come from."
      },
      {
        "name": "workerFactory",
        "type": "() => Worker",
        "required": false,
        "notes": "Required only for tools that declare `thread: \"worker\"`. See the README."
      },
      {
        "name": "pageUrl",
        "type": "(id: string) => string",
        "required": false,
        "notes": "Given a tool id, the URL of its full page, which card and embed modes link out to."
      },
      {
        "name": "rootMargin",
        "type": "string",
        "required": false,
        "notes": "How far ahead of the viewport `page` and `embed` modes start loading. Default \"200% 0px\"."
      },
      {
        "name": "debounceMs",
        "type": "number",
        "required": false,
        "notes": "Milliseconds of quiet before a typed change runs. Default 150."
      },
      {
        "name": "highlight",
        "type": "(source: string, lang: string) => Node",
        "required": false,
        "notes": "Paint a `code` result with the host's own highlighter. The runtime does not bundle one: that would roughly double the package, and a host usually already has one. Return a `Node`, not a string. A string-returning hook would need `innerHTML`, which SECURITY.md forbids. Omit the hook to keep readable preformatted text."
      },
      {
        "name": "styles",
        "type": "HostStyles",
        "required": false,
        "notes": "The host's own stylesheets, adopted into every tool's shadow root after the runtime's. A sheet, CSS text, a list of either, or `{ \"*\": [...], \"tool-id\": [...] }` for one tool to differ. The class names and attributes they may rely on are listed in STYLING.md."
      },
      {
        "name": "wide",
        "type": "\"fit\" | \"scroll\"",
        "required": false,
        "notes": "A graph wider than its plot: `fit` (default) scales it down to the plot's width, `scroll` draws it at its natural size in a plot that scrolls. A tool's `wide` attribute overrides it."
      },
      {
        "name": "wideToggle",
        "type": "boolean",
        "required": false,
        "notes": "Whether a scaled graph offers the reader a button to switch between the two. Default true."
      },
      {
        "name": "share",
        "type": "\"none\" | ShareScheme",
        "required": false,
        "default": "the query-parameter scheme",
        "notes": "The share button on `page` and `embed` tools, a link icon that copies a link to the tool with its inputs. By default the link is this page's URL with each input as a query parameter named `<tool id>.<input id>`, `?percentiles.method=linear`, and a tool opening on a page reads its values back from those. `\"none\"` removes the button and reads nothing; a `{ url, read }` pair is your own scheme. Inputs marked `share: false` are never written or read. A tool's `share` attribute overrides it."
      },
      {
        "name": "shareLabel",
        "type": "string",
        "required": false,
        "notes": "Text beside the share button's icon, which is otherwise shown alone. A tool's `share-label` overrides it."
      },
      {
        "name": "sharePosition",
        "type": "\"top\" | \"actions\"",
        "required": false,
        "default": "\"top\" on a page, \"actions\" in an embed",
        "notes": "Where the share button sits: `top`, the end of the title row, or `actions`, the end of the Run row. A tool's `share-position` overrides it."
      }
    ],
    "attributes": [
      {
        "name": "tool",
        "type": "string",
        "required": true,
        "notes": "The id of the tool to show, as its manifest declares."
      },
      {
        "name": "mode",
        "type": "Mode",
        "required": false,
        "default": "\"page\"",
        "notes": "`card`: name, blurb, the `primary` inputs and the first result part, fetching the tool's code only when the reader opens it. `page`: every input, the full result and the tool's links. `embed`: `page` without the title, for the middle of an article. `page` and `embed` fetch when they scroll near the viewport."
      },
      {
        "name": "parts",
        "type": "number",
        "required": false,
        "default": "1",
        "notes": "How many parts of a grouped result to show. A card always caps; on `page` and `embed` there is no cap unless this is set."
      },
      {
        "name": "more",
        "type": "\"link\" | \"expand\"",
        "required": false,
        "default": "\"link\"",
        "notes": "What stands for the parts `parts` hid: `link`, a line of text pointing at the full tool, or `expand`, a button that shows them in place without running the tool again."
      },
      {
        "name": "layout",
        "type": "\"default\" | \"answer-first\"",
        "required": false,
        "default": "\"default\"",
        "notes": "`answer-first` draws the result above the form, for a tool that opens a page about something else."
      },
      {
        "name": "controls",
        "type": "\"default\" | \"none\"",
        "required": false,
        "default": "\"default\"",
        "notes": "`none` draws the result and nothing else: no form, no Run, no status line. The page drives the tool through `values` and `run()`."
      },
      {
        "name": "wide",
        "type": "\"fit\" | \"scroll\"",
        "required": false,
        "default": "\"fit\"",
        "notes": "A graph wider than its plot: `fit` scales it down to the width, `scroll` draws it full size in a plot that scrolls. Overrides `defineToolHost({ wide })`."
      },
      {
        "name": "wide-toggle",
        "type": "\"true\" | \"false\"",
        "required": false,
        "default": "\"true\"",
        "notes": "`false` hides the expand and fit button on a scaled graph. Overrides `defineToolHost({ wideToggle })`."
      },
      {
        "name": "reset",
        "type": "\"default\" | \"none\"",
        "required": false,
        "default": "\"default\"",
        "notes": "`none` leaves out the Reset button, for a tool where it adds nothing, such as one with a single input."
      },
      {
        "name": "links",
        "type": "\"all\" | \"page\" | \"none\"",
        "required": false,
        "default": "\"all\"",
        "notes": "What goes under the tool: `all`, the manifest's links; `page`, only \"Open the full tool\", pointing at `pageUrl(id)`; `none`, nothing. For a tool embedded in an article, where a row of references sits between the answer and the prose. A card shows \"Open the full tool\" for anything but `none`. A retired tool keeps its links whatever this says, because its notice points at them. Also a property, `host.links`."
      },
      {
        "name": "share",
        "type": "\"default\" | \"none\"",
        "required": false,
        "default": "\"default\"",
        "notes": "`none` leaves out the share button, and the tool then reads nothing from the page's URL either. Overrides `defineToolHost({ share })`."
      },
      {
        "name": "share-label",
        "type": "string",
        "required": false,
        "default": "\"\"",
        "notes": "Text beside the share button's icon, which is otherwise shown alone. Overrides `defineToolHost({ shareLabel })`."
      },
      {
        "name": "share-position",
        "type": "\"top\" | \"actions\"",
        "required": false,
        "default": "\"top\" on a page, \"actions\" in an embed",
        "notes": "Where the share button sits: `top`, the end of the title row, or `actions`, the end of the Run row. Overrides `defineToolHost({ sharePosition })`."
      }
    ]
  }
}
