# hotty — HTML over the TTY

    Version:  0.4
    Status:   Draft. Versions 0.x may change incompatibly (§15).
    Licence:  CC BY 4.0 (this document); the conformance vectors are
              Apache-2.0.

## Abstract

hotty lets a program running in a terminal show **surfaces**: small HTML
documents drawn by the terminal into rectangles of cells, the way the kitty
graphics protocol places images. The program sends markup and deltas
in-band, as escape sequences in its ordinary output, so it works over a pty,
over SSH and through anything else that carries a terminal's bytes. The
terminal lays the markup out, draws it, and handles focus, typing and hover
itself. What the user does comes back to the program as events on its input.

There is no script and no network. The program is the application: it holds
the state and decides what every event means. The terminal is the view. A
surface can do nothing the program did not send it, and can learn nothing
the program did not give it.

![hotty-demo full screen in xterm.js: the web shell runs `sh deploy.sh`, whose
askhot questions — a choice, a text field, a confirmation, a form — are
surfaces the program hears; a chart streams from a pipe; a metrics chart is
hovered for its values, a moment pinned and a heatmap shown; a document
scrolls a row at a time, with a table, code and an SVG diagram in it; cards,
images and charts scroll in a feed; and an agent's transcript, diff, approval
and load test follow.](demo.gif)

