# Data entry — which pattern for which job

Which editing shape a job takes, and the laws that hold inside each. Which entry draws it is
[catalog.md](./catalog.md); its props are `dist/<module>.d.ts`.

## The decision table

| The job | The shape |
|---|---|
| Edit a field of an existing record, in place | the control's own `onSave` |
| Fill a NEW record, or a draft the reader may abandon | `FormField` fieldset, in a dialog or a page |
| Pick one of a known set | by option count — see below |
| Find an existing thing or make one | the `Combobox` family |
| Add repeated structured rows | `ChargeLines`, a priced band |
| Give a file's columns a home each | `ColumnMap` |
| Move a record to its next stage | a handoff: a stage transition, never a message |
| Attach files | `InlineFiles` on a row, `RecordFiles` for a record's section, the `FilesEditor` compound where the section owns a verb bar |

**The right-input-per-field law.** A field's control follows its DATA type — a date takes a date
editor, a member a member editor, a bounded set a choice control. A text box that
accepts anything is what a field falls back to when nobody decided what it holds.

**A date editor's value is a canonical ISO string; `@lotics/ui/date_picker_value` reads and builds
one.** A second copy of that conversion disagrees on the edges it exists for.

**An incoming column is MAPPED, never matched by name** — `ColumnMap` draws each guess AS a guess,
refuses two columns onto one field, and names what nothing reached.

**A dialog is a draft, so it does not derive the ERROR per keystroke**: validate on commit, or the
reader is corrected while still typing.

## A field that saves itself — the preferred way to edit an existing record

**ONE AXIS: who owns the draft.** A form owns it and commits in its footer (`onValueChange`); a
FIELD owns it and writes it itself (`onSave` — async, saving and a refusal in place, Escape
reverts, blur or Enter commits, the write registered so an act in the same gesture waits for it).
One component, one sheet, one anatomy either way; both together does not compile; flipping an
instance remounts it.

**Everything a record states is changed among its facts.** A contact is also reached from under
the name (`RecordPage.contacts`); that strip acts, it does not correct.

**The box does not move, because nothing swaps** — the control is the value at rest and under the
caret. Read-only prose rests as the absent mark on one line. An editable link keeps its DOOR as a
verb on the field's surface: one target per destination, none where it points at nothing.

**`numberOfLines` declares what ENTER means**, and wrapping does not: one line commits on Enter,
more makes Enter a newline and the commit a blur or a button.

**The gate cannot see a CAPTURED object**: an act takes an ID and re-reads the row, or it commits
against a value that has since changed. A field counts what it saved as current until `value`
catches up, so a lagging parent does not flicker the old value back.

