# hotty SDKs

    Version:  0.4, with SPEC.md 0.4
    Status:   Draft. It changes with SPEC.md (§15 there).
    Licence:  CC BY 4.0

## Abstract

An **SDK** is a library through which a program speaks hotty in one
language. This document says what every SDK provides, whatever its
language: its components, what each one does, the options each takes and
their defaults, and the names they go by. A program written against one SDK
reads the same against another, once the language's idioms are applied.

An SDK has two layers. The **wire layer** encodes and decodes, and does no
I/O; the conformance vectors check it. The **SDK layer** does the I/O for a
kind of program: one that prints and exits, one built on a UI framework, a
test. It is described here so that SDKs agree where a program would notice,
and follow their language everywhere else.

The reference implementation is
[hotty-go](https://hotty.neuroplast.io/docs/hotty-go/). Its names are the
canonical ones, and where it falls short of this document, §5.3 says so.

## Contents

1. Introduction
2. Principles
3. The wire layer
4. The SDK layer
5. Conformance

Appendices: A. Names · B. Rationale

---

## 1. Introduction

[SPEC.md](https://hotty.neuroplast.io/docs/protocol/) defines the wire: what a program and a host send each
other. This document defines how a library presents the wire to a program.
Where the two disagree, SPEC.md is right and this document is wrong.

The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT** and **MAY**
are to be interpreted as in SPEC.md §2.

- **Program**, **host**, **surface**, **placement**, **reply**, **event**:
  as in SPEC.md.
- **SDK**: a library that speaks hotty for programs in one language.
- **Wire layer**: the part of an SDK that builds commands and reads what the
  host sends. It does no I/O (§2.1).
- **SDK layer**: the part that reads and writes a terminal, or works through
  whatever stands for one (§4).
- **Canonical name**: the name this document gives a component, an
  operation, an option or a field. It is hotty-go's, and an SDK spells it in
  its language's case (Appendix A).
- **Environment**: what the program runs in: a process with a terminal, a
  UI framework that owns the screen, an editor, a page.

## 2. Principles

### 2.1 Two layers

- **The wire layer is pure.** It **MUST NOT** do I/O, start threads, or read
  a clock: where it needs the time (the Detector, §3.8), the caller passes
  it in. Given the same input, it returns the same output.
- **It depends on the language's standard library only**, and on base64,
  zlib and msgpack where that library lacks them. msgpack writes a
  program's deltas and menus and reads what a host sends (SPEC.md §3.3).
  zlib is needed only to compress what a program sends, which is optional
  (§3.3); a host never compresses what it sends (SPEC.md §3.3).
- **The SDK layer is built on the wire layer's public interface**, so a
  program that needs something the SDK layer does not do can do it from the
  same parts.

### 2.2 Canonical concepts, idiomatic surface

- Every component, operation, option and field in §3 has a canonical name.
  An SDK **MUST** provide each under a name a reader maps to it at a glance:
  the canonical name in the language's case (`SetText`, `set_text`,
  `setText`). Where the name is a keyword of the language, the SDK picks the
  nearest word (`del` is `delete` in Python) and says so in its
  documentation.
- **Options take the language's form**: functional options in Go, keyword
  arguments in Python, a table in Lua, a struct or a builder in Rust, an
  object in TypeScript. What an option is called, and its default, are
  canonical.
- **Errors take the language's form**: an error value, an exception, a
  result type. What they carry (a code and a detail, §3.9) is canonical.
- **Absence takes the language's form**: Go's zero value and `ok`, Python's
  `None`, Lua's `nil`, Rust's `Option`. The vectors write it `null`.
- **Data takes the language's form**: what a message or a control holds
  (§3.2, §3.9) is a field or an accessor, and a question about it
  (`Has`) a method or a function. hotty-go's are plain structs with
  no methods but what its codec generates: a field (`ev.Value`), and a
  function beside the type (`hotty.Light(caps)`).

### 2.3 Shared defaults

The defaults in this document (quiet levels, timeouts, limits, the keymap
of §3.10) are the same in every SDK, so a program behaves the same in every language. An SDK
**MUST NOT** choose other defaults; it **MAY** let the program change them.

### 2.4 Protocol only

An SDK's contract is SPEC.md. Everything a program does with the protocol
that the protocol does not define, such as rendering Markdown, highlighting
code, drawing charts, or building forms from a schema, is presentation, and
outside this document. An SDK **MAY** ship such helpers beside it. They
depend on the SDK; the SDK never depends on them, and nothing in §3 or §4
needs them.

What a text field does with a key is the protocol's (SPEC.md §10.2), so
keymaps (§3.10) and editing a field in cells as a host edits it (§4.6) are
in this document.

### 2.5 Three renditions

Every program has three renditions (SPEC.md §14): surfaces on a host, cells
on a terminal that is not one, and plain data when there is no terminal. An
SDK makes the three branch points plain:
- **No terminal:** opening the terminal fails (§4.1). The program writes
  plain data.
- **Not a host:** detection says no (§3.8). The program draws in cells, and
  sends nothing of hotty but the query.
- **A host:** surfaces.

### 2.6 Leave the terminal clean

Replies and events arrive on the program's input, and whatever reads the
input after the program exits, a shell for instance, reads them as typing.
An SDK makes it easy, and its documentation says how, for a program to:
- read every reply its commands caused before it exits (a fence, §4.1);
- leave no surface that still reports events: create the ones it leaves on
  the screen detached, or detach them (SPEC.md §5.5).

### 2.7 Forward compatibility

As SPEC.md §15 has programs do:
- control keys the SDK does not know are kept in the message, and change
  nothing;
- an event of a kind it does not know is an event all the same: its kind is
  reported as it came;
- fields it does not know, in the capabilities or a detail, are ignored.
  Reading a message **MUST NOT** fail because of its body: a body that
  does not decode leaves absent what it would have filled (§3.9).

### 2.8 Environments

Some environments already do part of what the SDK layer does. A UI
framework's input parser may hand over complete OSC sequences; an editor may
own the terminal's output and hand its input over as events (§4.2). There,
the SDK layer uses what the environment provides: it feeds complete
sequences to the Decoder (§3.6) and writes through the environment's
output. The wire layer is the same everywhere.

### 2.9 Updates cost what they change

A program that redraws a surface's tree, itself or through a framework,
diffs the new tree against the last one it sent and sends what changed as
the ops of one delta (SPEC.md §6.1): a `text` op where a cell's text
changed, 56 bytes, where morphing the whole tree of a small table sends 930.
A whole-tree `morph` without a target stays the fallback, and keeps what the
user was doing. A stylesheet that several documents share goes once, as a
resource (SPEC.md §7.1), not in each document. An SDK's helpers that build
documents (§2.4) do both.

## 3. The wire layer

An SDK **MUST** provide every component of this section.

| component | does | Go | vectors |
| --- | --- | --- | --- |
| constants (§3.1) | the protocol's numbers and named values | `Number`, `Chunk`, … | — |
| Control (§3.2) | an ordered list of keys and values | `Control` | — |
| Encode (§3.3) | one command as OSC sequences | `Encode` | `encode` |
| commands (§3.4) | a builder for each action | `Doc`, `Place`, … | `build` |
| names (§3.5) | surface names | `ValidName`, `SurfaceName` | — |
| Decoder (§3.6) | OSC sequences into messages | `Decoder` | `wire`, `decode` |
| Scanner (§3.7) | hotty sequences out of a byte stream | — | `scan` |
| Detector (§3.8) | whether the terminal is a host | `internal/detect` | `detect` |
| messages (§3.9) | replies, events, capabilities, errors | `Reply`, `Event`, `Caps`, `Error` | `decode` |
| keys (§3.10) | key names, keymaps, the SDK's keymap | `ParseKey`, `DecodeKeys`, `KeyText`, `Keymap`, `TerminalKeys` | `keys`, `keymap` |

### 3.1 Constants and named sets

| name | value |
| --- | --- |
| `Number` | `"7279"`, the OSC number |
| `Chunk` | 4096, the most base64 bytes in one sequence |
| `MaxSize` | 1000, the most columns or rows of a surface |
| `MaxName` | 64, the longest surface name |
| `Version` | `"0.4"`, the protocol version the SDK speaks, which its query lists (SPEC.md §4) |
| `DefaultMax` | 16777216 (16 MiB), the most bytes a Decoder decodes one message to unless its `Max` says otherwise (§3.6) |

And these sets, each value under its canonical name:
- **event kinds**: `click`, `change`, `input`, `submit`, `press`, `focus`,
  `blur`, `fit`, `hover`, `view`, `dragstart`, `drag`, `dragend`,
  `command`, `caps` (SPEC.md §9);
- **features**: `passthrough`, `hover`, `count`, `menu` (SPEC.md §4);
- **modifier bits**: `ModShift` 1, `ModAlt` 2, `ModCtrl` 4, `ModSuper` 8
  (SPEC.md §9);
- **error codes**: `EINVAL`, `ENOENT`, `ENOTARGET`, `EDETACHED`, `EQUOTA`,
  `EBUDGET`, `EVERSION` (SPEC.md §3.6);
- **delta ops**: `morph`, `inner`, `replace`, `append`, `prepend`, `before`,
  `after`, `remove`, `attr`, `unattr`, `text`, `var`, `reveal` (SPEC.md
  §6.1);
- **quiet levels**: `ReplyAlways` 0, `ReplyOnError` 1, `NoReply` 2
  (SPEC.md §3.5).

### 3.2 Control

A command's control is an **ordered** list of keys and values: keys go out
in the order they were added. It reads a key's value and whether it is
present (`get`), and sets one, in place if present, at the end if not
(`with`).

### 3.3 Encoding

`Encode(control, payload)` returns one command as one or more OSC
sequences. Its output is fixed except where the protocol leaves a choice to
the program (compression), so the `encode` vectors compare bytes.

- **Values are cleaned.** Each character a value may not hold becomes one
  `_`: `:`, `;`, `=`, a control character, and any character outside ASCII,
  one `_` for each Unicode code point, and one for each byte that is not
  UTF-8 (SPEC.md §3.2). Keys are the SDK's own and are not cleaned.
- **Keys go out in order**, as `key=value` joined by `:`.
- **The payload is base64**, the standard alphabet, padded. An empty payload
  is sent as nothing, with no `;` before it.
- **Compression is the program's choice.** An SDK **MAY** compress a payload
  with zlib and mark it `o=z`, and then only a payload of 256 bytes or more,
  and only when compressing makes it smaller. It compresses at zlib's
  default level, 6: a stylesheet of 20 KB comes out a third smaller than at
  level 1, for about a millisecond. `o=z` follows the other keys.
- **`o` and `m` are Encode's.** A control given with either goes out
  without it, and Encode sets them as this section says.
- **Chunks.** A payload whose base64 is longer than 4096 bytes is split
  into chunks of exactly 4096, the last one excepted. Nothing of 4096 or
  less is chunked.
  - The first chunk carries the whole control, then `m=1`.
  - Every further chunk carries only `m`: `1`, or `0` on the last.
- **Every sequence ends with ST** (`ESC \`).

### 3.4 Commands

A builder for each action returns the command's sequences, ready to write.
Each takes the reply options of §3.4.2 and has the default quiet level shown,
which SPEC.md leaves to the program; these are the levels every SDK uses.

| builder | sends | parameters | default `q` |
| --- | --- | --- | --- |
| `Query` | `a=q:n=<n>:v=<Version>`, `late=1` with the `Late` option, then DA1 (`ESC [ c`) | n | 0 |
| `Doc` | `a=doc:s` and the HTML; `d=1` with the `Detached` option, `scroll` with `Scroll` | surface, html | 1 |
| `Place` | `a=place` (§3.4.1) | surface, placement | 1 |
| `PlaceAt` | `ESC 7`, `ESC [ <y+1> ; <x+1> H`, `Place` with `C=1`, `ESC 8` | surface, x, y, placement | 1 |
| `Hide` | `a=hide:s` | surface | 2 |
| `Delta` | `a=delta:s` and its ops as msgpack (§3.4.3) | surface, ops | 2 |
| `SetText` | `Delta` with one op, `Text` | surface, target, text | 2 |
| `SetVar` | `Delta` with one op, `Var` | surface, target, name, value | 2 |
| `SetAttr` | `Delta` with one op, `Attr` | surface, target, name, value | 2 |
| `RemoveAttr` | `Delta` with one op, `Unattr` | surface, target, name | 2 |
| `MorphTo` | `Delta` with one op, `Morph` | surface, target, html | 2 |
| `Reveal` | `Delta` with one op, `RevealOp`: `where` is `start`, `end`, or empty for the nearest (SPEC.md §6.1) | surface, target, where | 2 |
| `Res` | `a=res:id:type` and the data | id, mime, data | 2 |
| `DelRes` | `a=del:id` | id | 2 |
| `Del` | `a=del:s` | surface | 2 |
| `DelAll` | `a=del` | — | 2 |
| `Detach` | `a=detach:s` | surface | 2 |
| `Focus` | `a=focus:s[:t]`; `t` only when given | surface, target | 2 |
| `Blur` | `a=blur:s` | surface | 2 |
| `Sync` | `CSI ? 2026 h`, the commands, `CSI ? 2026 l` (SPEC.md §6.3) | commands | — |
| `WithdrawLate` | `a=q:q=2`: withdraws a query that asked for a late answer (SPEC.md §4) | — | — |
| `Edit` | `a=edit:take=<names>`, the names joined by `,`, in the order given; `take=` empty takes none (SPEC.md §10.6) | names | 2 |
| `Menu` | `a=menu` and the menu as msgpack (below); with no menu, no payload, which leaves the program none (SPEC.md §10.7) | menu | 2 |
| `MenuShow` | `a=menu:show=1` and `{"seq": [str]}`, the keys typed; with none, no payload, which shows the top level (SPEC.md §10.7) | keys | 2 |
| `MenuHide` | `a=menu:show=0` | — | 2 |
| `Touch` | `a=touch` and `{"areas": [area]}` as msgpack (below); with no areas, no payload, which leaves none (SPEC.md §9.6) | areas | 2 |

- **`Doc` and `Place` answer errors**, since a refused document or placement
  (`EQUOTA`, `ENOENT`) is something a program must hear. Everything else
  answers nothing unless asked: a program that sends deltas every frame would
  otherwise fill its input with replies.
- **`PlaceAt`** places a surface at screen cell (x, y), counted from 0, and
  leaves the cursor where it was: for a full-screen program, which places
  surfaces where it draws them. The coordinates have no leading zeros.
- **`Sync`** takes commands already built, and brackets them.
- **`Menu`** takes a `MenuTree`: `Label` and `Items`, each item a
  `MenuItem` with `Key`, `Label`, `Name`, `Enabled` and `Items`, its own.
  It writes one msgpack map, each value in the smallest of its type's
  forms: `label` unless it is empty, `items`; each item `key`, `label` and
  `name` unless they are empty, `enabled`, and `items` unless there are
  none. A program on myelin names each binding of its `Tree` and hands it
  over. The program keeps it within eight levels (SPEC.md §10.7).
- **`Touch`** takes a list of `TouchArea`, each with `C`, `R`, `W`, `H`
  and `Drag`: the screen's cells it covers, counted from 0, and `"x"`,
  `"y"` or `"xy"`. It writes each area as a map of all five, in that
  order. The program keeps to 256 areas, `W` and `H` at least 1, and
  sends them again when what it drew moves, or the screen changes
  (SPEC.md §9.6).

#### 3.4.1 Placement

A `Placement` says where and how a surface is shown (SPEC.md §5.2):

| field | key | default | sent |
| --- | --- | --- | --- |
| `Cols` | `c` | — | always |
| `Rows` | `r` | 0, which is `auto` | always: the number, or `auto` |
| `Window` | `x`, `y`, `w`, `h` | none | all four, or none: none when absent, when all are 0, or when it shows the whole surface (rows given, and the window is 0, 0, cols, rows) |
| `Z` | `z` | 0 | when not 0 |
| `Press` | `p` | absent | `p=1` when on, `p=0` when off, nothing when absent |
| `Fit` | `f` | absent | as `Press` |
| `Hover` | `v` | absent | as `Press` |
| `KeepCursor` | `C=1` | false | when true |

`Press`, `Fit` and `Hover` belong to the surface (SPEC.md §5.2), so each is
absent, on or off: a placement that leaves them absent leaves them as they
were. Go spells them `hotty.On` and `hotty.Off`, with the zero value
absent.

#### 3.4.2 Reply options

Every builder but `Query`, `Sync` and `WithdrawLate` takes two options:
- **`N(n)`** numbers the command and asks for its reply: `n=<n>`, and
  quiet level 0 unless the program gives `Q`.
- **`Q(q)`** sets the quiet level. A `Q` given wins over the level `N`
  implies, whatever the order in which the program gives them.

**`q` goes out only when the wire does not imply it** (SPEC.md §3.5): it is
left out where it would be 0 with `n`, or 1 without, and for the query. So
`Doc` and `Place` carry no `q`, a delta carries `q=2`, and `Doc` with `N(4)`
carries `n=4` alone.

`Query` takes one option of its own, **`Late`** (`late=1`): the program
asks for a late answer (SPEC.md §4).

Each key goes out once. `Doc` also takes **`Detached`** (`d=1`) and
**`Scroll(axes)`** (`scroll=<axes>`, SPEC.md §5.1): a bitmask of
`ScrollVertical` (1) and `ScrollHorizontal` (2). `0`, the default, sends
no key; any other value goes out as given, for the host to judge.

#### 3.4.3 Ops

`Delta(surface, ops…)` sends one delta of the ops given, in order (SPEC.md
§6.1). An **`Op`** has `Op`, `Target`, `Key` and `Value`, and a builder for
each op makes one:

| builder | op | parameters |
| --- | --- | --- |
| `Morph` | `morph` | target (empty: the whole tree), html |
| `Inner`, `Replace`, `Append`, `Prepend`, `Before`, `After` | the same, in lower case | target, html |
| `Remove` | `remove` | target |
| `Attr` | `attr` | target, name, value |
| `Unattr` | `unattr` | target, name |
| `Text` | `text` | target, text |
| `Var` | `var` | target, name, value |
| `RevealOp` | `reveal` | target, where |

The body is one msgpack map, `{"ops": [op, …]}`, each value in the smallest
of its type's forms, and each op a map with its keys in this order:
- `op`;
- `t`, unless the target is empty;
- `k`, for `attr`, `unattr` and `var`;
- `v`: for an op that takes one (SPEC.md §6.1), always, even empty; for
  `reveal`, only when `where` is `start` or `end`.

Targets and values are not cleaned (§3.3): they travel in the body, as the
document has them. A `Delta` of no ops is still sent, and the host answers
it `EINVAL` (SPEC.md §6.1).

### 3.5 Names

- **`ValidName(s)`**: whether `s` is a surface name: 1 to 64 of `A`–`Z`,
  `a`–`z`, `0`–`9`, `_` and `-`.
- **`SurfaceName(s)`**: `s` made one: each other character becomes `_`, it
  is cut to 64, and an empty name becomes `_`.

### 3.6 Decoder

A Decoder turns OSC sequences into messages, one at a time, joining chunked
ones. A new Decoder is ready to use.

- **`Feed(seq)`** takes one OSC sequence, `ESC ] …`, **with or without its
  terminator** (an environment may strip it, §4.2), and returns a result and,
  when the result is `complete`, the message:

  | result | when |
  | --- | --- |
  | `not_hotty` | the sequence is another OSC, or another number (`ESC ] 72790`). The caller handles it; a chunked message in progress is not affected |
  | `partial` | a chunk of a message that is not complete yet |
  | `complete` | a whole message |
  | `invalid` | a malformed message (SPEC.md §3.7), dropped |

- **The control is strict.** It is SPEC.md §3.2's grammar: a key that does
  not parse, a value outside printable ASCII, a key that appears twice, or
  an empty control is malformed.
- **Chunks** (SPEC.md §3.4):
  - A control with only `m` continues the message in progress; with none
    in progress, it is malformed.
  - Any other message arriving before the last chunk aborts the one in
    progress, which counts as malformed, and is then decoded as usual. If it
    is malformed itself, that makes two (SPEC.md §3.7).
  - The pieces of base64 are joined before they are decoded, so a chunk may
    end anywhere in a base64 quantum.
- **The payload** is base64, with or without padding, whitespace ignored.
  Any other byte makes the message malformed. A payload marked `o=z` is
  inflated if the SDK has zlib; one that does not reports it `invalid`.
  Hosts never compress (SPEC.md §3.3), so only a relay meets one.
- **The message** has the control without `m` and `o`, and the payload as
  bytes.
- **`Invalid`** counts the malformed messages dropped so far.
- **`Max`** is the most bytes one message decodes to, after base64 and
  zlib; 0 is `DefaultMax`. Past it the message is `invalid`, counted once
  (SPEC.md §3.4, §3.7): a chunked one holds no more base64 than `Max` bytes
  could take, zlib's overhead included, and its further chunks are dropped
  up to its last, which reports `invalid` without counting again. Zlib
  stops inflating past `Max`. A host that decodes with a Decoder reports
  its `Max` as `limits.message` (SPEC.md §13).

### 3.7 Scanner

A Scanner cuts hotty sequences out of a byte stream, however the reads split
it. An SDK that reads a raw terminal uses one; so does a relay, on a
program's output. An environment that hands over complete sequences needs
none (§2.8), but the wire layer has one all the same.

- **`Feed(bytes)`** returns segments, in order:
  - `pass`: bytes that are not hotty's, unchanged: keys, mouse reports,
    other OSCs, other answers;
  - `osc`: one complete hotty sequence, from `ESC ] 7279 ;` to its
    terminator (ST or BEL), ready for the Decoder;
  - `da1`: optionally, an answer to Primary Device Attributes,
    `CSI ? <digits and ;> c`, for an SDK that reads DA1 itself (§4.1). A
    Scanner that does not look for them passes them.
- **It holds only what may still become a segment of its own**: bytes that
  are the start of `ESC ] 7279 ;` (or of a DA1 answer, when it looks for
  them), and a hotty sequence in progress. Everything else goes out in the
  same `Feed` that brought it. So the segments do not depend on how the
  stream was split, and a key is never kept waiting for the next read.
- **`Holding()`** returns what it holds, and **`InSequence()`** whether
  that is a hotty sequence in progress (or one too long, being dropped),
  which the next `Feed` goes on with, rather than the start of a segment,
  which a program reading keys may flush after a moment as typing.
- **An ESC inside a hotty sequence** that does not begin its ST ends the
  sequence unfinished: the sequence is dropped as malformed, and the ESC
  begins what comes next.
- **A hotty sequence longer than 64 KiB** (65536 bytes, its terminator not
  counted) is dropped as malformed, up to its terminator. No host sends one:
  chunks are at most 4096 bytes of base64.
- **`Flush()`** ends the stream: what is held goes out as `pass`, except a
  hotty sequence in progress, which is dropped as malformed. A program
  reading keys flushes a lone ESC held at the end of a read, as the Escape
  key; that is the SDK layer's choice (§4.1).
- **`Invalid`** counts the sequences it dropped.

### 3.8 Detector

A Detector decides whether the terminal is a host (SPEC.md §4). It is a
state machine with the time passed in, in milliseconds, so it behaves the
same in every SDK and environment, and the `detect` vectors check it step
by step.

- **`Start(now)`** returns the query to send: `Query(n)`, `n` 1 unless the
  program chooses another, with `Late` when the program asks for a late
  answer (SPEC.md §4).
- **`DA1(now)`**: a DA1 answer arrived. It returns whether the answer was
  detection's. **`Reply(r, now)`**: a reply arrived; it returns whether it
  answers the query. **`Tick(now)`**: the time is now. **`End(now)`**: the
  input ended, or the caller gave up.
- **It exposes** `State` (`detecting`, `native` or `text`), `Caps` (the
  host's, when native), `Decided`, `Done`, and `Deadline`: when to call
  `Tick` next, or none once done.
- **Every input first lets the time pass**: whatever was due at or before
  `now` happens before the input is taken.

The rules, with their times:

| rule | |
| --- | --- |
| a reply that answers the query (`re=q`, the query's `n`): `a=ok`, its capabilities decoding (§3.9), their `V` the SDK's `Version` | the terminal is a host: `native`, decided, with its capabilities. Done when the DA1 behind it arrives, or 300 ms after the reply, or at 1500 ms, whichever comes first |
| any other reply that answers the query: an error (`EVERSION`, or another), or capabilities that do not decode or name another version | the terminal is no host the program can use (SPEC.md §4): `text`, decided. Done as for a host's reply |
| a DA1 before any reply | it may answer a question asked before the query (SPEC.md §4). It opens a grace of 150 ms: a reply within it still answers the query |
| the grace ends with no reply | `text`: not a host. Decided and done |
| 1500 ms with no answer at all | `text`. Decided and done |
| the input ends | `text` if nothing was decided. Done |
| with `Late`, a reply that answers the query once the state is `text`, and makes the terminal a host (the first rule) | a late answer: `native`, with its capabilities. Still decided and done |

- **Which inputs are detection's.** Every DA1 from the start until done is
  detection's, and the SDK layer swallows it: it answers the query's fence or
  an earlier question, and is no key. After done, a DA1 is not
  detection's. A reply that answers the query, `a=ok` or `a=err`, is
  detection's whenever it arrives, even after the state is `text`; a late
  one changes nothing, unless the query asked for it (`Late`), and then
  only the first that makes the terminal a host. Replies with another `n`,
  and everything else, are not.
- **Decided and done** differ only when the terminal answers the query:
  whether it is a host the program can use is known when its reply
  arrives, and the detection is over when nothing more of it will arrive. A program that must not wait, such as a framework's adapter
  (§4.3), acts when decided; one that hands the terminal on, when done.

### 3.9 Messages

A decoded **`Message`** has its control (a map of keys to values) and its
payload (bytes). It reads as a reply or as an event.

**The body** (SPEC.md §3.3) is decoded when the reply or event is read, each
field as its type.
- Fields the SDK does not know are ignored (§2.7), whatever their type.
- A body nested more than 32 levels deep does not decode. An SDK stops
  reading there, so that no body can exhaust its stack: TinyGo's is 64 KB,
  and a few hundred levels crash it.
- Nor does a body that holds, anywhere in it, a field the SDK knows or
  not, what SPEC.md §3.3 has no host send: a nil, a map key that is not a
  string, a key given twice in one map (keys compared as the strings they
  are, whatever their form), a string that is not UTF-8, an int further than 2^53 − 1 from
  zero, or a timestamp of another size, with a second's nanoseconds or
  more, or with seconds further than 2^53 − 1 from 1970. Each is checked
  as the body is read, in every SDK alike, so a body decodes in all of
  them or in none: an int past 2^53 − 1 is not rounded where numbers are
  doubles, nor kept where they are not.
- A body that does not decode leaves absent everything it would have
  filled, and the rest of the message stands. That is a body that is not
  one msgpack map, one that breaks a rule above, or one with a field the
  SDK knows holding another type. What it fills is the capabilities, an
  error's code and detail, or what an event's detail carries.

**`Reply`** (SPEC.md §3.6), when the action is `ok` or `err`:

| name | from |
| --- | --- |
| `OK` | `a=ok` |
| `Re` | `re`: the action answered |
| `N` | `n`, as a number; absent when the command had none |
| `Surface` | `s`; absent when the command named none |
| `Cols`, `Rows` | `c` and `r`, as numbers, in a reply to `place` |
| `Code`, `Detail` | the error's body, when not OK |
| `Op` | `i`, as a number, in an error answering a delta: the op at fault (SPEC.md §6.1); absent otherwise |
| `Caps` | the capabilities, the body of an ok reply to `q` |
| `Err` | the reply as an error, carrying `Code` and `Detail`; none when OK |

**`Event`** (SPEC.md §9), when the action is `ev`: `Surface` (absent for
`command` and `caps`, which belong to no surface), `Kind` (as it came,
known or not), `Target` (`t`, empty when absent), and what the detail
carries, each absent when the event does not carry it:

| name | holds | for |
| --- | --- | --- |
| `Value` | `value`, a string | `click`, `change`, `input` |
| `Checked` | `checked`, a boolean | `change` |
| `Fields` | `fields`: the pairs of a name and a value, in order (SPEC.md §9) | `submit` |
| `Mods` | `mods`: the modifier bits (§3.1), 0 when absent | `click`, `press`, `dragstart`, `drag`, `dragend` |
| `Link` | `href`, and `url` if present | `click` on a link |
| `FitRows` | `r` | `fit` |
| `Drag` | `c` and `r`; the steps `x` and `y` in the element dragged, and `tx` and `ty` in a `t` with `drop` (as `TX` and `TY` where the language capitalises), each absent when the detail has none (SPEC.md §9.1) | `dragstart`, `drag`, `dragend` |
| `Hover` | `c` and `r`, or `out` | `hover` |
| `Area` | `area`: `c`, `r`, `w` and `h`, the element's cells | `click`, `press` |
| `Count` | `count`: the click's place in its run of clicks (SPEC.md §9), 2 for the second click of a double click; 1 for a click whose detail has none | `click` |
| `View` | `Areas`, from `in`: each id in view to its cells as `Area` holds them, leaving out one whose area lacks a field, and none when `in` is absent; and `AtEnd`, from `end`, false when absent (SPEC.md §9.5) | `view` |
| `Command` | `Name`, `Key` and `Text`, from `name`, `key` and `text`, each empty when absent, and `HasText`, whether `text` is there (SPEC.md §10.6); absent when the detail has no `name` | `command` |
| `Changed` | the capabilities that changed, a `Caps` with only the fields the detail has (SPEC.md §4) | `caps` |

The detail is read by the event's kind, so a form field named `value` is in
`Fields` and nowhere else. An event of a kind the SDK does not know carries
none of these, and its payload is in its message.

**`Caps`** (SPEC.md §4) has every field SPEC.md lists: `V`, `CellCSS`
(`w`, `h`, from `cellcss`), `Scale`, `Scheme`, `Mod`, `Features`,
`Limits`, `Net`, `Edit`, `Host`, `Version`. It also answers:
- `Has(feature)`: whether `feature` is in `features` (SPEC.md §4);
- `Light`: the scheme is `light`;
- `Merge(changed)`: the capabilities with the fields of a `caps` event's
  `Changed` in place of their own, and without those it empties (`{}`,
  `[]` or `""`: absent now, SPEC.md §9).

**An error** carries `Code` and `Detail`, and its message names both.

### 3.10 Keys

What a text field does with a key (SPEC.md §10.2, §10.4), as data: so that a
program that draws its fields in cells edits them as a host edits them on a
surface, and the keymap it gives a surface is the one it uses in cells. And
which keys any other element gives the program, or scrolls with, for a
host.

- **`ParseKey(name)`** reads a key's name as SPEC.md §10.4 has hosts read
  one, with myelin key names' default reading (`Shift+Ctrl+A`, `PageDown`,
  `esc`), and returns the key, its canonical name (`ctrl+shift+a`,
  `page_down`, `escape`). A name the reading refuses (empty, `meta+a`,
  `ctrl+`, a modifier given twice) is absent, and so is one with `mod`,
  which only a Platform reads (`ReadKey`). Its vectors are that
  document's: the keys section carries its `read` vectors.
- **`Platform`** is how a host reads bindings: `Mod`, `ctrl` or `super`,
  what `mod` stands for (SPEC.md §10.4), and `Edit`, its edit shortcuts,
  from each action to its keys (SPEC.md §10.2). The zero Platform is
  `ctrl` with the default edit shortcuts. `PlatformOf(caps)` takes both
  from the capabilities, each the zero Platform's when absent.
  `DefaultEdit(mod)` returns the default edit shortcuts for a `mod`, in
  SPEC.md §10.2's order, each key read: `ctrl+shift+z` and `ctrl+y` for
  `redo` where it is `ctrl`.
- **`ReadKey(platform, name)`** reads a key's name as `ParseKey` does,
  with `mod` standing for the Platform's (myelin key names §4.4): on
  `ctrl`, `mod+s` is `ctrl+s`, and `mod+ctrl+s` is absent. The keys
  section carries that document's `mod` vectors for it.
- **`DecodeKeys(input)`** reads input from the terminal, the bytes a
  program reads, as SPEC.md §10.4 names it: a list with one entry for each
  key, its name, or absent for input that is no key it names (a mouse
  report, a sequence it does not know). It is pure: an `ESC` at the end of
  the input is `escape`, and the caller passes each read whole (a
  framework's parser has already cut the input into keys).
- **`KeyText(key)`** is the text a key types in a field (SPEC.md §10.2): a
  character's, a letter with `shift` as its capital, a space for `space`,
  and none for any other key, or a key with `ctrl`, `alt` or `super`.
- **`Keymap`** is a set of bindings, key to action, read on a Platform.
  `ParseKeymap(platform, value)` reads a `data-keys` value as SPEC.md
  §10.2 has hosts read it, dropping the bindings a host ignores, each key
  with `ReadKey`: on `ctrl`, `mod+a` and `ctrl+a` are one key, the later
  binding's. `Format()` writes one back: each key once, where it was
  first bound, as its last binding wrote it, with its last action, as
  `key=action`, separated by a space. A key is written canonical, and one
  written with `mod` keeps it, first (`mod+shift+z`).
- **`Resolve(platform, multiline, values…)`** builds a field's keymap:
  SPEC.md's default keymap for the Platform's `mod`, with its `Edit`, for
  an `input` or for a multi-line field, then each `data-keys` value in
  turn, the root's first. A binding to a scroll action is left out where
  it stands, in its own value too: it neither acts nor overrides an
  earlier binding of its key (SPEC.md §10.2).
- **`Binds(key)`** says whether a keymap binds the key, or, for a key with
  `shift` that is not a character and that it does not bind, the key
  without `shift` (SPEC.md §10.2). A host that has two names for a press
  (SPEC.md §10.4) asks what follows of the physical one where the keymap
  binds it, and of the other one otherwise.
- **`Lookup(key)`** on a resolved keymap returns what the field does with
  the key: an action; `insert` for a key it types (`KeyText`); or absent
  when the key is not the field's (it reaches the program, or `tab` moves
  focus). `tab`, `shift+tab` and `escape` are never the field's, nor a key
  bound to `program` or `prefix`, or to an action the field does not
  have.
- **`Selects(key)`** says whether the field selects with the key (SPEC.md
  §10.2, Shift selects): `Lookup` returns a move for it, and its name has
  `shift` (`shift+arrow_left`, `ctrl+shift+end`, not `alt+<`). A field
  then does the move with `Extend` (§4.6).
- **`Program(key)`** says whether a keymap gives the key to the program: it
  binds the key to `program` or `prefix`, with the fallback of `Binds`. On
  an element that is not a text field, a host asks it
  of the element's keymap, `ParseKeymap` of its `data-keys` values joined
  with a space, the root's first, on the host's Platform, with no default
  keymap; a key it gives
  reaches the program before the element or a scroll uses it (SPEC.md
  §10.2, keys for the program).
- **`Prefix(key)`** says whether a keymap binds the key to `prefix`, with
  the same fallback without Shift. A host asks it of a key `Program` gives
  the program, and from then on gives the program every key (SPEC.md
  §10.5).
- **`Scroll(key)`** returns the scroll action a keymap binds the key to,
  with the same fallback, or absent. A host asks it of the
  keymap it asks `Program` of, for a key the element does not use (SPEC.md
  §10.2, scrolling keys).
- **`TerminalKeys`** is the SDK's keymap, the same in every SDK (§2.3): the
  keys of Bubble Tea's text input and text area (bubbles), which a program
  that edits fields in cells is likely to share, but for `mod+a`, which
  selects all, as in a GUI field and SPEC.md's default keymap: `ctrl+a`
  on Linux, while on macOS `ctrl+a` stays the line's start. A program puts it in the
  `data-keys` of an element that holds its fields, and gives its cells
  rendition the same keymap, `Resolve(platform, multiline, TerminalKeys)`.

  | action | keys |
  | --- | --- |
  | `char-backward`, `char-forward` | `arrow_left`, `ctrl+b`; `arrow_right`, `ctrl+f` |
  | `word-backward` | `alt+arrow_left`, `ctrl+arrow_left`, `alt+b` |
  | `word-forward` | `alt+arrow_right`, `ctrl+arrow_right`, `alt+f` |
  | `line-start`, `line-end` | `home`; `end`, `ctrl+e` |
  | `delete-char-backward` | `backspace`, `ctrl+h` |
  | `delete-char-forward` | `delete`, `ctrl+d` |
  | `delete-word-backward` | `alt+backspace`, `ctrl+w`, `ctrl+backspace` |
  | `delete-word-forward` | `alt+delete`, `alt+d`, `ctrl+delete` |
  | `delete-to-line-start`, `delete-to-line-end` | `ctrl+u`; `ctrl+k` |
  | `line-previous`, `line-next` | `arrow_up`, `ctrl+p`; `arrow_down`, `ctrl+n` |
  | `page-up`, `page-down` | `page_up`; `page_down` |
  | `input-start`, `input-end` | `alt+<`, `ctrl+home`; `alt+>`, `ctrl+end` |
  | `select-all` | `mod+a` |
  | `newline` | `ctrl+m` |

  It leaves `enter` to SPEC.md's default (`submit` in an `input`, `newline`
  in a multi-line field). In the order of this table, it is the value
  vectors call `terminal_keys`.

## 4. The SDK layer

What a program does with a terminal depends on the kind of program, and on
what it runs in. This section describes the components an SDK **SHOULD**
provide for the kinds of program its language is used for, by what they do,
then by the shape hotty-go gives them. An SDK follows its language and its
frameworks in everything else.

### 4.1 Terminal

For a program that is not a full-screen framework's: a command that prints
documents and exits, one that asks a question, a chart that streams. Go:
`hottyterm.Term`.

- **Opening.** `Open(name)` uses the process's terminal, whatever its
  standard output is (a pipe, say): `/dev/tty`, or the platform's console.
  With no terminal, it fails, and the program writes plain data.
  `New(in, out, name, size, known)` makes one from streams, for a test or an
  environment with no file (a terminal in a page). `known` tells it the
  terminal is a host, and with what capabilities, without asking.
- **One reader.** Everything the terminal sends is read once, by the
  terminal: keys, the mouse, pastes, replies and events. What a call waits
  for goes to that call (detection's answers, a fence's DA1, a numbered
  reply); everything else goes to one event stream, in the order it
  arrived. Calls that wait and a reader of events may run at once.
- **Detection.** `Detect()` runs the Detector (§3.8), once: later calls
  return the first answer. It returns when the Detector is done, so that
  nothing of the detection is left for whatever reads the terminal next. A
  terminal made with `known` answers at once.
- **A late answer.** A terminal opened with `Late` asks for one (SPEC.md
  §4). If it comes, it goes to the event stream as the host's capabilities,
  and `Detect()` answers with it from then on. Until it comes, the
  terminal stays in raw mode, or the answer would be echoed, and `Close()`
  withdraws the query (`WithdrawLate`), so that no answer reaches whatever
  reads the terminal next.
- **Requests.** `Request(build)` sends one command numbered with `N` and
  waits for its reply, 3 s at most. The number is never 1, which is the
  query's. An error reply is a reply, not an error; no reply in time is an
  error.
- **The fence.** `Fence()` waits until the terminal has taken everything
  written before it (it sends DA1, which terminals answer in order), 1 s at
  most, and returns the replies nothing read: the errors of commands sent
  with `q=1`, mostly. A program calls it before it exits.
- **The line.** `LineStart()` moves the cursor to the start of a line, where
  a surface placed at the cursor belongs: it asks where the cursor is (CPR),
  and writes CR LF if it is not at column 0. A terminal that does not
  answer within 1 s is taken to be at one.
- **Printing.** `Print(name, html, placement)` sends a document detached and
  places it at the cursor, in one write, and returns the surface's full
  name: for a document that stays in the scrollback among the program's
  output.
- **Writing.** `Send(commands…)` writes commands in one write, so that
  nothing the program prints lands between them.
- **Names.** `Surface(name)` makes a surface name of the program's own: a
  prefix, a dash and the name, made valid. The prefix is the program's name
  and a number unique on the terminal (its process id), or what the
  environment provides, so that two runs never share a surface. Every name
  is remembered (`Surfaces()`), and `DetachAll()` detaches them all.
- **Closing.** `Close()` gives the terminal back as it was (its mode, its
  file). It sends nothing but a withdrawal of a late query (above): what is
  left on the screen, and the replies not read, are the program's to
  settle first (§2.6).
- **Raw mode.** Calls that wait for the terminal put it in raw mode for
  themselves. A program that reads keys asks for it once.
- **The polyfill.** `KittyGraphics()` **MAY** be provided: whether a
  terminal that is not a host shows kitty graphics, so that a program can
  suggest a polyfill (SPEC.md §14).

### 4.2 Push environments

In some environments the SDK does not read the terminal: the environment
hands it what arrives, and writes for it. Neovim is one: its TUI parses the
input and raises `TermResponse` with each OSC, DCS and APC sequence, without
its terminator; a plugin writes to the terminal with `nvim_ui_send`. There:

- **Input is pushed.** The SDK layer receives sequences in callbacks, and
  feeds the Decoder and the Detector from them. It needs no Scanner.
- **Waiting is asynchronous.** Detection, requests and fences complete
  through callbacks or coroutines, with timers the environment provides.
- **What is detection's is advisory**: the environment may hand the same
  answer to other listeners. Others ask DA1 too, so a stale DA1 is routine,
  and the Detector's grace (§3.8) matters.
- **The terminal can change.** An editor can attach another UI, or come
  back from suspension; the SDK layer detects again, and sends documents
  again, when the environment says so.
- **A late answer is the environment's**, since detection is: one query
  asks for it for every session, and it is withdrawn while a shell has the
  terminal (on exit and suspension).
- **What the environment lacks, the SDK lacks**: CPR, `LineStart`, `Print`
  and raw mode may not exist. Surface names take a prefix the program
  supplies.
- **Batches do not nest.** Where the environment brackets its own frames in
  synchronized output, the SDK sends its commands within them, and does not
  add brackets of its own.

### 4.3 Session: a framework's adapter

For a full-screen program built on a UI framework, which redraws its frame
as its state changes. Go: `hottytea.Session`, for Bubble Tea. Other
frameworks get their own adapter, with the same responsibilities.

- **Modes.** `Detecting` until the Detector decides, then `Native` (a host)
  or `Text` (not one). In `Text` the Session sends nothing, and the program
  draws everything in cells.
- **A late answer.** A Session made with `Late` asks for one (SPEC.md §4),
  through its terminal when the terminal detects. If it comes while the
  mode is `Text`, the mode becomes `Native`, ready is sent again, and then
  the program is asked for a layout: a program that asks for a late answer
  draws either rendition whenever the mode says. Until it comes, `Close()`
  withdraws the query (`WithdrawLate`).
- **Layout.** With each frame, the program says which surfaces it wants
  where: name, rectangle on the screen, an optional clip (the part of the
  screen the surface shows in, such as a scrolling region), `Keep`, `Z`,
  `Press`, `Fit`, `Hover`, `Scroll` (the axes the host scrolls the document
  along, §3.4.2), and a function that returns the document. The Session
  sends only what changed:
  - a document once, attached, with its `Scroll`, and again only when the
    host has lost it or its `Scroll` changed;
  - a placement (`PlaceAt`) when its rectangle, window or options changed,
    with `Press`, `Fit` and `Hover` only when they differ from what the
    surface has (SPEC.md §5.2), on or off; a surface clipped to nothing is
    as good as not wanted;
  - for a surface no longer wanted, `Hide` if `Keep`, else `Del`.
- **Order.** Its commands go out with the framework's frames, in the same
  stream, so that a placement lands with the cells around it.
- **Relayout.** A framework's renderer erases, scrolls and switches screens
  on its own, and a host drops placements with them (SPEC.md §5.4). The
  Session watches the output on its way out, and asks the program for a new
  layout when it sees: erase in display (2 or 3), scroll up or down, insert
  or delete lines, a new scrolling region, reverse index, leaving the
  alternate screen, or a full reset. An environment that knows its own
  redraws (an editor) uses its events instead.
- **Lost documents.** `ENOENT` in answer to a placement means the host lost
  the document (a reset, the alternate screen); the Session sends it again
  with the next layout.
- **Limits.** At most 48 surfaces by default, fewer when the host's
  `limits.surfaces` says so, or when an `EQUOTA` says so. To make room, it
  deletes the surfaces not wanted, the longest unseen first; under a limit
  the host set, a new surface waits for room.
- **Messages.** What the host sends comes back to the program as the
  framework's messages: ready (the mode is known), an event, an error (a
  command refused), an ack (the ok of a numbered command), relayout, and
  pong (a round trip timed). Everything else passes as it was.
- **Closing.** `Close()` deletes the surfaces the Session sent;
  `DetachAll()` detaches them instead, for a program that leaves them on
  the screen.

### 4.4 Test host

A host that runs inside a test, so that a program's tests need no terminal.
Go: `hottytest.Host`.

- **The terminal around it.** It answers what terminals answer: DA1, the
  cursor's position, the background colour, and, when asked to, the kitty
  graphics query. It tracks the alternate screen and full resets as a host
  does (SPEC.md §5.4), and keeps the cells the program printed. Made as a
  terminal that is not a host, it answers no hotty command, until the test
  makes it a host, as a multiplexer's terminal attaching does: then it
  answers a query that asked for a late answer and was not answered
  (SPEC.md §4), and every command after.
- **The host.** It keeps every surface's document with the delta operations
  and the morph of SPEC.md §6, answers as SPEC.md §3.6 has hosts do, and
  passes the host vectors' `send`, `inspect` and `key` steps. It speaks
  the SDK's `Version` only: it answers a query that lists it with its
  capabilities, and any other with `EVERSION` (SPEC.md §4). It lays
  nothing out: `r=auto` gets an estimate the test may replace, nothing in
  it scrolls, so a key bound to a scroll action goes on as if its keymap
  did not bind it (SPEC.md §10.2), and it sends no `view`. Nor can it
  measure where in an element the pointer is: a drag of an element with
  `data-steps` carries the steps the test gives, in it and in `t` (SPEC.md
  §9.1). A drag goes to the element the test names, which may be one with
  `drop` in its `data-on`. So it skips the vectors marked `scroll`,
  `steps`, `drop` and `view` (conformance/README.md), which every other
  host passes.
- **Strict by default.** A malformed message, an `EINVAL`, or a hotty
  command other than the query sent to a terminal that is not a host fails
  the test. A lenient host records them instead.
- **The user.** It plays the user: typing, pressing a key as SPEC.md §10.2
  has a host take it, clicking, filling a field, checking a box, choosing
  an option, submitting, pressing, dragging, and any event the test makes
  up.
- **Inspection.** The test reads each surface: its document, an element's
  attributes and text, the focused element, whether it is detached, and
  where it is placed. It also reads the touch areas the program sent for
  each screen, which it checks as a host does and keeps per screen,
  though no finger reaches them (SPEC.md §9.6).

An SDK that does not build one **MAY** test its SDK layer against another
implementation's host, over a pipe.

### 4.5 Relay

A relay stands between programs and a host, as a multiplexer does (SPEC.md
§2): it answers the query with the host's capabilities, gives each
program's surfaces names of their own on the host, places them where the
program's pane is, and passes events back to the program whose surface they
came from. plx's `pkg/hottyrelay` is one. This section is reserved: what a
relay does will be specified here, with its own vectors.

### 4.6 Field

A text field's value, caret and selection, edited as SPEC.md §10.2's actions
edit them, for a program that draws its fields in cells (the second
rendition, §2.5). Go: `hottyedit.Field`, a module of its own for its
grapheme segmentation.

- **The field.** `Value`, `Caret` (a count of characters, grapheme
  clusters, from the start), `Anchor`, `Multiline`, `Password`, and
  `Rows`, the rows it shows (1 when absent), for `page-up` and
  `page-down`.
- **The selection** runs from `Anchor`, the end where it began, to the
  caret (SPEC.md §10.2). Nothing is selected when `Anchor` is absent or at
  the caret. `Selection()` returns its start and end, equal when nothing
  is selected, for the rendition to draw. `Select(anchor, caret)` sets
  both, for a selection the user makes with the pointer, and
  `Select(p, p)` puts the caret at `p` with nothing selected. Go keeps the
  anchor behind `Anchor()` and `Select`, since a `Field` literal's zero
  anchor would select.
- **Rows are lines.** A field in cells does not wrap: each line is a row,
  and the place along it is a count of characters. A rendition that wraps
  moves the caret between its rows itself.
- **`Do(action)`** does an action, and reports whether the value changed,
  so the program knows when to report an `input`. A selection comes first,
  as SPEC.md §10.2 says: a delete deletes it, a move starts from its start
  or its end and ends it, and `select-all` selects the whole value.
  `submit` and `program` are the program's, and change nothing, as are
  `copy`, `cut`, `paste`, `undo` and `redo`: a Field holds no clipboard
  and no history, which a program that keeps them adds around it.
- **`Extend(action)`** does a move as Shift does it (SPEC.md §10.2): the
  anchor stays, or is set where the caret is, and the caret moves from
  where it is. An action that is not a move is `Do`'s.
- **A run of row moves,** `line-previous`, `line-next`, `page-up` and
  `page-down`, with `Do` or `Extend`, keeps the place along the row it
  started from; any other action, typing, or `Select` ends it.
- **`Type(text)`** types text at the caret, in place of the selection.
- **`Key(keymap, key)`**, where an SDK has it, does what the keymap says
  of the key: types its `KeyText`, does its action, or, when
  `Selects(key)`, extends with it.

An SDK whose language has no grapheme segmentation **MAY** count code
points instead, CR LF still one, and says so (§5.3); it then skips the
vectors that require `graphemes`. So does its `DecodeKeys`, for a character
of more than one code point.

## 5. Conformance

### 5.1 Vector sections

`conformance/vectors.json` holds the vectors of hosts and of SDKs
([`conformance/README.md`](https://hotty.neuroplast.io/docs/conformance/)):

| section | checks | runs it |
| --- | --- | --- |
| `vectors` | a host: replies, documents, events | hosts, test hosts (§4.4) |
| `wire` | decoding a stream of commands | hosts and SDKs |
| `build` | the builders, their defaults and options (§3.4) | SDKs |
| `encode` | `Encode`: cleaning, chunks, compression (§3.3) | SDKs |
| `decode` | the Decoder and the messages (§3.6, §3.9) | SDKs |
| `scan` | the Scanner (§3.7) | SDKs |
| `detect` | the Detector (§3.8) | SDKs |
| `keys` | key names and `DecodeKeys` (§3.10, SPEC.md §10.4) | hosts and SDKs |
| `keymap` | `ParseKeymap`, `Resolve`, `Lookup`, `Program`, `Scroll` and `TerminalKeys` (§3.10, SPEC.md §10.2) | hosts and SDKs |
| `edit` | the actions on a value: a Field (§4.6) | hosts, and SDKs with a Field |

An SDK conforms when it meets every **MUST** of this document and passes
every SDK section, `edit` where it provides a Field.

### 5.2 Runners

- **Names in the vectors are canonical**, in snake case: a runner maps
  `set_text`, `keep_cursor` and `fit_rows` to its own names. That mapping is
  what keeps SDKs aligned.
- **`requires`.** A vector that needs something an SDK may not have yet
  names it in `requires`. A runner declares what it implements, and skips
  the vectors whose `requires` it lacks, so the list of what an SDK lacks is
  its runner's, not a list kept by hand.
- **A copy, checked.** An SDK keeps a copy of `vectors.json` in its tests,
  and a test that compares it with the spec's when a checkout is at hand
  (hotty-go: `make vectors`, `TestVectorsAreCurrent`). The runner checks
  that the vectors' version is the SDK's.

### 5.3 Implementations

| SDK | language | wire layer | SDK layer |
| --- | --- | --- | --- |
| [hotty-go](https://hotty.neuroplast.io/docs/hotty-go/) | Go | the reference; complete, with what a relay writes as a host (replies, events, `Message.Placement`, `CursorBelow`) | `hottyterm`, `hottytea`, `hottytest`, `hottyedit` (§4.6) |
| `clients/python` (this repository) | Python | complete | `Hotty`, for the examples |
| [hotty-lua](https://hotty.neuroplast.io/docs/hotty-lua/) | Lua | complete, under LuaJIT, Lua 5.1, Neovim and gopher-lua (plx's) | `hotty.nvim` (§4.2) |

hotty-go's runners (`vectors_test.go`, and `hottyedit`'s for `edit`) run
every SDK section and skip nothing. hotty-lua's (`tests/vectors.lua`, under
each of its runtimes) and the Python client's count code points (§4.6) and
skip only the vectors that require `graphemes`. plx's `sdk/hotty` (Go) is a
relay's codec, to be replaced by hotty-go's, which has all that plexos
listed it uses. hotty-blitz's `hotty-wire` (Rust) is a host's; its tests
run the `keys`, `keymap` and `edit` sections.

---

## Appendix A. Names

The canonical names, in each language's case. Go's are hotty-go's.

| canonical | Go | Python | Lua | Rust | TypeScript |
| --- | --- | --- | --- | --- | --- |
| `Encode` | `hotty.Encode` | `hotty.encode` | `hotty.encode` | `hotty::encode` | `encode` |
| `Query` | `hotty.Query` | `query` | `query` | `query` | `query` |
| `Doc` | `Doc` | `doc` | `doc` | `doc` | `doc` |
| `Place`, `PlaceAt` | `Place`, `PlaceAt` | `place`, `place_at` | `place`, `place_at` | `place`, `place_at` | `place`, `placeAt` |
| `Hide`, `Delta` | `Hide`, `Delta` | `hide`, `delta` | `hide`, `delta` | `hide`, `delta` | `hide`, `delta` |
| `Op` and its fields | `hotty.Op{Op, Target, Key, Value}` | `Op(op, target, key, value)` | a table with those keys | `Op { op, target, key, value }` | `{ op, target, key, value }` |
| `Morph`, `Inner`, `Replace`, `Append`, `Prepend`, `Before`, `After`, `Remove`, `Attr`, `Unattr`, `Text`, `Var`, `RevealOp` | `hotty.OpMorph`, `hotty.OpInner`, …, `hotty.OpText`, `hotty.OpVar`, `hotty.OpReveal` (`hotty.Text` is a detector's state) | `morph`, `inner`, …, `text`, `var`, `reveal_op` | as Python | as Python | `morph`, `inner`, …, `text`, `varOp`, `revealOp` |
| `SetText`, `SetVar`, `SetAttr`, `RemoveAttr`, `MorphTo`, `Reveal` | the same | `set_text`, `set_var`, `set_attr`, `remove_attr`, `morph_to`, `reveal` | as Python | as Python | `setText`, `setVar`, `setAttr`, `removeAttr`, `morphTo`, `reveal` |
| `Res`, `DelRes` | `Res`, `DelRes` | `res`, `del_res` | `res`, `del_res` | `res`, `del_res` | `res`, `delRes` |
| `Del`, `DelAll` | `Del`, `DelAll` | `delete`, `del_all` | `del`, `del_all` | `del`, `del_all` | `del`, `delAll` |
| `Detach`, `Focus`, `Blur`, `Sync` | the same | `detach`, `focus`, `blur`, `sync` | as Python | as Python | `detach`, `focus`, `blur`, `sync` |
| `WithdrawLate` | `hotty.WithdrawLate` | `withdraw_late` | `withdraw_late` | `withdraw_late` | `withdrawLate` |
| `Edit`, `Menu`, `MenuShow`, `MenuHide` | `hotty.Edit`, `hotty.Menu`, `hotty.MenuShow`, `hotty.MenuHide` | `edit`, `menu`, `menu_show`, `menu_hide` | as Python | as Python | `edit`, `menu`, `menuShow`, `menuHide` |
| `Touch`, `TouchArea` and its fields | `hotty.Touch`, `hotty.TouchArea{C, R, W, H, Drag}` | `touch`, `TouchArea(c, r, w, h, drag)` | `touch`, tables with those keys | `touch`, `TouchArea { c, r, w, h, drag }` | `touch`, `{ c, r, w, h, drag }` |
| `MenuTree`, `MenuItem` and their fields | `hotty.MenuTree{Label, Items}`, `hotty.MenuItem{Key, Label, Name, Enabled, Items}` | `MenuTree(label, items)`, `MenuItem(key, label, name, enabled, items)` | tables with those keys | `MenuTree { label, items }` | `{ label, items }` |
| `Placement` and its fields | `Placement{Cols, Rows, Window, Z, Press, Fit, Hover, KeepCursor}` | `Placement(cols, rows, window, z, press, fit, hover, keep_cursor)` | a table with those keys | `Placement { cols, rows, … }` | `{ cols, rows, window, z, press, fit, hover, keepCursor }` |
| a flag on, off or absent (`Press`, `Fit`, `Hover`) | `hotty.On`, `hotty.Off`, the zero value | `True`, `False`, `None` | `true`, `false`, `nil` | `Option<bool>` | `true`, `false`, `undefined` |
| `N`, `Q`, `Detached`, `Scroll`, `Late` | `hotty.N(n)`, `hotty.Q(q)`, `hotty.Detached()`, `hotty.Scroll(axes)`, `hotty.Late()` | `n=`, `q=`, `detached=`, `scroll=`, `late=` | `{ n = …, q = …, detached = …, scroll = …, late = … }` | builder methods | `{ n, q, detached, scroll, late }` |
| `ScrollVertical`, `ScrollHorizontal` | `hotty.ScrollVertical`, `hotty.ScrollHorizontal` | `SCROLL_VERTICAL`, `SCROLL_HORIZONTAL` | `SCROLL_VERTICAL`, `SCROLL_HORIZONTAL` | `SCROLL_VERTICAL`, `SCROLL_HORIZONTAL` | `SCROLL_VERTICAL`, `SCROLL_HORIZONTAL` |
| `Decoder.Feed`, `Invalid`, `Max` | `(*Decoder).Feed`, `.Invalid`, `.Max` | `Decoder.feed`, `.invalid`, `.max` | `decoder:feed`, `.invalid`, `hotty.decoder({ max = … })` | `Decoder::feed`, `.invalid`, `.max` | `Decoder.feed`, `.invalid`, `.max` |
| `DefaultMax` | `hotty.DefaultMax` | `DEFAULT_MAX` | `DEFAULT_MAX` | `DEFAULT_MAX` | `DEFAULT_MAX` |
| `Scanner`, `Detector` | `Scanner`, `Detector` | `Scanner`, `Detector` | `scanner`, `detector` | `Scanner`, `Detector` | `Scanner`, `Detector` |
| `Holding`, `InSequence` | `(*Scanner).Holding`, `.InSequence` | `Scanner.holding`, `.in_sequence` | `scanner:holding`, `:in_sequence` | `Scanner::holding`, `::in_sequence` | `Scanner.holding`, `.inSequence` |
| `Reply`, `Event`, `Caps` | `Reply`, `Event`, `Caps` | the same | tables with the fields in snake case | the same | the same |
| `FitRows`, `Mods`, `CellCSS` | `FitRows`, `Mods`, `CellCSS` | `fit_rows`, `mods`, `cell_css` | as Python | as Python | `fitRows`, `mods`, `cellCss` |
| `ModShift`, `ModAlt`, `ModCtrl`, `ModSuper` | `hotty.ModShift`, … | `MOD_SHIFT`, … | `MOD_SHIFT`, … | `MOD_SHIFT`, … | `MOD_SHIFT`, … |
| a message as a `Reply` or an `Event`, `Err` | `hotty.ReplyOf(m)`, `hotty.EventOf(m)`, `hotty.Err(r)` | `m.reply()`, `m.event()`, `r.err()` | `m:reply()`, `m:event()`, `r:err()` | methods | `m.reply()`, `m.event()`, `r.err()` |
| `Has`, `Light`, `Merge` | `hotty.HasFeature(c, feature)` (`hotty.Has` asks a control), `hotty.Light(c)`, `hotty.Merge(c, changed)` | `c.has(feature)`, `c.light()`, `c.merge(changed)` | `c:has(feature)`, … | methods | `c.has(feature)`, … |
| `Command`, `Changed` | `Event.Command`, `Event.Changed` | `e.command`, `e.changed` | as Python | methods | `e.command`, `e.changed` |
| a control's `get` and `with` | `hotty.Get(ctl, k)`, `hotty.With(ctl, k, v)` | the SDK's own | the SDK's own | the SDK's own | the SDK's own |
| `ParseKey`, `DecodeKeys`, `KeyText` | `hotty.ParseKey`, `hotty.DecodeKeys`, `hotty.KeyText` | `parse_key`, `decode_keys`, `key_text` | as Python | as Python | `parseKey`, `decodeKeys`, `keyText` |
| `Keymap`, `ParseKeymap`, `Resolve`, `Binds`, `Lookup`, `Program`, `Prefix`, `Scroll`, `Format` | `hotty.Keymap`, `hotty.ParseKeymap`, `hotty.Resolve`, `(Keymap).Binds`, `.Lookup`, `.Program`, `.Prefix`, `.Scroll`, `.Format` | `Keymap`, `parse_keymap`, `resolve`, `Keymap.binds`, `.lookup`, `.program`, `.prefix`, `.scroll`, `.format` | `parse_keymap`, `resolve`, `keymap:binds`, `:lookup`, `:program`, `:prefix`, `:scroll`, `:format` | as Python | `Keymap`, `parseKeymap`, `resolve`, `.binds`, `.lookup`, `.program`, `.prefix`, `.scroll`, `.format` |
| `TerminalKeys` | `hotty.TerminalKeys` | `TERMINAL_KEYS` | `TERMINAL_KEYS` | `TERMINAL_KEYS` | `TERMINAL_KEYS` |
| `Platform`, `PlatformOf`, `DefaultEdit`, `ReadKey` | `hotty.Platform{Mod, Edit}`, `hotty.PlatformOf`, `hotty.DefaultEdit`, `(Platform).ReadKey` | `Platform(mod, edit)`, `platform_of`, `default_edit`, `read_key` | a table `{ mod = …, edit = … }`, `platform_of`, `default_edit`, `read_key` | as Python | `{ mod, edit }`, `platformOf`, `defaultEdit`, `readKey` |
| `Field`, `Do`, `Type` | `hottyedit.Field`, `(*Field).Do`, `.Type` | `Field`, `.do`, `.type` | `field`, `:do_action` (`do` is a keyword), `:type` | `Field`, `do_action`, `type_text` | `Field`, `.do`, `.type` |

In the vectors, every name is in snake case: `place_at`, `keep_cursor`,
`fit_rows`, `cell_css`.

## Appendix B. Rationale

- **Why two layers.** What goes on the wire is the same in every program;
  how a program reads its terminal is not. A pure wire layer can be checked
  as data, in every language, by the same vectors, and an environment that
  does its own I/O (an editor, a page) still uses all of it.
- **Why shared defaults.** A program ported from one language to another
  should not start filling its input with replies, or wait a different time
  for an answer. Defaults are behaviour, and behaviour is what the program's
  user sees.
- **Why the Scanner and the Detector are in the wire layer.** Both are
  pure: bytes in, segments out; inputs and times in, a decision out. Both
  are where SDKs went wrong first: a reply cut across two reads read as
  keys, a DA1 left for the shell, a terminal that is a host taken for one
  that is not. As state machines with vectors, they are right once, in every
  language.
- **Why `Doc` and `Place` answer errors and nothing else does.** A refused
  document or placement leaves a hole in the program's screen, which the
  program must hear about; a delta that names a missing element is the
  program's bug, which a test finds. Asking for every reply would fill the
  input with them.
- **Why a `Q` given wins over `N`.** Options in most languages have no
  order: keyword arguments, a table, a struct. The rule that needs no order
  is the one every SDK can follow.
- **Why the SDK has a keymap of its own.** SPEC.md's default keymap is
  small, so that a program that names no keys loses none. A program that
  edits fields in cells, though, has a keymap already, usually its
  framework's, and wants its surfaces' fields to edit the same. Bubble
  Tea's is the one the reference SDK's programs have; one keymap shared by
  every SDK keeps a program the same in every language (§2.3).
- **Why a body decodes whole or not at all.** A codec generated from the
  types (Go's msgp, Rust's serde) stops at the first field of the wrong
  type. A rule every SDK can keep is the one such a codec keeps, and a
  host that sends a wrong type fails the vectors instead.
- **Why presentation is out.** hotty carries any HTML. How one program
  presents its content is that program's choice, and an SDK that builds
  documents would make it for every program, in each language differently.

## Appendix C. Moving from 0.2

Informative. hotty 0.3 breaks 0.2 (SPEC.md §15), as a fold of what 0.2
grew. A program and its SDK move together, and its tests' fake hosts with
them. These are the places a port goes wrong:

- **A delta is a list of ops.** `Delta(surface, op, target, key,
  payload)` became `Delta(surface, ops…)`, the ops built by `Text`,
  `Attr` and the rest (§3.4.3; in Go `OpText`, `OpAttr`, …). A frame's changes to one surface go in one
  delta, which the host applies as one; the one-op shorthands (`SetText`,
  …) are still there for a single change.
- **The flags stay on the surface.** `p`, `f` and `v` hold until the
  program turns them off (SPEC.md §5.2). A program that placed a surface
  without `Press` to stop its presses now sends `Press: Off`. A Session
  (§4.3) sends them when they change.
- **Ask by feature.** `Supports`, `Sends`, `Drags`, `Hovers`, `Views` and
  the capabilities' `Passthrough`, `Scroll`, `Steps`, `Drop`, `Count` and
  `Menu` are gone: everything a 0.3 host does, it does, and `HasFeature(c,
  feature)` answers for `passthrough`, `hover`, `count` and `menu`.
- **The cell is in CSS pixels.** `Cell`, in device pixels, is gone, and
  `CellCSS` is the host's own field. There is no `resize`: a new font is a
  `caps` event with the new `CellCSS`, which `Merge` folds in.
- **A submit's fields are pairs**, in order: two fields of one name are
  two pairs. Code that looked a name up in a map looks it up in the pairs.
- **Modifiers are bits.** A drag's `Keys` list is `Mods`, and clicks and
  presses carry it too: `Mods & ModShift` for Shift.
- **An empty target is absent.** `Target` reads the same, empty, but a
  host's `t=` with nothing after it is gone from the wire, and a fake host
  that sends one is out of date.
- **No key sequences in the host.** `data-key` and the capabilities'
  `Keys` are gone. A program binds its leader to `prefix` (SPEC.md §10.5),
  reads the keys after it, and sends `a=focus` when the sequence ends. Its
  menu goes to `Menu` and `MenuShow`, as in the cells.
- **A menu is a tree.** `MenuLevel` is `MenuTree`, with no `Seq`, and an
  item has a `Name` and its own `Items` in place of `Group`. A program
  sends its tree when what it can do changes, `MenuShow(seq)` for each key
  of a sequence, and `MenuHide()` when it ends, where 0.2 sent a level for
  each key and hid it with an empty `Menu`. A pick, from a
  multiplexer's palette or an agent, reaches it as `command` with the
  item's name.
- **Errors only, unless asked.** A builder sends no `q` where the wire
  implies it, and `N(n)` alone asks for the reply. A fake host must answer
  a command without `q` only with an error.

## Appendix D. Moving from 0.3

Informative. hotty 0.4 breaks 0.3 (SPEC.md §15): it names keys as *myelin
key names* 0.1 does (SPEC.md §10.4). A program and its SDK move together,
and its tests' fake hosts with them. These are the places a port goes
wrong:

- **Every key's name changes.** `Control+a` is `ctrl+a`, `Meta` is
  `super`, `ArrowLeft` is `arrow_left`, `PageDown` is `page_down`, `Enter`
  is `enter` and `Space` is `space`. A program's `data-keys`, its menu's
  keys and `seq`, and the keys its tests expect all move. myelinkey's
  `ParseHotty` reads the 0.3 names, for a script that ports them.
- **`mod`, not `Mod`.** A binding writes `mod+s`, and one with `mod` and
  `ctrl` or `super` as well is refused. The capabilities' `mod` is
  `"ctrl"` or `"super"`, and `edit` names its keys the new way. `ParseKey`
  refuses `mod`: a binding read on a host's Platform goes through
  `ReadKey`.
- **A capital is no Shift.** A host reads `G` as `g`, so a binding of
  Shift with a letter writes `shift+g`, and a 0.3 `Control+A` is
  `ctrl+shift+a`. The text a key types is its `KeyText`: `shift+a` types
  `A`.
- **Shift falls back only from a key that is not a character.** An
  unbound `shift+arrow_left` is looked up as `arrow_left`, but `shift+g`
  is a key of its own.
- **A press can have two names.** A host that sees the key itself names
  it by its place and by what it types (`ctrl+key_w`, `ctrl+w`), and
  looks the place up first (`Binds`). A host that reads bytes names what
  they carry.
- **The modifier bits keep their values,** under myelin's names:
  `ModControl` is `ModCtrl`, and `ModMeta` is `ModSuper`.