*The hotty website, [hotty.neuroplast.io](),
running in xterm.js with
[xterm-addon-hotty](https://hotty.neuroplast.io/docs/xterm-addon-hotty/), one of
the two hosts that share no code and pass the conformance vectors. Every
surface — the askhot questions, the charts, the table, the diagram, the cards,
the diff, the approval — is HTML the program sent in-band, in the terminal's
own output; the cells around them are the program's too.*

## Contents

1. Introduction
2. Conventions
3. The envelope
4. Detection and capabilities
5. Surfaces
6. Deltas
7. Resources
8. The host stylesheet
9. Events
10. Keyboard and focus
11. Text
12. Security
13. Limits
14. Degradation
15. Versioning and extensions
16. Conformance

Appendices: A. Examples · B. Rationale · C. Prior art

---

## 1. Introduction

A terminal program has cells: monospace characters with a few
attributes. hotty adds a second kind of output: a **surface**, one HTML
document, placed on a rectangle of cells. Inside it the host lays out and draws
the document at the terminal's own resolution, with proportional type,
tables, form controls, SVG and images. Outside it, everything stays a
terminal.

hotty is designed around four constraints:

- **In-band.** Everything a surface needs travels in the program's output
  stream. Nothing is loaded from files or the network, so a program behaves
  the same locally, over SSH and inside a container.
- **The program is the application.** Surfaces have no script. The program
  changes the document with deltas and hears about events. It never runs
  code in the terminal.
- **Degradable.** A program asks whether the terminal is a hotty host before
  using it, and paints ordinary text when it is not. Terminals that are not
  hosts ignore hotty sequences.
- **Cheap to update.** Changes are deltas addressed by element id, so a
  program changing one number sends a few bytes, and a host repaints what
  changed.

### Non-goals

hotty is not a web browser in a terminal:
- it has no URLs to visit;
- it loads nothing remotely;
- it runs no JavaScript or WebAssembly;
- it has no cookies or storage.

It is also not a replacement for cell-based TUIs: surfaces sit beside
terminal text, and a program can use both.

## 2. Conventions

The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT** and **MAY**
are to be interpreted as described in RFC 2119 and RFC 8174 when, and only
when, they appear in bold.

- **Program**: the process whose output the terminal displays.
- **Host**: whatever renders hotty for that program. Usually the terminal
  emulator itself. It can also be a pty shim between the program and a
  terminal (a *polyfill*; §14). A multiplexer between a program and a
  terminal either is a host itself, or passes hotty through and keeps
  placements consistent with its own screen and scrollback.
- **Session**: a program's connection to a host, from the first hotty command
  until a full reset or the end of the program's output.
- **Surface**, **resource**, **delta**, **event**: §5, §7, §6 and §9.
- `ESC` is 0x1B, `BEL` 0x07, `ST` the two bytes `ESC \`. `CSI` is `ESC [`.
- **CSS pixel**: CSS's reference pixel. A host's **scale** is device pixels
  per CSS pixel.

## 3. The envelope

### 3.1 Sequences

Every hotty message, in both directions, is an operating system command:

```
ESC ] 7279 ; <control> [ ; <payload> ] ST
```

- **7279** is the ASCII codes of `H` and `O`.
- A host **MUST** also accept `BEL` as the terminator. Hosts send `ST`.
- A terminal that is not a hotty host ignores the sequence, as it ignores any
  OSC it does not know. That is what makes hotty safe to try (§4).

### 3.2 Control

`<control>` is a list of `key=value` pairs separated by `:`.

```
control = pair *( ":" pair )
pair    = key "=" value
key     = ALPHA *( ALPHA / DIGIT / "-" / "_" )
value   = *( %x20-39 / %x3C / %x3E-7E )    ; printable ASCII except ":" ";" "="
```

- A key appears at most once.
- A host **MUST** ignore keys it does not know (§15).
- **A value is printable ASCII.** Whoever sends one, a host or a program,
  **MUST** replace each character a value may not hold with `_`: `:`, `;`,
  `=`, a control character, and any character outside ASCII. It replaces
  each Unicode code point with one `_`, and each byte that is not UTF-8 with
  one `_`. An element whose id is `café` is reported as `t=caf_`, so a
  program that needs its ids back gives them in ASCII.

### 3.3 Payload

`<payload>` is the command's body, **base64** encoded (RFC 4648, standard
alphabet).
- Hosts **MUST** accept base64 with or without padding, and ignore
  whitespace in it.
- A program **MAY** compress a body with zlib (RFC 1950) before encoding
  it, and mark it `o=z`. A host **MUST NOT** compress what it sends (replies
  and events), so a program needs no zlib to read them.
- An empty body is sent as nothing, and the `;` before it may then be
  omitted.
- Text bodies (markup) are UTF-8.
- **Bodies with fields are msgpack**
  ([MessagePack](https://github.com/msgpack/msgpack/blob/master/spec.md)).
  The body of a reply (§3.6, §4) or an event (§9) is one msgpack map, with
  nothing after it, and so are a program's deltas (§6.1), touch areas
  (§9.6) and menus (§10.7). Documents are text, and resources bytes.
- **Each field has a type.** This document shows a body as JSON with types
  in place of values: `{"c": int, "keys": [str]}` is a map with an int
  `c` and an array of strings `keys`, and `{str: int}` a map from strings
  to ints. The types are `int`, `float`, `str` and `bool`.
  - Whoever sends a body **MUST** send each field as its type. A field it
    has nothing for is left out, not sent as nil.
  - An int is at most 2^53 − 1 from zero either way, so that a reader
    whose numbers are doubles (JavaScript, Lua 5.1) holds it exactly.
  - A body holds no nil, and its maps are keyed by strings, each given
    once. A string is UTF-8.
  - Any of msgpack's forms of a type is that type: an int of any size,
    signed or not, a float of 32 or 64 bits, a string, an array or a map
    of any length.
  - A time, in a field that carries one, is msgpack's timestamp
    (extension type −1): 4, 8 or 12 bytes, its seconds at most 2^53 − 1
    from 1970 and its nanoseconds under a second. No field of this version
    carries one.
  - A body that breaks any of these, anywhere in it, in a field a reader
    knows or not, does not decode (SDK.md §3.9). A host answers a body
    from a program that does not decode with `EINVAL` (§3.6).

### 3.4 Chunks

A message whose encoded payload is longer than 4096 bytes **MUST** be split
into chunks of at most 4096 payload bytes each:

- The first chunk carries the whole control plus `m=1`.
- Every further chunk carries only `m`. Every chunk but the last has `m=1`;
  the last has `m=0`.
- The chunks of one message are contiguous. A hotty message with any other key
  arriving before the last chunk aborts the message in progress. The host
  **MUST** discard that message and process the new one normally.
- Other output (terminal text, other escape sequences) may appear between
  chunks.
- A host **MAY** limit the size of a reassembled message, decoded: what
  base64 and zlib leave. It reports a limit it enforces as
  `limits.message` (§13). It **SHOULD** accept at least 1 MiB decoded.
  Past its limit, it drops the message and its further chunks, up to the
  last.
- A program **MAY** limit the messages it decodes from its host the same
  way.

### 3.5 Common keys

| key | meaning |
| --- | --- |
| `a` | the action: `q`, `doc`, `place`, `hide`, `delta`, `res`, `del`, `detach`, `focus`, `blur`, `edit`, `menu`, `touch` from the program (§4–§10); `ok`, `err`, `ev` from the host |
| `s` | a surface name: `[A-Za-z0-9_-]{1,64}` |
| `n` | a request number chosen by the program, echoed in the reply |
| `q` | quiet: `0` reply always, `1` reply only on error, `2` never reply. Absent: `0` for a query (`a=q`) and for a command with `n`, `1` for any other |
| `o` | `z`: the payload is zlib-compressed |
| `m` | `1`: more chunks follow; `0`: last chunk |

### 3.6 Replies

The host answers each command, unless `q` says not to, with:

```
ESC ] 7279 ; a=ok|err [:n=<n>] [:s=<s>] :re=<action> [:<more>] [; <payload>] ST
```

- `n` and `s` are echoed when the command had them, and `re` names the action
  answered.
- **Only errors, unless asked.** A command without `q` is answered only
  with an error, unless it has `n` or is a query (§3.5). A program numbers
  the commands whose answer it reads, such as a placement's rows (§5.2).
- `a=ok` replies to `place` carry `c` and `r` (§5.2); the reply to `q`
  carries the capabilities (§4).
- `a=err` carries the body `{"code": str, "detail": str}`, and, answering a
  delta, the key `i` in its control: the index of the op that failed, from
  0 (§6.1). The codes:

| code | when |
| --- | --- |
| `EINVAL` | an unknown action or op; a bad surface name; a missing `s`, `t`, `k`, `c` or `id`; `c` or `r` out of range; a program's body that does not decode, or holds a field of another type (§3.3) |
| `ENOENT` | no surface of that name |
| `ENOTARGET` | no element with that id. The detail is the id, or the missing ids comma-separated (§6.2) |
| `EDETACHED` | the surface is detached (§5.5) |
| `EQUOTA` | over one of the host's limits (§13) |
| `EBUDGET` | the host gave up on work that exceeded its time budget (§13) |
| `EVERSION` | a query (§4) lists no version the host speaks, or none at all. The detail is the versions the host speaks, comma-separated |

Replies travel on the program's input, interleaved with whatever the user
types. A program that wants none, not even an error, sends `q=2`.

### 3.7 Malformed messages

A malformed message:
- a control that does not parse;
- a payload that is not valid base64 or zlib;
- a message past the host's limit (§3.4, §13);
- an aborted chunked message.

Each counts once: a malformed message that arrives before the last chunk of
another aborts it (§3.4), and makes two, the aborted message and itself.

The host **MUST NOT** act on it. It sends no reply, since it cannot know what
the program meant. It **MAY** log it.

## 4. Detection and capabilities

A program finds out whether it has a host by sending a query, then Primary
Device Attributes as a fence:

```
ESC ] 7279 ; a=q:n=1:v=0.4 ST   CSI c
```

Every terminal answers DA1. A host answers the query first:

```
ESC ] 7279 ; a=ok:n=1:re=q ; <base64 msgpack> ST
```

If the DA1 answer arrives with no hotty reply before it, there is no host. The
program **SHOULD** then fall back (§14).

**Versions.** `v` lists the hotty versions the program speaks,
comma-separated: `v=0.4` for this document, and `v=0.3,0.4` for a
program that speaks 0.3 as well. A version is `major.minor`, two numbers written without
leading zeros, and versions compare as those numbers, the major first.
- A host answers in the highest version it shares with the program: `a=ok`,
  with that version's capabilities, whose `v` names it. It speaks that
  version from then on.
- A host that shares none, or gets a query with no `v`, answers `a=err`
  with `EVERSION` (§3.6), the versions it speaks, comma-separated, as the
  detail.
- A program **MUST** take as no host a terminal that answers its query with
  an error, or with capabilities that do not decode (§3.3) or whose `v` it
  did not list, and fall back (§14). A program and a host of different
  versions, which may read each other's messages differently, then never
  meet.

A DA1 answer may belong to a question asked earlier, by the program or by
whatever ran before it. A program **MAY** wait a little after a DA1 that
arrives before any reply, for the reply and the DA1 behind it. A host answers
the query before that DA1, so the wait costs a terminal that is not a host
only that little. SDK.md sets how long.

**A late answer.** What stands between the program and the terminal may not
know yet whether there will be a host: a multiplexer running a pane with no
terminal attached. A program that can start using hotty after it has fallen
back asks for a late answer with `late=1`:

```
ESC ] 7279 ; a=q:n=1:v=0.4:late=1 ST   CSI c
```

- A host answers it as any query, at once.
- What passes hotty through and cannot answer it yet **MAY** hold it, and
  answer it once, with the reply a host would send, as soon as there is a
  host. It holds one query at a time: any other `a=q` takes its place. It
  forgets the query when the program it runs ends and on a full reset
  (RIS).
- The program takes such a reply, whenever it arrives, as detection finding
  a host, if it is one the program can use (Versions, above), and may use
  hotty from then on (§14). Without `late=1`, a reply that arrives once the
  program has fallen back changes nothing.
- A program that will no longer take a late answer, such as one about to
  exit, withdraws its query with `a=q:q=2`: a query that wants no answer,
  which takes the held one's place, so that no reply reaches whatever reads
  the terminal after it. A host answers it with nothing, as `q=2` says.

The capabilities, the body of that reply, are a map of these fields (§3.3):

| field | type | meaning |
| --- | --- | --- |
| `v` | str | the hotty version the host answers in (Versions, above): `"0.4"` for this document |
| `cellcss` | `{"w": float, "h": float}` | one cell's size in CSS pixels (§5.3) |
| `scale` | float | device pixels per CSS pixel when the host answers: a hint for the resolution of images, which a zoom changes without telling (§5.3) |
| `scheme` | str | `"dark"` or `"light"`: the terminal's colour scheme |
| `mod` | str | `"ctrl"` or `"super"`: the modifier `mod` stands for in bindings (§10.4) |
| `features` | [str] | the optional features the host has (below). Absent: none |
| `limits` | `{str: int}` | the host's limits (§13), such as `{"resources": <bytes>, "surfaces": <count>, "message": <bytes>}` |
| `net` | `{str: [str]}` | the host's network policy (§7.2), from directive to sources, such as `{"img-src": ["https://example.com"]}`. Absent or empty: the host fetches nothing from the network |
| `edit` | `{str: [str]}` | the host's edit shortcuts (§10.2), from each action to its keys as §10.4 names them: `{"copy": ["ctrl+c"], …}`. A host that reports them takes edit commands from a program in the cells (§10.6) |
| `host` | str | optional: a name for the implementation |
| `version` | str | optional, with `host`: the implementation's version, as dot-separated numbers compared one by one (`"0.0.10"` is after `"0.0.9"`) |

`v`, `cellcss`, `scale`, `scheme` and `mod` are always there. Programs
**MUST** ignore fields they do not know.

**What every host does.** A host of this version does all that this
document describes: every action, delta op and event kind, scrolling
(§5.3), steps and drops (§9.1), and `view` (§9.5). What a host may lack is
a **feature**, and `features` lists those it has:

| feature | the host |
| --- | --- |
| `passthrough` | lets the pointer pass through the parts of a surface that take none (§9.3). Without it, every window takes the pointer wherever it is |
| `hover` | says where the pointer is, with `hover` (§9.4). Without it, a program never hears that the pointer has left |
| `count` | says that a click repeats one before it, with `count` (§9). Without it, every click reads as the first of its run, and a program offers another way to do what a double click would |
| `menu` | keeps a program's menu, shows its levels and lets the user pick from it (§10.7). Without it, a program shows its menus itself |

A program **MUST** ignore a feature it does not know: later versions add
them (§15).

**Changes.** The capabilities describe the host when it answers, and some
change during a session: `cellcss` with the terminal's font (§5.3),
`scheme` with its theme, `mod` and `edit` with the user's settings, and any
of them where a multiplexer answers for several terminals and the one whose
keys reach the program changes (§2). The host then sends `caps` (§9) with
every field that changed since the program last heard them. A zoom, or a
display with another pixel density, changes `scale` alone and sends
nothing: a document reads one device pixel from `--hotty-px` (§8).

## 5. Surfaces

A **surface** is one HTML document, named by the program, and at most one
**placement**: a rectangle of cells showing it.

### 5.1 Documents: `a=doc`

```
ESC ] 7279 ; a=doc:s=<name>[:d=1][:scroll=<axes>] ; <HTML> ST
```

- The payload is an HTML document or a fragment, parsed with the HTML parsing
  algorithm. It creates the surface, or replaces its whole document.
- Replacing a document keeps the surface's placement and size.
- `d=1` creates the surface detached (§5.5), so a program that will hear
  nothing from a document, even one it keeps changing with deltas, gives it
  up in the same command. A document sent without `d=1` makes the surface
  the program's, whether it was detached or not.
- `scroll` lets the document scroll (§5.3) along the axes it names, a
  bitmask: `1` vertically, `2` horizontally, `3` both. Absent or anything
  else, it does not scroll. Like `d`, it belongs to the document: a new
  document sent without it does not scroll.
- The document's `<base href>` sets its base URL (§7.3), and its
  `<meta name="hotty-network">` asks for network access (§7.2).
- The host applies its stylesheet (§8) and the security rules (§12) before
  the document is shown.

### 5.2 Placement: `a=place`

```
ESC ] 7279 ; a=place:s=<name>:c=<cols>[:r=<rows>|auto][:x=<col>][:y=<row>][:w=<cols>][:h=<rows>][:z=<n>][:p=0|1][:f=0|1][:v=0|1][:C=1] ST
```

- **Size:** the surface is `c` columns wide and `r` rows tall, and its
  document is laid out at that size (§5.3). Both range from 1 to 1000; a
  missing `r` is `auto`.
- **`r=auto`:** the host lays the document out `c` columns wide and uses the
  smallest number of rows that holds its content, up to 1000. The reply,
  where the program asks for one (§3.6), reports the rows chosen:
  `a=ok:…:c=<c>:r=<r>`.
- **The window:** `x`, `y`, `w` and `h` choose the part of the surface the
  placement shows: `w` columns and `h` rows, starting `x` columns and `y`
  rows into the surface. By default `x` and `y` are 0, and `w` and `h` reach
  the surface's right and bottom edges, so a placement with none of them
  shows the whole surface. A window that does not lie inside the surface, or
  that has no cells, is `EINVAL`. The rest of the surface is laid out as
  usual and not shown.
- **Position:** the placement covers the window's `w` × `h` cells, with its
  top-left corner at the cursor's cell.
- **Stacking:** `z` orders placements that overlap, as CSS `z-index` orders
  boxes: a placement with a greater `z` is above one with a smaller, and of
  two with the same `z`, the surface created later (by the `a=doc` that
  created it) is above. `z` is an integer from -1000 to 1000, else `EINVAL`;
  a placement without it has 0. It belongs to the placement: placing the
  surface again without `z` puts it back at 0. Every placement is above the
  cells, whatever its `z`. Where placements overlap, the topmost one under
  the pointer receives it (§5.3).
- **What the program hears:** `p`, `f` and `v` ask for events (§9). Each
  belongs to the surface, not to the placement: `1` turns it on and `0`
  off, and it holds until the program sends it again. A placement without
  one leaves it as it was, so a program that places a surface every frame
  sends it once. A new surface has none of them, and any other value is as
  if absent.
  - **Presses:** with `p=1`, the program hears of every press in the
    window, whatever it lands on, as `press` (§9).
  - **Fit:** with `f=1`, the program hears `fit` (§9) whenever the number
    of rows the document needs at the placement's width — the rows `r=auto`
    would choose — differs from the rows it last heard. Each placement
    starts from its own rows: `r`, or for `r=auto` the rows chosen, whether
    or not a reply carries them (§3.6). A document's height changes after
    it is placed when a resource it refers to arrives or is replaced
    (§7.1), an image or font it fetched loads (§7.2), a delta changes it
    (§6), or a cell's size in CSS pixels changes (§5.3). The placement
    keeps its size: the footprint is the program's (§11), and placing the
    surface again with the new rows is the program's to do.
  - **Hover:** with `v=1`, the program hears `hover` (§9.4) each time the
    element the pointer is over in the window changes, and when the
    pointer leaves the window. (`h` is taken: it is the window's rows.)

  How often each is sent, and when a surface sends none, is in §9.
- **Moving and removing:** placing a surface that is already placed moves
  it, or shows another window of it: the old placement is removed. A program
  scrolling a region of the screen that holds a surface places it again with
  the part in view. A change of window alone does not change the document's
  layout, and a host **SHOULD** make it cheap. `a=del` removes a placement
  (§5.4).
- **The cursor:** unless the command has `C=1`, the host then moves the
  cursor as if by `h` times IND (index) followed by CR. It ends at the start
  of the line below the placement, scrolling if necessary. With `C=1` it does
  not move.

### 5.3 Geometry

- The surface's viewport is its size, `c` × `r` cells, in CSS pixels,
  whatever window of it is shown (§5.2). Media queries and viewport units
  refer to it. The document is laid out at that width, and at that height
  for anything sized by the viewport.
- Only the window is shown, and only the window receives the pointer: where
  windows overlap, the topmost (§5.2, `z`). A press with Alt held is the
  exception: it passes every surface (§9.2). On a host with `passthrough`,
  so does the pointer over the parts of a window that take none (§9.3).
- **What does not fit is clipped, and a surface scrolls only where its
  document asks** (`scroll`, §5.1). Along an axis it did not ask for:
  - The host **MUST** clip overflow as `overflow: hidden` does: the
    document's root, and any element whose `overflow` is `auto` or
    `scroll`.
  - It shows no scrollbars, and keeps every scroll offset at zero,
    whatever the document's CSS or script-free defaults would do (such as
    scrolling a focused element into view).
  - The one exception is a text field's own text, which follows its caret.
  - **A program that needs to scroll** does it the way it scrolls cells:
    it hears the terminal's wheel input (below) and moves its surfaces.
    A surface partly out of view shows its visible part through a window
    (§5.2). So the whole screen scrolls, with its surfaces in it.
- **A scrolling document** scrolls along the axes it asked for as a page
  does in a browser:
  - Its root, and any element whose `overflow` is `auto` or `scroll`,
    scroll their overflow, with the host's scrollbars. CSS
    `scrollbar-width`, `scrollbar-color` and `scrollbar-gutter` apply, and
    a scrollbar takes pixels inside the rectangle, never cells: the
    footprint stays the program's.
  - The user scrolls it with a wheel, a touchpad or a touch drag over its
    window that drags no element (§9.1), and, while the surface has the
    keyboard (§10), with the keys a browser scrolls with and those the
    focused element's keymap binds to a scroll action, but not those it
    gives the program (§10.2). A key a browser scrolls with that can move
    nothing goes on to the program, as every key the surface does not use
    does. Focus scrolls an element into view.
  - A gesture scrolls the innermost element under the pointer that can
    still move that way. Where none can, it goes on outward, and past the
    root to the terminal, as if over the cells beneath (§9), unless CSS
    `overscroll-behavior` stops it there.
  - Scrolling is local: the program hears of it only where it asks, which
    elements are in view (§9.5), and moves it only by focus (§10.1) or by
    bringing an element into view (`reveal`, §6.1). A delta keeps the
    offsets (§6.2), and a new document starts at the top left.
- When the cell size changes, the rectangle keeps its cells and changes
  its pixels:
  - **A zoom**, the user enlarging or shrinking the terminal for the
    moment, or **a display** with another pixel density, changes how many
    device pixels a CSS pixel is. `scale` (§4) follows, so a cell and the
    root font keep their size in CSS pixels, and the document keeps its
    layout, as a page in a frame does under a browser's zoom. The host
    draws it again at the new scale, with `--hotty-px` at its new value
    (§8), and the program hears nothing.
  - **The terminal's font**, a new family or size in its settings,
    changes a cell's size in CSS pixels. The host lays the document out
    again, with no involvement from the program, and sends `caps` with the
    new `cellcss` (§4, §9).
- Pixels inside the rectangle **MAY** differ between hosts, as text does
  between fonts. The cell footprint **MUST NOT**.

### 5.4 Lifetime

A placement behaves like a kitty graphics placement:

- It is anchored to the line it was placed on, and moves with it as the
  screen scrolls.
- It is removed when that line leaves the scrollback. The surface's document
  remains, and the program may place it again.
- A placement made on the alternate screen belongs to that screen. It is
  removed when the terminal leaves the alternate screen, and the host
  **SHOULD** delete the surface as well.
- A full reset (RIS) deletes every surface.
- A multiplexer that passes hotty through, when the program a pane runs
  ends or it starts another in its place, **SHOULD** delete the surfaces
  and resources that program left, and no other pane's, as a full reset in
  that pane would. Where it keeps the pane's text on the screen, it keeps
  the detached surfaces with it (§5.5). The next program starts a session
  of its own, and cannot know their names.

A program removes a placement and keeps its surface with `a=hide`:

```
ESC ] 7279 ; a=hide:s=<name> ST
```

- The document stays as it is, with what the user did in it, and deltas
  still apply to it. Placing the surface again shows it as it is then,
  without sending it again.
- A program hides the surfaces it expects to show again soon, such as a card
  scrolled out of view, and deletes the rest.
- Hiding a surface that has the keyboard gives the keyboard back to the
  terminal, as `a=blur` does (§10.1).
- `ENOENT` if there is no such surface. Hiding a surface that is not placed
  does nothing.

`a=del` deletes explicitly:

| command | effect |
| --- | --- |
| `a=del:s=<name>` | delete the surface and its placement (`ENOENT` if there is none) |
| `a=del:id=<id>` | delete a resource (§7) |
| `a=del` | delete every surface |

### 5.5 Ownership: `a=detach`

A surface belongs to the program that sent its document: it reports to that
program (§9), and takes the keyboard on its behalf (§10). Once the program
has nothing more to hear from a surface, it **detaches** it:

```
ESC ] 7279 ; a=detach:s=<name> ST
```

or creates it detached, with `d=1` on the `a=doc` that sends its document
(§5.1).

- **A program that leaves surfaces on the screen when it exits**, such as
  the documents a command prints among its output, **SHOULD** detach them
  before it exits, or create them detached. Whatever reads the terminal's
  input after it, a shell for instance, knows nothing of them.
- **What runs a program**, such as a shell, **MAY** detach the surfaces the
  program named once it exits, if it knows their names.
- **A detached surface stays on the screen as text does.** It is placed,
  hidden, changed by deltas and deleted as before, and moves with its line
  (§5.4).
  Nothing in it reaches the program any more:
  - **It sends no events** (§9), of any kind, `press`, `hover` and drags
    included, whatever its placement's `p` and `v` and its `data-on`. A
    drag under way when it is detached ends with nothing more reported
    (§9.1).
  - **It never has the keyboard.** A surface that has the keyboard when it
    is detached gives it back to the terminal, and sends neither `change`
    nor `blur`. A click in it takes nothing, and `a=focus` is `EDETACHED`.
    A program that wants what the user typed in a focused field sends
    `a=blur` first, and detaches once `blur` arrives (§10.1).
  - **Its form controls are disabled**: every `input`, `select`, `textarea`
    and `button`, including those deltas add later, acts as if it had the
    `disabled` attribute. They match `:disabled`, and the user can neither
    focus, edit, toggle nor activate them. What the user had typed into
    them stays. The document itself is unchanged: its elements report the
    program's attributes (§16).
  - **What is local stays** (§9): hover, selecting text, toggling a
    `<details>`, and hyperlinks, which belong to the terminal and open as
    before. Over anything else, the host **SHOULD NOT** show a pointer that
    promises a click (a link's hand), whatever the document's `cursor`
    asks for.
- **Until its next document.** A surface stays detached until an `a=doc`
  without `d=1` replaces its document.
- `ENOENT` if there is no such surface. Detaching a detached surface does
  nothing.

## 6. Deltas

### 6.1 `a=delta`

```
ESC ] 7279 ; a=delta:s=<name> ; <base64 msgpack ops> ST
```

A delta changes one surface's document, addressing elements by id. Its
body is one msgpack map (§3.3), `{"ops": [op]}`: the changes, in the order
they apply. Each op is a map:

```
{"op": str, "t": str, "k": str, "v": str}
```

- `op` names what it does (the table below);
- `t` is its target, the id of an element, written as the document has it,
  so that an id outside ASCII is addressed as it is (§3.2);
- `k` is a name, for `attr`, `unattr` and `var`;
- `v` is its value: the HTML, the text or the value the table says.

| `op` | `v` | effect |
| --- | --- | --- |
| `morph` | HTML | morph the target into `v` (§6.2) |
| `inner` | HTML | morph the target's children into `v`'s nodes |
| `replace` | HTML | replace the target with `v`'s nodes, without morphing |
| `append`, `prepend` | HTML | insert `v`'s nodes as the target's last or first children. An element of `v` whose id is already a child of the target is morphed where it is, not added again |
| `before`, `after` | HTML | insert `v`'s nodes before or after the target |
| `remove` | none | remove the target |
| `attr` | a value | set the target's attribute `k` |
| `unattr` | none | remove the target's attribute `k` |
| `text` | text | replace the target's children with one text node |
| `var` | a value | set the custom property `--k` on the target (a `k` that already starts with `--` is used as is) |
| `reveal` | `start`, `end`, or none | scroll the target into view (below) |

- **One unit.** A host applies a delta's ops in order and shows none of
  the states between them, as if they were bracketed in synchronized
  output (§6.3). A program that changes a surface in one frame sends one
  delta.
- **Checked first.** The host checks the whole body before it applies any
  op, and answers `EINVAL`, applying nothing, when: the body does not
  decode (§3.3), is not such a map, or has no ops; an op is not a map,
  names an op the table does not have, or holds `op`, `t`, `k` or `v` as
  anything but a str; `t` is missing or empty where the op needs one
  (every op but `morph`, for which an empty `t` is none), or `k` for
  `attr`, `unattr` and `var`; or `reveal` has a `v` other than `start` or
  `end`. Where one op is at
  fault, the reply names the first such by its index, `i` (§3.6). An op
  that takes a `v` and has none takes it as empty; a `v` that an op does
  not take is ignored, as is a key of an op that this table does not name
  (§15).
- **A missing target** is `ENOTARGET`, with the id as the detail. That op
  changes nothing, and the ops after it still apply. The reply names the
  first op that failed by its index, `i`, and a host that answers with no
  error applied every op.
- **Context:** HTML in `v` is parsed as a fragment in the context of the
  element it will become a child of, so table rows, cells and list items
  parse as they would in place.
- **The cheap path:** `var` and `text` are the ops that change every frame
  (a bar's width, a clock). A host **SHOULD** make them cheap.
- **Reveal** changes no node. It scrolls each box that holds the target,
  the innermost first and the document's root last, so that the target's
  border box is in view: its top at the top of each box with `start`, its
  bottom at the bottom with `end`, and with no `v`, by as little as brings
  it into view, as a browser's `scrollIntoView` does with `block:
  "nearest"`. Across, it always scrolls as little as it can. It scrolls
  only what scrolls (§5.3): in a document that asked for no scrolling, it
  moves nothing. It moves neither focus nor the keyboard (§10), and sends
  no event of its own: elements that report whether they are in view
  report it as after any scroll (§9.5).

### 6.2 Morph

Morphing keeps the identity of elements that stay: their focus, the text a
user is typing, their scroll position, and whether a `<details>` is open.
Morphing a live node *old* into a parsed node *new*:

1. **Two text nodes:** if the data differs, set old's data to new's. Two
   comments: leave old as it is.
2. **Two elements match** when their tag name and namespace are equal and
   their `id`s are equal or absent on either side. Then:
   - make old's attributes equal to new's: add the missing ones, change the
     different ones, and remove the extra ones;
   - if old is the focused element, leave its `value` as it is;
   - morph old's children into new's (step 4).
3. **Otherwise**, replace old with new.
4. **Children.** For each of new's children, in order, find an old child:
   - A new child with an `id` takes the old child with that id, if any.
   - A new child without an `id` takes the first old child after the last
     match that is the same kind of node: an element with the same tag and no
     id, a text node, or a comment. Old children whose id the new list still
     wants are skipped.

   Then:
   - Old children not taken are removed.
   - Taken children are morphed and put in the new order, moved rather than
     recreated.
   - New children with no match are inserted.

**Controls whose values the program sets.** When an attribute that
sets a control's default changes (`value` on inputs, textareas and selects;
`checked` on checkboxes and radio buttons; `selected` on options), the host
**MUST** also set the control's current state to it, unless the control is
focused. The program's value wins, except while the user is editing.

**`morph` without `t`** matches each top-level element of `v` to the
document element with the same id, and morphs it. Elements whose id is not in
the document are skipped and reported in one `ENOTARGET`, their ids
comma-separated in the detail. The others are still applied. This lets a
program that redraws its whole tree every frame keep the terminal-side state
of what did not change.

**HTML with other than one top-level element**, given to `morph` with a
`t`, replaces the target with all of its nodes.

### 6.3 Order and transactions

- The host applies commands in the order they arrive. It **SHOULD** render
  at most once per display frame.
- One delta is already one unit (§6.1). For a batch of several commands,
  or of several surfaces, a program brackets it in synchronized output (DEC
  private mode 2026: `CSI ? 2026 h` … `CSI ? 2026 l`). The host **MUST NOT**
  show a state from inside the batch. It may hold commands until the batch
  ends, or apply them without drawing.
- A host that gives up waiting for the end of a batch (terminals time these
  out) **MAY** apply what it holds.

## 7. Resources

A surface loads what the program sent it (§7.1), and from the network only
what the network policy allows (§7.2).

### 7.1 In-band resources

```
ESC ] 7279 ; a=res:id=<id>:type=<MIME type> ; <bytes> ST
```

A **resource** is named bytes with a MIME type, stored per session: a
stylesheet shared by several surfaces, an image, a font.

- **References:** documents refer to a resource as `cid:<id>` (RFC 2392's
  scheme, as HTML e-mail refers to its attachments). A host **MUST** resolve
  `cid:` in URL-valued attributes (`src`, `srcset`, `href` of
  `<link rel=stylesheet>`, `poster`, SVG `href`) and in CSS `url()` and
  `@import`. Those include inline `style` attributes, `<style>` elements and
  stylesheet resources.
- **Updates:** sending a resource again replaces it, and every surface that
  refers to it is drawn again. `a=del:id=<id>` deletes it, after which
  references to it fail as if missing.
- **Late arrival:** a reference to a resource that does not exist yet is
  resolved when it arrives. A placement made with `f=1` hears whether that
  changed the document's height (§5.2).
- **Quota:** the host reports its quota under `limits.resources`. A resource
  that would exceed it is refused with `EQUOTA`. A resource is one message,
  so `limits.message` (§13) also caps the largest resource the host takes.
- **The program's values stand:** a host that rewrites references in order
  to load them (to `blob:` URLs, say) **MUST** still treat the attribute as
  having the program's value: in morphs, in `inspect` (§16), and in event
  details.

### 7.2 The network

The **network policy** decides what a surface may fetch from the network. It
has two halves, and a URL may be fetched only when both allow it.

**The host's half** is what the host allows. Its user sets it, or the page
that embeds it. It lists **sources** per **directive**:

| directive | covers |
| --- | --- |
| `img-src` | images: `<img>`, `srcset`, `<picture>`, `poster`, images in SVG, and images in CSS |
| `media-src` | `<audio>` and `<video>` |
| `font-src` | `@font-face` |
| `style-src` | stylesheets: `<link rel=stylesheet>` and `@import` |

- A source is an origin, `http` or `https` with an optional port
  (`https://example.com`, `http://localhost:8080`), or `https:`, which
  matches every HTTPS origin.
