# hotty conformance vectors

`vectors.json` is what a host and an SDK must do, as data.

The host sections (`vectors`, `wire`) are run by two hosts that share no
code:
- hotty-blitz: `crates/hotty-blitz/tests/conformance.rs`, which runs the
  `keys`, `keymap` and `edit` sections too, the edits on a real surface;
- xterm-addon-hotty: `tests/e2e/conformance.spec.ts` and
  `tests/unit/conformance.test.ts` (with `keys`, `keymap` and `edit`).

hotty-go's test host runs `vectors` too (`hottytest/conformance_test.go`).

The SDK sections (`wire`, `build`, `encode`, `decode`, `scan`, `detect`,
`keys`, `keymap`, `edit`; SDK.md §5) are run by:
- the Python client: `clients/python/test_vectors.py`, every section;
- hotty-go: `hotty_test.go` (`wire`), `vectors_test.go` (`build`,
  `encode`, `decode`, `scan`, `detect`, `keys`, `keymap`) and
  `hottyedit/field_test.go` (`edit`);
- hotty-lua: `tests/vectors.lua`, every section, under each of its
  runtimes.

The Python client and hotty-lua count code points, not grapheme clusters
(SDK.md §4.6), and skip the vectors that require `graphemes`.

A disagreement is either a bug or a line of `SPEC.md` or `SDK.md` that says
too little.

## Format

```
{ "version": "0.4",
  "vectors": [ { "name": …, "steps": [ step, … ] } ],
  "wire":    [ { "name": …, "stream": …, "commands": [ … ], "invalid": n } ],
  "build":   [ … ], "encode": [ … ], "decode": [ … ], "scan": [ … ],
  "detect":  [ … ] }
```

**Numbers.** A number in a body has a type (SPEC §3.3). In this file, a
number written with a fraction or an exponent (`720.0`) is a float, and
any other an int, so `{"w": 720.0}` is not matched by an int 720. A host's
runner reads the file keeping the two apart: Python's `json` does, Go's
with `UseNumber`, Rust's `serde_json` (`is_f64`), and JavaScript's
`JSON.parse` with a reviver that reads the source text.

Each vector starts on a fresh host, with `mod` `ctrl` and the default
edit shortcuts (SPEC §10.2, §10.4), whatever its platform. A vector with
`"requires"` applies only to a host that has what it names, and others skip
it. A list names several, all required:
- a feature (SPEC §4): `"passthrough"` (§9.3), `"hover"` (§9.4),
  `"count"` (§9), `"menu"` (§10.7);
- `"touch"`, a host that takes touch (SPEC §9.1, §16);
- `"commands"`, a host with a terminal that takes edit commands (§10.6);
- `"mouse"`, a host with a terminal that reports the mouse (§9.6), whose
  runner tells what it reported (Touch, below);
- `"undo"`, a host whose text fields keep a history and cut (§10.2);
- `"pressed"`, a host that sees the key the user pressed (§10.4): its
  runner offers a key step's key as pressed, by both its names, and then
  as the bytes a terminal sends for it, as the host would;
- `"scroll"` (§5.3), `"steps"`, `"drop"` (§9.1) and `"view"` (§9.5): what
  every host of this version does, named so that a host that lays nothing
  out, an SDK's test host (SDK.md §4.4), skips it. Nor do test hosts keep a
  history, so they skip `"undo"`.

Its steps come in seven kinds.

**Send** `{ "send": {control}, "payload": text?, "body": value?, "reply":
…, "events": … }`: the command goes to the host as the program would send
it. It is base64-encoded and chunked as needed. `payload` is UTF-8 text;
`body` is a msgpack body (a delta's ops, a menu), as JSON: the runner
writes it as msgpack, its numbers as Numbers (above) say, and sends that.
- `"reply"` absent: replies are not checked.
- `"reply": null`: the command must produce no reply.
- `"reply": {…}`: the first reply (`a=ok` or `a=err`) must match. Every key
  but `code`, `detail`, `body` and `types` is compared with the reply's
  control, and one whose value is `null` must be absent from it; `code` and `detail` with the body of an `a=err`. `body` gives
  fields the body of an `a=ok` must have, equal as values (Numbers, above);
  fields it does not name are not checked. `types` gives the type of
  each field of the body (SPEC §3.3): `"int"`, `"float"`, `"str"`, `"bool"`,
  `[T]` an array of T, `{"name": T, …}` a map with those fields, and
  `{"*": T}` a map of any names to T. The body must decode (SDK.md §3.9) and
  each field it has be of its type; a field it lacks, or one `types` does
  not name, is not checked.
- `"events"`: the events the command makes the host send, as for a pointer
  step (below). Some follow the command by a frame or a load (`fit`, SPEC
  §5.2), so a runner waits for them, and then a frame or two more to see
  one too many.

**Inspect** `{ "inspect": [surface, id], "expect": … }`: the host reports the
element as the program wrote it:
- `tag`;
- `attrs`: the attributes, with the program's values (a `cid:` URL stays
  `cid:` whatever the host loads);
