---
title: "The format"
description: "A readable tour of GistUI Format 1.0 - text and lines, grammar, recovery rules, catalogs, the abstract tree, runtime, limits, security and versioning."
---

> Documentation Index
> Fetch the complete documentation index at: https://gistui.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# The format

This page summarises the specification. The normative text is [`spec/gistui-format.md`](https://github.com/GistUI/GistUI/blob/main/spec/gistui-format.md) in the repository; section numbers below refer to it.

## Text and lines

*Sections 4 and 5.*

- A program is UTF-8 text. A byte-order mark is ignored. Lines end with LF, CRLF or CR.
- Each line is classified: a statement, a table row, a comment (`#`), a fence, or prose.
- A prose line (anything else) is ignored and reported as `prose-ignored`. It never breaks a program.
- There are two modes. In **document** mode the whole text is a program. In **inline** mode the text is chat with programs in `gistui` code fences.
- `#gistui 1` on the first line is the version pragma. It is optional.
- A statement normally ends at its line end. An open bracket continues it onto the next line.

## Grammar

*Section 6.*

```text
program   = { line NL }
stmt      = ID "=" ( expr | table )
stateDecl = "$" ID "=" expr
patch     = ID "." ID "=" expr
append    = ID "+=" expr
table     = row { NL row }          row = "|" cell { "|" cell }
expr      = lambda | cond
lambda    = ( ID | "(" [ID {"," ID}] ")" ) "=>" expr
cond      = or [ "?" expr ":" expr ]
primary   = call | builtin | STRING | NUMBER | "true" | "false" | "null"
          | "$" ID | ID | "[" [args] "]" | "{" [pairs] "}" | "(" expr ")"
call      = COMP "(" [args] ")"     builtin = "@" ID "(" [args] ")"
arg       = ID (":"|"=") expr | expr
STRING    = JSON string    COMP = [A-Z]\w*    ID = [a-z_]\w*
```

## Recovery

*Sections 7 and 8.*

Text that is not strict is still read, by numbered rules. A processor **must** apply them and **must** report each application. This is what makes two implementations agree on imperfect input.

| Example | Rule (in short) | Diagnostic |
|---|---|---|
| A call still open at the line end | Closed there | `lenient-syntax` |
| A string still open at the line end | Ended there | `unterminated-string` |
| `'text'` | A string | `lenient-syntax` |
| `key=value`, `"key": value` | A named argument | `lenient-syntax` |
| `Card(a b)` | The missing comma is supplied | `lenient-syntax` |
| `card(…)` | The component `Card` | `lenient-syntax` |
| `// note` | A comment | `lenient-syntax` |
| `.5`, `+3`, `05` | The numbers 0.5, 3, 5 | `lenient-syntax` |
| An unexpected character | Skipped; the statement is kept | `parse-failed`, fixed |
| Nothing recoverable | The statement is dropped | `parse-failed` |

A **generator** must write strict text and must not rely on recovery. Recovery is for the processor.

## Catalogs

*Section 9.*

A catalog lists components. For each: its name, its positional arguments in order, whether it takes children (and of which types), its props with their types, its aliases, and an optional table mapping.

Prop types: `string` (optionally `format: "url"`), `number`, `boolean`, `enum`, `node`, `nodes`, `data`, `array`, `object`, `action`, `state`, `any`.

Bare words are resolved by the catalog, in this order:

1. `key:WORD` where the prop is an enum: an enum value.
2. A word that is a statement id: a reference. A defined statement always wins.
3. A word that is a boolean prop of the enclosing component: a flag.
4. A word that is a value of exactly one enum prop of the enclosing component: that prop.
5. Anything else: a reference that may be defined later.

Misspelled names use the **closest-name rule**: a name within a small edit distance of exactly one catalog name is taken as that name, and reported.

A catalog has a JSON manifest form (`schemas/catalog.schema.json`), so tools in any language can read it.

## The tree

*Section 13.*

Processing gives an **abstract tree**: a set of nodes, each with an id, a type, props, children and dynamic entries for runtime expressions.

- Node ids are stable. A statement's node has the statement's id; an inline component gets a derived id such as `kpis/1`. A renderer can key on them.
- Special types: `#text` for a string child, `#pending` for a reference not defined yet, `#unknown` for a component nothing matches, `#expr` for a runtime expression in child position.
- The tree has a JSON form (`schemas/tree.schema.json`), which the conformance suite compares.

## Edits

*Section 14.* Replace (`id = …`), patch (`id.prop = …`), append (`id += …`) and delete (`id = null`). Described for users in [Edits and multi-turn](/docs/language/edits/).

## End of stream and validity

*Sections 15 to 17.*

At the end, open brackets are closed, references to nothing are dropped, cycles are broken, and unused statements are reported.

**Chunking invariance:** a processor must give the same final tree and diagnostics however the input is divided, from one piece to one character at a time.

A program has two validity flags:

- **strict**: no diagnostic at all. This is what a generator should produce.
- **lenient**: every problem was repaired by a rule of the format.

## Runtime

*Sections 18 to 20.*

- **Values:** null, booleans, numbers, text, lists, objects, component references.
- **State:** `$name` variables with declared defaults; a host may supply initial values.
- **Operators and built-ins:** a fixed set (see [State and expressions](/docs/language/state-and-expressions/)). No user-defined functions.
- **Queries** (`@query`) call a tool from the *query* set, re-run when their arguments change, and have a default value.
- **Mutations** (`@mutation`) call a tool from the *mutation* set, and only through `@run` in an action list.
- **Action lists** run steps in order and stop at the first failure.

## Envelopes

*Sections 20.3 and 26.4.*

Two JSON objects cross the boundary between a renderer and its host.

**The action envelope** is what a renderer reports when a person acts (`schemas/action.schema.json`). Its `type` is one of `send`, `submit`, `open`, `emit`, `select`, `change`, `error`. The form submit is described in [Actions and forms](/docs/guides/actions-and-forms/).

**The capability object** is what a host tells a generator it may write (`schemas/capabilities.schema.json`):

```json
{
  "gistui": "1.0",
  "profiles": ["core", "interactive", "tools"],
  "mode": "document",
  "edit": false,
  "catalog": { "id": "https://gistui.com/catalogs/default", "version": "1.0.0" },
  "tools": [
    { "name": "get_sales", "kind": "query", "args": "{range:str}", "description": "Sales by month" },
    { "name": "update_goal", "kind": "mutation", "args": "{goal:num}" }
  ]
}
```

A generator must not write state or actions for a host without the `interactive` profile, nor queries or mutations without `tools`.

## Limits

*Section 21.* The values are part of 1.0, because they decide which diagnostics a program gets.

| Processor limit | Value |
|---|---:|
| Nesting depth in one statement | 100 |
| Binary operators in one chain | 500 |
| Nodes owned by one statement | 5,000 |
| Nodes in one program | 50,000 |
| Values in one data statement | 200,000 |
| Component instances in the tree | 50,000 |
| Depth of the tree | 400 |

| Runtime limit | Value |
|---|---:|
| Evaluation steps in one pass | 500,000 |
| Items of one `@each` | 5,000 |
| Generated nodes | 20,000 |
| Length of text made by `+` or `@join` | 1,000,000 |
| Queries with a refresh timer | 20 |
| Refresh interval | at least 5 s |
| Tool calls | 60 in any 10 s |

No input may make a processor or runtime fail with an unhandled error, exhaust the stack, or hang.

## Security

*Section 23.*

- **Threat model:** the program is written by a model that may have been steered by hostile content. The host, the catalog and the renderer are trusted; the program is not.
- **No code execution.** Expressions are interpreted. There is no way to reach the host environment.
- **URL schemes.** Links and `@open` allow http, https, mailto and tel.
- **URLs that load by themselves** (images, video, backgrounds) follow a host policy: an allow-list of hosts, and a rule that a URL assembled from data is not loaded unless its host is listed. This closes the path where an image request carries private data out.
- **Markdown** is rendered without raw HTML.
- **Tools.** Queries must be read-only. Mutations run only from a person's action.
- **Messages from the UI** must be distinguishable from typed text when they go back to a model.
- **Deception.** A renderer should keep generated UI visually inside its container.

The user-facing guide is [Security](/docs/guides/security/).

## Accessibility and internationalisation

*Sections 24 and 25.* A renderer must expose roles, names and states for interactive components, support keyboard use, manage focus for dialogs, and honour reduced motion. Text direction follows the content; number and date formatting follows the host locale.

## Versioning

*Section 26.*

- The version is `1.0`. A processor that sees an unknown major version in the pragma reports `unsupported-version` and continues.
- A host must accept diagnostic codes it does not know and treat them by severity.
- Media type: `text/vnd.gistui` (not registered yet). File extension: `.gistui`. Inline mode is `text/markdown` with `gistui` fences.
- Catalogs have ids and versions of their own.

Source: https://gistui.com/docs/protocol/format/index.mdx