- A host's policy is empty unless its user or embedder grants something.
  The host reports it in its capabilities (`net`, §4).
- A host run by a person (a terminal) **SHOULD** start with an empty policy.
  Every fetch tells a server that the document is being shown, and where
  from; that is the user's decision.

**The document's half** says what it needs:

```html
<meta name="hotty-network" content="img-src https://example.com; font-src https:">
```

- The syntax is CSP's: directives separated by `;`, each followed by its
  sources separated by spaces.
- A document with no such element asks for nothing, and fetches nothing
  from the network.

**Fetching:**
- A host **MUST NOT** fetch a URL unless a source in each half matches it,
  for the directive that covers it.
- It **SHOULD** send no referrer and no credentials (cookies,
  authorization) with what it fetches.
- A reference the policy does not allow fails as a missing resource does.
- What is fetched arrives after the document is laid out, as a late
  resource does (§7.1), and changes its height the same way: a program that
  sizes a surface by its content places it with `f=1` (§5.2).
  The program's value still stands (§7.1).

### 7.3 The base URL

- A document's base URL is the `href` of its first `<base>` element when
  that is an absolute `http` or `https` URL, and `https://hotty.invalid/`
  otherwise. Relative references resolve against it: for fetching, still
  subject to §7.2, and for links (§9). A form's empty `action` resolves
  too, and goes nowhere (§9).
- The base URL grants nothing by itself: a fetch needs the network policy,
  and nothing navigates.

## 8. The host stylesheet

Every surface gets a stylesheet from the host that hands it the terminal's
look. Its rules **MUST** lose to any rule in the document, as a user-agent
stylesheet's do; a cascade layer below all author styles achieves this. It
contains at least:

```css
:root {
  color-scheme: dark;              /* or light: the terminal's */
  --hotty-fg: …;                   /* the terminal's foreground */
  --hotty-bg: …;                   /* and background */
  --hotty-ansi-0: …;               /* … the 16 ANSI colours … */
  --hotty-ansi-15: …;
  --hotty-accent: var(--hotty-ansi-12);  /* dark; ansi-4 when light */
  --hotty-cell-w: …px;             /* one cell, in CSS pixels */
  --hotty-cell-h: …px;
  --hotty-px: …px;                 /* one device pixel, in CSS pixels */
  --hotty-font: <the terminal's font family>, monospace;
  font-family: var(--hotty-font);
  font-size: <the terminal's font size>;
  line-height: var(--hotty-cell-h);  /* so 1rlh is one row */
  color: var(--hotty-fg);
  background: var(--hotty-bg);
  overflow: hidden;                /* §5.3 */
  accent-color: var(--hotty-accent);
}
body { margin: 0; }

/* Controls in the terminal's colours. :where() keeps this rule below the
   state rules after it, such as :disabled. */
:where(input:not([type=checkbox], [type=radio], [type=range]), textarea,
       select, button) {
  color: var(--hotty-fg);
  background: var(--hotty-bg);
  border: 1px solid var(--hotty-ansi-8);
}
button:enabled, input:is([type=button], [type=submit], [type=reset]):enabled {
  background: var(--hotty-ansi-8);
}
::placeholder, :disabled { color: var(--hotty-ansi-8); }
:focus-visible { outline: 1px solid var(--hotty-accent); }
:any-link { color: var(--hotty-accent); }
::selection { color: var(--hotty-bg); background: var(--hotty-accent); }
```

- `prefers-color-scheme` follows the terminal's scheme.
- **The palette dresses the controls.** Fields, buttons, checks, focus,
  links and selected text take the terminal's colours, not the engine's,
  so they look the same on every host and like the terminal around them.
  A document that styles its own controls wins, as with every rule here.
  - `--hotty-accent` is the terminal's blue: `--hotty-ansi-12` when the
    scheme is dark, `--hotty-ansi-4` when it is light, as plain blue is
    hard to read on most dark backgrounds. A document may use it as the
    terminal's accent.
  - `ansi-8` (bright black) is the dim grey themes give for secondary
    text: borders, buttons, placeholders and what is disabled. A disabled
    button keeps the background, so its text stays legible.
- **`--hotty-px`** is one device pixel, `1px` over `scale` (§4), so that a
  border or a rule can be exactly one device pixel thick, or a box
  snapped to device pixels, at every zoom: `border-top: var(--hotty-px)
  solid`.
- When the terminal's theme or font changes, the host updates the
  stylesheet and draws every surface again. A zoom changes `--hotty-px`
  alone, neither `--hotty-cell-*` nor `font-size`: the host draws every
  surface again at the new scale (§5.3).

The custom-property prefix `--hotty-` and the attribute prefix `data-hotty-`
are reserved for this specification.

## 9. Events

```
ESC ] 7279 ; a=ev:s=<name>:e=<kind>[:t=<element id>] [; <base64 msgpack detail>] ST
```

The host reports what the user did. Events are not replies: they are sent
whatever the value of `q`. Like replies (§3.6), they arrive on the program's
input, interleaved with the user's keys, so a program that places surfaces
reads hotty messages from its input. A detached surface sends none (§5.5).

| `e` | when | detail |
| --- | --- | --- |
| `click` | activating a `button`; an `a` or `summary`; an `input` of type `button`, `submit` or `reset`; or any element with `data-on~=click` | `{"area": area, "mods": int}`, the element's cells and the modifier keys held (below); with `"href": str` and `"url": str` for links (below), `"value": str` when the element has a `value` attribute, and `"count": int` for a repeated click (below) |
| `change` | a checkbox or radio button toggled, or an option of a `select` picked (§10.2), at once; a text control or `textarea` whose value changed, when the change is committed (focus leaves it, including when the surface loses the keyboard) | `{"checked": bool, "value": str}` for checkboxes and radio buttons, `{"value": str}` otherwise |
| `input` | every edit of a control with `data-on~=input` | `{"value": str}` |
| `submit` | a form submitted (a submit button, or Enter in a text field) | `{"fields": [[str, str]]}`: the form's fields, each its name and its value, in tree order, the submitter's included; `[]` for a form with none |
| `press` | the primary button pressed, a tap, or a touch that becomes a drag (§9.1), anywhere in the window of a surface whose `p` is on (§5.2), except a mouse's or a pen's press with Alt held (§9.2) and where the pointer passes through (§9.3) | `{"area": area, "mods": int}`: the cells of the element `t` names (below), and the modifier keys held; no `area` without `t` |
| `dragstart` | a mouse's or a pen's primary button pressed, or a touch that drags (§9.1), on an element with `data-on~=drag`, without Alt (§9.2) | `{"c": int, "r": int, "mods": int}`: the pointer's cell and the modifier keys held, with `"x": int` and `"y": int`, its step, on an element with `data-steps` (§9.1) |
| `drag` | during a drag, the element under the pointer changed, or the pointer's step in the dragged element or in `t` (§9.1) | the same, with `"tx": int` and `"ty": int`, the step in `t`, where `t` has `drop` in its `data-on` and `data-steps` (§9.1) |
| `dragend` | the drag ended: the button released or the finger lifted, wherever the pointer is (§9.1) | as for `drag` |
| `focus` | the user focuses an element: the surface gains the keyboard, or its focus moves to another of its elements (§10.1); `t` names the element focused | none |
| `blur` | the surface loses the keyboard (§10) | none |
| `fit` | on a surface whose `f` is on (§5.2), the rows the document needs at the placement's width changed | `{"r": int}`: the rows `r=auto` would choose now |
| `hover` | on a surface whose `v` is on (§5.2), the element the pointer is over changed, or the pointer left the window (§9.4) | `{"c": int, "r": int}`: the pointer's cell (§9.1); `{"out": true}` when it left |
| `view` | which elements with `data-on~=view` are in view, or where they are, changed, or whether the document shows its end (§9.5) | `{"in": {str: area}, "end": bool}`: each element in view by its id, with its cells (below) |
| `command` | a key of an edit command the program takes, a menu of the terminal (§10.6), or a pick from the program's menu (§10.7) | `{"name": str, "key": str}`, with `"text": str` for a paste |
| `caps` | a capability changed (§4) | the fields that changed, as the capabilities have them. One that became absent comes as its type's empty value: `"edit": {}`, the host takes no edit commands now (§10.6), and `"features": []`, it has none of them |

- **`t` names an element**, by its id, and is absent where none is meant:
  in a `blur`, a `fit` and a `view`, a `hover` that says the pointer left,
  and where the element found has no id (below).
- **Events of the terminal,** `command` and `caps`, belong to no surface,
  and have neither `s` nor `t`.
- **Modifiers.** `mods` is the modifier keys held, as bits: 1 `shift`,
  2 `alt`, 4 `ctrl` and 8 `super` (§10.4), left out when none is. A press and a
  drag's events carry those held at that moment, a click those held at its
  release, or with the key that clicked.

**What asks for each event, and how often.** An event is sent only where
the program asked for it, and as often as this table says:

| `e` | asked for by | how long | how often |
| --- | --- | --- | --- |
| `click`, `change`, `input`, `submit` | the element: its kind, or its `data-on` (`click`, `input`) | while it is so | each time |
| `focus`, `blur` | nothing: the keyboard is always told | — | each time |
| `dragstart`, `drag`, `dragend` | the element: `drag` in its `data-on`, and `drop` for a target (§9.1) | from a press to its release | for each element, cell or step crossed (§9.1) |
| `press` | `p=1` on the surface (§5.2) | until `p=0` | each press |
| `hover` | `v=1` on the surface (§5.2) | until `v=0` | each change, at most one a frame (§9.4) |
| `fit` | `f=1` on the surface (§5.2) | until `f=0` | at most one a frame, with the rows of the layout drawn in it |
| `view` | the element: `view` in its `data-on` (§9.5) | while it has it | once a scroll rests, else at most one a frame |
| `command` | `a=edit` (§10.6), and an item with a name in the program's menu (§10.7) | until the program takes others, or sends another menu, or leaves the screen | each key, each pick |
| `caps` | the query (§4) | the session | each change |

- **What needs a window.** A surface's `press`, drags, `hover`, `fit` and
  `view` need a placement the host draws: a surface hidden (§5.4), or
  placed and scrolled out of view, sends none of them. When it is drawn
  again, it reports how what it would say differs from what the program
  last heard: the rows (counted from the placement's own, §5.2), the
  element under the pointer, the elements in view.
- **Detached** (§5.5), a surface sends none, and what the program last
  heard stays as it was: once a document makes the surface the program's
  again, it hears how that differs.
- The order of a `fit` with other events the same change causes is not
  set.

- **The element that reports** a `click` is the nearest one, from the target
  of the click outward, that is one of those kinds. If it has no `id`,
  nothing is reported: the id is the program's handle. A link is the
  exception: its `href` is the handle, and `t` is then absent.
- **Where the element is.** A `click`'s and a `press`'s `area`, and each
  of a `view`'s, is `{"c": int, "r": int, "w": int, "h": int}`: the cells
  the element's border box covers as the user sees it, scrolled (§5.3)
  included. `c` and `r` are the first column and row it touches, counted
  from 0 at the surface's top left cell as a drag's are (§9.1), and `w` and
  `h` the columns and rows it spans. An element partly clipped or scrolled
  away reports its whole area, so `c` and `r` may be negative or past the
  surface. A click from the keyboard carries it too. A program places what
  belongs next to the element by it, as a browser places a select's list
  or a menu by its control.
- **Repeated clicks.** A click that quickly follows another on the same
  element carries `count`: its place in that run of clicks, 2 for the
  second click of a double click, 3 for the third of a triple click. The
  host counts as its platform does, by the user's own settings for how
  quickly and how close together the clicks come, and not by a time of
  its own. Every click of a run is reported: a double click is a `click`,
  then a `click` with `"count": 2`. A click on another element, or on
  another surface, starts a new run. A click without `count` is the first
  of its run, and so is every click from the keyboard. A host that counts
  clicks has the feature `count` (§4).
- **A press** is reported whatever it lands on: a control, an element with
  a handler, plain text, or empty space. `t` is the id of the nearest
  element that has one, from the pressed element outward, and absent if
  none has. It comes before every other event the press causes, on this
  surface or another (`change`, `blur`, `focus`), and so before the `click`
  its release may make. A press the host takes for a hyperlink's own
  gesture (below) is the terminal's, and is not reported, and neither is
  a press with Alt held (§9.2), which is the program's.
- **Links.** A link's detail carries `href`, the program's value, and `url`,
  the `href` resolved against the document's base URL (§7.3). `url` is
  absent when it would be under `https://hotty.invalid/`.
- **A surface never navigates.** A click on a link is an event, and the
  program decides what it means: it may show another page, or ask its
  platform to open the link. The host never opens a link itself, whatever
  the gesture, except a hyperlink (below). Forms never submit anywhere:
  `submit` is all that happens.
- **Hyperlinks.** A link with `target="_blank"` is a hyperlink, as OSC 8
  makes one of text in cells. It belongs to the terminal, not the program.
  The host **MUST** treat it exactly as it treats an OSC 8 hyperlink:
  - the same gesture opens it;
  - hovering it gives the same feedback, such as the pointer and the
    address;
  - the same policies apply, such as the schemes it opens, a confirmation,
    and a setting that turns hyperlinks off.

  Its address is its `url`: a link without one is not a hyperlink. A click
  on a hyperlink is not reported.
- **Local behaviour stays local.** HTML's own default actions happen in the
  host without a round trip:
  - focus, the caret and typing in text fields;
  - toggling checkboxes, radio buttons and `<details>`;
  - hover, and the pointer's shape: `:hover` needs no program (a
    placement may still ask to hear where the pointer is, §9.4). Over a
    surface, a host that shows a pointer shows the one the document asks
    for (CSS `cursor`, and as in a browser, a pointer over a link and a
    text cursor over text). Over a hyperlink it shows what it shows over
    an OSC 8 hyperlink;
  - selecting text, where CSS `user-select` allows it (§11).
- **Gestures that scroll are the terminal's,** except over a document that
  scrolls (§5.3), which takes them while it can move that way. A wheel, a
  touchpad's scroll, or a touch drag over any other surface, unless it
  drags an element (§9.1), does what it would do over the cells beneath
  it. That is scrollback, or on the alternate screen, the program's wheel
  input. The host **MUST** pass them on, with the position of the pointer
  or the finger. Taps and long presses, and clicks and drags (§9.1) that
  do not begin with Alt held (§9.2), stay the surface's. Over the cells, a
  program can take a finger's drag as the mouse's (§9.6).
- **The detail** is a msgpack map (§3.3), base64-encoded. Values are the
  program's (§7): an `href` is reported as the document has it. A detail
  that would have no field is left out, with no payload: a `press` with no
  `t` and no modifier held.
- **Unknown kinds.** A program **MUST** ignore an event whose kind it does
  not know: later versions add kinds (§15).

### 9.1 Drags

An element opts in to drags with `drag` in its `data-on`, as it opts in to
clicks with `click`. A **drag** is a press of a mouse's or a pen's primary
button on such an element, the moves of the pointer while the button is down,
and its release; or a touch on such an element that does not pan (below), the
finger's moves, and its lift. A program hears through drags what a click
cannot carry: a range dragged across a grid, a handle dragged to where it is
let go. An element that is only a place to drop on opts in with `drop`
(below): a drag can go to it, and it is never dragged itself.

