# Terset format, version 1

TERse SETs. Terset writes a JSON object or array as short lines. Any list of
two or more objects becomes one tab-separated table when that is smaller
than compact JSON.

## Data

The encoder accepts a root JSON object or array. Its values are plain
objects, dense arrays, strings, booleans, null and finite numbers.
Undefined, cycles, symbol keys, non-plain objects and depth above 500 are
errors. Numeric `-0` is an explicit error. Text `"-0"` round-trips. An empty
root object is `{}`. Key order is JavaScript own enumerable key order,
including the integer-key ordering JSON uses.

## Lines and cells

Each root field is `key: value`, or `key:` followed by a table header and
rows. A root array is a table with no name line, or compact JSON. Lines use
LF. There is no added final LF. An absent final one-column cell is an empty
row, so that text ends with LF. Simple keys match
`[A-Za-z_$][A-Za-z0-9_$.-]*`. Other keys are JSON strings.

A table has one tab-separated header, with any first field. Each row has
exactly that many tab-separated cells, including trailing empty cells.
An empty cell means the field is absent. Empty text is `""`. Null is `null`.
Numbers, booleans and null keep their types in every ordinary column.

A leading bare `id` header marks string ids. Its cells are bare strings,
even `1`, `true` or `007`. This needs every row to start with a nonempty
string id with no edge whitespace and no troublesome characters. For any
other first `id` column the header is written `"id"`, which selects
ordinary typed cells. An `id` column that is not first uses ordinary cells.

Other strings are written bare unless they are empty, have edge
whitespace, start with a quote, bracket or brace, contain control
characters, DEL, U+0085, U+2028 or U+2029, or look numeric. Numeric-looking
includes `007`, `+5`, `1e3`, `.5` and `-0`. The words true, false, null,
undefined, NaN, Infinity and -Infinity are quoted as text. Other scalars and
nested values are compact JSON. Tabs and LF inside text use JSON escapes.
Lone surrogate code units are JSON-escaped, so UTF-8 transport cannot
replace them. Astral pairs are kept as they are.

No legend is written into the data. The package exports one rules line,
`RULES`, to put once in the system prompt, outside the data:

> Tables use tab-separated headers and rows. Empty cells mean absent. JSON quotes distinguish numeric, boolean or null text from scalars. Nested cells use JSON. A leading bare id header marks string IDs.

## Table selection and fallback

An array of two or more objects becomes a table when the table is strictly
shorter than the array's compact JSON in UTF-8 bytes. The header is built
in row order by appending first-seen fields. Each row's keys must follow
that order. Fields may be absent, the first field included. Empty object
rows are allowed when the list has at least one field. No id is required.
Numeric ids, booleans, null and mixed-type columns are supported.

There is one fallback. The whole list stays compact JSON if it has fewer
than two rows, a row that is not an object, no fields, a key order that
does not fit the header, or a table that is not smaller. Inside a root
object this is a `key: JSON` line. At the root it is the JSON array itself.

Tables are chosen only at the root or for direct fields of a root object.
Nested lists and objects stay compact JSON cells. Terset does no sorting,
no column reordering, no constant hoisting and no nested shaping.

One-column strings that look like a root `key:` line are JSON-quoted. If an
id looks like such a line, the header uses the typed `"id"` mode instead.
Root field lines hold no raw tabs. Rows with many columns are recognized by
the exact header width. One-column tables end at a root field line. This
keeps boundaries clear with no counts, row markers or indentation.

## Decoder

The decoder accepts only the encoder's canonical output. It parses JSON
with JSON.parse and never evaluates text. It rejects duplicate keys and
extra cells. It re-encodes its result to reject noncanonical input,
duplicate nested keys included. Properties are defined as own data, so
`__proto__` and `constructor` cannot change an object's prototype. The
depth limit also applies to JSON cells. The same input always gives
byte-identical output.

## Limits

Numeric negative zero is rejected. Depth above 500 is rejected. Text
`"-0"`, lone surrogates, 100 kB strings and 5,000 rows round-trip. Large
finite numbers keep their IEEE-754 value. Precision lost before encoding
cannot come back. NFC and NFD text stay different code units, in values and
in keys. There is no other Unicode or size limit.