- `text`: the text content;
- `children`: the child elements as `[tag, id or null, text]`.

`"expect": null` means there is no such element. Otherwise every key of
`expect` must equal the reported value exactly (keys it leaves out are not
checked).

**Pointer** `{ "pointer": "move" | "down" | "up" | "leave", "s": surface,
"at": …, "keys": [ … ], "events": [ … ] }`: a mouse, as the user would move it (SPEC
§9.1, §16).
- `"move"` puts the pointer on surface `s` at `at`: an element's id, for
  the centre of its box, or `[c, r]`, for the centre of that cell of the
  surface, counted from its top left cell (§9.1). A cell can lie outside
  the window, outside the surface, and over another surface. Moving onto
  another surface `s`, with no button down, takes the pointer off the one
  it was on, as a terminal does: that one is left first (SPEC §9.4).
- `"down"` and `"up"` press and release the primary button where the
  pointer is.
- `"wheel"` turns the wheel where the pointer is, by `"by": [c, r]`, columns
  and rows of the surface (positive: right and down), a cell's pixels each.
  It is one gesture: a runner may send it as several wheel events (a
  browser scrolls no more than a page for one), lets what it scrolls come
  to rest before the step's events are read, and lets the gesture end
  before the next step, so each wheel step is a gesture of its own.
- `"leave"` takes the pointer out of the terminal's window.
- `"keys"`: the modifier keys held during the step (`shift`, `ctrl`, `alt`,
  `super`); none when absent.
