# Contract grammar and vocabulary reference This is a descriptive reference for the compiler on `main`, not a proposal for new syntax. The [human guide](contract-for-humans.md) teaches the language; the [agent guide](contract-for-agents.md) gives the implementation loop. The parser, type checker, and lowering rules are authoritative when this summary and implementation disagree. The grammar below describes authored forms. Context-sensitive restrictions are listed separately: parsing a construct does not imply it types, lowers, bakes, or runs on every host. `IDENT`, `FIELD`, and `TAG` are identifier tokens accepted in their respective contexts; reserved-word handling differs by context. ## Contents - [Lexical rules](#lexical-rules) - [Declarations](#declarations) - [Components and actions](#components-and-actions) - [Views](#views) - [Expressions and types](#expressions-and-types) - [Authored tests](#authored-tests) - [Standard functions and intrinsics](#standard-functions-and-intrinsics) - [Built-in tags](#built-in-tags) - [Events](#events) - [Host commands](#host-commands) - [Semantic restrictions](#semantic-restrictions) ## Lexical rules The notation uses `=` for a production, `|` for alternatives, `[ … ]` for optional syntax, `{ … }` for repetition, and quoted text for literal tokens. `NL`, `INDENT`, and `DEDENT` describe logical line boundaries. `block(X)` means an indented run of `X` entries, `NL [ INDENT { X } DEDENT ]`; blank and comment-only lines do not count as entries. Most bodies may be empty (a shape, a style, `props`, an action, a `for` body, a `case` arm, a test). The bodies of `if`/`else`, a `keyframes` or `font` block, and a component need at least one entry. - A UTF-8 file may begin with one byte order mark (U+FEFF). Formatting preserves it; source spans count its three bytes in the first line's columns. - A name starts with `[A-Za-z_]`, followed by ASCII alphanumerics/underscores or a hyphen immediately followed by an ASCII letter. Thus `font-size` is a name, `a-b` is a name, and `a - b` is subtraction. - Number tokens begin with a digit. A dot is part of the number only when a digit follows (`12`, `12.5`), so `rows.0.steps` is a list index and then a field. An exponent is part of the number only when digits follow its `e` or `E` and optional sign (`1e300`, `2.5E-3`, `4e+2`), so `2em` is a number and a unit; one past the largest double is refused. Numeric parsing rejects invalid spellings. Negative values are unary minus applied to a number; as an attribute's whole value (`min=-40`, `step=-0.5`, `margin-top=-8`, `min=-1e300`) it is the negative literal, the number it is, as `40` is, and a text field's `min`, `max` and `step` take a number of either sign as a range's do. Separators, leading-dot numbers, and a trailing dot are not supported forms. Units belong in quoted strings. - Strings use `"…"`. Their escapes are `\n`, `\t`, `\"`, `\\`, ``\` ``, `\$`, and a Unicode code point in hex, as JavaScript and Swift write it: `\u{2011}` (one to six digits; `\u{1F600}`, never a surrogate pair) or `\u2011` (four). Any other is refused, naming these. Single quotes are not delimiters. - Templates use backticks and `${expression}`. Spaces around the expression are optional: `${ a }` and `${a}` mean the same. Interpolation scanning balances nested braces, strings, and templates. A template is one physical source line. Template text takes a string's escapes (`` `${a}\t${b}` `` is a tab), and `\${` is literal text, not an interpolation. - A hex color may be written bare, as CSS writes it: `#` and 3, 4, 6 or 8 hex digits (`color=#1f9d62`, a keyframe's `background-color=#1f9d6244`) is the string `"#1f9d62"`, and the formatter prints it quoted. Any other `#` is refused, saying so. - `//` starts a line comment outside a string/template. No block comments; `#` is not a comment. - Indentation uses spaces; a tab in indentation is refused. At bracket depth zero, indentation emits block tokens. Inside `()`, `[]`, and `{}`, newlines and indentation continue the logical expression instead. - No statement semicolons, single-quoted literals, JSX, or arbitrary braces for value interpolation. `{}` belongs to inline option-match syntax, not objects. The lexer reserves no separate keyword token kind. Sixteen words are reserved, and only where a name is bound ([`names.rs`](../contract/syntax/src/parser/names.rs), LLP 1088 D5): `when`, `if`, `else`, `for`, `in`, `match`, `case`, `as`, `fn`, `and`, `or`, `not`, `true`, `false`, `none`, `some`. A binder is a component's props, injects and provided names; states, derives, resources, mutations and actions; action and `fn` parameters and `fn` names; `let`, a `for` item and index, `case some(x)`, an arrow parameter; and a shape name (a shape name builds the shape, so `shape none` is refused). There a reserved word is refused as "`in` is reserved in Contract (it shapes an expression); choose another name", and is never a value. Every other keyword is contextual: a keyword only where its construct starts, a name at every binder and in every expression. They are `component`, `font`, `shape`, `style`, `from`, `state`, `derive`, `resource`, `mutation`, `action`, `task`, `mount`, `view`, `props`, `provide`, `inject`, `slot`, `children`, `key`, `queue`, `refresh`, `writes`, `test` and `expect`. `refresh`, like `send` and `let`, begins a statement only before a name, so `action refresh`, `press=refresh` and `refresh feed` each have one parse. Shape fields, named arguments (`Flags(none=1)`), members after `.` and attribute names (SVG's `in`) admit every word. ## Declarations ```ebnf file = { declaration } ; declaration = use | shape | function | style | decoration | atom | keyframes | font | sound | theme | timeline | color-profile | routes | component | members-block | test ; use = "use" use-name { "," use-name } "from" STRING NL ; use-name = IDENT [ "as" IDENT ] ; shape = "shape" IDENT block(field) ; field = FIELD ":" type NL ; function = "fn" IDENT "(" [ typed-params ] ")" ":" type "=" ( expr NL | NL INDENT expr NL DEDENT ) ; typed-params = typed-param { "," typed-param } [ "," ] ; typed-param = IDENT ":" type ; style = "style" IDENT block(style-line | part) ; decoration = "decoration" IDENT ( style-line | block(style-line) ) ; atom = "atom" IDENT style-line [ block(style-line | state) ] ; style-line = { FIELD "=" style-literal } NL ; part = "::" "placeholder" NL block(style-line) ; style-literal = STRING | NUMBER | "-" NUMBER | "true" | "false" ; keyframes = "keyframes" IDENT block(keyframe-line) ; keyframe-line = selector { "," selector } { FIELD "=" frame-value } NL ; selector = "from" | "to" | NUMBER "%" ; frame-value = STRING | NUMBER | "-" NUMBER | constant-call ; constant-call = IDENT "(" [ arguments ] ")" ; theme = "theme" block(IDENT "=" STRING NL) ; (* the root file's; LLP 1095 D4 *) timeline = "timeline" IDENT NL ; (* a clock timeline `animation-timeline` names *) color-profile = "color-profile" "--" IDENT style-line ; (* CSS's @color-profile: --brand src="…" *) font = "font" STRING ( "=" STRING NL | block(font-face) ) ; font-face = NUMBER [ NUMBER ] [ "italic" ] "=" STRING NL ; (* a weight, or a variable face's range *) sound = "sound" STRING NL ; routes = "routes" IDENT block(route-row) ; route-row = [ "tab" ] IDENT STRING { attribute } NL [ route-children ] | "notfound" { attribute } NL ; route-children = INDENT route-row { route-row } DEDENT ; ``` A font face's weight is a whole number in 1–1000. Keyframe percentages are in 0–100; `from` and `to` are 0% and 100%. Lowering checks whether keyframe properties are animatable and constant-call values can be evaluated at compilation. Styles accept literal style attributes and explicitly styleable props (currently `buttonStyle`), not arbitrary expressions or event props. A part is CSS's pseudo-element as a block: `::placeholder`, then its declarations indented beneath it, in a `style` (literal) or under an `input` or `textarea` before its children (where a value may compute, as the element's own attributes do). It admits `color` alone (CSS `::placeholder { color }`, LLP 1104 D3), its alpha its own; the field's own block wins over its class's, and without one the placeholder keeps the platform's colour. Another property in it is `lower-unknown-attr`; on another element `lower-attr-tag`; `::before` and `::after` are `syntax-part` (Contract has no generated box: write a child), as is any other part. `placeholder-color` and `-exact-placeholder-color` are refused with the block to write. A state part is a block of one state's rows (LLP 1121 D8): `:active`, `:hover`, `:focus-visible`, `:disabled` or `:checked` alone on its line, then rows indented beneath it, in a `style` (literal) or under a node before its children (where a value may compute). One state a block, once at a place, never nested and never combined (`syntax-state`). `:disabled` and `:checked` (when `checked`, `aria-checked`, `aria-selected` or `aria-pressed` is true) take `background-color`, `color`, `border-color` (each side), `opacity`, `box-shadow` and `-exact-tint-color`, and fold into the node's own rows, `:checked` under `:disabled`; `:active` takes `background-color`, `color`, `opacity` and `-exact-press-scale` and lowers to the press paint (LLP 1061 D7: its `opacity` an absolute target), `:hover` and `:focus-visible` take `background-color`, `color`, `border-color`, `opacity`, `box-shadow` and `scale` (which stands in for the node's and moves on its `transition`). Active targets may compute or clear with `none`; hover and focus targets are literals (`lower-state-literal`; a token's name is one). Another row is `lower-state-row`; native buttons and list rows accept `:active` and keep their other native states. A host rejects properties its projected native component cannot express, names them in the journal and keeps the native component. A part on another native control, or (for `:active`, `:hover` and `:focus-visible`) a run inside a paragraph is `lower-state-target`. An `:active` row beside the same paint's `-exact-press-` row is `lower-state-press-paint`. The node's block wins over its class's, row by row. A length attribute's string is CSS's own text, read by the kernel: a px or other absolute length, a percentage, a viewport length, `auto`, `env(safe-area-inset-*)`, `env(viewport-segment-* x y)`, `env(titlebar-area-x|y|width|height)`, a layout region's `env(-exact--inset-*)`, or a `calc()`, `min()`, `max()` or `clamp()` of these (`clamp(15px, env(safe-area-inset-bottom), 60px)`; LLP 1001 §2). Anything else is `lower-attr-value`, with the kernel's reason. The layout regions (LLP 1123 D10) are the platform's own spacing, measured from the layout viewport, inside the safe area by default: `layout-margins` (UIKit's 16 or 20 points), `readable-content` (a comfortable reading width), and `safe-area-corners`, `layout-margins-corners` and `readable-content-corners`, the same kept clear of rounded corners. `padding-left="env(-exact-layout-margins-inset-left)"` puts a header where a UIKit screen's would be. On a host without them (the web, Linux, Windows) each is the safe area. The title-bar area is [Window Controls Overlay](https://wicg.github.io/window-controls-overlay/)'s: the part of the title bar beside the window's controls. On the Mac, under `viewport-fit="cover"`, it is AppKit's title bar less the traffic lights and their margins (`78 0 822×32` in a 900-point window with a left-to-right title bar, `0 0 822×32` with a right-to-left one); all zero in full screen and while a toolbar fills the title bar (an app with routes has one, LLP 1115.002 D2). An app without routes whose manifest's `display_override` puts `window-controls-overlay` first gets the platform's tall title row, an empty unified toolbar over the page, whatever its `viewport-fit`, and the area is that row's beside the traffic lights; `exactViewport().displayMode` is then `window-controls-overlay`. Chrome gives the same names to the installed app. Everywhere else it is undefined, as in a browser tab: `env(titlebar-area-height, 0px)` takes its fallback, and without one the row takes its initial value. `-exact-fit="shrink"` on a `text` (CSS has no such property; LLP 1116.001 D1.4) draws a line too wide for its content box at the scale that fits: its whole line under `white-space="nowrap"`, else its longest word. It shrinks down to half its size, and a display size (more than twice the root font size) down to the root size; below that it overflows, as UIKit's `adjustsFontSizeToFitWidth` truncates. The kernel picks the scale and every host draws at it; `"none"`, the default, never shrinks. A `sound` names a WAV under the app's `assets/` (LLP 1096 D1): 16-bit integer or 32-bit float PCM (or `WAVE_FORMAT_EXTENSIBLE` naming one), one or two channels, 8–96 kHz, at most 10 s. The compiler reads its header and refuses anything else with the file named and the conversion to run (`lower-sound-format`, `lower-sound-long`, `lower-sound-path`, `lower-sound-unreadable`). `sound` is a word only at the start of a top-level line, so a `state sound` is a slot. Sounds are app-wide, as fonts are. A `use` specifier is a relative path (`./` or `../`) to a `.contract` file that stays inside the using file's root — the app directory, or the package it belongs to (`contract-use-path`); an `exact:` built-in (`exact:motion`, `exact:content`; `contract-use-builtin`); or a package name, `name[/sub]` or `@scope/name[/sub]`, found in the nearest `node_modules` above the using file and mapped through its `package.json` `exports` (a string, or the `contract` or `default` condition), else `index.contract` (`contract-use-package`). A package outside the app needs a `name` in its `package.json` (`contract-package-unnamed`): its components' identities are named by it (LLP 1121 D15). A root file linked into the app's tree from elsewhere (a symlink, as conform's corpus fixtures are) that is in no named package is the app's own, named where the link sits (one in a named package keeps the package's name); a file a `use` reaches is its target's. `contract sources ` prints every file a compile reads, as JSON. A file's names are its own declarations and the names its `use` lines list, each optionally renamed with `as` (LLP 1091): a component, shape, function, style, keyframes, or timeline, declared by the used file or named by its own `use` lines. A name another file declares and this one does not name is refused (`contract-use-missing`): one refusal a file, naming every `use` line the file lacks — the line it has for that file, extended, or a new one — and `contract fmt --uses ` writes them (`bun exact.mjs update` does too, for an app outside this repo). A name two files declare, or one the compiler gave on a collision (`Card__ui`), is left to the author and said. The keyframes an `animation`, `animation-name` or `-exact-exit-animation` literal names, and a `-exact-clock(Name)` literal, resolve in the file that writes them; a name computed at run time is matched as written. Beside a computed value, a word is renamed when it is the name whatever the value is; one that is the name for some values and a keyword for others (`${x} linear 1s`), when it is also keyframes another file's of its name renamed, is refused (`contract-animation-ambiguous`): quote the name (`'linear'`). A package's export may be its module declaration, `exact.json` (LLP 1133.001 D1): `use getCurrentPosition, GeolocationPosition from "@exact/geolocation"` brings the module's sources and sends and its shapes. A module's source is read by a `resource` and its send by a `send`, each `as shape` what it answers (`contract-module-kind`, `contract-module-answers`), with keyword arguments only: the module's options (`maximumAge=600000`), each omitted one left to the platform, and each platform's words as dotted keywords (`apple.desiredAccuracy="threeKilometers"`), checked against the words that platform's implementation declares (`contract-module-positional`, `contract-module-argument`, `call-argument-twice`). The device answers it, never the build: its placeholder is `else empty()` (`contract-module-placeholder`). A send that answers nothing (`"answers": null`, LLP 1133.004 D5) is a statement in an action, as a host command is (`showNotification(title="Done", tag=t)`, from `@exact/notifications`: [notifications](reference.md#notifications)); a `send` to one is refused (`contract-module-answers`), and so is a statement calling a send that answers something (`contract-module-kind`). A module's event (D6) is heard with an attribute on the root component's outermost node, `column notificationclick=opened`, naming an action of the root that takes a prefix of the event's arguments (an untyped parameter takes the event's type; `contract-event-handler`); anywhere else, or a second time, it is refused (`contract-event-root`). Each event is delivered once, in order, as its own commit; a host raises one only once the app has drawn its first frame, so one that starts the app is held until then. Fonts are app-wide. A used file cannot declare routes. The root file's first component remains the root. A name both declared and used (`contract-use-shadows`), one name used from two declarations (`contract-use-duplicate`), and cycles are refused. A route pattern is an absolute path with literal or whole `:name` segments. Indentation determines its parent. The optional fallback is `notfound` without a path; with none, the compiler writes it and its screen (a navigation root drawing `for e in stack(nav)`). Route policy fields use attribute syntax and are validated by the [route/document compiler](../contract/lower/src/routes.rs). ## Components and actions ```ebnf component = "component" IDENT block(section) ; section = "props" block(field) | "inject" block(field) | "provide" block(provider) | "slot" NL | "state" IDENT [ ":" type ] "=" expr [ "persist" ] NL | "derive" IDENT "=" expr NL | resource | mutation | action | task | sequence | "members" STRING NL | "view" block(node) ; members-block = "members" IDENT block(member) ; member = "state" IDENT [ ":" type ] "=" expr [ "persist" ] NL | "derive" IDENT "=" expr NL | resource | mutation | action | task | sequence ; provider = FIELD [ "=" expr ] NL ; resource = "resource" IDENT "=" source-call [ "for" IDENT "in" expr [ "key" "=" expr ] ] [ "with" expr { "," expr } ] [ "when" expr ] "as" "shape" type [ "else" source-call ] [ "transient" | "keep" ] NL ; mutation = "mutation" IDENT "as" "shape" type [ "queue" | "parallel" ] [ "refreshes" IDENT { "," IDENT } ] [ "then" IDENT ] NL ; action = "action" IDENT [ "(" [ action-params ] ")" ] block(statement) ; action-params = action-param { "," action-param } [ "," ] ; action-param = IDENT [ ":" type ] ; task = "task" IDENT ( "mount" | gate ) block(schedule) ; gate = "when" expr [ "key" "=" expr ] | "key" "=" expr ; schedule = "every" "(" "frame" "," IDENT ")" NL | ( "every" | "after" ) "(" expr "," IDENT { "," timer-option } ")" NL | "change" "(" IDENT ")" NL ; timer-option = "wait" "=" ( "frame" | "update" | "scrollend" | "idle" ) | ( "max" | "tolerance" ) "=" NUMBER ; (* max needs wait=scrollend or idle; tolerance excludes wait *) sequence = "sequence" IDENT ( "mount" | gate ) block(play) ; play = "play" "(" expr { "," IDENT "=" expr } ")" NL ; statement = IDENT "=" expr NL | "let" IDENT "=" expr NL | "send" IDENT "=" source-call NL | "refresh" IDENT NL | IDENT "(" [ arguments ] ")" NL | if | statement-match ; if = "if" expr block(statement) [ "else" ( if | block(statement) ) ] ; statement-match = "match" expr block(statement-case) ; statement-case = "case" "some" "(" IDENT ")" block(statement) | "case" "none" block(statement) ; source-call = IDENT "(" [ arguments ] ")" ; ``` A component may write some of its members in other files (LLP 1091.002), so a large root can be split by feature under the line cap. `members "./composer.contract"` in its body stands for the members that file declares under `members Name`, as if they were written at that line: the program, and the plan, are the one-file program's. The path is a relative `use` path (`contract-use-path`). A members file holds one `members` block, named for the component that lists it (`contract-members-owner`), listed by one line (`contract-members-twice`, `contract-members-unlisted`); the block holds `state`, `derive`, `resource`, `mutation`, `action`, `task` and `sequence`, and the once-only sections and `members` lines stay in the component's own body (`syntax-members-section`). Every file of a component sees all its members, props and injects; a name declared in two of its files is refused naming both (`contract-members-duplicate`). Any other name a members file calls (a shape, a `fn`, a component) comes from its own `use` lines or is declared in it, beside the block; another file, the root among them, names such a declaration on a `use` line. The component's rules hold for its members wherever they are written: a child's members file declares no resource. There is one `props`, `inject`, `provide`, `slot`, and `view` section at most in a component. Only the root may own resources, mutations, and tasks, except that any component may read the device's facts, `exactViewport()` and `exactPage()` with no arguments (`EmptyState` reads `accessibilityTextSize`): they open no request, and every instance reads the root's one answer. A task's body has exactly one schedule. A gated task (`when cond`, LLP 1092) has its timer only while `cond` holds, armed from the commit that made it true; `key=` re-arms it from the commit that changed the key (compared as a `for` key), and `key=` alone is `when true key=…`. The gate is a bool (`type-task-gate`), the key a string, number or bool (`type-task-key`), and neither reads `performanceNow()`, directly or through a derive or a `fn` (`analyze-task-gate-clock`). The named timer action takes no parameters; a millisecond interval is a literal whole number of at least 1. A `change(action)` schedule requires `key=` and accepts no timer options. It queues one action after the first active commit, a changed key or a reopened gate. Pending changes coalesce, a closed gate cancels them, and a refused action waits for another key change or activation. The host applies each preceding commit before the action runs; the virtual clock need not move. Cycles through change actions and their trigger inputs are refused (`analyze-change-cycle`). A `sequence` (LLP 1125 D2) declares what should play, as a value, and the runner plays it: the gate is a task's, and its body is exactly one `play(tracks, bpm=, from=, countIn=, clock=, clockOut=, ended=)` (`contract-sequence-body`). `tracks` is a list of a shape whose fields are, in order, `beats: number` (the track's length, which it loops at), `loops: number` (0 is forever), `offset: number`, `output: string` and `steps: S[]`; a step's shape `S` has, in order, `beat: number` (from the track's start, in quarter notes), `src: string` (a declared sound, or `""`), `gain: number`, `group: string`, `note: number`, `velocity: number`, `channel: number`, `duration: number`, `haptic: string` (a `haptic()` kind, or `""`), then optionally `playbackRate: number` and `pan: number` (`type-sequence-play`, which also types the options and refuses an unknown or repeated one). `bpm=` is required unless `clock=` is given; `from=` (default 0) is the beat at the commit that armed it, and below 0 it is a count-in: `countIn=`'s steps play once over `[from, 0)`, their beats counted from `from`. `ended=` names a parameterless action run when the last finite track ends, at that time. The gate, key and arguments read no `performanceNow()` (`analyze-task-gate-clock`, `analyze-sequence-clock`), and only the root declares one (`type-child-resource`). The arguments are read again after every commit that stands: an equal value changes nothing, and a different one ends the sequence's voices that have not started (`by edit`) and plays the new value from that commit, a new `bpm` without a jump in phase. A step whose `note` is not −1, on a track whose `output` names a MIDI output, is a note-on there (`velocity` 1–127, `channel` 0–15) and a note-off `duration` beats later (on `output=""`, no output, it goes to no device and `exactMIDI().sent` lists it with `output: ""`); a track's `offset` (within ±500 ms) moves its samples', notes' and haptics' times, as a latency is lined up by ear. `clockOut=""` sends Timing Clock 24 times a beat, Start before the first tick at a beat at or after 0, and Stop at the stop or the end ([MIDI](#midi-exactmidi-sendmidi-midimessage)). `clock=""` follows that input's MIDI clock instead (LLP 1125 D9): the sequence is `waiting` until the input sends Start, which puts beat `from` at the next tick; its tempo comes from the last 48 ticks' timestamps (`bpm=`, default 120, until 13 have come), its position counts ticks, and a hit not yet within 50 ms of being due that a new tempo or phase moves by more than 2 ms is taken back (`by clock`) and played again. Stop silences it as the gate would and leaves it `waiting` (reason `clock`); Continue resumes at the Song Position sent since, else where Stop held it. The input is opened whether or not an element hears it, and its Start, Continue, Stop and Song Position are still heard as `midimessage`. A sequence that follows a clock sends none (`clockOut=` is ignored and journaled), and a changed `clock=` restarts it, as a changed key does. One sequence plays at a time; leaving the foreground on iOS stops it (reason `background`) unless `audio_session` is `playback` or `playback-mix` with `audio` in `host.ios.backgroundModes`. `resource t = exactTransport() as shape T` reads it: `state` (`stopped`, `waiting`, `countin`, `playing`), `reason` (`gate`, `ended`, `background`, `bpm`, `clock`, or `""`), `bpm`, `anchorAt` and `anchorBeat` (the beat now is `anchorBeat + (performanceNow() - anchorAt) * bpm / 60000`) and `clock` (`internal`, or `midi` while following, when the tempo is rounded to 0.1 and it and the anchor are said again only when they move by 0.5 bpm or 1/48 of a beat, or the state does); it is answered by a commit of its own after each change, as an answer's `then` runs. ### Live Activities: `startActivity`, `updateActivity`, `endActivity` The three reserved mutation sources (LLP 1126.001 WP9) compile on every host. They run on iOS through an ActivityKit arm loaded at first use; other hosts refuse the send with `… is not linked into this artifact`. Declare the entry in `app.json`'s `live_activities`, its state shape and slots as in `apps/caltrain/activities.contract`. Each send answers a record whose only field is `id: string`; the compiler refuses other result shapes: ```contract shape DepartureState status: string at: number shape ActivityHandle id: string component App mutation activity as shape ActivityHandle action follow(state: DepartureState) send activity = startActivity("departure", state) action delay(id: string, state: DepartureState) send activity = updateActivity(id, state, some("Train delayed"), none) action finish(id: string, state: DepartureState) send activity = endActivity(id, state, "immediate") view text "Train" ``` `state` is a record, checked against the entry's state shape at runtime. An update's optional third argument is `option` (the alert's title, with the default sound); its optional fourth is `option` (the stale date in Unix milliseconds). Ending takes `"default"`, `"immediate"`, or a Unix millisecond dismissal date. A string policy must be one of those literals or an `enum<"default", "immediate">` value; arbitrary strings are refused at compile time. Attributes plus state obey the same 4 KB bound as the bake. Update and end validate against the generation in which the activity started. Keep that state shape while its activities are alive; a new shape cannot update or end an older instance with incompatible state. A compatibility-changing binary update follows LLP 1126.001 §5 Q6’s device gate. `resource activities = exactActivities() as shape Activities` reads a record whose fields are `activities: Activity[]`, `frequentPushesEnabled: bool` and `linked: bool` (the host packaged its arm). An activity has `id`, `kind`, `state` (a JSON string), `activityState` (`pending`, `active`, `stale`, `ended`, `dismissed`) and `token: option`. Readers may name only the fields they need. Off iOS the list is empty and frequent pushes are false. A first demand enumerates existing activities, including after a relaunch without a send; later token, state and content updates re-answer the source. A token is absent until ActivityKit supplies it, and exists only for an entry declaring `push: true`. The data module sends it to its own backend. Push-to-start and the `remote-notification` background mode stay refused; an app with activities registers for remote notifications only when it grants `device.push`. ### MIDI: `exactMIDI`, `sendMIDI`, `midimessage` MIDI is Web MIDI's, by its names (LLP 1125 D6–D8), and needs the grant line `device.midi ` on every host (`Permissions-Policy: midi` on the web); without it `state` is `denied` and a request is refused by name. `resource m = exactMIDI() as shape M` reads `state` (`prompt` until `requestMIDIAccess()` asks, then `granted`, `denied`, or `unavailable` where there is none: WebKit, tvOS, Linux and Windows), `inputs` and `outputs`, each a list of a port shape of `id` (stable across replug), `name`, `manufacturer` and `state` (`connected`, `disconnected`; a port seen stays listed), and `sent`, the last 128 messages that went out, in the order they went, each `output`, `data` and `timeStamp` (runner ms; no Timing Clock, nothing taken back) — any other field is refused at the bake (`bake-midi-field`). It changes by itself as ports come and go, and, when it declares `sent`, as each message goes. `requestMIDIAccess()` takes no arguments (`type-request-midi-access`). `sendMIDI(output, data, at=)` sends `data`, the bytes of one or more whole MIDI 1.0 messages (no running status, no SysEx; a literal one is checked, `type-send-midi`), to an output by `id` at `at` on the runner's clock (the commit's time by default, and the past is now); what is not well-formed, an output not connected, or the 1,025th message waiting is dropped and journaled (note-offs and Stop never are). `""` is no output: what is sent there reaches no device, needs no access, and `sent` lists it with `output: ""` (an activity log with no device, LLP 1125 D6). The runner keeps the queue: a host hands a message to its output 50 ms before it is due and holds it until then, so an edit or a stop takes back what is still held, and a note already handed still gets its note-off. A sequence's notes and clock go through the same queue. `midimessage=action` on an element with `midiinputs=["", …]` hears each message from those inputs; every mounted element that declares it hears it, in tree order, each in its own commit. Its action may take a `MIDIMessageEvent`: `input`, `data` (`number[]`), `timeStamp` (runner milliseconds) and `beat` (the sequence's beat then, −1 while stopped or waiting for a clock). Control Change, pitch bend and pressure keep only their latest value within one delivery, never across a note of the same input and channel; notes and transport messages are never dropped or reordered; Timing Clock and Active Sensing are not dispatched (a followed input's ticks go to its sequence). `onMIDIMessage` and `midiInputs` are refused with these names. `else if c` is `else` around one nested `if c`, in an action and in a view alike: the same tree, so the same plan. A view refuses `when` and `each` (`syntax-view-keyword`) and `else when` (`syntax-else-keyword`), naming the edit. Both option-match arms are required exactly once. Actions have no loops, returns, or awaits. A standalone call statement `name(args)` is a host command when `name` is one. When `name` is also an action, an `action` prop or an injected action of the component the statement is written in, the call is refused (`syntax-call-ambiguous`, LLP 1089 D1), inside an action of that name too: rename the action (Caltrain's wrapper is `action chooseScheme`, which calls the command `setScheme(s)`). When `name` is not a host command, it calls an action of the same component, an `action` prop or an injected action (LLP 1089), never a function. A call is expanded in place: the callee's statements run where the call stands, in the caller's one commit, reading the state the action started with. Its arguments complete the callee's parameters after any curried where it was bound; no event payload is appended. A call gives no value (`type-call-value`), passes no action as an argument (`type-call-action-arg`), and never recurses, directly or through other actions (`syntax-call-cycle`). An action whose calls would expand past 1,024 statements is `syntax-call-size`. `let` is recognized as the local-declaration statement when followed by a name; a writable slot named `let` can still appear in an ordinary assignment. A resource's optional `with` supplies one or more request context expressions, after its call arguments and before `as shape`. The source receives both lists in order; a change to either asks again. Only admission of an eligible persisted answer at boot compares the call arguments alone (LLP 1027.005), and so does `current` (below). An empty call is valid; an empty `with` is not. `else` keeps its place after the shape. A resource's optional `when cond`, before `as shape`, asks it only while `cond`, a `bool`, holds (LLP 1161.002; `type-when-not-bool`). While it is false the resource is idle: nothing is asked, a request in flight is let go (an upload's goes on), `pending` and `failed` are false and `failure` is `none`, and `refresh` asks nothing. It keeps the value it showed, which is no longer an answer: `current` is false. When `cond` turns true the resource is asked again, forced, even at the arguments it had, and is current once that answer lands. A resource idle at boot shows its placeholder. A resource over a list takes no `when` (`syntax-resource-for-when`). A resource over a list, `resource r = source(x) for x in list as shape S`, asks its source once for each element (LLP 1017.000 P4c, amended 2026-11-04). Each request is keyed as a `for` row is: by `key=`, or, without it, by the element, which must then be a string, number or bool (`type-resource-for-key`). The arguments and the key read the element. An element that joins starts its request, one that leaves lets only its own request go, and the others go on. `r` reads as `S[]` in the list's order, each element its shape's zero until its answer; an element whose key repeats an earlier one's reads that one's answer. `pending(r)` and `failed(r)` are true when any element's request is pending or has failed, `current(r)` when every element's is, and `failure(r)` is the first failure. It takes no `with` or `else` (`syntax-resource-for-with`, `syntax-resource-for-else`), and asks a data source, not one the runner answers (`lower-resource-for-source`). A mutation's `refreshes r` shows the source's overlay over each element from the send, and asks every element again when the reply lands. Each element retires the write independently once its answer was asked after the landing, unless its overlay keeps it; leaving the list also retires it. A resource's `else` is a placeholder. `else empty()` is the resource type's zero, with `field=constant` overrides for a record. `else source(values…)` names a source whose arguments are plain values, not state; it is answered once at build. `transient`, last on the line, keeps the answer out of the device's storage: no launch shows it before the source answers, and an answer kept before is forgotten (LLP 1156.005 D4). A mutation's `then` names a parameterless action and follows `refreshes` when both are present. `queue`, right after the shape, makes every send of the mutation wait its turn (LLP 1092): one request in flight, later sends asked in order, each after the reply before it and its `then`. `parallel`, in the same place, asks every send when it is made (LLP 1161.000): each is its own request in flight, at most 64 at once (the 65th refuses its action, `QueueFull`), and its reply lands whatever else is out. A reply lands in the slot unless a later send has already concluded (answered or failed), `pending(m)` is true while any send is out, and an assignment forgets no request. A `parallel` mutation takes no `then` (`type-parallel-then`): its replies land in any order. Its source is called once per send and may have several calls outstanding; a source that reads local storage, changes it and writes it back orders its own work or is `queue`. With neither, a send forgets the one in flight: the newest wins. The clauses after the shape may continue onto deeper-indented lines, each starting with its keyword, in the same order (`mutation edited as shape Jump refreshes page`, then `then followEdit` indented under it). Action effects are inferred: `writes` clauses are refused. Component `contract` sections are also refused. ## Views ```ebnf node = element | component-use | view-if | for | view-match | children ; element = TAG { attribute | expr | special-word } NL [ element-body ] ; attribute = FIELD "=" expr | "autofocus" ; element-body = INDENT { continuation | element-part } { node } DEDENT ; continuation = FIELD "=" expr { attribute } NL ; element-part = "::" "placeholder" NL block(continuation) ; component-use = CAPITALIZED_IDENT "(" [ named-args ] ")" NL [ node-children ] ; named-args = FIELD "=" expr { "," FIELD "=" expr } [ "," ] ; node-children = INDENT node { node } DEDENT ; view-if = "if" expr block(node) [ "else" ( view-if | block(node) ) ] ; for = "for" IDENT [ "," IDENT ] "in" expr "key" "=" expr block(node) ; view-match = "match" expr block(view-case) ; view-case = "case" "some" "(" IDENT ")" block(node) | "case" "none" block(node) ; children = "children" NL ; special-word = "document" | "switch" | "multiple" ; ``` `special-word` is contextual: `scroll document`, and the admitted input forms, not three universal positional flags. The lowering pass restricts an element's positional values, children, and properties. `button "Label"` inserts a text child. `autofocus` is the bare boolean convenience spelling. A deeper element line is treated as an attribute continuation only when it starts with `name=`; once child nodes begin, later lines are children, not continuations. Continuations may sit deeper than the children that follow them; `fmt` moves them to the children's level. A repeated attribute on one element is refused. A component use takes named arguments; its name starts uppercase to distinguish it from an element. A `for` key is a string, number, or boolean, with unique stable values required for useful row identity. Its optional second binding is the numeric row index. `if` conditions are boolean and `match` subjects are options. Root regions are refused: wrap them in an element. `children` requires a declared slot. ## Expressions and types ```ebnf type = type-atom { "[" "]" } ; (* string[] is a list of strings *) type-atom = "number" | "string" | "bool" | "unit" | IDENT | "action" | "option" "<" type ">" | "enum" "<" STRING { "," STRING } ">" ; expr = ternary ; ternary = binary [ "?" expr ":" expr ] ; binary = unary { binary-op unary } ; unary = ( "-" | "not" | "!" ) unary | postfix ; postfix = primary { "." FIELD [ "(" [ arguments ] ")" ] } ; primary = NUMBER | STRING | TEMPLATE | "true" | "false" | "none" | "some" "(" expr ")" | list | IDENT | IDENT "(" [ arguments ] ")" | "(" expr ")" | inline-match ; list = "[" [ expr { "," expr } [ "," ] ] "]" ; arguments = argument { "," argument } [ "," ] ; argument = expr | arrow | FIELD { "." FIELD } "=" expr ; arrow = ( IDENT | "(" [ IDENT { "," IDENT } ] ")" ) "=>" expr ; inline-match = "match" expr "{" "case" "some" "(" IDENT ")" "=>" expr "," "case" "none" "=>" expr [ "," ] "}" ; ``` The `binary` production is precedence-resolved, not a claim that all operators have the same precedence. From weakest to strongest: | Level | Operators | Associativity | | --- | --- | --- | | Conditional | `? :` | Nested arms parse as expressions | | Or | `or`, `||` | Left | | And | `and`, `&&` | Left | | Equality | `==`, `!=` | Left | | Comparison | `<`, `<=`, `>`, `>=` | Left | | Additive | `+`, `-` | Left | | Multiplicative | `*`, `/`, `%` | Left | | Prefix | `-`, `not`, `!` | Right | | Postfix | `.` | Left | Parentheses override precedence. Comparisons do not create Python-style chained comparison semantics. Logical operators short-circuit; ternary/match evaluates only its selected arm. Conditions require booleans. Named arguments are accepted only by the corresponding constructs: component uses, a native module's sources and sends (the only place a dotted name, `apple.desiredAccuracy=`, is one), record construction/copy, localization (`t`), placeholders (`empty`), canvas `surface=` bindings, and specialized host commands (`share`, `scrollIntoView`). Ordinary function calls are positional. Arrow expressions are admitted as the callbacks to `map` and `filter`, with at most item and index parameters; they are not first-class values or event handlers. An app-declared shape's call constructs a record: either all named fields, or one positional base followed by zero or more replacements. A `list` literal is a list of its items, left to right; their types meet as a ternary's arms do (`type-list-item` when they do not), and a trailing comma is kept. There is no spread (`[...xs, x]` is refused), object literal, bracket indexing, assignment expression, `??`, `?.`, or `===`. A call on a value, `e.f(args)`, is the roster function `f` with `e` as its first argument, for the functions the table below writes that way: the web's string and array methods of that name and meaning (`s.trim()`, `xs.filter(x => …)`, `n.toFixed(2)`). `e.length` is the count of a list or text, or, when `e` is a record whose shape declares a field named `length`, that field, as in TypeScript; on a record without one it is refused. The free spelling (`trim(s)`, `length(xs)`) is refused with the edit (`syntax-method-form`), and so is any other method (`xs.reduce(…)`: `syntax-method-call`); the rest of the roster is called as a function (`first(xs)`, `round(n)`). A list type is `T[]`, as in TypeScript (`Item[]`, `option[]`); `list` is refused with the edit (`syntax-list-type`). A `state` may declare its type (`state picked: option = none`), which is otherwise inferred; a `derive`'s type is its expression's and takes no annotation. `any` in an internal roster signature describes special typing; it is not a source-language type annotation. ## Authored tests ```ebnf test-file = { launch | test | property } ; (* a top-level launch line: every test's *) test = "test" STRING [ "on" HOST { "," HOST } ] block( { launch } { step } ) ; HOST = "web" | "macos" | "ios" | "tvos" | "linux" | "windows" | "android" ; launch = "size" NUMBER "x" NUMBER NL (* written 1200x800 *) | "epoch" ( STRING | NUMBER | "now" ) NL (* "2026-09-21T12:00:00Z", Unix ms, or now *) | "time-zone" STRING NL (* an IANA zone, "America/New_York" *) | "at" STRING NL (* the location it opens at, "/item/7#moves" *) | "locale" STRING NL (* a BCP 47 tag, "fr-FR" *) | "seed" NUMBER NL (* 0 through 2^32 - 1 *) | "fresh" [ STRING ] NL (* the Snapback dev server emptied first, seeded from the file *) | "before" "data" NL (* the first step does not wait for data *) | "prefer" IDENT IDENT NL (* CSS media feature and value, before observations *) | "fail" "fetch" STRING [ "times" NUMBER ] NL ; (* armed before the first data load *) step = "tap" "midi" STRING ( HEX { HEX } | "clock" NUMBER NUMBER ) NL (* bytes into an input; or clock ticks: bpm, ms *) | "tap" STRING [ "hover" | "dblclick" | "contextmenu" | "pinch" NUMBER [ "at" NUMBER NUMBER ] | "wheel" [ "-" ] NUMBER [ "-" ] NUMBER | "history" [ "-" ] NUMBER | "into" STRING | "modifiers" STRING | "mediasession" STRING [ NUMBER ] ] NL | "tap" STRING "drag" ( "to" STRING [ "at" NUMBER NUMBER ] | [ "-" ] NUMBER [ "-" ] NUMBER ) { ( "press" | "over" | "hold" ) NUMBER | "from" NUMBER NUMBER | "mouse" } [ "during" { STRING } ] NL | "type" STRING ( STRING [ "append" ] | "key" STRING [ "down" | "up" | "for" NUMBER ] | "copy" | "cut" | "paste" STRING ) NL | "pick" STRING ( STRING { STRING } | "cancel" ) NL | "authorize" [ "cancel" ] NL (* the held sign-in, against the app's server (oauth recipe) *) | "clock" ( "settle" | "data" | "+" NUMBER [ "real" ] | NUMBER ) NL | "clock" "held" { IDENT } [ NUMBER ( "s" | "ms" ) ] NL (* real time, the clock frozen, until held requests reply *) | "clock" "until" "state" IDENT { "." ( IDENT | NUMBER ) } "==" scalar "within" NUMBER NL (* real time, the clock frozen, until it holds *) | "resize" NUMBER "x" NUMBER NL (* the window, mid-test: 800x600 *) | "prefer" IDENT IDENT NL (* change a media preference now *) | "prefer" midi-fact { midi-fact } NL (* each fact at most once *) | "reload" NL | "fail" "fetch" STRING [ "times" NUMBER ] NL (* later fetches whose URL starts with it fail *) | "pass" "fetch" STRING NL (* it stops failing *) | "request" METHOD STRING [ json ] [ "as" ( IDENT | STRING ) ] [ "expect" NUMBER ] NL (* the driver writes through the app's server *) | "close" NL (* the window's close button *) | "back" NL (* the platform's Back *) | "screenshot" STRING NL | "perf" NL block( step { step } ) (* counts its steps' work; no launch line, `clock … real` or `perf` in it *) | "expect" "perf" ( "query" IDENT | ( "built" | "evaluated" ) STRING ) ( "==" | "<=" ) NUMBER NL (* a count of the last `perf` block *) | "expect" "no" "failures" NL (* nothing failed that the test did not arrange *) | "expect" "tree" ( "has" | "missing" ) STRING NL | "expect" ( "disabled" | "enabled" ) STRING NL (* a control's `disabled` (LLP 1116.010 D3) *) | "expect" "refusal" STRING NL (* one observed refusal of the named mutation slot *) | "expect" "text" STRING "==" STRING NL | "expect" "state" IDENT { "." ( IDENT | NUMBER ) } "==" test-value NL | "expect" "sound" ( "has" | "missing" ) STRING [ "at" NUMBER ] [ "gain" NUMBER ] [ "ends" NUMBER ] [ "playbackRate" NUMBER ] [ "pan" [ "-" ] NUMBER ] [ "by" IDENT ] NL | "expect" "wakelock" ( "held" | "released" ) NL | "expect" "midi" ( "sent" | "missing" ) STRING HEX { HEX } [ "at" NUMBER ] NL | "expect" "mediasession" ( IDENT "==" ( STRING | "none" ) | ( "has" | "missing" ) STRING ) NL ; midi-fact = "midi.state" ( "prompt" | "granted" | "denied" | "unavailable" ) | ( "midi.inputs" | "midi.outputs" ) STRING ; (* names, comma-separated *) HEX = HEXDIGIT HEXDIGIT ; (* one byte, as in 90 3c 64 *) test-value = [ "-" ] NUMBER | STRING | "true" | "false" | "none" | "[" "]" ; scalar = [ "-" ] NUMBER | STRING | "true" | "false" | "null" ; METHOD = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" ; json = "{" [ STRING ":" json { "," STRING ":" json } ] "}" (* strict JSON: quoted keys, no *) | "[" [ json { "," json } ] "]" (* trailing comma; brackets may *) | STRING | [ "-" ] NUMBER | "true" | "false" | "null" ; (* span lines *) ``` `on web` runs a test on the hosts it names and skips it on the others, saying so (LLP 1146 D7): a test of something only one host has, such as the browser's history after a reload. `perf` runs its indented steps and counts the work they did (LLP 1146 D3), for the `expect perf` lines after it. `query channelList` is how often the resource's data source was asked; `built "MemberList"` the views created and virtual rows constructed at that component's plan sites (a component the plan declares and never built counts 0), else under the view with that `testId`; `evaluated` the bindings evaluated there. The counts repeat when the steps and the answers are the test's, on the agent's clock, so `clock … real` is refused in a block and the block in a run under platform timing. `query` and `built` read the same on every host; `evaluated` is each runtime's own, so a test that compares it names one host. The development source map names components; a host without one says so. A drive's `perf during "…"` shows the same counts while a test is being written. `expect no failures` fails when, since the test launched or its last `expect no failures`, a resource was refused while no `fail fetch` fired, a send failed that no `expect refusal` names, or the runner said a resource was asked again and again (LLP 1146 D4). Without it, a test still prints those as `unarranged:` notes, and verify counts them as findings. Properties live beside the scripts in a test file; a file declaring a component cannot declare a property. They compile to nothing an app runs. `contract verify` checks them against each script's observations on the runner and Lean. ``` always "the phone is on one of its five screens" expect state screen in "lock" "home" "settings" "display" "messages" always "the counter is a whole number of at least one" expect state counter whole expect state counter >= 1 always "a probe's result is absent or an answer" expect state tsResult in none some always "once the photo has loaded it stays loaded" expect state ready stays true after "increment" "increment adds one" expect state counter == old + 1 after "reset" "reset puts the turns back and counts itself" expect state turns == 0 expect state resets == old + 1 after "rotate" "rotating touches nothing but the turns" only writes turns after "done" "done writes nothing and focuses the button" writes none command focus "done" after clock "the probe never touches what the user set" only writes tsResult rustResult rustExecutorResult never refuses "reset" ``` ```ebnf property = "always" STRING block( { hold } ) | "after" ( STRING | "clock" ) STRING block( { hold | frame } ) | "never" "refuses" STRING NL ; hold = "expect" "state" IDENT ( "==" ( test-value | "old" [ ( "+" | "-" ) NUMBER ] | "not" "old" ) | "in" { test-value | "none" | "some" } | ( ">=" | "<=" ) [ "-" ] NUMBER | "whole" | "stays" test-value ) NL ; frame = "only" "writes" { IDENT } NL (* no other root slot changes *) | "writes" "none" NL | "command" IDENT { test-value } NL ; (* the commit issued this host command *) ``` `old` is admitted only in `after`, `stays` only in `always`, and frame lines only in `after`. The braces in the EBNF mean repetition, not written braces. A read names a whole root state or derive by its bare identifier; fields and indices are stage 2. Before any script runs, verify rejects a derive whose body, dependencies or called functions reach a resource, mutation status, clock or geometry read. A mutation's result slot is a state and may be named. `always` checks every printed configuration, including boot. `stays v` means that a previous observation equal to `v` must remain `v`. `after "action"` reads the value before an input and after its following `land`; the input must have only that successful handler commit, and its landing only `then` and `queue` commits. Other commits, including a commit omitted from the trace, make the pair counted only. `after clock` checks clock advances and landings not paired with inputs, over all their commits. `only writes` compares every root slot, including queue results. `never refuses` reads the action on each commit line; an action never run is reported as not exercised. Poisoned configurations are counted, not checked. Numbers use binary64 arithmetic and IEEE comparisons; other values compare by their whole printed text. These checks cover observed steps, not every commit inside a step, and are not proofs. `contract test ` emits an object with `tests` and `properties` arrays. Each property carries `kind`, `name`, its declaration `line` and parsed `lines`; each line has its source `line` and an `op` (`expect-state`, `only-writes`, `command`). Values carry a `kind` and, for numbers, strings and bools, a `value`. The device runner currently reads the scripts; property checking on a device is a later lane. `expect refusal "saved"` names a mutation slot, after `clock data` or `clock settle` lands its send. It asserts one observed refusal since the previous such assertion for that slot. Verify accepts that observed failure; other refusals remain findings. A refused send keeps the slot's prior value and skips its `then`; assert the rendered failure code or message as well. Targets are driver test ids. `expect disabled "send"` (`enabled`) reads the control's `disabled`, which every host's tree carries (LLP 1116.010 D3). A `tap` on a disabled control does nothing, and fails its step on every host, `send is disabled`: say what the app refuses with `expect disabled`, then `expect enabled` once what enables it has happened. Each test is a session of its own, opened with its launch lines: `size 1200x800`, `epoch "2026-09-21T12:00:00Z"`, `time-zone "America/New_York"`, `locale "fr-FR"` and `seed 7` are the driver's `--size`, `--epoch`, `--time-zone`, `--locale` and `--seed`, so they lead the test's steps, each once; `at "/item/7#moves"` opens the app at that location, fragment included, as `--url` with a path does. Written at the top of the file they apply to every test that does not name its own; either way they override the drive's flags. Media preferences (`prefer prefers-color-scheme dark`) also lead a test or the file, and may change between steps. Defaults merge by feature name; duplicate names among leading lines are errors. A bad name or value fails that test at its line. The latest values survive `reload` and verify's relaunch. A file whose assertions depend on the date says so in the file. `epoch now` is the machine's clock, read once as each test launches, for a test whose app talks to a live backend on real time (`snapback4 dev`); its dates differ run to run, so its expects do not name them. `fresh` empties each `snapback4 dev` server the app fetches (a loopback `net.fetch` grant) before the test opens, through the server's development door `POST /dev/reset`; `fresh "seed.json"` then seeds it with that file's `[{"table": …, "row": {…}}]` (a path from the test file). At the top of the file it runs before every test. A server without the door (a snapback4 older than the release that adds it answers 404) fails the test, saying so. Before the first step, and after a `reload`, the driver waits for the app's data as `clock data` does (its module activated, every request in flight answered and each answer's `then` landed, the clock unmoved); `before data` skips the wait. `fail fetch ""` (LLP 1103) makes every later fetch whose URL starts with the prefix fail as a refused connection does, on every host: a TypeScript source's `fetch` rejects with `FetchError` of kind `"Network"`, a Rust source's request settles `Failed { kind: Network }`, and the request never goes out. `times N` (a positive whole number) fails only the next N; `pass fetch ""` stops it. Leading the steps, or at the top of the file, `fail fetch` is a launch line, armed before the app's first data load; later it is a step. Several prefixes may be armed; the longest matching prefix decides, and arming a prefix again replaces it. A file's line applies to every test that does not arm the same prefix itself. A counted fault that matched no fetch by the test's end (or before it is armed again) fails the test at the line that armed it, and `reload` relaunches with the table as it is then. A stream (`exactStream`, a WebSocket) is not matched. `request POST "/polls/1/votes" {"option": 2} as bob` (LLP 1116.006 D8) is the driver, not the app, writing through the app's server as another person: a method, a path in quotes under the server URL app.ts calls (its first local one, `http://127.0.0.1:8787`; or a whole `http://` URL), an optional JSON body (sent as `content-type: application/json`), and `as `, sent as `Authorization: Bearer ` (the `rest` pattern's tokens are `ada` and `grace`; `as "token-grace"` for a token that is not one word). A status outside 200 through 299, or no answer, fails the step naming the status and the body's start; `expect 409` after the persona (a status from 100 through 599, LLP 1116.010 D3) is the test of a refusal: that status passes it, any other (a success too) fails it. The app shows the write when it next reads, so follow it with `clock +` and `clock data` before the `expect` (a failed `expect` before the clock moved says so). `test`, `verify` and `agent --test` run the server on an empty database (one already answering there, your dev server, is refused). Runs take the server's port in turn (another checkout's run on it is waited for, and named). `fail fetch` does not reach a `request`: the driver sends it, not the app. Arguments after the entry in app.json's `commands.server` are passed through in order. For a server that keeps no database, set `"server": { "database": false }` beside `commands` to omit `DB` and skip scratch database creation and removal. `tap "id" drag to "other" [at x y]` ends on the other node (LLP 1094 D12). `tap "id" drag dx dy` is the driver's `tap … drag` (from the node's middle, or `from x y` in its box, in points; `press`, `over`, `hold` in milliseconds; each once). It is a finger where the carrier has one (the web, iOS: on a simulator the touch runner's real gesture, LLP 1080.000 §11, which a test with a drag starts for its session; a phone has none yet and fails the step); `mouse` makes it the left button on the web, with the page's pointer `fine`, so a desktop path is what runs (iOS refuses it; macOS and Linux drag with the mouse anyway). A finger's drag the browser takes to scroll an ancestor ends in `panrelease` and a `pan cancelled` journal line naming the `touch-action` that keeps it. `during "op" …` is last: each quoted op is a read (`tree`, `layout`, `state`, `logs`, `screenshot`) or `clock`, run while the finger is down, after the move and before the hold. `press` and `hold` advance the virtual clock by those milliseconds (a game's ticks), except under `--timing platform`, where the platform's own clock owns the gesture. `tap "id" pinch [at x y]` is the driver's pinch: `scale` greater than 0, about the node's middle or about `at` in its box. `tap "id" wheel ` is the driver's wheel (the agent's `tap wheel dx dy`) at the node's middle, in points (CSS pixels on the web): a positive `dy` scrolls on toward the content's end, so its offset grows, and a negative one back toward its start; a positive `dx` scrolls right. It lands at once, with no momentum, so a Mac's scroll can be tested where a drag selects text instead. `tap "root" history ` traverses browser history on the navigation root: a nonzero 32-bit integer, negative for Back and positive for Forward. It uses the browser's `history.go`, including entries kept across `reload`; other hosts report unsupported. Scope these tests with `on web`. `tap "id" dblclick` and `contextmenu` are the driver's forms of the same names (no Linux carrier double-clicks). `tap "list" into "key"` brings a virtualized list's row into view by its key, so a row outside the rendered window can be tapped by its own id on the next step. `type "id" "text"` sets the field's value, as Playwright's `fill`; `append` adds the text after the value the tree shows (a prefilled reply). `type "id" key "Name"` presses the key; `down` and `up` are the two halves, and `for ` holds it that long on the virtual clock (`down` or `up` together with `for` is refused). `reload` restarts the app on the store it had: the web page reloads its current history entry in the same profile, keeping Back and Forward entries; each separate test starts with an isolated history. A native app relaunches on the same scratch store. Its state starts over and `performanceNow()` is 0 again, at the date the test had reached (`epoch` plus its `clock` steps); what the app stored is what it reads, so persistence is testable. `close` presses the window's close button as ⌘W or the red button does (the driver's `close`): its `beforeunload` handlers hear it, and a window one keeps stays open, so the test goes on to the app's own "Save changes?" (on the web the browser's "Leave site?", answered "Stay"). A window that closes, by `close` or by a press the app answers with `close()`, takes the session with it, and a step after it fails naming the line. macOS and the web; iOS and Linux close no window and refuse it. `back` is the platform's Back, as a person takes it, from what is on top: an open dialog's cancel (a modal `dialog` or `role="alertdialog"` popover: Escape on the web and macOS, the alert's Cancel on iOS), else the top sheet dismissed (a `navigationPresentation="modal"` route: iOS's swipe down), else the pushed route popped (iOS's Back button, the browser's Back, ⌘[ on macOS, Alt+Left on Linux). A route goes back as those do: its Back control pressed, else the root's `navigate` with the location beneath, else the router's own `back`. One the app refuses (a disabled Back control, `closedby="none"`) is a note, and the test goes on; with nothing to go back from the step fails. It names no target: `tap` the app's own control to press it. `type` on a `select` chooses an enabled option by value, else by its one label; on a date, time or range input it sets the value in HTML's format; on a checkbox it takes `true` or `false`, on a radio `true`. A target out of view is scrolled into view first. An input step ends with what it settled (an answer given in the input's turn, and its mutation's `then`; on a native host, not one given while another answer's storage is in flight, LLP 1097); otherwise the clock stands still between steps: what an input starts (a reply on real time, a transition) lands at a `clock` step, as `clock settle` (or `clock data`, which lands replies and their `then`s without moving the clock); a timer fires when the clock reaches or passes its time (`clock +N`), and `clock settle` fires one only if it reaches it while advancing to a motion's end. `expect text` reads the node's text, else a control's value (a `select`'s chosen value, not its options), else its descendants' text in order (a button's label), else a field's value. `expect state name.field` reads a field of a record at any depth, and `name.0` a list index (a whole number from 0); a missing field fails naming the fields there. The value is a number, including a negative (`== -3`), a string, a bool, `none`, or `[]` — not an expression (`-1 + 2` is refused). `expect wakelock held` and `expect wakelock released` compare `state.wakelock.held`. This is the runner's holder record: displayed elements requesting the lock, not whether the OS or browser granted it. A failure names the holding view ids and their testIds when present, or says nothing holds it. `expect sound has "assets/x.wav"` passes when the runner's record of voices (every host's, under the driver's clock) holds one of that source matching each clause given: its start (`at`, runner milliseconds), its `gain`, its end (`ends`), its `playbackRate` and `pan` (a voice that set neither has 1 and 0), and how it ended (`by end|group|cut|stop|cancelled`); `missing` is the negation. A dropped call is not a voice, and a test that asks about a voice the 1,024-voice record no longer holds fails and says so. MIDI's steps (LLP 1125 §4): `prefer midi.state granted midi.outputs "OP-XY, IAC" midi.inputs "keys"` sets what a request finds under the driver (granted unless named) and the ports, by name (a port named no longer is disconnected); `tap midi "keys" 90 3c 64` is one delivery from that input now, refused unless the input is connected and a mounted element hears it or a sequence follows it; `tap midi "keys" clock 125 2000` sends Timing Clock from that input at that tempo for that long from now, as an arm does, a delivery every 50 ms with the clock moved to each (a tempo whose tick is a whole number of milliseconds, such as 125 or 100, keeps every stamp exact); `expect midi sent "OP-XY" 99 4c 7f at 500` passes when the runner queued that message for that output and has not taken it back (at that time, when given), `missing` the negation. Each byte is two hex digits. `tap "audio" mediasession "seekforward"` calls the handler the platform would call for that media session action (LLP 1098 D10): `play`, `pause` or one of the six; its number is a seek's `seekOffset` (absent: the element's own) and `seekto`'s time (required there). It is never a press, needs no box, and is refused when the element does not own the session or does not offer the action. `expect mediasession` reads `state.mediaSession`: `owner` (by testId, or `none`), `title`, `artist`, `album`, `artwork` (as authored) or `playbackState` `==` a string, or `has`/`missing` an offered action. The test compiler emits steps as JSON; the agent driver executes them. Its interactive API (`bun exact.mjs agent --help`) is larger than this test-file grammar. ## Standard functions and intrinsics The complete current `stdlib` name inventory is below. `T` and `U` express generic relationships enforced by the type checker, not user-declarable type parameters. Signatures are authored forms; localization's internal lowered signature differs. An app's `fn` of one of these names shadows it in every free call (`indexOf(xs, x)`), as a JavaScript function declared over a global does: a name the inventory gains later never breaks an app that declared it first. A method call (`xs.indexOf(x)`, `s.length`) is always the built-in, so a file that declares a `fn`, shape or action of that name and also calls it as a method is refused (`syntax-method-shadowed`), since the call would otherwise reach the app's function, which TypeScript's method never does. `contract vocab ` prints one function's entry from these tables (its rows and examples) beside the roster's signature, so what a row says here is what `vocab` says. Every roster function has a row and an example, and each example written `` `call` is `value` `` holds: a contract test evaluates them. A row written `x.f(…)` is called on its first argument, as the web's method is ([expressions](#expressions-and-types)); the others are called as functions. | Call | Result / restriction | Example | | --- | --- | --- | | `performanceNow()` | Milliseconds on the runner's clock since this launch of the app (it restarts at 0 on every launch; the driver's clock under the agent, from 0), as the web's `performance.now()`, not `Date.now()`: the date is `time.epochAtZero + performanceNow()`, with `resource time = exactTime() as shape T` declared, and that sum is what anything stored or sent takes ([timer recipe](recipes/timer-that-survives-relaunch.md)). `now()` is refused (`type-now-renamed`, LLP 1109 D1) with both repairs. A read does not schedule a render, and a derive that reads it is not read again as time passes (when it is depends on the host's clock), so a value that follows the clock comes from a timer: keep the time in state that a `task … every` action writes | `performanceNow() - startedAt`, in a timer's action, is the milliseconds since `startedAt` | | `formatTime(ms, offsetMinutes, "short")` | String; fixed offset east of UTC, en-US formatting (`exactTime().utcOffset` is the zone's offset now, answered again when it changes) | `formatTime(1790000000000, 0, "short")` is `"2:13 PM"` | | `formatDate(ms, offsetMinutes, "medium" or "month-year" or "iso")` | String; format is a literal choice, not an expression containing `or`. `"iso"` is `YYYY-MM-DD`: the date at that wall time, which is `toISOString`'s date part at a whole-minute offset (a fractional offset's sub-millisecond wall time is not clipped again, as no style's is) (LLP 1102 §3.4); every style prints `""` outside years 1–9999 | `formatDate(1790000000000, 0, "medium")` is `"Sep 21, 2026"` | | `formatDay(then, now, offsetMinutes, "day" or "day-time" or "list")` | String; `then`'s day named from `now`'s, both at the one offset (its calendar days, so a DST change between them is not applied), en-US (LLP 1116.020 D8). `"day"` is `Today`, `Yesterday`, `Tomorrow`, else `formatDate`'s `"medium"`; `"day-time"` adds ` at ` and `formatTime`'s time (`Today at 4:42 PM`, `Oct 11, 2026 at 4:42 PM`); `"list"` is Mail's and Notes' list label: the time today, `Yesterday`, the weekday two to six days back, else `10/11/26` (a later day too). Pure: `now` is the app's ticked clock ([dates recipe](recipes/dates-and-days.md)); `""` when either instant is outside years 1–9999 | `formatDay(1790000000000, 1790000000000 + 86400000, 0, "day")` is `"Yesterday"` | | `formatNumber(n, "compact")` | String; admitted deterministic compact format (`1.2K`) | `formatNumber(1249, "compact")` is `"1.2K"` | | `formatNumber(n, "decimal")`, `formatNumber(n, "percent")`, `formatNumber(n, "currency", "USD")` | String; `Intl.NumberFormat("en-US")`'s deterministic forms (LLP 1116 D8): `"decimal"` is grouped with at most three fraction digits (`1,234.567`), `"percent"` multiplies by 100 with none (`0.256` is `26%`), `"currency"` takes an ISO 4217 code literal and prints its symbol and fraction digits (`$1,481.47`, `-$5.00`, `¥1,235`, `CHF 5.00`; the codes are USD, EUR, GBP, JPY, CNY, INR, CAD, AUD, NZD, HKD, SGD, CHF, SEK, NOK, DKK, PLN, MXN, BRL, KRW, ZAR, TWD, ILS, PHP and VND). Each rounds the value's shortest decimal digits half away from zero, as `Intl` does (`1.005` is `$1.01`, where `toFixed` says `1.00`), and signs a negative zero (`-0`); `NaN` and the infinities print `""`. The code is written with `"currency"` only (`type-format-style`) | `formatNumber(1481.47, "currency", "USD")` is `"$1,481.47"` | | `n.toFixed(digits)` | String; JavaScript's `Number.prototype.toFixed`: the binary value rounded (`(1.005).toFixed(2)` is `"1.00"`), a tie away from zero, `-0.001` at 2 is `"-0.00"`, `1e21` and up as `toString` prints; one declared difference: `NaN` and the infinities print `""` (LLP 1054.000.003 D7). `digits` is a whole-number literal 0–100 (LLP 1102 §3.2) | `(3.14159).toFixed(2)` is `"3.14"` | | `formatDecimal(units, digits)` | String; an integer count of a smallest unit as a decimal, exactly: `formatDecimal(1234, 2)` is `"12.34"`, `formatDecimal(-5, 2)` is `"-0.05"`, `-0` is `"0.00"`; a count that is not an integer, or not finite, is `""`, so money is a count of cents, or `formatDecimal(round(price * 100), 2)` for a price of at most two decimals under a trillion. `digits` is a whole-number literal 0–20 (LLP 1102 §3.2) | `formatDecimal(1234, 2)` is `"12.34"` | | `value.length` | Number; list item count or string UTF-16 code-unit count | `[4, 5, 6].length` is `3` | | `value.isEmpty()` | Boolean; empty string or list | `"".isEmpty()` is `true` | | `value.toString()` | String; number, boolean, or string conversion | `(42).toString()` is `"42"` | | `floor(n)` | Number | `floor(2.7)` is `2` | | `ceil(n)` | Number; `Math.ceil` | `ceil(2.1)` is `3` | | `round(n)` | Number; JavaScript's `Math.round`: a half rounds up (`round(2.5)` is 3, `round(-2.5)` is -2), not away from zero. Two decimals is `round(v * 100) / 100` | `round(2.5)` is `3` | | `parseNumber(text)` | `option`; `some` for a decimal numeral in the text, trimmed as `trim` does: an optional sign, digits with an optional fraction or a fraction alone, an optional exponent (`" 12.5 "`, `"-3"`, `".5"`, `"1e3"`), the nearest double as `Number()` reads it; `none` for anything else (`""`, `"12px"`, `"0x1F"`, `"1_000"`, `"Infinity"`), past the largest finite, or a nonzero numeral that rounds to zero (LLP 1102 §3.1) | `parseNumber(" 12.5 ")` is `some(12.5)` | | `calendarDiff(from, to, "years" or "months")` | `option`; whole years or months from one `YYYY-MM-DD` date to another, counted as an age is: a period completes when `to`'s month and day reach `from`'s (months compare the day), so a Feb 29 start completes a year on Mar 1 of a common year and a Jan 31 start a month on Mar 1, as Temporal's `PlainDate.until` counts with `largestUnit` `"years"` or `"months"`. When `to` is earlier, the count back, negated; `none` when either is not a real date (LLP 1102 §3.4) | `calendarDiff("1990-05-10", "2026-05-09", "years")` is `some(35)` | | `max(a, b)` | Number | `max(3, 7)` is `7` | | `min(a, b)` | Number | `min(3, 7)` is `3` | | `sin(rad)` | Number; `Math.sin`, CSS's `sin()`. The native runners' `libm` gives the same bits on every target; the web's `Math.sin` may differ in the last bits | `sin(0)` is `0` | | `cos(rad)` | Number; `Math.cos`, CSS's `cos()`, computed as `sin` is (a point on a ring is `cx + r * cos(a)`, `cy + r * sin(a)`) | `cos(0)` is `1` | | `sum(xs)` | Number; a list of numbers added left to right from 0, as `xs.reduce((a, x) => a + x, 0)` (`sum([])` is `0`), a list step an item; `sum(xs.map(x => …))` totals a field | `sum([1, 2, 3])` is `6` | | `range(n)` | `number[]`; Exact's own, not the web's: the whole numbers from 0 to `n - 1` in order (`range(0)` is `[]`), a list step an item. `n` is a whole number at least 0: another literal is refused, and another computed value traps (`TypeMismatch`), refusing the commit that made it. `range(a, b)` is refused: the numbers from `a` are `range(b).slice(a)`, a window `range(n).slice(a, b)` | `range(4)` is `[0, 1, 2, 3]` | | `text.includes(substring)` | Boolean, case-sensitive literal substring | `"grocery".includes("roc")` is `true` | | `xs.includes(item)` | Boolean; `T` a string, number or bool, compared by SameValueZero (`NaN` is found, `-0` is `0`) | `["a", "b"].includes("b")` is `true` | | `text.startsWith(prefix)` | Boolean, case-sensitive | `"/settings".startsWith("/")` is `true` | | `text.endsWith(suffix)` | Boolean, case-sensitive | `"photo.png".endsWith(".png")` is `true` | | `text.trim()` | String, JavaScript whitespace/line-terminator trimming | `" milk ".trim()` is `"milk"` | | `text.slice(start, end?)` | String; JavaScript's `slice` over UTF-16 code units: fractions truncate, NaN is 0, a negative index counts from the end, an omitted `end` is the end; a cut surrogate half is U+FFFD | `"Tuesday".slice(0, 3)` is `"Tue"` | | `xs.slice(start, end?)` | `T[]`; the items, the indices clamped as text's are | `[1, 2, 3, 4].slice(1)` is `[2, 3, 4]` | | `xs.concat(T[])` | `T[]`; both lists' items in order (`[...xs, x]` is `xs.concat([x])`) | `[1, 2].concat([3])` is `[1, 2, 3]` | | `xs.flat()` | `T[]`; a list of lists' items in order, one level, as the web's `flat()`. It takes no depth (`flat(2)` is refused): two levels are `xs.flat().flat()`, and the web's `flatMap` is `xs.map(x => …).flat()` when the callback returns a list (`xs.map(x => …)` when it returns one value) | `[[1], [2, 3]].flat()` is `[1, 2, 3]` | | `text.indexOf(substring)` | Number; the first match's position in UTF-16 code units, `-1` for none, `0` for `""` | `"a,b".indexOf(",")` is `1` | | `xs.indexOf(item)` | Number; `T` a string, number or bool, compared by `===` (`NaN` is never found, `-0` is `0`); `-1` for none | `["a", "b"].indexOf("c")` is `-1` | | `text.split(separator)` | `string[]`; the pieces between the separator's matches, left to right; `""` splits into UTF-16 code units (a cut surrogate half is U+FFFD), and `"".split("")` is `[]` | `"a,b,c".split(",")` is `["a", "b", "c"]` | | `text.replaceAll(find, with)` | String; every match of the string `find`, left to right; `$$`, `$&`, `` $` ``, `$'` in `with` (`$1` is literal); an empty `find` inserts at every code-unit boundary; no regular expressions | `"a-b-c".replaceAll("-", " ")` is `"a b c"` | | `text.toLowerCase()` | String; Unicode's default lowercase, final sigma kept, no locale (the web core links the case tables by use) | `"Milk".toLowerCase()` is `"milk"` | | `text.toUpperCase()` | String; Unicode's default uppercase through the same tables, its full mappings included (`"ß"` is `"SS"`), no locale. Text on screen is uppercased by `text-transform="uppercase"`, which each platform applies as it draws; `toUpperCase` is for a string handed elsewhere: a native view's argument, `share`, a value stored or sent | `"Straße".toUpperCase()` is `"STRASSE"` | | `first(T[])` | `option` | `first([4, 5])` is `some(4)` | | `xs.at(index)` | `option`; truncates index toward zero, negative from end | `[4, 5, 6].at(-1)` is `some(6)` | | `xs.map(callback)` | `U[]`; callback returns one value | `[1, 2].map(x => x * 10)` is `[10, 20]` | | `xs.filter(callback)` | `T[]`; callback returns boolean | `[1, 2, 3].filter(x => x > 1)` is `[2, 3]` | | `xs.join(separator)` | String; `xs` a list of strings, numbers or bools, each printed | `["a", "b"].join(", ")` is `"a, b"` | | `sortBy(xs, x => key)`, `sortBy(xs, x => key, "desc")` | `T[]`; the items in a stable order of one key, a number or a string, computed once an item (the callback is `map`'s, item and index). Numbers ascend by `<` with `NaN` counted as `0` (so `NaN`, `-0` and `0` tie); strings by UTF-16 code units as `<` compares them, no locale. `"desc"` (a literal, as is `"asc"`, the default) reverses the order of the keys, not of ties: equal keys keep their input order in both directions, so a second key is an earlier sort (`sortBy(sortBy(xs, x => x.created), x => x.bpm)` is by bpm, ties oldest first). Exact's own function, not TypeScript's `toSorted`, whose comparator is refused; a locale's collation, a stored order and rows the view has not loaded are the data module's (LLP 1116.016 §5) | `sortBy([3, 1, 2], x => x)` is `[1, 2, 3]` | | `encodeURIComponent(text)` | String | `encodeURIComponent("a b&c")` is `"a%20b%26c"` | | `encodeRouteSegment(text)` | String; routing's checked segment encoding | `encodeRouteSegment("a b")` is `"a%20b"` | | `open(router, location)` | Router | `top(open(nav, "/item/7")).name` is `"item"` | | `push(router, location)` | Router; same top URL does not add a visit | `depth(push(nav, "/item/7"))` is `2` | | `replace(router, location)` | Router; the top visit takes the location and keeps its id. A stack's first visit keeps its root route: a location matching another route is refused and the router is unchanged (a first-launch screen: docs/recipes/first-launch.md) | `top(replace(push(nav, "/item/7"), "/item/8")).url` is `"/item/8"` | | `back(router)` | Router | `depth(back(push(nav, "/item/7")))` is `1` | | `select(router, tabName)` | Router | `nav = select(nav, "search")`, in an action, shows the `search` tab | | `go(router, location)` | Router; the top, a pop to it on the selected stack, another tab whose top it is, or else a push onto the selected stack, whatever tab declares its route (into the route's declared tab: `open`) | `top(go(nav, "/item/7")).name` is `"item"` | | `stack(router)` | `Entry[]` | `stack(push(nav, "/item/7")).length` is `2` | | `top(router)` | `Entry` | `top(nav).name` is `"home"` | | `depth(router)` | Number | `depth(nav)` is `1` | | `params(router, parameterName)` | `string[]`: that parameter's non-empty values over the selected stack, bottom first, duplicates kept (one entry's own value is `entry.params.`, `""` where its route does not bind it; the name must be a parameter of some route) | `params(push(nav, "/item/7"), "id")` is `["7"]` | | `searchParam(entry, name)` | String | `searchParam(top(push(nav, "/item/7?tab=notes")), "tab")` is `"notes"` | | `fragment(entry)` | String: what the entry's location had after `#`, fragment-encoded as the browser's `hash` is, `""` when none. It is no part of the screen: `/item/7#moves` is the visit to `/item/7`, and a `push`, `replace` or `go` that changes only the fragment changes it on that visit. Written as `path("item", id) + "#moves"`, or `href="#moves"` on a `link` to the screen shown. Once the route's content has arrived, each host scrolls to the element with that `id` in the entry's route, as a browser scrolls to a fragment (`top`, or no such element: the route's top) (LLP 1153 D5) | `fragment(top(push(nav, "/item/7#moves")))` is `"moves"` | | `sharedFiles(entry, name)` | `SharedFiles`: `{files: Picked[], dropped, pending}`, the files a received share carried, by the token in the entry's `name` parameter; `pending` counts those still being copied, and a token the host did not set this launch answers no files (LLP 1133.024 D3) | `sharedFiles(top(nav), "files").files.length` is the count of shared files that have landed | | `t("key", name=value, …)` | Localized string; validates tables/placeholders | `t("greeting", name=user.name)` is the `greeting` string of the app's locale with `{name}` filled | | `frame(id)` | `Geometry`, actions only; last layout where the viewer sees it, as `getBoundingClientRect`: in the viewport, every scroll offset and transform applied | `frame("card").height`, in an action, is the card's height as drawn | | `rangeFrame(id, start, end)` | `Geometry`, actions only; a textarea's UTF-16 range as `Range.getBoundingClientRect()` (on a `text markup="markdown"`, the union of the atom pills the range meets, [inline atoms](#inline-atoms-atom-atoms-atomfaces-atompress)): collapsed, the caret's rectangle; unavailable past the text, while composing, or with no host reader ([decorations](#plain-textarea-decorations), LLP 1114.007) | `rangeFrame("composer", caret, caret)`, in an action, is where a mention popover goes | | `measure("id")` | `Geometry`, actions only; literal id, the border box as if `height` were `auto`, other styles kept (padding, border, `min-height`, `max-height`, insets): 0 for a bare box whose children are all absolutely positioned (a navigation root); `frame` is the laid-out box | `measure("note").height`, in an action, is the note's natural height | | `elementFromPoint(x, y)` | `option`, actions only; the `id` of the front-most of `frame`'s boxes at the viewport point, or of its nearest ancestor with one (DOM's `elementFromPoint(x, y)?.closest("[id]")?.id`): a box reaches as far as its `-exact-hit-slop`, ancestors' overflow clips apply, `pointer-events: none` and hidden boxes are passed over | `elementFromPoint(x, y)`, in a pointer action, is `some("card-3")` over that card | | `elementsFromPoint(x, y)` | `string[]`, actions only; own ids of every hit box, including hit ancestors, front to back; id-less boxes skipped, with `elementFromPoint`'s clips, transforms and skips | `elementsFromPoint(e.clientX, e.clientY)` sees through an Inspect overlay | | `querySelectorAll(selector)` | `string[]`, actions only; only `[data-word~="value"]` for a declared `data-*` word; own ids of mounted, displayed, non-inert matches in tree order, excluding covered routes and unselected tabs; any other dynamic selector gives `[]` | `querySelectorAll("[data-paint~=\"accent\"]")` lists the accent nodes | On Apple, Linux and Windows, `frame` of an inline `text` run returns the viewport-space union of its line fragments, using the run's own font ascent and descent and excluding collapsible spaces at a soft wrap. Preserved spaces and tabs remain in the rectangle, including at a soft wrap. An empty run has a zero-width rectangle at its insertion point with its font's height. Scrolling moves this rectangle; clipping does not trim it. Linux and Windows read the acknowledged paragraph, including multi-column placement and affine transforms; a perspective-projected paragraph is unavailable. These readers give a cross-run ligature's advance to its first character's run; later runs have zero-width rectangles at its end, as in Chrome. Windows shares the Linux presenter; runtime verification was on Linux. Android and the terminal have no inline-run reader. For `elementsFromPoint`, an id-less hit outside a named ancestor's own box includes that ancestor once before lower layers. This keeps its first entry equal to `elementFromPoint`'s nearest-id answer; LLP 1001 §1 declares the rare difference from Chrome's raw filtered list. An id-less hit inside that box is skipped even when it covers a named sibling. Both point reads skip covered navigation routes, inert subtrees, and closed dialogs or popovers. Open painted top-layer content uses its presented frame and escapes clips on its authored ancestors. A macOS alert projected as an `NSAlert` sheet or an iOS UIKit confirmation has no authored hit boxes. For `querySelectorAll`, a closed popover or dialog is hidden by its presenter; an open one is included when it matches. A wide split list is included only where the host presents it (on the web); native hosts hide the unselected route. The router functions (`open` through `sharedFiles`) and `encodeRouteSegment` exist only in an app with a `routes` declaration. `<`, `<=`, `>` and `>=` take two numbers or two strings. Two strings compare as JavaScript's do, by UTF-16 code units in order with a proper prefix first, no locale (`"09:30" < "10:00"`, `"Z" < "a"`). `slice` and `replaceAll` work on code units as JavaScript does and make the whole result well formed once: a lone surrogate half is U+FFFD, since the native runners hold Unicode scalar values. A result past the runner's string bound (64 MiB of UTF-8) traps on every executor. `padStart` is not in Contract; the refusals say what to write instead. Neither `indexOf` nor `split` takes the web's second argument (a start position, a limit), and `split` takes no regular expression. `concat`, `split`, `sum`, `range`, `flat`, and `slice`, `includes` and `indexOf` over a list, take list steps on the evaluation's budget as `map` and `join` do (LLP 1090): one for each item `concat`, `slice`, `split`, `range` or `flat` makes (and `flat` one for each list it reads), taken before the list is built, one for each item `sum` adds, and one for each item `includes` or `indexOf` scans up to its match. A list they build is bounded as any built value is. `sortBy` takes one step an item, as its key is computed, and none for its comparisons: a stable merge sort makes at most n·⌈log₂ n⌉ of them, which is the cost the budget does not count. So 5,000 rows filtered and then sorted take 10,000 of the 65,536 steps, and the sort of 30,000 is about 450,000 comparisons on every evaluation that reads it, which a `derive` makes once per change of what it reads. Compiler intrinsics and special forms additionally include: | Form | Meaning | Example | | --- | --- | --- | | `pending(resourceOrMutation)` | Boolean; request is in flight | `pending(saved)` is `true` from `send saved = …` until its reply | | `current(resource)` | Boolean; it shows an answer to its current call arguments: not its placeholder, not an answer kept from other call arguments or across idleness (`when`). A refresh of the same arguments, or a changed `with` value, leaves it true (LLP 1161.001) | `if not current(detail)` shows "loading" for a newly selected item, and not after a refresh | | `failed(resourceOrMutation)` | Boolean; current resource request failed without an answer | `failed(weather)` is `true` once the weather request fails | | `failure(resourceOrMutation)` | `option`: `none` until the current request fails without an answer, then `some({ code, message, reason })` (LLP 1109 D3); `code` is the nonempty source `reason` when supplied, otherwise the infrastructure vocabulary below, the same on every host; `message` is what the agent's `state.failed` shows, and says more and differs by host; `reason` is the source error’s string `reason` (a Snapback refusal’s code such as `SLOT_FULL` or `E_RULE`), else `""`, on every host | `match failure(weather)` with `case some(f)` reads `f.code`, `"offline"` with no connection | | `path("route", args…)` | Checked encoded route location | `path("item", "7")` is `"/item/7"` | | `empty(field=value, …)` | Resource placeholder's zero record with overrides | `resource days = loadDays() as shape Days else empty()` shows `Days`' zero until the answer | | `some(expr)` | Construct an option | `some(3)` is an `option` holding 3 | | `DeclaredShape(field=value, …)` | Construct a record | `Day(key="mon", ml=0)` is a `Day` | | `DeclaredShape(base, field=value, …)` | Copy a record | `Day(d, ml=d.ml + 250)` is `d` with `ml` changed | `failure(x).code` preserves a domain refusal code (`SLOT_FULL`, `E_RULE`, `E_OFFLINE`) when the source throws an error carrying that string as `reason`. Otherwise it uses one of six infrastructure words, by what reached the runner (`runner/src/failure.rs`): | Code | When | | --- | --- | | `offline` | A request the source made reached no server (no connection, DNS, TLS, a reset, the driver's `fail fetch`) and the source let the rejection through | | `timeout` | A request's deadline (`exactTimeout`) passed, let through | | `refused` | The host refused a request outside the app's grants or limits (admission, or a response over its size limit), let through | | `shape` | The answer is outside the resource's declared shape | | `storage` | A storage call failed (a coded storage refusal: `denied`, `full`, `EBUSY`, a filesystem error, storage unavailable here), let through | | `error` | Anything else: the source's own error (a TypeScript `throw` or rejection, a Rust source's `Err`), an aborted request, a host that cannot make it, a module over its time budget | "Let through" means the TypeScript module rethrew the `fetch` or storage rejection or did not catch it; an error the module makes of its own is `error`. An HTTP error status is an answer, never a failure: a module that throws on a 500 fails with `error`. A Rust source's own error is `error` on every host. A request the caller aborted is `error`. A response over its size limit (`exactIndependentHttp.maxResponseBytes`, else the host's 64 MiB; a stream's event or message over its ceiling) is `refused` on every host: the server answered, and the host will not take more than the app said it would; asking again gets the same answer, which `offline` would not imply. A new code is a breaking change; branch with a default case. `failure(x).reason` retains the domain string as well, empty when none was supplied. For a mutation, failure belongs to its latest send: a new send, a successful answer, or an explicit assignment clears it. A `parallel` mutation's belongs to its latest send to conclude: a send's failure shows unless a later send has already answered or failed, and an earlier send's success, which does not land in the slot then, leaves a later send's failure standing. A failed request retains its previous value, does not retry, and does not run `then`. A synchronous throw that refuses the sending action still rolls that action back; only an admitted request's failure persists. Finite string parameters are written `enum<"draft", "published">`. Members must be quoted, distinct strings, with at least one member. A literal call argument must be a member; a dynamic argument must have a compatible declared enum type. Shapes may use enums too; a source answer outside the declared vocabulary fails its shape check. Generated TypeScript uses the corresponding string literal union. An enum's placeholder is its first member. Enums use ordinary string values at the data seam. `Router`, `Tab`, `Entry`, and `Params` are introduced by routes. The compiler also declares `Geometry` (geometry reads), `Failure` (`failure(x)`'s `{ code: string, message: string, reason: string }`), `MarkdownSelection` (the `select` payload) and `Picked` (a file input's `change` payload). None of these is constructible. The actual sources are [`format.json`](../plan/tables/format.json), [`runner/src/stdlib.rs`](../runner/src/stdlib.rs), and the compiler's type/lowering modules. A function available in JavaScript, CSS, Swift, or a data module does not automatically become a Contract function. ## Built-in tags The compiler's built-in tag vocabulary is grouped here by use. It is not an HTML parser: arbitrary HTML names outside this table are not implicitly accepted. Several tags share a kernel node type with different fixed properties. | Family | Names | | --- | --- | | Boxes / layout | `view`, `box`, `row`, `column`, `scroll`, `list` | | Structure | `main`, `header`, `nav`, `section`, `footer`, `article`, `aside`, `dialog`, `hr` | | Tables / description lists | `table`, `caption`, `thead`, `tbody`, `tfoot`, `tr`, `th`, `td`; `dl`, `dt`, `dd` | | Text and controls | `text`, `h1`–`h6`, `time`, `button`, `link`, `input`, `textarea`, `select`, `option`, `progress` | | Media / metadata | `image`, `video`, `audio`, `iframe`, `canvas`, `head` | | SVG scene | `svg`, `g`, `path`, `polyline`, `polygon`, `circle`, `ellipse`, `line`, `rect` | | SVG definitions | `defs`, `symbol`, `use`, `clipPath`, `marker`, `mask`, `pattern` | | SVG color / text / embedding | `linearGradient`, `radialGradient`, `stop`, `tspan`, `foreignObject` | | SVG filters | `filter`, `feBlend`, `feColorMatrix`, `feComponentTransfer`, `feComposite`, `feConvolveMatrix`, `feDiffuseLighting`, `feDisplacementMap`, `feDropShadow`, `feFlood`, `feFuncR`, `feFuncG`, `feFuncB`, `feFuncA`, `feGaussianBlur`, `feMerge`, `feMergeNode`, `feMorphology`, `feOffset`, `feSpecularLighting`, `feTile`, `feTurbulence`, `feDistantLight`, `fePointLight`, `feSpotLight` | `h1` to `h6` are `text role="heading" aria-level=1` to `6` (LLP 1116.017 D1). `text` inside SVG has SVG semantics. A hyphenated tag names a native module: the bake checks it against `app.json`'s `modules` list (`bake-unknown-module`), and its unknown attributes, and an SVG element's own props, pass to the module as one object. A known attribute binds to the module's box, and the box takes only layout, box and paint rows, handlers, `testId`, `id`, `class`, `data-*`, `role`, ARIA, `disabled` and `inert`; any other known name (`color`, `value`, `command`, `href`) is `lower-native-attr` (LLP 1024 D1, LLP 1088 D7.2). A package cannot declare a view prop whose name writes a layout row (`width`, `padding`, `flex`): the bake refuses the declaration with `bake-view-prop-layout`, naming the package, view and prop. Other declared props go to the view when written on the tag, even under a known name (`exact-camera`'s `object-fit`). A class's rows style only the box: `object-fit` in a class never configures the camera's viewfinder; write it on `exact-camera` for that. A renamed spelling (`fontSize`) is refused whatever is declared. A capitalized name is a component use, not a built-in tag. Platform support can further restrict an admitted tag, notably native `foreignObject`. `contract vocab` lists every tag and attribute the compiler admits, including `h1`–`h6` and the compile-time attributes `class`, `part` and `-exact-row-look`, with each style row's codec, values and default, the renamed spellings and the contextual restrictions: `cargo run -q -p contract -- vocab padding` for one name, no name for the card (what an app writes, at most 12 KB), `--all` for everything, `--json` for the same entries as JSON. From an app made by `exact new`, run `bun exact.mjs contract vocab`. `padding`, `margin`, `inset`, `border-width`, `border-style` and `border-color` take CSS's one to four values (`padding="12px 40px"`: top and bottom 12, sides 40), and `border-radius` its one to four corners (`border-radius="18px 18px 0 0"`: top-left, top-right, bottom-right, bottom-left; no `/` elliptical radii), as a literal or a literal arm; `gap` takes its row and column gaps, and `overflow` and `overscroll-behavior` their x and y values, the same way (`gap="8px 16px"`, `overflow="hidden auto"`). A template is split the same way at its own text's white space, each interpolation part of one value that also holds text, outside parentheses (`` border-radius=`${r}px ${r}px 0 0` ``: the top corners `r`). Another list (`` `${a} ${b}` ``, a concatenation, a `fn` that returns one) is refused (`lower-css-shorthand`): write the longhands, each of which may be computed. A computed single value (`padding=pad`) is every side's. An empty or whitespace-only string explicitly clears a style row: it keeps the author's declaration without an own value (inherited on inherited rows, otherwise initial). For example, `background-color=dot` with `dot` `""` overrides class and platform fills with a transparent background. An absent optional value (`none`) removes the binding's declaration and exposes those lower-priority values. `border-style` and each `border--style` accept `none`, `hidden`, `solid`, `inset`, `dotted` and `dashed`. The two patterned styles keep their authored border widths in layout. AppKit, UIKit and Linux paint their dots and dashes; `column-rule-style` still accepts only `none`, `hidden` and `solid` on native hosts. CSS hyphens are part of the authored name. `testId` and admitted host-specific props retain their declared spelling. `text-wrap-style="balance"` and its `text-wrap="balance"` shorthand are inherited; `auto` restores ordinary greedy wrapping. `pretty` and `stable` are refused with the supported values, `auto | balance`. Every host balances the lines before a paragraph's first forced break, up to six, at the narrowest width that keeps their count, as Chrome does (LLP 1001 §6). `-webkit-font-smoothing` takes `auto` or `antialiased` and is inherited; `none` and `subpixel-antialiased` are refused. It changes how glyphs paint, never how text measures (LLP 1019 D12). `-exact-hairline` is a length every length row takes but a font's size and line height (LLP 1115.000 D10.3): one device pixel, 1 px over the display's scale, which each host tells the kernel as it tells it the root font size (UIKit's `displayScale`, AppKit's `backingScaleFactor`, the browser's `devicePixelRatio`, the pixels a point a Linux, Android or Windows shell draws at; 1 where a host says none). It is a third of a point on a phone and half on a Retina Mac, in layout and in paint, and follows a window to another display. Write it where a separator's thickness was `0.5`, which is one device pixel only at 2x: `border-width`, the `border` shorthand (`border-bottom="-exact-hairline solid -exact-separator"`), a `height`, a `gap`; literal or bound. `hr` is that line: one solid top edge a hairline thick in the separator role, as wide as what holds it (a flex column included), with no margin of its own. Its `color`, `border-*` and `margin` are yours to write. `-exact-accessory-checkmark-width` and `-exact-accessory-checkmark-height` are length ramps (LLP 1137): UIKit's list-cell checkmark at the root font size, growing with the text size as a text style does (19 × 17.33 at the default size, 48 × 43.33 at the largest). They are taken where `-exact-hairline` is; a grouped list's checkmark in a row of your own already has them on iOS. `-exact-accessibility-stack-gap` is 8 through the largest standard size and wider than any line at the accessibility sizes: as the `column-gap` of a row that wraps, it stacks the row's items there, as UIKit's cells do (a grouped row's title and value already take it on iOS). `-exact-row-look="platform"` on an element says its box's look (its `background`, `border`, `padding` and `box-shadow` rows) is its own where it stands alone and the platform's row where it is a grouped list's row (LLP 1139): there those rows are dropped and the list draws the row, as Exact Content's `AnswerTile` is a tile in a quiz and a list's cell in a poll. Elsewhere it does nothing. A grouped row's edge or background image its author wrote otherwise stays the author's on every host, iOS's carried row included. An `image` takes no `loading` attribute: Exact decides when its picture is fetched (LLP 1048.007 D5). On the web a lone image loads with the page, and the page's first one is preloaded. An image a `for` repeats is written `loading="lazy"`, the first screen's rows included: the browser fetches those at the page's first layout, not while it parses the document, and the rest as they come near. Native hosts fetch the picture of every image that is mounted. A `button` is the platform's own control by default (LLP 1104 D1). Your bare box (`appearance="none"`, or a button given a background, border, radius or rich children) lays out as Chrome's `