# Model reference

State: `{ draft: number, sending: number | null, confirmed: number, failed: boolean }`, starting at `{ draft: 0, sending: null, confirmed: 0, failed: false }`.

| Event | Allowed when | Effect |
| --- | --- | --- |
| `edit` | always | `draft + 1` |
| `save` | nothing in flight and `draft !== confirmed` | `sending = draft`, `failed = false` |
| `ok` | a save is in flight | `confirmed = sending`, `sending = null` |
| `fail` | a save is in flight | `sending = null`, `failed = true` |

A rejected event is reported as a `transition` finding and leaves the state unchanged.

Status, in order of precedence:

1. `saving`: `sending !== null`
2. `saved`: `draft === confirmed`
3. `error`: `failed`
4. `unsaved`: otherwise

## Trace format

```json
{ "steps": ["edit", { "event": "save", "expect": "saving" }] }
```

A step is an event name or an object with `event` and an optional `expect` (`saved`, `unsaved`, `saving`, `error`). Other fields, such as `description`, are ignored.

## Report

`{ ok, errors: [{ code, index, message }], steps: [{ index, event, status, draft, sending, confirmed, failed }], final }`. Codes: `schema`, `event`, `transition`, `expectation`. `index` is the zero-based step.

`scripts/model.mjs` also exports `step`, `statusOf`, and `booleanStatusOf`, the flag-based shortcut used for comparison in the demo.
