# form

Fields a person fills in to add a row to a table or change one, such as a new agent's rules or a ticket's status.

`table` is the table it writes; `mode` is `insert` (the default: a new row) or `update` (the row the address's
`?id=` names, its fields filled in). `fields` lists 1 to 20 fields: `name` (the column written), `label`, `kind`
(`text`, `textarea`, `number`, `date`, `email`, `password`, `select`, `switch`, `checkbox`, `lookup`), `required`
(marked in the label), `min`/`max` for numbers, `pattern`, `hint`; a `select` needs `options` and a `lookup` needs
`lookup: { table, label, value }`. `submit` is `{ label, approval, then }`: `approval: true` shows a confirm
card before writing; `then` is the screen opened after. Every field is checked again on the server; a refused
field says why under it and takes the focus.

Category: input. Tier 1. Contract version 1.

## When to use

- A person adds something, such as a new agent described in plain language.
- A person corrects one record, such as a ticket's subject or agent.
- A request that someone approves before it is written, such as an order.

## When to use another block

- The same column changed on many rows at once. Use [`grid`](/catalog/grid.md).
- A yes or no on a row someone else asked for. Use [`approval`](/catalog/approval.md).
- A link to the screen where the form lives. Use [`button`](/catalog/button.md).

## Props

- `id` (name: a-z, 0-9 and -): The block's id: its Markdown anchor and its actions' route (`/act/<id>/<action>`). Default `<type>-<n>`, n counting blocks of this type depth-first from 1.
- `table` (table, required): The table it writes.
- `mode` (one of insert, update, default `insert`): insert adds a row; update edits the row `?id=` names.
- `fields` (list of 1–20: mapping, required): The fields, in order.
  - `name` (column, required): The column the field writes.
  - `label` (text ≤ 60, required): The field's label.
  - `kind` (one of text, textarea, number, date, email, password, select, switch, checkbox, lookup, default `text`): The field's control.
  - `required` (true or false, default `false`): The form refuses an empty value; the label carries a mark.
  - `min` (number): The smallest value (number) or length (text).
  - `max` (number): The largest value (number) or length (text).
  - `pattern` (text ≤ 200): A regular expression the value must match.
  - `options` (list of 1–100: text, number, true or false): A select's choices (select only).
  - `lookup` (mapping): A lookup's table and columns (lookup only).
    - `table` (table, required): The table the choices come from.
    - `label` (column, required): The column a person sees.
    - `value` (column, required): The column the field stores.
  - `hint` (text ≤ 200): A line under the field.
- `submit` (mapping): The submit button.
  - `label` (text ≤ 40, default `Save`): The button's words.
  - `approval` (true or false, default `false`): Through the confirm page first.
  - `then` (name: a-z, 0-9 and -): The screen shown after a successful submit; default this one.

## Data

Reads in update mode, the row of `table` whose id the address's `?id=` names.

Shape: a row with every field's column.

## Actions

- `insert`: inserts the form's fields as one row into the table. Needs every field valid; reversible; confirm page when declared; cost write.
- `update`: writes the form's fields to that row. Needs every field valid, and the row `?id=` names; reversible; confirm page when declared; cost write.

## States

- **ready**: the fields, empty (insert) or filled from the row (update)
- **invalid**: the fields again, each refused one with its sentence
- **error**: the row could not be read or written: the sentence; the page still answers 200

## Examples

### Build an agent

```yaml
form:
  table: agent_drafts
  fields:
    - { name: name, label: Name, required: true, hint: How people will see it in the queue }
    - { name: starts_when, label: Starts when, kind: textarea, required: true }
    - { name: budget_usd, label: Monthly model budget ($), kind: number, min: 0, max: 100000 }
    - { name: test_on, label: Test first on, kind: select, options: [Last 100 tickets of this kind, No test run] }
  submit: { label: Test and save, then: agents }
```

As an agent reads it:

```markdown
<a id="form-1"></a>

Form: inserts a row into agent_drafts

- name "Name": text, required — How people will see it in the queue
- starts_when "Starts when": textarea, required
- budget_usd "Monthly model budget ($)": number, min 0, max 100000
- test_on "Test first on": select, one of Last 100 tickets of this kind, No test run

Submit "Test and save" · approval: no · then /a/catalog/agents

- #form-1.insert "Test and save": POST /a/catalog/{screen}/act/form-1/insert · fields name, starts_when, budget_usd, test_on; inserts a row into agent_drafts · approval: no · cost: write

```

### Correct a ticket

```yaml
form:
  table: tickets
  mode: update
  fields:
    - { name: subject, label: Subject, required: true }
    - { name: agent, label: Agent, kind: lookup, lookup: { table: agents, label: name, value: name } }
    - { name: status, label: Status, kind: select, options: [Needs a person, Closed by agent] }
  submit: { label: Save ticket, then: ticket }
```

As an agent reads it:

```markdown
<a id="form-1"></a>

Form: updates the row of tickets that ?id= names

- subject "Subject": text, required
- agent "Agent": lookup, from agents (name = name): Identity = Identity, Apps = Apps
- status "Status": select, one of Needs a person, Closed by agent

Submit "Save ticket" · approval: no · then /a/catalog/ticket

- #form-1.update "Save ticket": POST /a/catalog/{screen}/act/form-1/update · id=row id, fields subject, agent, status; updates that row of tickets · approval: no · cost: write

```

### An order that needs approval

```yaml
form:
  table: orders
  fields:
    - { name: item, label: What to order, required: true }
    - { name: amount_usd, label: Amount ($), kind: number, min: 1, required: true }
    - { name: urgent, label: Needed this week, kind: switch }
  submit: { label: Request the order, approval: true }
```

As an agent reads it:

```markdown
<a id="form-1"></a>

Form: inserts a row into orders

- item "What to order": text, required
- amount_usd "Amount ($)": number, required, min 1
- urgent "Needed this week": switch

Submit "Request the order" · approval: yes

- #form-1.insert "Request the order": POST /a/catalog/{screen}/act/form-1/insert · fields item, amount_usd, urgent; inserts a row into orders · approval: yes · cost: write

```

## Invalid

```yaml
form:
  table: tickets
  fields:
    - { name: status, label: Status, kind: select }
```

Answers: `form.fields[0].options: a select field needs options, and only a select has them`

## Composition

- Sits in: screen, card, columns
- Pairs with: callout, progress, columns
- Seven fields or fewer; a long form is two screens.
- One form per screen.
- Apple HIG: Ask only for what is needed, label every field and say what went wrong next to the field.

## Telemetry

form.view, form.submit, form.invalid, form.confirm

JSON Schema: [/catalog/form.schema.json](/catalog/form.schema.json)