**THE FRAME IS THE PERMISSION, and there is ONE rest state** — every editor is framed before it is
touched, and a value nothing can change draws no box (`InlineValue`, or the register's cell). A
reading beside a field takes the control's own inset, or it jumps sideways on press.

**With `actions` the SURFACE owns the box** and the control surrenders its own: one box, never a
field inside a field. Pass the verb unconditionally and `disabled` it when idle, or the field
resizes as the value fills.

**`clearable` means the reader may EMPTY the field** — the ✕ on text, the named empty ROW on a
select, and on a date or number the licence for an emptied entry to commit rather than revert.

**A numeric `format` states what the box prints AND what its parser takes back**, so a unit `Intl`
cannot write goes BESIDE the field: inside the value, "8,200 kg" commits 8.2.

**A NOTE IS ALWAYS MULTI-LINE, and its reserve is a FLOOR** — three lines at rest holding anything
or nothing, growing with the prose, never a fixed tall box. In a table the CELL reserves the WIDTH
too: the editor never states its own, or a long value re-lays the column. **So does the read-only
spelling**, or a note reads whole only where the reader may edit it.

**A picker over a select FIELD keeps the field's colours** — hand it the field (`fieldOptions`);
mapping its options drops them silently. The badge is OPT-IN (`badge: "tonal" | "dot"`) because the
colours have to MEAN something: assigned BY POSITION they paint an in-transit order red.

## Choosing a CHOICE control — by option count, not by taste

| Options | Control |
|---|---|
| yes/no | a `Switch` with the field's word — never a two-option select |
| 2–4 peers, all worth showing | a chip group or radio set |
| 5–12 | a select |
| more, or growing | a combobox you type to narrow; `onSearchChange` hands the query to the caller and turns the local filter off — the rows that came back ARE the answer |
| a bounded set the reader ticks several of | multi-select |

**"No selection" is the PLACEHOLDER, never an option whose value is the empty string.**

**A chip may carry an ICON, and it earns one only when the set differs in KIND** — a glyph on every
chip of one kind is decoration.

**A choice that GATES the form is a fourth answer, and the answer is usually "don't ask".** Derive
it, or ask it once at the top and remember it.

**A `ChoiceStrip` is NOT a field control** — it chooses a mode of the view, not a value of the
record.

## A value rendered TWICE is the bug — not the resident editor

Only the prose a PERSON wrote is editable. **An EMPTY value still shows its FIELD** — absence is
information, and a field that vanishes when unset cannot be filled.

**A record's facts are the worked case** — a fact with `onSave` draws the field for its kind
(a boolean the switch), one without it the register's cell, and an empty field waits behind the
grid's fold; no Edit act, no mode, nothing to disagree.

## Forms

Fields lay out on `Stack columns={{ minWidth }}`, which reflows on the CONTAINER so no breakpoint
is named; a field whose value is longer than a peer's takes the whole row with `FormField span`.

**A form grid states its COLUMN gutter alone** — `gap="none" gapX={16}`: the row gutter is
`FormField`'s own trailing rhythm, and one knob for both axes either pays it twice or closes the
columns.

**A field's gloss sits UNDER its control**, the warning and the error after it: every field then
has the SAME block above its control, so a row of paired controls is level by construction.

**A form marks ONE side, the MINORITY** — `FormMarksContext` picks `optional` (default) or
`required`; a word beside ten labels marks nothing.

**Browser autofill is suppressed on search and kept on forms.** A control that owns a suggestion
list must suppress the browser's, or two lists open over each other.

## Line items and ledgers

A priced band is `ChargeLines`: create → preview → edit, and its closing row takes the pack's
"Total" rather than echoing the `heading` it sits under. **Money is ONE
token**: it breaks neither before the currency nor mid-figure, which prints a DIFFERENT number, so
the box grows or the name beside it gives way. A line is edited inside the DOCUMENT it belongs to.

## Handoff — a stage transition, never a message

Two shapes, decided by whether the receiving side is the same entity.

**(A) Same entity** — the record moves to its next stage and the checklist is its gate, and the act
confirms (`advance.confirm`) only where the move COSTS something.
**ROWS TICK ONE AT A TIME.** **A CHECKLIST
REPORTS; IT DOES NOT COLLECT** — a row that asks for a value is a field. **A status reports what is
OUTSTANDING, never what is settled.**

**(B) Different entity** — the handoff creates a record on the other side, and the two are linked.
The sending screen shows what was sent, not a copy of the receiving screen.


## Tags, dispositions and lifecycle

A tag or multi-value field is a multi-select; on a record surface the same field is its inline form.

**Lifecycle status is ASYMMETRIC by phase** — the control offers the stages reachable from where
the record IS, never the whole vocabulary.

## Files

**Cardinality is a property of the FIELD, and it changes the verb**: "Add file" against "Replace
file". **Nothing to attach TO yet is a real state**, and it is a sentence, not a disabled control.

### Intake — files land THREE ways, on every surface that holds files

Three paths, ONE named door. The drag and the paste come from one drop target wrapping the region,
empty or not. The named door follows what the section HOLDS: nothing, and it is the empty line, the
heading standing its verb down; something, and it is the heading's Add and the body draws none — a
shortfall STATES, it does not act.

`accept` is matched against the file's type and its extension. A paste cannot carry a filename, so
a pasted image is named by the surface; it routes to the focused region first, then to the
innermost registered sink. **Make the two invisible paths discoverable** — say
so in the empty state.

### The `FilesEditor` compound

The root owns what a host cannot reasonably re-implement — selection, upload state, the gallery.
A HOST verb is a plain button in the bar.

### Gated file CRUD is a PATTERN, composed locally, not a sealed kit component

`useSelectionMode()` for the mode, `shareOrDownloadFiles(files, { title, credentials })` for the
egress, `rotateImageToBlob(url, degrees)` for a correction.