- **Three events:**
  - `dragstart`, on the press (a touch's once it drags, below). `t` is
    the element that opted in: the nearest one, from the pressed element
    outward, with `drag` in its `data-on`. If it has no `id`, there is no
    drag.
  - `drag`, each time the element under the pointer changes. `t` is the
    nearest element with an `id` and `drag` or `drop` in its `data-on`,
    from the one under the pointer outward, and absent where there is none,
    outside the window included. While there is no `t`, a `drag` is sent
    each time the cell under the pointer changes instead. A `drag` is also sent
    each time a step (below) changes: `x` or `y` in the element dragged,
    `tx` or `ty` in `t`. A move that changes several of these, the element
    or the cell included, sends one.
  - `dragend`, once, on the release, wherever the pointer is. `t` is as
    for `drag`.

  A drag therefore costs a few events for each element it crosses, and one
  for each step, not one for every move.
- **The detail** of each is `{"c": int, "r": int, "mods": int}`, with
  `"x": int` and `"y": int` on an element with steps, and in `drag` and
  `dragend`, `"tx": int` and `"ty": int` where `t` is a drop target with
  steps (below):
  - `c` and `r` are the column and the row of the surface under the
    pointer, counted from 0 at the surface's top left cell, not its
    window's (§5.2). They go on counting outside the surface: negative
    above it and to its left, its size or more below it and to its right.
    A program that scrolls while the pointer is past an edge knows how far
    past it the pointer is.
  - `mods` are the modifier keys held (§9), left out when none is. Alt
    is never in `dragstart`'s: a press with Alt held starts no drag
    (§9.2). Alt pressed later is reported, and the drag goes on.
- **Where in the element: steps.** An element that opts in to drags, or to
  drops, may say how many steps its width and its height are divided into, with
  `data-steps="<x>"` or `data-steps="<x> <y>"`: whole numbers from 0 to
  2^53 − 1 (§3.3), `0` for none along that axis. A slider from 0 to 20
  has `data-steps="20"`, a vertical one `data-steps="0 20"`. Each event of a drag of it then says where in
  it the pointer is:
  - `x`, when the first count is not 0: the pointer's distance from the
    left edge of the element's border box, over the box's width, times
    the count, rounded to the nearest whole number (a half up), and
    clamped to 0 and the count. A box with no width gives 0.
  - `y`, when the second count is not 0: the same, from the top edge, over
    its height.

  The element is the one `dragstart` names, whatever `t` says later, and
  its box is where the user sees it at the time, scrolled (§5.3)
  included. The step is measured against it wherever the pointer is: over
  another element, outside the surface, or outside the window. So a
  slider follows a finger that slips off its track until it lifts, as a
  browser's range input does. Its counts are the ones it has when the
  drag starts. A value that is not one or two whole numbers in that range
  gives no steps. If the element leaves the document during the drag, `x`
  and `y` stay as they last were, as they do in a `dragend` that ends a drag
  early (below).
- **Where in the target.** When `t` has `drop` in its `data-on` (below)
  and steps, `drag` and `dragend` also say where in `t` the pointer is:
  `tx` and `ty`, measured against `t`'s border box as `x` and `y` are
  against the dragged element's, by the counts `t` has at that moment. So
  a list splits a row with `data-on="drag drop"` and `data-steps="0 1"`
  into halves, for an item to land before or after it, and a tree splits a
  folder with `data-steps="0 2"` into before, into and after: a quarter, a
  half and a quarter. Where there is no `t`, or it has no `drop` or no
  steps, there is no `tx` or `ty`, so a slider's drag over itself carries
  only `x`; a `dragend` that ends a drag early (below) has none either.
- **A place to drop on.** An element with `drop` in its `data-on` and an
  `id` can be `t` without being dragged. A press on it starts no drag of it
  (one of an element around it that opts in to drags starts as before), and
  its text can be selected as any other's (§11). An element with both is
  dragged and dropped on.
- **The pointer is the surface's until the release,** or the finger until
  it lifts, as a page's `setPointerCapture` makes it an element's. Every
  move goes to the drag, wherever the pointer is: over the cells, over
  another surface, or outside the terminal's window where the platform
  allows it. None of it reaches anything else: no other surface, no
  `hover` (§9.4), and no mouse reporting to the program.
- **A drag selects no text.** A press on an element that opts in starts no
  text selection, whatever its CSS, as if it had `user-select: none` (§11).
- **A touch drags only an element that opts out of panning.** A touch on an
  element that opts in is a drag, rather than a scroll, when the
  `touch-action` that Pointer Events determine for the touched element
  allows no pan along the touch's first move. That value is the touched
  element's own, with its ancestors', up to and including the nearest
  element that scrolls (§5.3), as the document's CSS sets them: what a host
  sets for its own handling of touch does not count. The host takes the
  first move once the touch has gone past its tap slop, along the larger of
  the two deltas, a tie counting as a pan. `pan-x` allows a horizontal pan,
  `pan-y` a vertical one, and `auto` and `manipulation` both; `pan-left`
  and `pan-right`, or `pan-up` and `pan-down`, count as their axis where
  the host's CSS engine parses them, which not every engine does. Any
  other value, `none` and `pinch-zoom` included, allows neither. Elsewhere
  `touch-action` changes nothing: a touch that does not drag pans as §5.3
  and §9 say.
  - Where the value allows both pans, as the initial `auto` does, nothing
    changes: a touch drag scrolls (§9), and a tap is a click, which only
    `click` reports.
  - **A touch presses when it becomes a drag,** and at that moment is a
    press as a mouse's is (Order, below): `press`, if the placement asked
    for it, then `dragstart`, both for where the touch began (its cell,
    and its step), then everything else the press causes. If the finger
    is by then over another element than the one `dragstart` names (or,
    where that names none, another cell), or at another step, a `drag`
    for where it is follows.
  - **A touch the host takes as a pan** presses nothing, and stays a pan to
    its end.
  - **A touch that lifts before going past the slop** is a tap, or a long
    press if held past the host's long-press time. Each is what it is on
    any other element (§9): a tap presses at its lift. A long press never
    becomes a drag.
  - **Alt.** A touch that begins with Alt held is never a drag. `mods` are
    the modifier keys held on a keyboard when the finger touched, and as
    for a mouse after.
- **Order.** The press that starts a drag is a press like any other
  (§10.1). `press`, if the placement asked for it, comes first, then
  `dragstart`, then everything else the press causes (`change`, `blur`,
  `focus`). On the release, `dragend` comes before any `click`.
- **A drag is a click only where it began.** When it ends on the element
  it started on (the nearest element with an `id` and `drag` in its
  `data-on`, from the one under the pointer outward, is `dragstart`'s `t`;
  without `drop`, that is `dragend`'s `t`), its release is the click it
  would have been without the drag: an element with `click` in its
  `data-on` as well reports `click`, after `dragend`. A drag that ends
  anywhere else reports no `click`. So does a touch's: one that lifts on
  the element it began on reports `click` after `dragend`.
- **What ends a drag early.** The host sends `dragend` at once, with no
  `t` and the last cell the pointer was on, when before the release:
  - the surface's placement goes away (`a=hide`, or its line leaving the
    screen);
  - a new document replaces the surface's (`a=doc`);
  - the host loses the pointer;
  - or, in a touch's drag, a second finger touches. The surface hears
    nothing more of the gesture until every finger lifts, and what the
    host does with it is its own. A second finger before a touch drags
    makes it no drag.

  A surface that is detached or deleted during a drag reports nothing more
  (§5.5). Nothing else ends a drag: neither a delta nor a new placement
  (moving the surface, or showing another window of it).

### 9.2 Presses with Alt

A press of the primary button with Alt held is the program's, never a
surface's. Wherever it lands, in any window and whatever its `z`, the host
handles it as a press on the cells beneath the surface.

- **With mouse reporting on,** the program hears it as it hears a press on
  the cells, Alt included: in the SGR encoding, `CSI < 8 ; <col> ; <row> M`.
  With mouse reporting off, the terminal does what it does with such a
  press on the cells, such as starting a rectangular selection.
- **The surface hears nothing of it.** No `press`, whatever `p` asked for,
  no `dragstart`, and no `click` on its release. It takes no focus, starts
  no selection, and opens no hyperlink. The surface under the pointer is no
  longer hovered: the pointer has left its window (§9.4).
- **The keyboard** goes back to the terminal, as on a click on the cells
  (§10.1): a surface that had it sends `blur`.
- **The gesture is the program's until the release.** Every move and the
  release go where those of a press on the cells go, over any surface, and
  none of them reaches a surface.
- **The press decides,** and the keys held later change nothing. A drag
  that a press without Alt began stays the surface's when Alt is pressed
  during it (its `mods` then have Alt, §9.1). A gesture that a press
  with Alt began stays the program's when Alt is let go.
- **On macOS, Alt is Option**, whether or not the terminal makes Option
  type as Alt for keys (Ghostty's `macos-option-as-alt`, say). That setting
  is about text, and a press has none.
- **Mouse and pen only.** A tap is a click whatever the keys held, and a
  touch with Alt held scrolls (§9.1).

### 9.3 Where the pointer passes through

A surface that only shows something, such as a frame drawn around cells,
should not keep the pointer from them. On a host with the feature
`passthrough` (§4), a surface takes the pointer only where an element is
there for it: where CSS hit testing finds an element at the point, as a
browser's does. Hit testing skips every box whose `pointer-events` is
`none`; the property inherits, so `html { pointer-events: none }` makes a
surface take no pointer, and an element in it with `pointer-events: auto`
takes it back over its own box.

Where a window takes no pointer, the pointer **passes through** it, as if
the window were not there:

- **To what is below:** the topmost other window at that point that takes
  the pointer there, or else the cells. Over the cells, the terminal does
  what it does with the pointer over cells: with mouse reporting on, the
  program hears presses, releases and motion as it hears them over cells
  (the SGR encoding, say), and the pointer has the shape the terminal
  gives the cells (the one the program set with OSC 22, for instance).
- **The press decides,** as in §9.2: a gesture whose press passed through
  is not the surface's until its release, wherever the pointer goes, and a
  gesture whose press a surface took (a drag, §9.1) stays that surface's
  over the parts that take no pointer.
- **The surface hears nothing of it:** no `press`, whatever `p` asked for,
  no `dragstart` and no `click`; it takes no focus and starts no selection.
  A press passing through takes the keyboard back as a click on the cells
  does (§10.1). The pointer there hovers nothing in the surface, and
  `:hover` matches nothing under it: it is out of the window (§9.4).
- **Gestures that scroll** reach the cells as everywhere (§9).

A host without `passthrough` gives a window the pointer wherever it is
(§5.3). A program that relies on the cells under a surface hearing the
pointer checks for `passthrough`, and otherwise keeps its surfaces off the
cells whose pointer it needs.

### 9.4 Hover

Hover stays local (§9): `:hover` and the pointer's shape need no program.
A program that asks, with `v=1` on the surface (§5.2), also hears where the
pointer is over the window, element by element: to show a hint of its own
for what is under it, or to know that the pointer has gone from what the
program draws itself, such as cells it lit while the pointer was over them.

- **Over the window.** `hover` is sent each time the element the pointer
  is over changes. `t` is the nearest element with an `id`, from the one
  under the pointer outward (the innermost one `:hover` matches), and
  absent where none has an id. The detail is `{"c": int, "r": int}`, the cell
  of the surface under the pointer, counted as for drags (§9.1): from the
  surface's top left cell, not its window's.
- **Out.** `hover` with no `t` and the detail `{"out": true}` is sent
  when the window loses the pointer. A window has the pointer while it
  receives it (§5.3): the pointer is in it, it is the topmost window there,
  and on a host with `passthrough` it takes the pointer at that point
  (§9.3). It loses the pointer when the pointer:
  - moves onto the cells, or onto another window;
  - moves onto a part of it that lets the pointer through (§9.3);
  - leaves the terminal's window, or the host loses it otherwise;
  - is pressed with Alt (§9.2): the gesture is the program's, and hovers
    nothing until its release.

  The pointer is in at most one window at a time. Moving from one window
  to another, the first one's out comes before the second one's `hover`.
- **Only changes.** A `hover` is sent when `t`, or whether the window has
  the pointer, differs from what the program last heard: neither a move
  within an element nor a new cell sends one. A surface starts from out
  when `v` is turned on. A host **MAY** send only the last of the changes
  between two frames it draws, so at most one per surface per frame.
- **A held button holds hover.** From a press a surface takes to its
  release, no surface sends `hover`: the pointer is the gesture's, and a
  drag reports its own targets (§9.1). At the release, the surface reports
  where the pointer is now, if that differs from what the program last
  heard, after every other event of the release (`dragend`, `click`,
  `change`, `focus`, `blur`). A window the pointer is then in, another one
  included, reports it at the pointer's next move. A press itself sends no
  `hover`.
- **Mouse and pen.** A touch's moves scroll (§9), or drag (§9.1), and
  hover nothing. At a tap's release, or the lift that ends a touch's drag,
  a host **MAY** report what the finger was on, as browsers leave `:hover`
  on what a tap touched.
- **Not the keyboard's.** Focus moving, to the surface or away from it,
  changes nothing: a window reports the pointer whether or not it has the
  keyboard (§10).
- **Under a pointer that does not move.** When a delta, a new document, a
  resource, or a new placement changes what lies under a pointer that has
  not moved, a host **MAY** report it only at the pointer's next move.
- **Lifetime.** It holds until the program turns `v` off (§5.2), across
  placements and documents. Hidden, or scrolled out of view, a surface
  sends none, and goes on from what the program last heard once it is
  drawn again (§9): an element with the same id under the pointer is not
  reported again. A detached surface sends none (§5.5); once a document
  makes it the program's again, it hears how the pointer differs from
  what it heard last.
- **Detection.** The feature `hover` (§4). Where it is absent, a program
  never hears that the pointer has left for a surface, and does not keep a
  state of its own that waits for it: it clears it on the next thing it
  hears instead, a key or a press.

### 9.5 View

Scrolling is local (§5.3). A program that marks elements with
`data-on~=view` hears which of them are in view, as a browser's
`IntersectionObserver` tells a page: to mark the section a reader is in, as
a table of contents does, or to fetch what comes into view.

- **In view.** An element with `view` in its `data-on` and an `id` is in
  view while some of its border box is shown: inside the window (§5.2), and
  neither scrolled away nor clipped by a box that holds it (§5.3). One that
  is not rendered (`display: none`, `visibility: hidden`) is not in view,
  and one without an `id` is never reported.
- **The event.** `view`, with no `t`, carries `{"in": {str: area},
  "end": bool}`: each element in view, by its id, with its area as a
  click's (§9), the whole of it however little is shown; and `end`,
  `true` when the document's root is scrolled as far down as it goes, as
  a root that does not scroll down always is. It names every element in
  view, not what changed.
- **When.** A host sends `view` once the document is laid out in a
  placement, and then each time what it would carry changes: an element
  comes into view or leaves it, an area changes, or `end` does. A scroll
  changes the areas as it moves, so while one moves, a gesture with its
  momentum or a smooth scroll, the host waits for it to come to rest and
  sends one `view` then. Otherwise it sends at most one per surface per
  frame it draws, with the layout drawn in it. A document with no element
  that asks sends none.
- **Lifetime.** It holds while an element asks, across placements and
  documents. Hidden, or scrolled out of view, a surface sends none, and
  reports how what it would carry differs from what the program last
  heard once it is drawn again (§9). A new document sends one once it is
  laid out, if an element in it asks. A detached surface sends none
  (§5.5).

### 9.6 Touch in the cells

A touch drag over the cells scrolls (§9), and on the alternate screen the
program hears it as its wheel. A program that draws a slider in its cells
and reads the mouse to move it can be dragged with the mouse but not with a
finger. It names the cells where a finger drags instead, as a document does
with `touch-action` (§9.1):

```
ESC ] 7279 ; a=touch ; <base64 msgpack {"areas": [area]}> ST
```

- **An area** is `{"c": int, "r": int, "w": int, "h": int, "drag": str}`:
  - `c` and `r`, the first column and row it covers, counted from 0 at the
    screen's top left cell (a mouse report counts from 1);
  - `w` and `h`, the columns and rows it spans, at least 1 each;
  - `drag`, which way a finger drags in it: `"x"` across, `"y"` up and
    down, `"xy"` either. A horizontal slider is `"x"`, as `touch-action:
    pan-y` makes one on a surface. A swipe the other way still scrolls.

  An area past the screen's edge covers what of it is on the screen. Where
  areas overlap, the last listed decides. A body with more than 256 areas,
  or one that is no such map, is `EINVAL`.
- **They last** until the program sends others, or `a=touch` with no body,
  which leaves none. The main screen and the alternate screen each keep
  their own, as edit commands are kept (§10.6): leaving the alternate
  screen drops its areas, and a full reset (RIS) drops both. They are cells
  of the screen, not of its text. When its lines scroll, the areas stay
  where they are, so a program that moves what it drew sends them again. A
  program that ends clears them, as it clears its menu (§10.7).
- **Only while the program reads the mouse.** An area does something only
  while the program has the terminal report the mouse's buttons, in any
  mode and encoding, and the terminal shows the screen rather than its
  scrollback. Otherwise a touch in it does what it does in any other cell.
- **A touch that drags.** A touch that begins in an area, without Alt held
  (§9.2), is decided as one on a surface is (§9.1). Once it goes past the
  host's tap slop, its way is the larger of the two deltas, and a tie
  counts as a pan.
  - If that way is one the area drags, the touch is the mouse's primary
    button. It is pressed on the cell where the touch began, moved with
    the finger, and released on the cell where the finger lifts. The
    terminal reports each of these as it reports the mouse's, in the
    program's mode and encoding, with the modifier keys held on a
    keyboard. A modifier with which the terminal keeps a mouse press for
    itself, such as Shift for selecting text, is left out. When the
    finger is by then on another cell, a move to it follows the press.
  - Otherwise the touch pans, as §9 says, and stays a pan to its end.
- **The finger is the program's until it lifts,** as the pointer is in a
  drag (§9.1). Every move goes to the program as the mouse moving with its
  button held: over a surface too, and past the screen's edge, where the
  terminal reports it as it reports a mouse dragged there. None of it
  reaches a surface, and nothing is selected.
- **A tap** in an area is a touch that lifts before going past the slop. It
  is the primary button pressed and released on its cell at the lift, so a
  slider jumps to where it was tapped. A long press is whatever the host
  makes of one anywhere, and never drags.
- **What ends a drag early.** A second finger, or the host losing the
  touch, releases the button on the cell the finger was last on. What the
  host does with the rest of the gesture is its own. A second finger
  before a touch drags makes it no drag. Nothing else ends a drag, new
  areas included.
- **Surfaces come first.** A touch that begins on a surface, where its
  window takes the pointer, is the surface's (§9.1): where it pans, it
  scrolls as §9 says, whatever areas lie beneath. A touch that passes
  through a window (§9.3) begins in the cells, and an area there applies.
  Where a touch lands is as the platform decides it, and a browser may
  give a touch just off a window's edge to the window.
- **Only a finger.** The mouse and a pen reach the program as they always
  have, and a touch outside every area is the terminal's, as it always
  was: its drag scrolls (§9).
- **A multiplexer that relays the mouse** to its panes sends the terminal
  the areas of the panes it shows whose programs read the mouse. It moves
  each to where its pane is, and cuts it to the part of the pane that
  shows: not past the pane's edges, nor under what the multiplexer draws
  over it, such as a menu or a dialog. It adds areas of its own where it
  drags in its cells, such as a border between panes. It sends them again
  whenever any of this changes. The drag then reaches the multiplexer as
  the mouse's does, and it passes the drag to the pane as it would the
  mouse's.

A host that takes no touch accepts `a=touch` as any host does, and nothing
comes of it.

## 10. Keyboard and focus

### 10.1 Who has the keyboard

A surface **has the keyboard** when the user clicks an element in it that
takes focus, or the program gives it with:

```
ESC ] 7279 ; a=focus:s=<name>[:t=<element id>] ST
```

- **With `t`:** the element is focused (`ENOTARGET` if there is none).
- **Without `t`:** the surface's focused element keeps focus, or else the
  first focusable element receives it.
- **Echo:** no `focus` event is sent for focus the program gave.
- **A prefix ends** (§10.5) with either form, whether or not focus moves.
- **The host says where the user puts focus.** Each time the user focuses
  an element of a surface, by a click or with Tab (§10.2), whether the
  surface had the keyboard or not, the host sends `focus`. Its `t` is the
  id of the nearest element that has one, from the focused element
  outward, and absent if none has. So a program knows which element the
  keys it is given come from (§10.2, *Keys for the program*). The `change`
  that focus leaving a text control commits comes first, and a click's
  `press` before both (§9).
- **A click** is a press of the primary button without Alt (§9.2), a
  tap, or a touch that becomes a drag (§9.1).
- **Elements that take focus** on a click, unless they are disabled:
  - `input`, `select`, `textarea` and `button`;
  - links with an `href`, except hyperlinks (§9), which are the terminal's
    whatever their `tabindex`;
  - the first `summary` of a `details`;
  - editing hosts (`contenteditable`);
  - any element with a `tabindex` of 0 or more.

  A click on a `label` is a click on its control.
- **A click on anything else takes nothing.** On a surface with no such
  element a click never takes the keyboard, and no `focus` is sent: every
  key would reach the program anyway (§10.2). A program that needs to know
  the user went to the surface all the same places it with `p=1`, and
  hears `press` (§9).
- **A click elsewhere gives the keyboard back** to the terminal: on the
  cells, on another surface, or inside this one on an element that does not
  take focus. The host sends `blur`.
- **A detached surface** (§5.5) never has the keyboard: `a=focus` is
  `EDETACHED`, whatever its `t`, and `a=blur` does nothing.

The program takes the keyboard back with `a=blur:s=<name>`. The focused
control commits its value first (a `change` may follow), and then `blur` is
sent.

### 10.2 Keys

While a surface has the keyboard, each key either goes to its focused element
or reaches the program. The surface is offered the key as the user pressed
it, before the terminal's own shortcuts, and then, if it does not use it,
as the program would read it (§10.4). A key the terminal turns into
other input, such as a binding that sends text or a key the user remapped,
is offered the second time as the key that input reads as.

| focused element | keys it uses |
| --- | --- |
| text field: an `input` of type `text`, `search`, `url`, `tel`, `email`, `password` or `number` (or a type the host does not know, which is `text`), a `textarea`, an editing host (`contenteditable`) | the keys its keymap binds, and printable characters (below) |
| an `input` of a date or time type | characters, `space`, `backspace`, `delete`, the arrows, `home`, `end`, `page_up`, `page_down`, `enter`, unmodified or with `shift` only |
| `select` | characters, `space`, `arrow_up`, `arrow_down`, `home`, `end`, `page_up`, `page_down`, `enter`, unmodified or with `shift` only |
| `button`, `a`, `summary`, checkbox, radio button | `space` and `enter`, unmodified or with `shift` only |

- **`tab` and `shift+tab`** move between the surface's focusable elements, and
  the host sends `focus` for the one they move to (§10.1). Past the last
  one, or before the first, the surface loses the keyboard: the host sends
  `blur` and the terminal has the keyboard again.
- **`escape`** is never used by the surface, except by a select's open list
  (below). It reaches the program, which decides, for instance by sending
  `a=blur`.
- **After a prefix** (§10.5), every key reaches the program, before
  anything below.
- **A key the element gives the program**, bound to `program` or
  `prefix`, reaches it, whatever the element or the scrolling (§5.3) would
  do with it (below, *Keys for the program*).
- **A key the element binds to a scroll action**, and does not use, scrolls
  the surface's document (below, *Scrolling keys*).
- **Every other key** reaches the program as ordinary terminal input, in the
  keyboard encoding the program has enabled, or as an edit command it takes
  (§10.6). A program's own keymap therefore keeps working while a surface
  holds the keyboard.

#### Selects

A focused `select` picks an option with its keys, as a browser's closed
select does on Linux and Windows. Each pick is heard at once (§9): `input`,
with `data-on~=input`, then `change`.
- **Up and Down** pick the option before or after the picked one; **Home**
  and **End** the first and the last.
- **A character** picks the next option after the picked one whose label
  starts with it, ignoring case, going round to the first. A host **MAY**
  match several characters typed in quick succession, as browsers do.
- **Page Up and Page Down** move further, by as much as the host likes.
- Disabled options are passed over, and a key with no option to go to picks
  nothing.

**The list** is the host's to show or not:
- A host **MAY** show no list. The select then works by its keys alone, and
  Space and Enter do nothing. A program that wants a list on every host
  draws its own: a button, and a surface at a higher z (§5.2) placed by the
  `area` its click reports (§9).
- A list a host shows opens as a browser's does, on a click or a key, and
  may be drawn past the surface's rectangle, over the cells and other
  surfaces, as a browser draws one past the page. While it is open it has
  every key, Escape and those the keymap gives the program included, and
  it closes on a pick, on Escape, and when the select loses focus.

#### Text fields

A text field's **keymap** binds keys to editing actions. It is the default
keymap, then the `data-keys` attribute of each element from the document's
root down to the field, the field's own last. Its bindings to scroll
actions are left out, each where it stands, as if it were not there (below,
*Scrolling keys*). Each `data-keys` overrides what
came before it key by key: a binding replaces the earlier binding of its key,
and leaves the others as they were. A program sets its keymap once, on an
element that holds its fields, and a field that needs one key otherwise says
only that.

`data-keys` is a list of bindings separated by ASCII white space (space,
tab, line feed, form feed, carriage return), as HTML's space-separated
tokens are. A binding is
`key=action`, split at its last `=`: the key is read as §10.4 reads one
(`ctrl+a`, `mod+s`, `alt+arrow_left`; `alt+==line-start` binds `alt+=`), and the
action is one of the table below or a scroll action (*Scrolling keys*). A
host **MUST** ignore a binding whose key does not parse or whose action it
does not know, and bindings of `tab`, `shift+tab` and `escape`, which keep
their meaning above.

The default keymap is the keys every host binds, then those of the host's
platform modifier, `Mod` (§10.4), then the host's edit shortcuts. Every
host binds:

| key | action |
| --- | --- |
| `arrow_left`, `arrow_right` | `char-backward`, `char-forward` |
| `alt+arrow_left`, `alt+arrow_right` | `word-backward`, `word-forward` |
| `home`, `end` | `line-start`, `line-end` |
| `backspace`, `delete` | `delete-char-backward`, `delete-char-forward` |
| `alt+backspace`, `alt+delete` | `delete-word-backward`, `delete-word-forward` |
| `arrow_up`, `arrow_down` | `line-previous`, `line-next` |
| `page_up`, `page_down` | `page-up`, `page-down` |
| `enter` | `submit` in an `input`, `newline` in a multi-line field |

Where `mod` is `ctrl`:

| key | action |
| --- | --- |
| `ctrl+arrow_left`, `ctrl+arrow_right` | `word-backward`, `word-forward` |
| `ctrl+home`, `ctrl+end` | `input-start`, `input-end` |
| `ctrl+backspace`, `ctrl+delete` | `delete-word-backward`, `delete-word-forward` |

Where `mod` is `super`:

| key | action |
| --- | --- |
| `super+arrow_left`, `super+arrow_right` | `line-start`, `line-end` |
| `super+arrow_up`, `super+arrow_down` | `input-start`, `input-end` |
| `super+backspace` | `delete-to-line-start` |
| `ctrl+a`, `ctrl+e` | `line-start`, `line-end` |
| `ctrl+b`, `ctrl+f` | `char-backward`, `char-forward` |
| `ctrl+p`, `ctrl+n` | `line-previous`, `line-next` |
| `ctrl+h`, `ctrl+d` | `delete-char-backward`, `delete-char-forward` |
| `ctrl+k` | `delete-to-line-end` |

The **edit shortcuts** are the host's, and the user's to change; a host
reports them as `edit` (§4). The same keys send a program in the cells the
edit commands it takes (§10.6). Unless the user changes them, they are:

| key | action |
| --- | --- |
| `mod+a` | `select-all` |
| `mod+c` | `copy` |
| `mod+x` | `cut` |
| `mod+v` | `paste` |
| `mod+z` | `undo` |
| `mod+shift+z`; `ctrl+y` where `mod` is `ctrl` | `redo` |

These are a text field's keys in a browser on Linux and Windows, where
`mod` is `ctrl`, and on macOS, where it is `super`: the Command key is
`super`, the Option key `alt` (§10.4), and `ctrl+a` and the keys after it
are Cocoa's. Shift selects with each move (below). A program that wants
one of them binds it to `program`. A host that sees only what the program
would read (§10.4) can offer a key only as that: in the legacy encoding a
terminal may send Control with Backspace as `0x08`, which is `ctrl+h`, and
a macOS terminal sends Command with the left arrow as `0x01`, which is
`ctrl+a`. It also names a character only by what it types, so a binding of
a physical key of the writing system (`ctrl+key_w`) holds only where the
host sees the key itself: one that should hold everywhere names the
character (`ctrl+w`).

A key goes to the field's keymap:
- **Bound:** the field does the action. A key bound to `program` or
  `prefix`, or to an action the field does not have (a multi-line one in
  an `input`, or `copy`, `cut` and `paste` where they have nothing to do,
  below), reaches the program.
- **Not bound, with `shift`:** a key that is not a character is looked up
  again without `shift`, so `shift+arrow_left` does what `arrow_left` does,
  and selects (below). A character with `shift` is not: `shift+g` is a key
  of its own, as `G` is in a browser.
- **Not bound, a character or `space`,** with no modifier but `shift`: the
  field types it at the caret. It types the text the key types: the
  character, a letter with `shift` as its capital, and a space.
- **Otherwise** the key reaches the program.

So a field uses only the keys its keymap names, and the characters it types:
a key the program did not give the field stays the program's.

The actions:

| action | what it does |
| --- | --- |
| `char-backward`, `char-forward` | moves the caret one character back, or forward |
| `word-backward` | moves the caret back over spaces, then over the word before them, to its start |
| `word-forward` | moves the caret forward over spaces, then over the word after them, to its end |
| `line-start`, `line-end` | moves the caret to the start, or the end, of its line |
| `delete-char-backward`, `delete-char-forward` | deletes the character before the caret, or after it |
| `delete-word-backward`, `delete-word-forward` | deletes from the caret to where `word-backward`, or `word-forward`, would move it |
| `delete-to-line-start`, `delete-to-line-end` | deletes from the start of the caret's line to the caret, or from the caret to the end of its line |
| `line-previous`, `line-next` † | moves the caret to the row above, or below, as near as it can to the place along the row where a run of these moves began; from the first row to the start of the value, from the last to its end |
| `page-up`, `page-down` † | moves the caret as many rows up, or down, as the field shows, the same way |
| `input-start`, `input-end` | moves the caret to the start, or the end, of the value |
| `select-all` | selects the whole value: the anchor at its start, the caret at its end |
| `copy` | puts the selected text on the clipboard, and leaves it selected |
| `cut` | puts the selected text on the clipboard and deletes it |
| `paste` | types the clipboard's text in place of the selection |
| `undo`, `redo` | undoes the field's last edit, or redoes the last one undone (below) |
| `newline` † | types a line break |
| `submit` | submits the field's form, as Enter does in a text `input` (§9 `submit`) |
| `program` | nothing: the key reaches the program |
| `prefix` | nothing: the key reaches the program, and so does every key after it (§10.5) |

† Multi-line fields only: a `textarea` and an editing host.

- **Characters** are grapheme clusters (Unicode UAX #29, extended), and a
  line break (CR LF, LF or CR) is one. The caret stops at the value's ends.
- **A space** is a character whose first code point has the Unicode
  `White_Space` property; a line break is one. **A word** is a run of
  characters that are not spaces.
- **A line** is the text between line breaks; an `input`'s value is one
  line. A **row** is a line as the field lays it out: a line that wraps is
  several rows.
- **A password field** (`type=password`) is one word: the word actions move
  to, or delete to, the start or the end of the value.
- **A selection** runs from its anchor, the end where it began, to the
  caret. The user makes one with the pointer, with Shift (below) or with
  `select-all`. It comes first: typing replaces it, and a delete action
  deletes it and nothing else. A move without Shift starts from its start
  when it goes back or up, and from its end otherwise, and the selection
  goes; `char-backward` and `char-forward` stop there.
- **Shift selects.** The moves are `char-backward`, `char-forward`,
  `word-backward`, `word-forward`, `line-start`, `line-end`,
  `line-previous`, `line-next`, `page-up`, `page-down`, `input-start` and
  `input-end`. A move done by a key whose name has `shift` (§10.4), bound
  with `shift` or looked up without it, moves the caret from where it is
  and keeps the anchor, which is where the caret was if nothing was
  selected. `shift+arrow_left` selects the character before the caret.
  `ctrl+shift+arrow_right`, `shift+end` and, in a `textarea`,
  `shift+arrow_down` go on by a word, to the line's end and by a row. When the
  caret comes back to the anchor, nothing is selected.
- **Every edit** is an edit for the `input` and `change` events (§9).
- **The clipboard** is the host's: the platform's, where there is one. A
  field has no `copy` or `cut` with nothing selected, nor in a password
  field, and no `paste` on a host that cannot read the clipboard: the key
  reaches the program, so Control+c on Linux still interrupts it. An `input` drops the
  line breaks from what it pastes, as HTML's does.
- **Undo.** Each text field keeps a history of its edits, as a browser's
  does, for as long as it is in the document:
  - Typing characters one after another is one edit, and so is deleting
    them one at a time, with `delete-char-backward` or
    `delete-char-forward`. The run ends with any other action, and when
    the pointer moves the caret. Every other edit, a cut and a paste among
    them, is one of its own.
  - `undo` puts back the value and the selection from before the last
    edit, and `redo` those from after the edit last undone. A new edit
    drops the edits undone.
  - With nothing to undo, or to redo, the key is still the field's, and
    does nothing.
  - A value the program sets (§6.2, *Controls whose values the program
    sets*) clears the history.

#### Keys for the program

A key that the focused element's keymap binds to `program` or `prefix`
reaches the program, before the element uses it and before the surface
scrolls with it (§5.3), whatever the element. It reaches it as terminal
input, which names no element: the last `focus` does (§10.1), or the
program's own `a=focus`.
- **Every focused element has a keymap**, read as a text field's is, but
  with its scroll actions: the `data-keys` of each element from the
  document's root down to it, the element's own last, each overriding what
  came before it key by key. A key with `shift` that is not a character
  and is not bound is looked up again without it. Only a text field starts
  from the default keymap.
- **Outside a text field,** a keymap gives only `program`, `prefix` and
  the scroll actions (below). Its other bindings still override farther
  ones key by key, but do nothing there: the element uses those keys as
  the table above says.
- **With no element focused** there is no keymap. A program that wants
  keys in a surface with nothing to focus gives an element a `tabindex`
  and focuses it.

So a program that works a list, a menu or a table with the arrows keeps
them on the element that holds it. On a button in a scrolling surface,
`data-keys="arrow_up=program arrow_down=program"` sends the arrows to the
program, where they would otherwise scroll the surface.

#### Scrolling keys

A focused element's keymap may bind keys to scroll actions, so that the keys
a program scrolls its cells with, such as a pager's `j` and `k`, scroll its
surfaces where the host scrolls them (§5.3):

| action | scrolls |
| --- | --- |
| `scroll-up`, `scroll-down` | up, or down, as far as the host scrolls with `arrow_up` and `arrow_down` |
| `scroll-left`, `scroll-right` | left, or right, as far as with `arrow_left` and `arrow_right` |
| `scroll-page-up`, `scroll-page-down` | up, or down, as far as with `page_up` and `page_down` |
| `scroll-half-page-up`, `scroll-half-page-down` | up, or down, by half the height of the box it scrolls (its scrollport) |
| `scroll-start`, `scroll-end` | to the top, or the bottom, as `home` and `end` |

- **What scrolls** is the nearest element that scrolls (the root, or an
  element whose `overflow` is `auto` or `scroll`), from the focused element
  outward, itself included. Where it cannot move that way, the key goes
  outward as a gesture does (§5.3), as far as `overscroll-behavior` lets
  it, and never past the root: a key never scrolls the terminal. Where
  nothing it may reach can move, the key is used, and does nothing, unlike
  a key a browser scrolls with, which then goes on to the program (§5.3).
- **Order.** A key bound to `program` or `prefix` is the program's
  (above). Any other key the focused element uses (the table at the start
  of §10.2) stays the element's: `space` on a button presses it, and a
  character on a select picks. A key the element does not use and its keymap binds to a scroll
  action scrolls, before the surface's own scrolling keys (§5.3), and does
  not reach the program.
- **Along an axis the document does not scroll** (§5.1 `scroll`), a scroll
  action does nothing, and the key goes on as if its keymap did not bind
  it.
- **A text field's keymap leaves scroll actions out.** A binding to one
  neither acts nor overrides the bindings before it, so a field inside an
  element that scrolls with `j` still types `j`, and a `textarea` keeps
  `page-down` on `page_down`. A farther binding of the key still holds: under
  `j=program` on the root, `j` in that field reaches the program. A field's
  own text follows its caret (§5.3).

So a program that pages its cells with `j`, `k`, `g`, `G`, Space and `b`
binds the same keys on the element that scrolls on a surface:
`data-keys="j=scroll-down k=scroll-up g=scroll-start shift+g=scroll-end
space=scroll-page-down shift+space=scroll-page-up b=scroll-page-up"`
(`shift+space`, or it would page down as `space` does). Its buttons keep
`space`, and its fields type every letter.

### 10.3 Key releases

hotty has no key events of its own. A program that needs to know when a key is
released (a game, say) enables the kitty keyboard protocol with *report event
types*. That is an ordinary terminal feature, and works the same with or
without hotty.
- The alternate screen keeps its own stack of keyboard flags, so a
  full-screen program enables them after switching to it.
- A surface that does not have the keyboard does not affect the keys at all.

### 10.4 Key names

A key is named as *myelin key names* 0.1 names it
([myelinkey/SPEC.md](https://github.com/neuroplastio/myelin/blob/myelinkey/v0.1.0/myelinkey/SPEC.md)
at the tag `myelinkey/v0.1.0`), in Ghostty's vocabulary: its modifiers,
each followed by `+`, then a physical key, a character or a modifier key.
`a`, `shift+a`, `?`, `enter`, `arrow_left`, `ctrl+a`, `alt+backspace`,
`ctrl+shift+arrow_right`, `super+enter` and `shift` are keys. That
document is part of this one for:
- **The names** (its §2):
  - the modifiers `super`, `ctrl`, `alt` and `shift`, written in that
    order, `super` being the platform's command key (Command on macOS,
    Super or the Windows key elsewhere), which the W3C calls Meta;
  - a physical key, a place on the keyboard: its W3C code value in snake
    case (`page_down`, `key_w`, `numpad_enter`);
  - a character, what the key types, as the layout makes it: a letter
    written small, with `shift` as a modifier (`shift+g`), and any other
    character with Shift in it (`?`);
  - a modifier key, a modifier tapped on its own (`shift`, `ctrl+shift`).

  The space bar, Enter, Tab, Backspace and Escape are physical keys only
  (`space`, `enter`, `tab`, `backspace`, `escape`), Caps Lock is no
  modifier, and `+` is the plus key (`ctrl++`).
- **Matching** (its §3): two keys are the same exactly when their names
  are, and keys of different kinds never match.
- **Reading** (its §4.1 and §4.4): a host reads a key a program writes
  (`data-keys`, §10.2; a menu, §10.7) with the default reading and the
  command modifier. It takes the modifiers in any order and case, with
  `cmd`, `opt` and `control`; physical keys as W3C code values
  (`PageDown`) and as `esc`, `pgdn`, `up` and the like; and a letter in
  either case, so `G` is `g`, not `shift+g`. It refuses `meta`, which some
  programs use for `super` and others for `alt`. Neither strict reading
  nor shifted capitals applies.
- **`mod`** is the command modifier (its §4.4), the platform's modifier
  for commands: `super` on macOS and `ctrl` elsewhere, unless the user
  sets it otherwise. A host reports which as `mod` (§4), and reads a
  binding with `mod` standing for it, so `mod+c` is `super+c` on macOS and
  `ctrl+c` on Linux. A binding with `mod` and `super` or `ctrl` as well
  is refused, so a keymap reads on every platform or on none. No key
  pressed is named with `mod`.

A host offers a surface that has the keyboard each key at most twice
(§10.2), named as below each time. The surface uses a key that its focused
element uses, `tab` and `shift+tab`, which move focus, and a key it scrolls
with (§5.3), but none after a prefix (§10.5). A press with two names, a
physical key and a character, or a sided modifier key and a modifier key,
is looked up by its physical name first, as myelin key names §3 has it:
the surface uses the press if it uses either name, and where a keymap
binds both, the physical binding wins. A press the surface gives the
program as pressed, by a binding of either name to `program` or `prefix`,
stays the program's when it comes again as bytes, whatever its one name
then is bound to: in a field, `ctrl+key_a=program` sends `ctrl+a` to the
program, though the field selects all with `ctrl+a`, and a `prefix`
binding starts its prefix.
- **As pressed, first.** A host that sees the key the user pressed (a
  terminal emulator, a browser) offers it before the terminal's own
  shortcuts and before the bindings that translate keys, named from the key
  itself: the modifiers held, with the platform's command key (Command on
  macOS, Super or the Windows key elsewhere) as `super`; the physical key;
  and the character it types, when it types one (a browser's `code` and
  `key`, myelin key names §7). It does so for the keys that type no text:
  a key that is only physical (`arrow_left`, `home`, `backspace`,
  `enter`), and a key with `ctrl` or `super`, which has both names
  (`ctrl+key_w` and `ctrl+w`, on a US layout). A modifier tapped on its
  own is named on its release, as its sided key and as a modifier key
  (`shift_left`, `shift`), and goes no further: a key reaches the program
  as bytes, from which a host names no modifier key (below), so a tap
  bound to `program` or `prefix` does nothing. Typing goes only the
  second way, through the platform's input methods. If the surface uses the key, it is the
  surface's, and the terminal does nothing with it, not even a shortcut
  bound to it. A host **MAY** keep keys it reserves for itself, as a
  browser keeps `ctrl+t`.
- **As the program would read it, otherwise.** A key the surface does not
  use goes on as it would with no surface: the terminal's shortcuts take
  the keys they bind, and the rest is what the terminal would send the
  program, the bytes of the keyboard encoding the program has set (legacy,
  xterm's `modifyOtherKeys`, or the kitty keyboard protocol) after the
  terminal's own bindings. The host names it from those bytes, read as
  this table says, with one name: the character the bytes carry, or the
  physical key the table names. Input that is several keys (a binding that
  sends text) is each of them in turn. A host that has only those bytes,
  such as one that runs as a program in a terminal, offers keys this way
  alone.

| input | key |
| --- | --- |
| a printable character: one grapheme cluster, in UTF-8 | that character, as myelin key names writes it: a capital letter is `shift` with its letter (`A` is `shift+a`) |
| `0x20` | `space` |
| `0x00` | `ctrl+space` |
| `0x01`–`0x1a`, but those below | `ctrl+a` … `ctrl+z` |
| `0x08` | `ctrl+h` |
| `0x09`, `0x0d`, `0x1b`, `0x7f` | `tab`, `enter`, `escape`, `backspace` |
| `0x1c`–`0x1f` | `ctrl+\`, `ctrl+]`, `ctrl+^`, `ctrl+_` |
| `ESC` before one of the above | that key, with `alt`: `ESC b` is `alt+b`, `ESC 0x7f` is `alt+backspace` |
| `CSI A`, `B`, `C`, `D`; `SS3 A` … `D` | `arrow_up`, `arrow_down`, `arrow_right`, `arrow_left` |
| `CSI H`, `SS3 H`, `CSI 1 ~`, `CSI 7 ~` | `home` |
| `CSI F`, `SS3 F`, `CSI 4 ~`, `CSI 8 ~` | `end` |
| `CSI 2 ~`, `CSI 3 ~`, `CSI 5 ~`, `CSI 6 ~` | `insert`, `delete`, `page_up`, `page_down` |
| `CSI Z` | `shift+tab` |
| `CSI 1 ; m X`, for each `X` above; `CSI n ; m ~` | the key above, with the modifiers in `m` |
| `CSI 27 ; m ; c ~` (`modifyOtherKeys`) | the key whose code is `c`, with the modifiers in `m` |
| `CSI c [: s [: b]] [; m [: e] [; t]] u`, and the kitty protocol's `CSI 1 ; m : e X` and `CSI n ; m : e ~` | the key whose code is `c`, with the modifiers in `m`; a release (`e` = 3) is no key |

- **Modifiers `m`:** absent, empty or below 2, no modifier. Otherwise
  `m` − 1 is a sum of bits: 1 `shift`, 2 `alt`, 4 `ctrl`, and 8 and 32
  `super` (8 is xterm's Meta and the kitty protocol's Super, 32 the kitty
  protocol's Meta). Other bits (Hyper, Caps Lock, Num Lock) name no
  modifier.
- **Codes `c`:** 9 is `tab`, 13 `enter`, 27 `escape`, 8 and 127
  `backspace`, and 32 `space`. Another code below 57344 is the character
  with that code point, a capital without Shift being its small letter, as
  Caps Lock makes it. Of the kitty protocol's codes from 57344, those of
  its keypad name what a legacy terminal sends for the key: 57399–57408 the
  digits `0` … `9`, 57409–57413 `.`, `/`, `*`, `-` and `+`, 57414 `enter`,
  57415 `=`, and 57417–57426 `arrow_left`, `arrow_right`, `arrow_up`,
  `arrow_down`, `page_up`, `page_down`, `home`, `end`, `insert` and
  `delete`. Its other codes, its modifier keys' among them, are no key: a
  host that reads bytes names no modifier key.
- **A character with Shift:** a letter with Shift is `shift` and the
  letter (`CSI 97 ; 2 u` is `shift+a`). Any other character is the one
  Shift types, without `shift`: the shifted key `s` when given, else the
  text `t` when given. When neither is, it is `shift` and the character
  `c` (`shift+1`). The base layout key `b` names nothing here.
- **`ESC [` and `ESC O`** begin CSI and SS3. Alone, they are `alt+[` and
  `alt+shift+o`.
- **Anything else,** such as a sequence this table does not name, is no key
  a field uses: it reaches the program.

### 10.5 Key sequences

A program reads its key sequences itself, a leader and the keys after it,
or `g g`, with the keymap it uses in its cells (myelin's, say), and shows
which keys can follow one with its menu (§10.7). On a surface it needs two
things of the host: the keys after the first, whatever is focused, and a
place to show the menu.

**A prefix.** A key that the focused element's keymap binds to `prefix`
(§10.2) reaches the program, as one bound to `program` does, and so does
every key after it, whatever the focused element or the scrolling (§5.3)
would do with it: `tab`, `shift+tab` and `escape` too.

```html
<main data-keys="mod+k=prefix">
  <ul id=files tabindex=0 data-keys="g=prefix">…</ul>
</main>
```

Above, `mod+k` anywhere in `main`, and `g` on the list, hand the program
the keys that follow.
- **The keys** reach the program as any key the surface does not use
  (§10.2, §10.4): after the terminal's own shortcuts, as terminal input in
  the encoding the program enabled, or as an edit command it takes
  (§10.6).
- **It lasts** until the program sends `a=focus` to the surface, with or
  without `t` (without it, focus stays where it is, §10.1), or `a=blur`;
  until the surface loses the keyboard; or until the user focuses an
  element of it with a press. The program hears of each that is not its
  own command (`blur`, `focus`, §9), so it always knows where its keys
  come from.
- **Nothing waits in the host.** The program keeps the sequence's time, and
  once it ends, by a key, a timeout or Escape, gives the keys back with
  `a=focus`. The keys typed before that are the program's.
- **A multiplexer that relays the surface** needs no word of it: the keys
  the surface uses never reach it, so while the surface has the keyboard it
  gives the pane every key it reads, after its own shortcuts, as it does in
  the cells.

### 10.6 Commands in the cells

A program in the cells reads keys as bytes, and to it an edit shortcut
(§10.2) is a key like any other: `ctrl+c` interrupts, `ctrl+v` quotes the
next key. A program that selects, edits or undoes in its cells, as a text
field does on a surface, takes the edit commands instead:

```
ESC ] 7279 ; a=edit:take=copy,paste,undo,redo ST
```

- **`take`** names the commands, the actions of the edit shortcuts
  (`select-all`, `copy`, `cut`, `paste`, `undo`, `redo`), comma-separated.
  Each `a=edit` replaces what the last took, and one with an empty `take`,
  or none, takes nothing. A host **MUST** ignore a name it does not know.
- **Per screen.** The main screen and the alternate screen each keep their
  own, as the kitty keyboard protocol keeps its flags (§10.3): a
  full-screen program takes its commands after switching to the alternate
  screen, and leaving it drops them. A full reset (RIS) drops both.
- **A key of a command taken,** one of the host's edit shortcuts (`edit`,
  §4), reaches the program as the event `command`, in place of its bytes,
  and so does each of its repeats. Its release sends nothing (§10.3).

  ```
  ESC ] 7279 ; a=ev:e=command ; <base64 msgpack detail> ST
  ```

  The detail is `{"name": str, "key": str}`: the command, and its key as
  §10.4 names it from the bytes the program would have read for it, one
  name: `ctrl+c`, never `ctrl+key_c`, whichever name of the press the
  shortcut binds. A `paste` also has `"text": str`, the text on the
  clipboard, and none when the clipboard holds no text or the host cannot
  read it.
- **The program decides** what the command means. It copies what it has
  selected, and with nothing selected takes the command as its key, as a
  text field does (§10.2): `ctrl+c` still interrupts.
- **The terminal's selection comes first.** While the user has text
  selected in the cells, a key of `copy` copies it, as the terminal's own
  copy does, and is not sent. A surface's selection is copied as §11 says.
- **A surface that has the keyboard** is offered the key first (§10.2). A
  key it does not use reaches the program as the command.
- **The terminal's menus,** such as an Edit menu or a context menu, send
  the command they name, with no `key`, where its key would send one. Their
  copy copies the terminal's selection first, as above. A pick from the
  program's own menu (§10.7) is one of them, and is sent whether or not
  the program takes the command it names.
- **In order.** A command reaches the program on its input, among the keys
  typed around it, as every event does (§9): a paste never overtakes what
  was typed before it. The terminal's own paste, by a key that is no edit
  shortcut, still sends the clipboard as it would without hotty, as a
  bracketed paste where the program asked for one.
- **A host that takes `a=edit`** reports its edit shortcuts as `edit`
  (§4). When they change, or `mod` does (§10.4), as when a multiplexer's
  viewer changes to one on another platform, it sends `caps` (§9).

### 10.7 Menus

A program tells the host what it can do now by sending its menu: its
actions, with the keys that reach them where keys do.

```
ESC ] 7279 ; a=menu ; <base64 msgpack menu> ST
```

- **The menu** is one msgpack map (§3.3), `{"label": str, "items":
  [item]}`: what it is for, and its items in the order to show them. Each
  item is `{"key": str, "label": str, "name": str, "enabled": bool,
  "items": [item]}`:
  - `key`, the key that reaches it after its group's, as §10.4 names it,
    `mod` included; none for an item no key reaches;
  - `label`, what it does;
  - `name`, the command a pick sends (below); none for an item only its
    keys reach;
  - `enabled`, whether it does anything now; an item without it is
    disabled;
  - `items`, the items after it, which make it a group; an empty list
    makes none.

  It is myelin's `Tree`, with a name for each binding. The top level's
  items are one deep, and items nest at most eight deep: a deeper menu is
  `EINVAL`.
- **It lasts** until the program sends another, or `a=menu` with no body,
  which leaves it none. The main screen and the alternate screen each keep
  their own, as edit commands are kept (§10.6), and a full reset (RIS)
  drops both. A program that ends clears its menu, as it pops the keyboard
  flags it pushed.
- **A pick.** An item that has a name can be picked from a list of the
  menu. A multiplexer **MAY** offer a pane's menu in a palette of its own,
  or give it to whatever drives the pane, such as an agent. A terminal
  **MAY** list the items where it already lists commands of its own, such
  as its command palette, and adds no list for them. Each item is offered
  under its groups' labels, with the keys that reach it if keys do, its
  name where it has no label, and one that is not enabled as disabled,
  which is not sent. A pick sends `command` (§10.6) with the item's name
  and no `key`, as the terminal's own menus send theirs. An item that no
  key reaches is picked only so.

**Showing a level.** While a sequence waits for its next key, or whenever
it wants to say which keys do what, a program shows the level that the
keys typed reach:

```
ESC ] 7279 ; a=menu:show=1 ; <base64 msgpack {"seq": [str]}> ST
```

- **`seq`** is the keys typed so far, as §10.4 names them, `mod` included,
  read against the items' keys as the host reads a binding (§10.4). With
  none, or no body, the level is the menu's top one.
- **The host shows it** over its cells and surfaces, where it likes: the
  items of the group the keys reach that have a key, each its key, read as
  `mod` says (§10.4), and its label, a group's marked as one, and one that
  is not enabled as disabled. It **MAY** show only as many items as fit,
  and **MAY** wait a moment before it shows a level, so that a sequence
  typed quickly shows none. Keys that reach no group show nothing, and
  stay the keys shown: a menu sent later that has the group shows its
  level, and since nothing shown lists a key, any key hides it.
- **Until** the program shows another, or sends `a=menu:show=0`, which
  hides it. Another value of `show` is `EINVAL`. The host also hides it on Escape, on a key the level does not
  list, when the screen changes and on a full reset (RIS), so that a
  program that ends while it waits leaves none behind. The keys still go
  where they would without it. A menu sent while a level shows replaces it
  with the same keys' level of the new menu.

A host that takes menus has the feature `menu` (§4). One that does not
answers `a=menu` with `EINVAL`, as an action it does not take, and a
program shows its menu itself: in its cells, or as a surface at a higher
`z` (§5.2).

## 11. Text

A surface is drawn as pixels, but it is never only pixels: the host holds its
document, and therefore its text.

- **Selecting** in a surface selects the text of its document, as in a
  browser: from a press, by dragging; a word, with a double click; a
  block, with a triple click. A text field selects with its keys too
  (§10.2). Where the platform has a primary selection, as X11 and Wayland
  do, a host **SHOULD** put the text there once it is selected, as
  browsers do.
- **There is one selection,** the terminal's on its cells or a surface's.
  A press anywhere else, on the cells or on another surface, drops a
  surface's, and a press on a surface drops the terminal's.
- **Copying** is the `copy` action (§10.2), by the keymap, as cutting and
  pasting are. A focused text field's selection is copied by a key its
  keymap binds to `copy`, and a password field's never is.
- **A selection in a document,** outside a text field, is copied by a key
  that the keymap of the element whose text it starts in, in tree order,
  binds to `copy`, read as a text field's keymap is: the default keymap,
  then the `data-keys` of each element from the root down to it. The host
  offers it each key first, before the surface that has the keyboard and
  the terminal's own shortcuts, whether or not the surface has the
  keyboard. A key it does not bind to `copy`, `program` included, goes on
  as if nothing were selected: under `mod+c=program`, `ctrl+c` on Linux
  interrupts the program while text is selected.
- **The terminal's own copy** and paste, from its own keys or its menu,
  are the user's whatever the keymaps say. Its copy **SHOULD** copy a
  surface's selection, so that no document makes its text uncopyable, and
  while a text field has the keyboard its paste **SHOULD** type into the
  field as `paste` does, and not reach the program. Its own selection on
  its cells it copies as it would without hotty.
- **`user-select: none`** is the document's to set, as in a browser. A host
  **MUST NOT** start a selection on an element whose `user-select` is
  `none` (its used value, CSS UI 4: the elements inside one with `auto`
  included), or on one that opts in to drags (§9.1). It **SHOULD** leave
  their text out of a selection that crosses them.
- **Wherever the host extracts the screen as text** (scrollback search, "copy
  all", a serialised buffer), it **SHOULD** use for each row a surface covers
  the text of the elements laid out on that row. `data-hotty-text` on an
  element replaces its text there: a chart can say what it shows.
- **The footprint** is the program's choice (`c` and `r`, or `r=auto`), so
  the terminal's grid stays a grid whatever the host draws inside it.

## 12. Security

A surface's markup comes from the program's side of the pty, which may be a
remote machine, or a file someone `cat`s. Hosts treat it as untrusted.

A host **MUST** ensure that:

- **no script runs:** no `<script>`, event-handler attributes, `javascript:`
  URLs, or script inside SVG;
- **nothing is fetched** except `cid:` resources, `data:` URLs, and what the
  network policy allows (§7.2). This covers references from attributes and
  from CSS (`url()`, `@import`, `@font-face`, `image-set()`). Prefetch and
  preconnect hints and nested documents (`<iframe>`, `<object>`, `<embed>`)
  are never fetched;
- **nothing navigates:** links, forms and `<meta http-equiv=refresh>` have no
  effect beyond the events of §9, and a host that opens a link (§9) opens it
  outside the surface. `<base>` only sets the base URL (§7.3);
- **nothing opens a window, dialog or popup;**
- **a surface cannot take the keyboard** from the terminal on its own
  (`autofocus`): only the user or the program gives it the keyboard (§10);
- **a surface cannot draw outside its rectangle**, `position: fixed`
  included;
- **a surface cannot read** the screen, the clipboard, other surfaces, or
  anything about the terminal beyond what §8 gives it. Events carry only the
  program's own ids and what the user entered into the program's own
  surface.

**Defence in depth:**
- Two independent layers are recommended: a sanitizer that removes what can
  run, navigate or load before markup reaches a live document, and an engine
  or policy that cannot fetch beyond the network policy even if the
  sanitizer misses something.
- The second layer must hold on its own: a sanitizer working on attributes
  misses requests carried by CSS (`url()`, `@import`, `@font-face`).
- A host built on a web engine inherits the policies of the page that embeds
  it. Its documentation must say what that page has to allow.

## 13. Limits

- A host reports its limits under `limits` (§4) and refuses commands that
  would exceed them with `EQUOTA`. Hidden surfaces (§5.4) count: a program
  that keeps many deletes the ones it needs least.
- A host reports a message-size limit as `limits.message`: the most bytes it
  accepts for one reassembled message, decoded (§3.4). A host that reports
  none enforces none; one with a limit **SHOULD** accept at least 1 MiB. A
  message past the limit is malformed (§3.7), not `EQUOTA`. A resource is
  one message (§7.1), so this limit also caps how large a resource the host
  takes.
- Rendering must not stall the terminal. A host **MAY** abandon a render
  that exceeds its time budget and report `EBUDGET`: pathological CSS such
  as huge blurs, deep nesting, or enormous documents.

## 14. Degradation

A program **SHOULD** work without a host.
- When detection (§4) finds none it can use, it paints its interface as
  terminal text (until a late answer finds one, §4), and should not send
  hotty commands to a terminal that is not a host.
- The same program can use hotty where it is available and text where it is
  not, with no change to how it runs.

Between the two, a **polyfill** host can serve terminals that show images:
- It sits on the pty between the program and the terminal, and passes every
  byte through except hotty messages.
- It renders surfaces itself and sends them as pixels: kitty graphics
  placements anchored where the cursor was, updated as frames change. A
  window is a placement of part of the image.
- It answers `a=q` on the terminal's behalf.

Because it runs next to the terminal, a program on the far side of SSH needs
no change. Pixels lose what a native host keeps: sharpness under zoom, and
the text of §11.

## 15. Versioning and extensions

- `v` in the capabilities is the protocol version, a string `major.minor`.
  From 1.0 on, a change that breaks existing programs or hosts gets a new
  major version, and additions that old hosts and programs can ignore get a
  new minor version. **Until 1.0, any minor version may break the previous
  one**: versions 0.x are drafts.
- **A program and a host agree on a version** before either reads the
  other (§4): the program's query lists the versions it speaks, and the host
  answers in the highest it speaks too, or with `EVERSION`. With none in
  common, the program falls back (§14). A host or a program **MAY** speak
  several versions.
- **A field keeps its type,** from 0.2 on: a version that needs another type
  for it adds a field under a new name, as 0.3's `cellcss` took the place
  of 0.2's `cell`. A reader then never meets a field it knows with a type
  it does not.
- A host **MUST** ignore control keys it does not know. An unknown action or
  delta op is `EINVAL`. A program **MUST** ignore event kinds (§9),
  capability fields and features (§4) it does not know.
- `host` and `version` (§4) only name an implementation. A program **MAY**
  use them to avoid a known bug of some of its versions, and falls back as
  if the bug were there when either is absent. What a capability field
  says, a program learns from the field, never from the name.
- **Extensions** by an implementation use a vendor prefix:
  - control keys `x-<vendor>-<name>`;
  - attributes `data-<vendor>-<name>`;
  - custom properties `--<vendor>-<name>`.

  An extension **MUST NOT** be required for a document or program to work on
  another host.
- `--hotty-*`, `data-hotty-*` and unprefixed control keys belong to this
  specification.

## 16. Conformance

A host conforms to hotty version 0.4 when:
- it meets every **MUST** of this document;
- it passes `conformance/vectors.json` (`conformance/README.md`).

The vectors check replies, error codes, every delta op, morph, context
parsing, resources, drags and their steps, presses with Alt, `fit`, hover,
`area`, scrolling, `reveal` and `view`, touch in the cells, prefixes,
edit commands, menus, keymaps and the envelope, and their `keys`, `keymap`
and `edit` sections a host's key names (§10.4), keymaps and actions
(§10.2). They inspect documents and events, not pixels. The vectors marked
with a feature (`"passthrough"`, `"hover"`, `"count"`, `"menu"`, §4) apply
to a host that has it, those marked `"commands"` to a host with a terminal
that takes edit commands (§10.6), and those marked `"mouse"` to a host with
a terminal that reports the mouse (§9.6).

To be tested, a host exposes a way to *inspect* an element, reporting:
- its tag;
- its attributes with the program's values (§7);
- its text;
- its child elements as `[tag, id, text]`.

It also lets a test move a mouse's pointer to the centre of an element, or
of a cell of a surface, press and release its primary button there, with
modifier keys held, turn its wheel there, take the pointer out of the
terminal's window, and press keys where the keyboard is. A host that takes
touch also lets a test touch a cell of a surface, or of the screen, with
one finger, move it, and lift it, and tells it whether a touch went on to
the terminal as a scroll. One whose terminal reports the mouse also tells
the test what the terminal reported of the mouse's primary button for a
touch in the cells (§9.6). A host
with `passthrough` also tells the test whether each of those reached the
surface or passed through it, and whether a wheel or a key went on to the
terminal. A host with `menu` also tells a test the level of the program's
menu it shows, and lets it pick an item by its name.

This is a test interface, not part of the wire protocol.

A library that speaks hotty for programs, an SDK, conforms when it meets
[SDK.md](https://hotty.neuroplast.io/docs/sdk-design/) and passes the vectors' SDK sections: `build`, `encode`,
`decode`, `scan`, `detect`, `keys` and `keymap`, and `edit` where it edits
fields (SDK.md §4.6).

---

## Appendix A. Examples

Detection (payload shortened):

```
program → ESC ] 7279 ; a=q:n=1:v=0.4 ST  CSI c
host    → ESC ] 7279 ; a=ok:n=1:re=q ; haF2ozAuM6djZWxsY3Nz… ST  CSI ? 62 ; 22 c
```

A card placed below the prompt, asking for the rows it took, then a delta
and an event:

```
program → ESC ] 7279 ; a=doc:s=card ; PGJ1dHRvbiBpZD1nbz5HbzwvYnV0dG9uPg== ST
          (<button id=go>Go</button>; no reply, as nothing went wrong)