- `"count"`, for `"down"` and `"up"`: the platform's count of this press
  and its release in a run of clicks, as an OS hands it over with them
  (macOS's `clickCount`, GTK's `n_press`, the DOM's `detail`); 1 when
  absent. A runner gives it to the host as the host's platform would.
- `"through"`: `true` if the step passes through the surface (SPEC §9.3),
  `false` if the surface gets it. Absent: not checked.
- `"terminal"`, for a wheel: `true` if the gesture went on to the terminal,
  as over the cells beneath (SPEC §9), `false` if it did not (the document
  scrolled, or `overscroll-behavior` stopped it, §5.3). Absent: not
  checked.
- `"events"`: every event (`a=ev`) the step makes the host send, in order,
  and nothing more. Each is compared as a reply is: every key but `detail`
  with the event's control, `null` for a key it must not have (`"t":
  null`: the event names no element, SPEC §9), and `detail`, when present, with its body,
  decoded: equal as values, each of its type (Numbers, above), and `null`
  when the event has none. Absent: events are not checked.

**Touch** `{ "touch": "down" | "move" | "up", "s": surface, "at": …,
"keys": [ … ], "terminal": …, "mouse": …, "events": [ … ] }`: one finger,
as the user would touch the terminal (SPEC §9.1, §9.6, §16). `"down"`
touches surface `s` at `at`, a cell `[c, r]` as for a pointer step, or,
without `s`, the cells: the screen's cell `[c, r]`, counted from 0 at its
top left. `"move"` moves the finger to
`at`, in moves of a runner's choosing, along the straight line from where
it was, so it passes the host's tap slop in that line's direction (the
vectors move along a row or a column, so where the finger enters an
element does not depend on those moves); `"up"`
lifts it. `"keys"` are the modifier keys held, as for a pointer step.
`"terminal"`, for a move: `true` if the touch went on to the terminal as a
scroll (SPEC §9), `false` if the surface or the program's mouse took it
(§9.6); absent, not checked. `"mouse"`: what the terminal reported of the
mouse's primary button for the step (§9.6), each `[kind, c, r]`, `kind`
`"down"`, `"move"` or `"up"` on the screen's cell `[c, r]`, counted from
0; `[]` for nothing, and absent, not checked. How many moves it reports
depends on the runner's moves, so a runner compares the two lists keeping
only the last move of each run of moves in each. `"events"` as for a
pointer step: what the step makes the host send.

**Key** `{ "key": name, "keys": [ … ], "terminal": …, "events": [ … ] }`: a
key pressed and released where the keyboard is (SPEC §10): on the surface
that has it, or else on the terminal. `name` is the key as the DOM's
`KeyboardEvent.key` has it (`"Enter"`, `"Tab"`, `"End"`, `"ArrowDown"`,
`"B"`), on a US layout, where its physical key is the one a US keyboard
has it on. `"keys"` are the modifier keys held, as for a pointer step. The
host names the key as SPEC §10.4 does. A runner that gives the host keys by
name names them so; one that presses keys on a terminal gets them from the
terminal's encoding, which, for the keys the vectors press, names the same
key, but the physical name of a character's key (`ctrl+key_w`, beside
`ctrl+w`). `"terminal"`:
`true` if the key reached the program as terminal input (§10.2), `false` if
the surface used it; absent, not checked. `"events"` as for a pointer step.

**Write** `{ "write": text }`: the program writes text to the terminal,
bytes that are not hotty's. The vectors write `CSI ? 1002 h` and `CSI ?
1006 h` together, so that the terminal reports the mouse's buttons and
drags in the SGR encoding, `CSI ? 1002 l`, so that it reports them no
longer, and `CSI ? 1049 h` and `CSI ? 1049 l`, the alternate screen and
back. A runner whose host has no terminal of its own does to the host what
a terminal does on these.

**Menu** `{ "menu": "shown" | "pick", "expect": …, "name": str, "events":
[ … ] }`: the program's menu, in a host that has `menu` (SPEC §10.7, §16).
- `"shown"`: the level the host shows, once any wait it makes before
  showing one has passed. `"expect"` is its items in order, each `{"key",
  "label", "group", "enabled"}`: the item's key and label as the program
  sent them, whether it is a group, and whether it is enabled; `null` when
  none shows.
- `"pick"`: the item named `"name"` is picked, as from a multiplexer's
  palette or a command palette the host already has. A host that offers
  no picks skips the vector.
  `"events"` as for a pointer step: what the pick makes the host send,
  `[]` for nothing.

The documents of pointer steps size their elements in cells
(`--hotty-cell-w`, `--hotty-cell-h`, SPEC §8), and point at centres that lie
well inside a cell, so the cell reported is the same on every host. Wheels
and keys scroll a document to an end, or bring an element into view where
any alignment ends at the same offset, never by a distance: how far a
wheel's notch or an arrow key moves differs between hosts.

A host never compresses what it sends (SPEC §3.3): a runner checks that no
reply or event it reads carries `o`.

**Wire vectors** check the envelope alone. The `stream` (JSON-escaped bytes)
must decode into `commands`: listed control keys equal, `m` and `o` never
present, and the payload is UTF-8 text. It must also report `invalid`
malformed or interrupted commands.

The vectors check the document, the events and the wire, not pixels (§5.3:
pixels may differ between hosts).

## The SDK sections

They check an SDK's wire layer (SDK.md §3). Names in them are canonical, in
snake case (SDK.md Appendix A): a runner maps `set_text`, `keep_cursor` and
`fit_rows` to its own. Strings are the UTF-8 of the JSON string. `null`
means absent: Go's zero value, Python's `None`. A vector with `"requires"`
applies to an SDK that implements what it names; a runner declares what it
implements, and skips the rest:

| `requires` | what it names |
| --- | --- |
| `options.unordered` | a `q` given wins over the `q` that `n` implies, whatever their order |
| `decode.abort-count` | a malformed message that aborts a chunked one counts twice (SPEC §3.7) |
| `decode.unterminated` | `Feed` takes a sequence without its terminator |
| `decode.max` | `Decoder.Max`: past it a message is malformed, counted once, and its further chunks dropped (SDK.md §3.6) |
| `scanner.da1` | the Scanner's `da1` segments |
| `graphemes` | text is split into grapheme clusters (UAX #29), not code points (SDK.md §4.6) |

**Build** `{ "build": builder, "args": {…}, "options": {…}?, "out": [ … ] }`:
the runner calls the builder with `args`, and with the reply options `n`
and `q`, `detached`, `scroll` and `late` from `options`, which have no
order. `sync` takes `args.commands`, each a `{ "build", "args", "options" }`
of its own. The output, split into raw bytes and hotty sequences, must be
`out`:
- `{ "raw": text }`: bytes that are not hotty's, such as `PlaceAt`'s cursor
  moves, `Query`'s DA1 and `Sync`'s brackets;
- `{ "cmd": { "control": {…}, "payload": text? } }`: one command, decoded.
  Its control has exactly these keys (`m` and `o` never), and its payload is
  this text, empty when absent, or the bytes of `"payload_b64"`: a delta's
  or a menu's msgpack, which `"body"` shows as JSON, as a Decode vector's
  `bodies` do (below), and runners ignore.

**Encode** `{ "control": [[key, value], …], "payload": text | "payload_b64":
…, "bytes"?: text, "chunks"?: [ { "control": text, "len": n } ] }`: the
runner calls `Encode` with the control in that order. With `bytes`, the
output must be exactly that: these are payloads that cannot be compressed,
under 256 bytes or random. With `chunks`, it is split into sequences, and
each one's control (between `ESC ] 7279 ;` and `;`) and base64 length must
match. Every case must also decode back to the payload, with the control's
keys in order, but `m` and `o`, which are Encode's (SDK.md §3.3).

**Decode** `{ "seqs": [ text, … ], "bodies"?: [ … ], "max"?: n,
"results": [ … ], "invalid": n, "messages": [ … ] }`: each sequence goes to
one Decoder's `Feed`, in order. `max`, when present, is the Decoder's `Max`
(SDK.md §3.6).
`results` are its results (`not_hotty`, `partial`, `complete`, `invalid`),
and `invalid` its count after the last. `messages` are the complete ones,
in order:
- `control`: exactly these keys and values;
- `reply`: `Reply`'s fields (`ok`, `re`, `n`, `surface`, `cols`, `rows`,
  `code`, `detail`, `op`);
- `event`: `Event`'s fields and accessors (`surface`, `kind`, `target`,
  `value`, `checked`, `fields` as a list of `[name, value]`, `mods`, `link`
  with `href` and `url`, `fit_rows`, `drag` with `c`, `r`, `x`, `y`, `tx`
  and `ty`, `hover` with `c`, `r` and `out`, `area`, `count`, `view` with
  `areas` and `at_end`, `command` with `name`, `key`, `text` and
  `has_text`);
- `changed`: a `caps` event's `Changed`, with `fields` the names of
  exactly the fields it has, as the body names them (`cellcss`), and those fields as `caps` checks them;
- `caps`: `Caps`'s fields (`cell_css` with `w` and `h`, `features`), and
  its answers: `has` maps a feature to the answer, and `light`. `null`: the
  reply carries none, as when its body does not decode.

Only the keys listed are checked, but `view`'s `areas`, which is compared
whole: the same ids, each with the same area. Numbers compare as
numbers.

A host's body in the sequences is msgpack in base64, which no one reads,
so `bodies` shows them as JSON (Numbers, above), one for each sequence:
- the body of the message the sequence starts, its maps in their order;
- `null` where the sequence starts no message, or one with no body;
- `{"hex": …}` for bytes the script below does not write: a body that is
  not one msgpack value, one in another of its types' forms than the
  smallest (an int wider than it needs, a float of 32 bits), or one holding
  what no host sends (a nil, a key that is not a string, an extension).

Runners ignore it. `scripts/bodies.py` writes the base64 in `seqs` from
it, splitting a chunked body where its chunks split it before, and a Build
vector's `payload_b64` from its `body`, and with `--check` fails when they
differ. The vector about base64 itself
writes its sequences by hand, and has no `bodies`.

**Scan** `{ "stream": text, "segments": [ … ], "held"?: text, "flush"?: [
… ], "invalid"?: n }`: the stream goes to a Scanner, which looks for DA1
answers only when the vector requires `scanner.da1`. The runner feeds it
whole, cut in two at every offset (or, in a long stream, at every offset
near its ends and at a stride between), and a byte at a time; each time,
the segments must be `segments` (adjacent `pass` segments merged), what it
holds after the stream `held` (none when absent), what `Flush` returns
`flush` (none when absent), and its count of dropped sequences `invalid`
(0 when absent). A segment is `{ "pass": text }`, `{ "osc": text }` or `{
"da1": text }`.

**Keys** checks key names (SPEC §10.4, SDK.md §3.10), in four forms:
- `{ "input": text, "keys": [ … ] }`: `DecodeKeys(input)` must return
  `keys`, one entry for each key, `null` for input that is no key;
- `{ "key": text, "canon": text | null }`: `ParseKey(key)` must return
  `canon`, `null` when the name does not parse. These are myelin key
  names 0.1's `read` vectors (its conformance/keys.json), as they are;
- `{ "key": text, "mod": "ctrl" | "super", "canon": text | null }`:
  `ReadKey` on a Platform whose `mod` is `mod` must return `canon`, `null`
  when the name does not parse. These are myelin key names 0.1's `mod`
  vectors that set no other option (strict reading or shifted capitals,
  which §10.4 does not use), as they are;
- `{ "key": text, "text": text | null }`: `KeyText(key)` must return
  `text`, `null` for none.

**Keymap** checks keymaps (SPEC §10.2, SDK.md §3.10), each on the
Platform in `"platform": { "mod"?, "edit"? }`, the zero Platform (`ctrl`,
the default edit shortcuts) where absent, in three forms:
- `{ "parse": text, "format": text }`: `ParseKeymap(platform, parse).Format()` must be
  `format`; with `"terminal_keys": true` and no `parse`, `TerminalKeys`
  formatted must be `format`;
- `{ "multiline": bool, "terminal_keys"?: true, "keys": [ … ], "lookup": {
  key: action | null } }`: `Resolve(platform, multiline, …)` with `TerminalKeys`
  first when `terminal_keys` is true, then each of `keys`. `Lookup` of each
  key of `lookup` must return its value: an action, `"insert"`, or `null`
  when the key is not the field's. `Selects` of each key of `selects`, and
  `Binds` of each key of `binds`, if there is one, must return its value;
- `{ "keys": [ … ], "program"?: { key: bool }, "prefix"?: { key: bool },
  "scroll"?: { key: action | null } }`: an element's keymap outside a text
  field, with no default keymap: `ParseKeymap` of `keys` joined with a
  space, the root's first. `Program` of each key of `program`, `Prefix` of
  each key of `prefix`, and `Scroll` of each key of `scroll`, must return
  its value, `null` for none.

**Edit** `{ "field": { "value", "caret", "anchor"?, "multiline"?,
"password"?, "rows"? }, "steps": [ … ] }`: a Field (SDK.md §4.6) made from
`field`, absent keys false, 1 for `rows`, and nothing selected when there is
no `anchor`. Each step is `{ "do": action }`, `{ "extend": action }`,
`{ "type": text }` or `{ "select": [anchor, caret] }`, and after it every
key the step lists must equal the Field's: `value`, `caret` and `anchor` (in
characters; with nothing selected, the anchor is the caret), and `changed`,
what `Do`, `Extend` or `Type` returned (false for `select`).
`"requires": ["graphemes"]` marks the vectors whose text has a character of
more than one code point, other than CR LF.

**Detect** `{ "n"?: n, "late"?: true, "steps": [ … ], "caps"?: {…} }`: one
Detector, with the query's number `n` (1 when absent), asking for a late
answer when `late` is true. Each step happens at time `at`, in
milliseconds, and is one of `start`, `da1`, `osc` (a sequence, decoded, and
given to `Reply`; its `body` shows the body, as a Decode vector's `bodies`
do, and runners ignore it), `tick` and `end`. After each, every key the step lists
must equal the Detector's: `took` (what `DA1` or `Reply` returned), `state`
(`detecting`, `native`, `text`), `decided`, `done`, and `deadline` (`null`
when there is none). `caps` is checked against the capabilities at the
end; `null`: there are none, as when the terminal is no host the program
can use.