program → ESC ] 7279 ; a=place:s=card:c=40:r=auto:n=2 ST
host    → ESC ] 7279 ; a=ok:n=2:s=card:re=place:c=40:r=2 ST
program → ESC ] 7279 ; a=delta:s=card:q=2 ; gaNvcHORg6JvcKR0ZXh0oXSiZ2+hdqVTdGFydA== ST
          ({"ops": [{"op": "text", "t": "go", "v": "Start"}]}: the button
           now reads "Start")
host    → ESC ] 7279 ; a=ev:s=card:e=click:t=go ; gaRhcmVhhKFjAKFyAKF3BKFoAQ== ST
          ({"area": {"c": 0, "r": 0, "w": 4, "h": 1}}: the user clicked it)
```

A page that asks for images from its own site, on a host whose policy grants
that origin (`"net": {"img-src": ["https://example.com"]}` in its
capabilities):

```
program → ESC ] 7279 ; a=doc:s=post ; … ST
          (<base href="https://example.com/blog/">
           <meta name="hotty-network" content="img-src https://example.com">
           <img src="cover.png"> <a href="../about">About</a>)
          (the host fetches https://example.com/blog/cover.png)
host    → ESC ] 7279 ; a=ev:s=post:e=click ; g6RhcmVhhKFjDKFyA6F3BaFoAaRocmVmqC4uL2Fib3V0o3VybLlodHRwczovL2V4YW1wbGUuY29tL2Fib3V0 ST
          ({"area": …, "href": "../about", "url": "https://example.com/about"}:
           the user clicked the link, and the program decides where it goes)
```

Values that change every frame, cheaply, in one delta that the host
applies as one:

```
program → ESC ] 7279 ; a=delta:s=dash:q=2 ; gaNvcHOShKJvcKN2YXKhdKNjcHWha6FwoXaiNDKDom9wpHRleHShdKVjbG9ja6F2qDEyOjAwOjAx ST
          ({"ops": [{"op": "var", "t": "cpu", "k": "p", "v": "42"},
                    {"op": "text", "t": "clock", "v": "12:00:01"}]}:
           --p: 42 on cpu, and the clock reads 12:00:01)
```

## Appendix B. Rationale

- **Why an OSC.** Terminals parse OSC sequences and ignore the ones they do
  not know, which makes probing safe. An APC or DCS would serve as well on
  the wire, but more of the software between a program and a terminal drops
  or mangles them.
- **Why `:` between keys**, not `;`: some terminals limit the number of
  `;`-separated OSC fields.
- **Why base64 always.**
  - Many terminals drop C0 bytes inside an OSC, so a `<pre>` would lose its
    newlines.
  - Raw bytes are at the mercy of every parser on the way.
  - zlib on markup more than pays for base64's third.
- **Why 4096-byte chunks:** some terminals ignore longer strings, and some
  multiplexers discard a string that takes too long to arrive.
- **Why values are ASCII.** A byte of UTF-8 may be 0x9C, which a parser that
  reads 8-bit controls takes for ST, and ends the sequence there. Neovim's
  input parser is one. An id cut to `_` is a smaller loss than an event cut
  in two.
- **Why msgpack, inside the base64.** A host's bodies were JSON until 0.2.
  - JSON has one kind of number, so a count of cells written `2.0` had to
    be read as `2`, and every SDK carried that rule. msgpack keeps ints
    and floats apart, and has a type for time.
  - Where a language has a codec generated from types (Go's msgp, Rust's
    serde), it reads a body, and no one writes a reader by hand. Where it
    has none, a decoder is under 200 lines.
  - Its maps are keyed by name, as JSON's are, so a version adds a field
    that an older reader ignores (§15).
  - It stays inside the base64, since its bytes include ST and BEL.
  - A document stays text, as it is HTML. A delta is msgpack since 0.3,
    since its ops are fields, and a menu is.
- **Why hosts do not compress.** Replies and events are small, and zlib
  would be a second dependency a program needs only to read them.
  Programs keep it for what they send, where documents are large.
- **Why ids and deltas.**
  - Addressing by id keeps a delta's effect predictable, and makes an update
    cost what it changes.
  - `morph` without a target also serves programs that redraw their whole
    tree (immediate mode) while keeping what the user was doing.
  - **One delta, many ops.** Until 0.3 each op was a message of its own,
    with an envelope and a control: 49 bytes to set `--p: 42`, and 755 for
    a frame of fifteen small changes, which zlib could not shrink, since it
    sees one message at a time. As one list they are 184. A frame's changes
    to a surface are one unit, so the host never shows half of them, with
    no synchronized output around them. And a target is a string in a
    body, so an id outside ASCII is addressed as it is.
  - **Ops are maps keyed by name,** as every body is: a later version adds
    a key an older host ignores. Positional arrays would save a dozen bytes
    an op, which zlib mostly saves anyway.
- **Why no script:** it keeps the sandbox small enough to defend, and the
  program in charge.
- **Why `fit` tells, and the host does not resize.** A document's height can
  change after its placement is answered: an image arrives, a font loads, a
  stylesheet is replaced. A host that grew the placement on its own would
  cover the cells below it, which are the program's (§11). `fit` tells the
  program the rows the content needs now; the program decides where they
  come from. It is opt-in so a program that never sizes by content hears
  nothing new.
- **Why a zoom keeps the layout.** A zoom is the user's, for the moment:
  the terminal's text grows, and a surface over it should grow with it,
  as a kitty image placed over cells does, but drawn again so it stays
  sharp. Growing the CSS cell instead would grow only what is sized in
  cells or rem, and leave images, borders and gaps at their old size in
  a bigger box. It would also lay the document out again, send `fit` and
  `caps`, and make a cell size the program read stale. Viewers of one
  pane in a multiplexer, each at its own zoom, would each lay it out
  differently. A browser zooms a page in a frame the same way: the frame
  keeps its size in CSS pixels, and its document its layout. A new font
  is different: the cell changes in CSS pixels, and the document is laid
  out for it.
  - **The cell in CSS pixels.** Until 0.3 the capabilities gave the cell in
    device pixels, beside `scale`, so a new font sent `resize` to every
    surface and the program asked again for the cell, while a zoom changed
    `scale` and told nothing, leaving a program that drew device pixels
    from it wrong. The cell in CSS pixels changes only with the font, and
    `caps` says so once. What needs a device pixel, a hairline or a box
    snapped to the screen's pixels, is the document's, and `--hotty-px`
    gives it there, right after every zoom.
- **Why the host grants and the document asks.** Markup can come from
  anywhere, a file someone `cat`s included, and every fetch tells a server
  who looked and where. So only the host (its user, or the page that embeds
  it) can allow the network. A document names what it needs, can only get
  less than the host allows, and fetches nothing unless it asks.
- **Why a link click is an event.** The program is the application: a click
  may mean another page in the program, not a URL to load. Hosts still give
  links the gestures users expect of links, the way terminals open OSC 8
  hyperlinks.
- **Why synchronized output for transactions:** it is the terminal's
  existing mechanism for showing a batch of output at once, and programs use
  it for flicker-free text.
- **Why windows:** full-screen programs scroll content that holds surfaces:
  a transcript, a document. Without a window, a surface at the edge of the
  scrolling region can only disappear or cover the program's frame. With
  one, the program shows the part in view, as a kitty placement shows part
  of an image, and the document is not laid out again as it scrolls.
- **Why detach.** Events arrive on whatever reads the terminal's input, and
  a host cannot tell when the program that drew a surface has gone: after it
  exits, a shell would read them as typed text. Only the program knows when
  it is done with a surface, and printing a document is the common case,
  hence `d=1`. A program that crashes cannot detach. What ran it can, if it
  knows the surfaces' names, and a multiplexer deletes those of the
  program a pane runs when it ends (§5.4); otherwise they keep reporting
  until they are deleted or leave the scrollback, as a crashed program can
  leave mouse reporting on.
- **Why a late answer.** A multiplexer can run a pane with no terminal
  attached: restored after a reboot, or started from a script. Detection
  then finds no host, and a program that settled on text at its start
  would stay text after a terminal that is a host attaches. The late
  answer is asked for, so a program that expects no reply after its fence
  never gets one, and it comes once, so it is not a stream of changes.
  Terminals report what changes later in the same way, such as the colour
  scheme (mode 2031) and the window's size (mode 2048): the program asks,
  and hears when there is something to say.
- **Why the query names versions.** Until 1.0 a version may change how
  bodies are read, as 0.2 did (§3.3), and a program that took a reply in a
  version it does not speak for a host would read nothing right. The
  capabilities alone cannot tell it, since a program may not be able to
  read them. The host, which reads the query whatever its version, decides,
  and an error is the one answer every version takes as no host.
- **Why `press`.** A program that shows several surfaces moves its own
  selection to the one the user goes to: a dashboard selects the card
  pressed, and a multiplexer that draws its panes and tools as surfaces
  focuses the one pressed, as it does for a press on cells through mouse
  reporting. A surface takes every press, and `focus` and `click` cover only
  what takes focus or has a handler, so a press on text or empty space
  told the program nothing. `data-on~=click` on a document's root would
  cover the program's own documents, but only on the release, and not a
  multiplexer relaying another program's documents, which it would have to
  rewrite.
  - **Asked for, not always sent**, because events reach whatever reads the
    input: a document printed by a program that exits without detaching
    (§5.5) would otherwise type an event into the shell on every click on
    its text. The programs that ask are those that read events.
  - **On the surface, until the program says otherwise,** so a program
    that places its surfaces every frame, as a multiplexer does, sends
    each once, and never loses them the one time it forgets. Until 0.3
    they belonged to the placement, and every placement had to repeat
    them. A multiplexer asks for every surface it relays, and passes a
    `press` on only to a program that asked for it.
  - **First**, as a browser's `pointerdown` comes before the focus moves: a
    program hears where the user went before it hears what that did.
- **Why drags.** A spreadsheet selects a range by dragging across it, and
  fills by dragging its handle. Clicks cannot stand in for either. The
  terminal's mouse reporting can, but it does not reach over a surface,
  where the pointer is the surface's.
  - **Opted in, element by element,** because elsewhere a mouse's drag
    selects text, which the reader of a document expects of it.
  - **Elements, not pixels.** An event for each element crossed is a few
    for each cell of a grid, which a round trip over SSH can carry; one for
    every move would not be.
  - **The cell in the detail** says what elements cannot: where the
    pointer is past the surface's edge, which a program that scrolls during
    a drag needs. Cells are the one unit that the program and every host
    share (§5.3). They are counted from the surface, not the screen, so a
    multiplexer relaying a surface passes its events on unchanged.
  - **The pointer is held,** as a browser's `setPointerCapture` holds it,
    so a drag that leaves the window still ends, and the program hears
    where.
  - **Steps say where in an element.** A slider, a splitter or a scrubber
    needs the pointer's place along one element, finer than a cell, and
    still after the pointer has left it, as a browser's range input keeps
    following. A cell cannot say it: the program does not know where the
    element is, since the layout is the host's. Pixels would cost an event
    for every move, so the program names the steps it can use instead. It
    hears one event for each, and the step is already the slider's value.
    They are measured against the element the drag started on, as
    `offsetX` is against the element that captured the pointer, and
    clamped, as a range input's value is.
  - **Touch only where an element opts out of panning,** because a touch
    drag scrolls (below): a surface that took every touch drag would trap
    the finger. `touch-action` is the web's switch for it, which maps,
    sliders and canvases already use, so the trap is per element and only
    over its area, a swipe the element still allows scrolls, and a second
    finger always gives the gesture back to the host. A tap is still a
    click.
  - **No text selection,** because one gesture cannot select both text and
    cells.
  - **In a browser's order:** the press is reported before the focus moves,
    as `pointerdown` comes before it, and the release before the click, as
    `pointerup` does. A browser clicks only where a press and its release
    meet, so a drag that ends elsewhere is no click.
  - **Not HTML's drag and drop** (`draggable`, `DataTransfer`). It moves
    data between pages and applications, through script, which a surface
    does not have. The program already holds the data.
- **Why hover is reported.** A multiplexer draws its own controls in cells
  next to the surfaces it relays, and lights them while the pointer is over
  them, from mouse reporting. When the pointer goes from such a control
  straight onto a surface, mouse reporting stops, and the control stays lit.
  A program that shows hints of its own for what is under the pointer, in
  a box that may reach past the surface, needs to know what that is.
  `:hover` alone can do neither.
  - **Asked for**, like `press` and for the same reason: events reach
    whatever reads the input. A multiplexer asks for the surfaces it
    relays, and passes `hover` on only to a program whose own placement
    asked.
  - **Elements, not moves,** as for drags: an event for each element
    crossed, which a round trip over SSH can carry. The id is the
    program's handle; the program wrote the document, and knows what its
    elements mean without being told their attributes.
  - **Out is said,** not left to the next event: going onto the cells, a
    program with mouse reporting hears motion there, but going onto
    another program's surface, or out of the terminal, it hears nothing.
  - **Not while a button is held,** because the gesture has the pointer
    (§9.1), and a drag already reports what it crosses.
  - **`v`,** because `h` is the window's rows.
- **Why a press with Alt passes a surface.** A surface takes every press
  in its window (§5.3), so a program could not start a gesture of its own
  over one. A multiplexer that draws its panes and tools as surfaces moves
  them with Alt and a drag, as a window manager moves windows, and a pane
  or a tool is mostly surface. Terminals already pass a layer this way:
  Shift takes a press past a program's mouse reporting, to the terminal's
  selection. Alt takes it past a surface, to the program.
  - **Alt,** because the other keys are taken. Ctrl, or Cmd on macOS, is
    the hyperlink gesture (§9). Shift is the terminal's way past mouse
    reporting, and Super is often the window manager's. A browser does
    little with an Alt-click (some save a link), and a surface has no
    downloads.
  - **As on the cells,** so the program hears it as it hears every other
    press: a report at a cell, with Alt set, then the moves and the
    release. A program that reads no mouse loses nothing a surface would
    have told it.
  - **No `press`,** whatever `p` asked for: the press is not the surface's,
    and the program hears it already, through its mouse reporting. Hearing
    it twice, it would act on it twice, such as focusing a pane and then
    moving it.
  - **The press decides,** because a gesture has one owner from its press
    to its release, as a drag holds the pointer (§9.1). A gesture that
    changed hands when a key did would leave each side half of it.
  - **Option on macOS,** because it is the key a Mac user holds for Alt.
    Ghostty reports it as Alt to mouse reporting whatever
    `macos-option-as-alt` says. That setting chooses what Option types,
    and a press types nothing.
- **Why hide:** sending a document and laying it out costs far more than
  placing it. A program scrolling through many surfaces keeps the ones that
  will come back, and only it knows which those are.
- **Why nothing scrolls unless it asks:** a surface is a rectangle of the
  program's choosing, as an image is.
  - A scroll inside a surface competes with the terminal's for the same
    wheel or drag, and on a touch screen a surface that takes drags traps
    the finger. So the terminal owns scrolling by default, and a surface's
    content is whatever the program lays out.
  - Some content cannot be laid out to fit: a select's list longer than
    the screen, a log beside a form. Paging it by placing windows (§5.2)
    on every wheel step costs a round trip per step. A document that asks
    scrolls locally, as a page's element does, and hands a gesture back
    to the terminal at its end, as a browser chains it to the page.
  - **Per axis,** because a list scrolls down and a wide table across, and
    an axis a document did not ask for keeps the default.
  - **On the document,** because whether content scrolls is the content's
    nature, as `overflow` is, while the placement only chooses where it
    shows.
  - **Scrollbars take pixels, not cells,** so a scrollbar's width, which
    differs between hosts, changes no footprint (§5.3).
- **Why controls take the palette:** a field drawn in an engine's own
  colours looks like a web page pasted into the terminal, and differs
  between hosts (Chromium's and Blitz's greys are not the same). The 16
  colours are the one palette every terminal theme designs for contrast,
  so the host stylesheet draws controls with them and nothing else.
- **Why `area`:** a program that shows something next to an element (a
  select's list in a surface of its own, a menu) needs to know where the
  element is, and only the host lays the document out. Terminals call a
  rectangle of cells an area (DEC's rectangular-area operations).
- **Why a field's keys are the program's.** A program that draws a field
  in cells reads its keys from the terminal and edits the field as its own
  keymap says. On a surface the same field is drawn by the host, and should
  edit the same: the same keys, the same moves. The host cannot know which
  keys the program binds, so the program says, in `data-keys`, and what it
  does not give the field stays its own.
  - **Offered as pressed first, then as the program reads them.** A
    focused field on a surface is a GUI control, and gets the keys a GUI
    field gets, before the terminal's shortcuts, as a page gets them
    before a browser's. The terminal's translations exist for programs in
    cells: a macOS terminal sends `ctrl+a` for Command with the left
    arrow, which a field would read as select all. What the field does not use goes on as
    before, so the terminal's shortcuts keep their keys elsewhere, and the
    user's remapping still reaches the program and the field as bytes. In
    cells a program reads only bytes, and Command with the left arrow
    stays `ctrl+a` there.
  - **Inherited,** because a program has one keymap and many fields: it is
    set once, and overridden where a field differs.
  - **A default with a GUI field's keys,** Shift selecting included:
    people's hands know them from every text field, whatever draws it. The
    default takes few of the program's keys: those of a terminal's line
    editor are mostly Control and Alt with a letter, and the one it does
    take, `ctrl+a`, readline's line start, is also Home. A program that
    needs one binds it to `program`.
  - **Readline's actions,** with the words and lines of Bubble Tea's text
    input and text area. Terminal programs share them, more or less, so a
    program's cells and its surfaces can share one implementation
    (SDK.md).
  - **Selection by Shift and `select-all`, from the anchor,** as GUI fields
    select. A program that draws a field in cells shows a selection as it
    shows a selected row, and an SDK's field keeps one (SDK.md §4.6).
  - **Copying, cutting, pasting and undo are the keymap's,** with a
    browser's keys: `ctrl+c`, `ctrl+x`, `ctrl+v` and `ctrl+z` edit in
    every GUI, and where nothing is selected, or outside a text field,
    they keep their terminal meanings (interrupting, nano's exit, quoting
    the next key, suspending). A program that needs one binds it to
    `program`, as a REPL that always interrupts with `ctrl+c`. The
    terminal's own copy and paste stay the user's, so no document makes
    its text uncopyable.
  - **One key per platform, `mod`,** because binding both `ctrl+c` and
    `super+c` takes a key from the program on each platform for nothing.
    The rows around it follow the platform too: `ctrl+a` is a line's
    start on macOS and `super+a` selects all.
- **Why edit commands in the cells.** A program that selects and edits in
  its cells needs the same keys as a field, and those keys mean something
  in a terminal. Neither side can decide alone: the terminal does not know
  whether the program has something selected, and the program cannot read
  the clipboard in order with its keys. So the terminal sends the command
  with its key, the paste with its text, and the program decides, as a
  field decides on a surface. It takes them by name, per screen, as the
  kitty keyboard protocol's flags are taken, so a shell and a full-screen
  editor run in one terminal each get what they asked for.
- **Why a finger in the cells becomes the mouse.** A program that drags
  with the mouse in its cells already reads presses, moves and releases. A
  finger can give it the same, but not everywhere: a terminal that took
  every touch drag as the mouse's would trap the finger, as a surface would
  (§9.1). Only the program knows where its sliders are, so it names those
  cells and the way each drags, as `touch-action` does for an element. The
  finger then reaches the program through the mouse reporting it already
  reads, so there is no new event to parse. Areas are cells of the screen
  and are kept per screen, as edit commands are, because the cells are
  where the program draws.
- **Why a prefix, and the program resolves sequences.** Leader keys and
  which-key menus are how keyboard programs grow past the keys they have
  (Spacemacs, Neovim, Zed, Helix). On a surface, after a leader, the next
  key could reach a focused text field before the program could take the
  keyboard back. 0.2 closed that race by resolving sequences in the host,
  from `data-key` attributes: a sequence engine in every host, a second
  keymap in the document, and the user's leader in the terminal's
  settings. A prefix closes it with one rule, and the program resolves
  its sequences with the keymap it uses in its cells, labels its menu
  from what it wrote, and keeps its own time.
  - **The menu shows a round trip later,** which which-key's own pause
    hides; the action it leads to needed the program anyway.
  - **Ended by the program,** with `a=focus`, which it already sends, so
    no new command and no state the program cannot see.
- **Why the host draws menus.** A program in its cells would have to draw
  over its own output, and one on a surface a surface of its own. One menu
  drawn by the host serves both, looks the same in every program, and
  tells the terminal which keys do what, which a multiplexer can show
  elsewhere, such as in its palette.
- **Why a menu is what a program can do, not only its keys.** A
  multiplexer's palette lists a program's actions and lets the user pick
  one without knowing its keys, and the multiplexer can give the same list
  to an agent that drives the program. So the menu holds every action the
  program offers now, keyed or not, and a level of it is shown by the keys
  typed: the tree is sent when it changes, and a key of a sequence costs a
  few bytes, where 0.2 sent a whole level for each.
  - **A pick is a command,** as the terminal's own menus send, so a
    program reads one event for a key it took, an Edit menu and a palette.
  - **Kept per screen,** as edit commands are, so a full-screen program's
    actions go with it, and the shell's come back.
  - **No palette of the host's own.** A terminal that has a command
    palette may list the items beside its own commands. One that has none
    adds none for a program's menu: the menu joins what the host already
    has, and the lists that pick from it are a multiplexer's and an
    agent's.
- **Why myelin's key names.** A program shares one keymap between its
  cells, its surfaces and its other tools, and the terminal it most often
  runs in, Ghostty, already has a spelling for keys: modifiers in lower
  case, physical keys by where they are, characters by what they type.
  hotty wrote the W3C's key values until 0.4.
  - **Cited, not copied,** so that hotty, plx and myelin cannot drift: a
    change to the names is a new version of that document, with its
    vectors, and of this one.
  - **Physical keys and characters both,** because some bindings follow
    the layout (`ctrl+w` deletes a word wherever W is) and others the
    place (`ctrl+key_z` is under the same finger on every layout). A press
    with both looks the place up first, as Ghostty does.
  - **Shift written with a letter,** so a binding says whether Shift is
    held, whatever Caps Lock does.
  - **From bytes, one name.** A terminal's encodings carry what a key
    types. Only the kitty protocol says where the key is, for some keys
    and only when asked, so a binding that must hold in cells names the
    character.
- **Why a host may show no select list.** A select's list opens past its
  control, and a surface is a rectangle of cells: drawn inside, a list
  would force small surfaces to grow, and drawn outside, it is something
  the host places over what the program placed.
  - **Programs draw their own lists anyway,** as web apps wrap or replace
    `<select>` with script. In hotty the program plays the script's part: a
    button, and a list in a surface of its own, placed by `area`.
  - **A closed select still works,** with keys as browsers' do, so a
    document that has one loses nothing but the list.
  - **A pick sends `change` at once,** as a browser's select does, rather
    than when focus leaves it: there is nothing more to commit.
- **Why any element can give keys to the program.** A program that works a
  list or a table with the arrows needs them on a focused button too, and
  in a scrolling surface the host would take them to scroll (§5.3). A page
  in a browser stops that with script (`preventDefault`); a surface has
  none, so the document says beforehand, in the `data-keys` it already
  reads for fields.
- **Why `focus` names the element.** Those keys reach the program as
  terminal input, which names no element, and Tab moves focus inside the
  host. A program with a field and a list could not tell which one the
  arrows came from, after a Tab, and acted on the one it last knew. A
  browser's `focusin` names its target in the same way.
  - **The keys stay input,** so the program reads them in the encoding
    it enabled and with its own keymap, as every other key. An event per
    key would carry a second encoding of them.
  - **Every move, not only the first,** because the surface keeps the
    keyboard while focus moves inside it. A program that only wanted to
    know the surface has the keyboard reads the first `focus` after a
    `blur`, as before.
- **Why scrolling keys.** Pagers and viewers in the terminal (less, vim,
  Bubble Tea's viewport, glow) share `j` and `k`, Space and `b`, `g` and
  `G`. A program that scrolls its cells with them should scroll its
  surfaces with the same keys, but only the host scrolls a surface
  (§5.3), and it cannot know which keys the program scrolls with, so the
  program says, as it does for a field's keys.
  - **The element's own keys come first,** so bindings on an element that
    scrolls take neither Space from the buttons in it nor a letter from
    its selects.
  - **A field leaves them out,** or `j` could not be typed in a field
    inside an element that scrolls with it.
  - **Never the terminal:** a key that reached an end and went on to
    scroll the screen would move what the program drew around the
    surface.
- **Why `reveal` and `view`.** Scrolling is local (§5.3), so a program
  could neither follow the reader nor take them somewhere: a table of
  contents beside a document marks the section in view and jumps to one
  on a click, and a long list fetches what comes into view.
  - **Elements opt in,** as they do for clicks: most documents scroll
    with nothing to tell, and a report on every scroll would fill the
    program's input as unasked replies would (§3.5). So the host also
    waits for a scroll to rest.
  - **Every element in view, with its whole area,** so the program keeps
    no state of its own to follow changes, and places what it draws by an
    element as it does by a clicked one (`area`).
  - **`reveal` moves no focus.** Focus already scrolls (§10.1), and taking
    it would move the keyboard from where the user is. Its payload names
    the alignments a reader needs, those of `scrollIntoView`; across,
    where nothing is read in order, as little as it can.

- **Why features.** Each addition within 0.2 got a flag of its own
  (`passthrough`, `scroll`, `steps`, `drop`, `count`, `menu`), beside
  lists of ops and events whose absence meant opposite things: no `ops`
  was every op, no `events` was no drag, hover or view. 0.3 makes what
  both hosts do the baseline, and one list names what a host may lack.
- **Why errors only, unless asked.** A program that sends deltas every
  frame and forgets `q` filled its input with replies. Errors still come,
  since a refused document leaves a hole in the screen; a numbered command
  asks for its reply by its number. Continuation chunks carried `q` until
  0.3, which did nothing: replies are per message.
- **Why `mods`.** Drags spelt their modifiers as names, `"ctrl"` and
  `"shift"`, and clicks and presses carried none, so Shift with a click
  could not extend a selection. One number, with §10.4's bits, is both
  shorter and the same everywhere.
- **Why a submit's fields are pairs.** A form may give two controls one
  name, as two checkboxes for one setting do, and a map holds a name once.
  Pairs in tree order are what a browser's `FormData` holds.

## Appendix C. Prior art

hotty takes from:

- **the kitty graphics protocol**: the shape of the envelope (key=value
  controls, chunks, `q`, `C=1`), and placements anchored to cells;
- **Hotwire and Turbo**: HTML over the wire, which the name follows, and
  appending by id;
- **Datastar and idiomorph**: morphing, and matching top-level elements by id;
- **RFC 2392**: the `cid:` scheme.

DomTerm, which renders HTML fragments in a terminal's output, is the closest
related work.
