@lotics/cli 0.316.0 → 0.316.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli.js +33 -6
- package/docs/document_templates.md +10 -0
- package/docs/workflows.md +2 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -1729,8 +1729,8 @@ async function saveSignedLinks(links, dir) {
|
|
|
1729
1729
|
}
|
|
1730
1730
|
|
|
1731
1731
|
// src/version.ts
|
|
1732
|
-
var VERSION = "0.316.
|
|
1733
|
-
var APP_SDK_VERSION = "0.124.
|
|
1732
|
+
var VERSION = "0.316.1";
|
|
1733
|
+
var APP_SDK_VERSION = "0.124.1";
|
|
1734
1734
|
|
|
1735
1735
|
// src/timezone.ts
|
|
1736
1736
|
function machineTimezone() {
|
|
@@ -1761,7 +1761,7 @@ var app_architecture_default = '# App architecture \u2014 a worked split\n\nThe
|
|
|
1761
1761
|
var working_in_a_workspace_default = "# Working in a workspace \u2014 the right means for each job\n\nOnce tables and apps exist, an agent does the team's work through them. The means depends on what the work is.\n\n| The work | The means | Why |\n|---|---|---|\n| A business operation: move an order on, issue a paper, notify someone | `run_app_workflow` on the app's act, `act_<key>` or the `workflow` it names | The outcome equals a person's press: status, stamps, documents, notifications. `update_records` on an act's field skips all of them. |\n| Data entry, a correction, loading the owner's lists | `create_records`, `update_records` | The table's rules hold for every writer. |\n| A rule or policy the business wrote | `get_workspace` with `search`, then `read_knowledge` | The business's own words, not a guess. |\n| A file someone hands over | `read_file_content`; `upload_file` to keep it (over MCP or the CLI) | Reads its content; the upload files it in the workspace. When your host passes no bytes, `upload_file` with `person: true` gives the person a link to drop it on. |\n| A paper an app makes | the act that makes it, which files it on the record | `generate_document` makes an unattached file, for a paper no app makes. |\n| A connected service (KiotViet, MISA, a carrier) | a workflow step, reached through a Workflow node | No tool calls a service directly (`workflows/reaching-a-connected-service`). |\n| A figure or list the business defines, or an operation it repeats | the app's declared Query or Workflow node: `run_app_query`, `run_app_workflow` | The node is the business's own definition, found by `get_workspace` with `search` and read in `get_app`. |\n| A count or sum no app answers | `aggregate_records` (grouped by `group_by`), `query_records` | |\n\nFind the means in this order: `get_workspace` maps the workspace; `search` it by what is asked (\"overdue\"), which matches an app's declared Query and Workflow keys and descriptions and names the matching keys under the app; `get_app` lists that app's queries and workflows with their inputs. Run the declared node. Only a question no node answers goes to `aggregate_records`.\n\n## Workspace settings and standing instructions\n\n`get_workspace` shows the workspace's settings (name, timezone, default currency) and, with kind `instruction`, its standing instructions. `set_workspace` changes them in one all-or-nothing patch: a setting it leaves out is unchanged, and `instructions` is a map keyed by instruction name where an entry creates or merges and `null` removes. A workspace-wide instruction arrives with the connection, in the instructions a host reads when an agent connects; the answer of `set_workspace` names any that no longer fit that block. An administrator writes them.\n\n## People\n\n`get_workspace` with kind `member` lists the members. `set_member` is an admin's: `members` is a map keyed by an email address or a member id, where an email no member holds invites that person (`role` is `member` or `admin`; the invitation email goes out), a current member's entry sets `name`, a removed member's entry restores them, and `null` removes a member or cancels the invitation pending to that address. A member's role cannot be changed through it. The whole map is checked before anything is written or sent. A member's tool list does not carry `set_member`, `set_group` or `set_workspace`.\n\n## Doing an operation\n\n`get_app` lists the app's acts as `act_<key>` (or the `workflow` an Act names) with what each takes. `run_app_workflow` with `dry_run: true` writes nothing\nand answers what the run would do; the same call without it runs. An act refuses a row its conditions do not hold for,\nand says why (`agent_surface` \xA7 A refusal).\n\n## Writing records\n\n`create_records` takes 50 records a call, 20 or more of one shape in columnar form (`field_keys` once, then `rows`);\n`update_records` changes one or many. Both name fields and select options by alias, and `field_values` gives each\ntype's value. Every write meets the table's rules (`model/write-rules`): every broken rule is listed at once, and a\nlocked record answers under `blocked`. The owner's own lists (a menu with prices, customers, open orders) are rows of their\ntables, loaded this way after `set_tables`.\n\n## Answering a question\n\n1. `get_app`: a Reading's query (its `key`) is the business's own figure for it. `run_app_query` with `alias` and\n `params` returns it; `filters`, `fields`, `sort` and `count` narrow the result.\n2. A period's total is one call per period, each with `from` and `to` or a date filter. `aggregate` groups a result by\n a column (`by`), a number's bands (`bands`) or a date column's day (`day`), and sums one (`sum`).\n3. A question no app answers is `aggregate_records` (counts and sums grouped by a field) or `query_records`, over one\n table; `filters` is the grammar of both.\n\n## A worked day\n\nA customer asks for the status of order 1042, then the order is shipped:\n\n```\nget_workspace kind \"app\" \u2192 app_id of the sales desk\nget_app app_id \u2192 aliases: order_list, act_ship, \u2026\nrun_app_query app_id, alias \"order_list\", params {\"search\": \"1042\"}\nrun_app_workflow app_id, alias \"act_ship\", inputs {\"record_id\": \"rec_\u2026\"}, dry_run true\nrun_app_workflow the same call without dry_run\naggregate_records table_id, group_by \"stage\" \u2192 how many orders stand at each stage now\n```\n";
|
|
1762
1762
|
|
|
1763
1763
|
// src/model_reference.md
|
|
1764
|
-
var model_reference_default = '# Tables and apps\n\nThe tables of a workspace and the apps over them, each a JSON document. A table is a document, an item of\nthe tool `set_tables`: its fields, options, views, how a row is recognised, and the rules its writes\nfollow. An app is a tree of components, the `body` of the tool `set_app`: one working register over one table, or a\ndashboard of readings. Each \xA7 is its own page, `model/<section>`.\nWhich treatment each job\'s screen needs: the `design` reference. Each treatment worked through in an\napp: the `examples` reference.\n\n**What the documents compose with**\n\n- **Tables and fields** (\xA7 Tables, \xA7 Read scope, \xA7 Field) \u2014 a table\'s columns, options and links. A table document sits\n in the `tables` list of a `set_tables` call; a field sits under its alias in the table\'s `fields`.\n- **Records** (\xA7 Records, then \xA7 Status) \u2014 how a row of each table is RECOGNISED: its title, the line\n under it, its picture, its status, the one number it stands for. Stated once per table\n and read by every surface that draws one of its rows.\n- **Write rules** (\xA7 Write rules) \u2014 what a write finds, bounds and picks among.\n- **Apps** (\xA7 App tree, then a page each: \xA7 Changing an app, \xA7 What an app may write, \xA7 App node, \xA7 List node, \xA7 Layouts, \xA7 Time and place layouts, \xA7 Publish desk,\n \xA7 Record node, \xA7 Section node, \xA7 Side node, \xA7 Page node, \xA7 Outside node, \xA7 Tab node, \xA7 Field node, \xA7 Act node, \xA7 Check node,\n \xA7 Reading node, \xA7 Progress node, \xA7 Statement node, \xA7 Export node, \xA7 Book node, \xA7 Query node, \xA7 Workflow node,\n \xA7 Composed page, \xA7 Tasks, \xA7 Rules of the app, \xA7 Register rules, \xA7 Block rules, \xA7 Record and act rules) \u2014 one working register over one table and the record each row opens,\n with the logs beside it: which fields go where, the acts, and the checks that guard them \u2014 or a dashboard of readings.\n How each thing LOOKS is the runtime\'s, one treatment per concept; no prop here changes it.\n A screen the job needs and no component states is told to the person \u2014 the job, what the app\n drew, the word wanted \u2014 never props bent to approximate it.\n\n**The working order**\n\n1. The tables and fields the jobs touch, and a `records` entry for every table an\n app lists, opens or picks. Which apps a business needs is `first_build`, \xA7 3.\n2. One `set_tables` call with the tables: a link names the table it points at by the `ref` of another\n item of the call, and the answer carries every table id and the aliases of the fields it added. A\n call carries at most 50 tables, 200 first rows a table, and 2000 rows and 2000 documents in all; more\n tables go in a further call, which names the earlier tables by alias. A call is checked whole and writes\n nothing when refused, listing everything wrong; `dry_run` is for reading the grants a write would make or\n deciding `force`.\n3. One `set_app` call per app. The tree names its tables and document templates by alias wherever it points at one;\n the App node\'s `tables` and `templates` maps are filled by `set_app`, never written.\n `set_app` with the tree as `body` creates the app, named by its `name`; a refused tree writes nothing and is listed whole, and `dry_run` is for reading the permissions the write\n would grant and remove (on the CLI, `lotics app apply <file>`, with `--check` for `dry_run`).\n4. Change an app afterwards one node at a time: `get_app` prints the `tree:`, each node\'s address under `node addresses:`\n and the `version` line, which is the `base_version_id` to send (the tree and addresses go to the app\'s author alone);\n `set_app` with `app_id`, `base_version_id` and `nodes`, a map from a node\'s address to a\n patch, changes it (`model/changing-an-app`), and its answer states the permissions the change grants and removes. A table\n is changed by `set_tables` with its `table_id`, a merge patch over the document `get_table` answers. To go back,\n send the tree you saved before the change as `body` with `app_id`: it replaces `children` but keeps every App prop it\n omits (`words`, `scope`, `period`, `icon`), so null those. (On the CLI, `lotics app pull <app_id>` writes the tree as JSON and `lotics app apply\n <file>` sends an edited file, refused when the app has moved past the version the file was pulled at.) An app\n saved before apps were trees has no tree: `get_app` says so beside what it lets a person write, and `set_app` with\n `app_id` changes it by its whole tree as `body`, which replaces it, or by its name, description, icon and color alone.\n\n## The rules\n\n- **A `set_tables` call is all or nothing.** Every item is checked before any is written; an item\n without `table_id` makes a table, one with it patches that table.\n- **An app\'s labels decide its language.** A body names no locale \u2014 the words an\n app draws itself (its buttons, empty states, refusals) are in the language its\n own labels are written in, so a Vietnamese desk beside English ones answers in\n Vietnamese. The generated screens read the kit\'s\n pack, and a generated WRITE cannot: its refusals run on the server, so they are\n worded in that same language. Mix the two within one app and it answers in two. The app\'s own `words`\n (its words for a table, a field or an option) count as its labels: Vietnamese words over an English table\n make the app answer in Vietnamese.\n- **`set_tables` and `set_app` check all of it before anything is written**, and\n report every problem in one run rather than the first. The tables half of this\n page ends its keys with the rules the check enforces there, and \xA7 Rules of the app holds the app\'s, one sentence each\n under an id; a finding names its rule\'s id (`[register.filter]`), and\n `model/<rule id>` is that one rule\'s page.\n\n<!-- generated:start rules-model -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `model.schema` | Every key is one its table lists, holding the type its row gives, and every required key is stated. |\n| `model.names-declared` | Every alias a key names is declared: a table of the App\'s `tables`, a field of the table the node reads, an option of the select it names, an Act `key` of the App, a template of its `templates`, or an app. |\n| `model.tables` | A call declares at least one table and at most 50; send the rest in a second call. |\n| `model.rows-cap` | Rows are a sample: at most 200 per entity, 2000 per call and 2000 documents attached \u2014 a real data set goes in with `create_records` once the tables exist. |\n| `model.alias-unique` | An alias is unique where it is named: tables, fields and views in their table, options in their select. |\n| `model.label-unique` | A label is unique where it is found by its label: fields and views in their table, options in their select \u2014 and no view takes its table\'s own label, which names the whole-table grid. |\n| `model.template-kind` | An Act\'s `template` is an html, excel or word document template and an Export\'s `template` an html or excel one, never an email or pdf-form one. |\n| `model.template-marker` | Every marker of a Word template an Act makes is filled by that Act: a field of its entity, or a list of the child rows its record lists (the entity\'s own alias over several rows). |\n| `model.template-act` | *Noted, never refused:* In an App none of whose Acts runs an authored `workflow`, every html, excel or word template is made by an Act (`template`, `templates`) or filled by an Export \u2014 the note names the app over the table whose fields it prints most, where the Act belongs. |\n| `model.filter` | A filter \u2014 a view\'s, a rollup\'s \u2014 tests fields of the entity it reads, through links as `entity.field` hops each standing on the entity the last lands on, by options its select declares. |\n\n<!-- generated:end rules-model -->\n\n## Top level\n\nEvery table of keys on this page is generated from the grammar a table document and an app tree are written in, so it is the whole of what a key may hold.\n\n<!-- generated:start top-level -->\n\n#### set_tables call\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `tables` | list of [Table item](#table-item) | yes | One or more, all or nothing; each item is a table document with these keys added. It creates a table (a `label` and `fields` at least) or, with `table_id`, patches one: the document it states merges into the table\'s own |\n| `rows` | map of alias \u2192 list of [Row](#row) | no | First records of the tables the call creates, keyed by each one\'s `ref` |\n| `force` | boolean | no | Consent to losing what rows hold: a field deleted, an option removed |\n| `dry_run` | boolean | no | Answer what the call would change, writing nothing: for reading the grants a write would make or deciding `force`. A refused call writes nothing too and lists every reason, so a dry run checks no more than the call does |\n\n#### Table item\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `ref` | alias | no | A created table\'s handle in this call: other items link to it, and `rows` are keyed by it |\n| `table_id` | text | no | The table to patch; absent, the item creates one. A patch states only what changes: `null` removes a key, a list replaces whole |\n\n<!-- generated:end top-level -->\n\n**A `set_tables` item is a table document** (\xA7 Tables) with `ref` and `table_id` added, and the call\ntakes no other key. **A `set_app` body is an app tree** (\xA7 App tree), whose root is an App node: its `tables` and\n`templates` each map a local alias the tree names to the id of the table or document template it means, from the\n`set_tables` answer or `get_table`. The tree is the only home of the app\'s `name`, `description`, `icon` and `color`;\n`set_app` takes it whole to create, or as a merge patch over the App node with `app_id`, or `nodes` with `app_id` to change one, and with `base_version_id` (the\nversion you read) it refuses a write over a newer one. Nothing is built code. An unknown key is an error, never ignored.\n\nA smallest call, two tables of plain fields, a select and a two-way link \u2014 nothing else is needed to write it:\n\n```json\n{\n "tables": [\n {\n "ref": "customer",\n "label": "Customers",\n "singular": "Customer",\n "fields": {\n "name": {"type": "text", "label": "Name", "required": true},\n "tier": {"type": "select", "label": "Tier", "options": [{"alias": "gold", "label": "Gold", "color": "yellow"}, {"alias": "standard", "label": "Standard", "color": "gray"}]},\n "orders": {"type": "link", "label": "Orders", "table": "order", "multi": true, "pair": "customer"}\n }\n },\n {\n "ref": "order",\n "label": "Orders",\n "singular": "Order",\n "fields": {\n "code": {"type": "text", "label": "Code", "required": true},\n "customer": {"type": "link", "label": "Customer", "table": "customer", "multi": false, "pair": "orders"},\n "amount": {"type": "number", "label": "Amount"}\n }\n }\n ]\n}\n```\n\n### Aliases\n\nEvery `alias` is a lowercase slug \u2014 a letter, then letters, digits and\nunderscores (`unit_price`, `so_1001`). Aliases are how a document and an app tree\nname a field, an option, a view or a table; they are never shown to anyone. `label` is\nwhat a person sees.\n\nLabels must be unique within their namespace \u2014 two fields on one table, two options\non one field, two views on one table cannot share a label.\n\n## Tables\n\n<!-- generated:start table -->\n\n#### Table document\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | The table\'s name in the workspace |\n| `singular` | text | no | One row of this table, in the business\'s own words \u2014 what a create\'s button and panel name |\n| `description` | text | no | The table\'s description, written onto the table in the workspace, where a caller of an app\'s queries and workflows reads it beside them |\n| `fields` | map of alias \u2192 [Field](#field) | yes | The table\'s columns, each under its alias, which is its key here and so is never stated inside it |\n| `read_scope` | [Read scope rule](#read-scope-rule) | no | Which rows a member reads. Absent, every member with access to the table reads every row. |\n| `unique` | list (at least one) of list of alias (at least one) | no | Sets of fields whose values no two live rows share \u2014 each a list of field aliases (text, number, date, a single select, or a link holding one row \u2014 `multi: false`); a single text field states `unique: true` on itself instead of a one-field set. A create or update landing a second row with the same values is refused. |\n| `views` | list of [View](#view) | no | Saved views, in the order they are listed; with none, the table still opens on its default grid |\n| `title` | alias | no | The alias of the field that names a row wherever a link or a record shows one; absent, the first text field |\n| `colors` | any value | no | Row colors: a list of { filters, color }, the filter reading fields of this table by alias and a select\'s options by alias |\n| `records` | any value | no | How a row is recognised: the keys of the Record, Status and Cadence tables below but `title`, which sits above; a table they name is an id or a `ref`. A status `history` that names a `ref` no item of the call declares is made by the call as a table, `dates` add each date field they name, and a status with stages each stage and each open option they name none for its `<option>_at`, unless the document declares it; each is listed in the result\'s notes |\n| `natural_key` | list of alias (at least one) | no | The field aliases a row of this table is RECOGNISED by: an app\'s add and find-or-create reuse the row they find by these values, minting one only where nothing matches. A record tool or the API creates the row it is handed. |\n| `no_overlap` | [No overlap](#no-overlap) | no | No two rows hold overlapping spans \u2014 a create or update that would is refused, and so is a change to the dates or states of the row a span is read through that would make two rows overlap |\n| `checks` | map of alias \u2192 table check (`workflows/table-checks`) | no | The conditions a write of the table\'s rows may not meet, each under its alias: a merge patch edits one, and `null` removes it. Expressions name fields by alias |\n| `workflows` | map of alias \u2192 table workflow (`workflows/table-workflows`) | no | The effects that follow every committed write of the table\'s rows \u2014 the grid\'s, an import\'s, an agent\'s, any app\'s \u2014 each under its alias: a merge patch edits one, and `null` removes it. A body names fields, options and tables by alias |\n\n<!-- generated:end table -->\n\n<!-- generated:start rules-entity -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `entity.singular` | A table a List\'s `create` is mounted over \u2014 the register or a child List that adds rows \u2014 states its `singular`: one row of it, in the business\'s words. |\n| `entity.required-cycle` | Required links never wait on each other in a loop: no row of the loop could be created first. |\n| `entity.link-format` | A text field of `format: "link"` holds web addresses; one whose rows hold a phone or a mail address states no format. |\n| `entity.read-scope` | A `read_scope` clause names a group (`member_of`) or a field of its table reached through one-row links: `"self"` a member field, options a single select declares. |\n| `entity.unique` | A `unique` set names two fields or more of its table once each, each holding one value to compare \u2014 text, a number, a date, a single select, a one-row link \u2014 and is stated once; one field alone is `unique: true` on it. |\n\n<!-- generated:end rules-entity -->\n\n**`unique` is a set of values no two live rows share.** Each entry names fields\nof this table holding ONE value \u2014 text, number, date, a single select, a link\nwith `multi: false` \u2014 and a create or update landing a second row with the\nsame values is refused. A set of one text field is that field\'s own `unique:\ntrue`, so it is refused here. A create carries each set in the names its panel\nsends, so the panel can name the duplicate before the write does.\n\n**`singular` is what a create says** \u2014 `New Order`, `Add Claim line`, `H\u1ED3 s\u01A1\nm\u1EDBi` \u2014 while `label` names the table, so without it the button reads `New\nOrders`. Nothing derives it: English plurals are irregular, and no language is\nexempt. In a language without plural forms it is usually the label itself,\nless any word for the collection. A List that leaves `create` to its default adds nothing over a table that states\nnone (nor does the app\'s `words` state one); a `create` list stated over such a table is refused (`entity.singular`).\n\n**An app that only reads a table\'s rows draws them with `edit: false`** and states `create: false` and `remove: false` \u2014 a log of what the system sent, a copy of\nwhat another system holds. The app keeps the section and opens each row at rest, with no Add and no Delete; a party\nof it is picked, never found or minted; and the app writes no create, update or remove for it. It shapes that app, not who may write: a direct write \u2014 the records\nAPI, the chat agent, an import \u2014 follows the table\'s access, as on any table, and another app over the same rows\nstates its own tree.\n\n**`read_scope`** \u2014 which rows a member reads \u2014 is its own page, `model/read-scope`.\n\n## Read scope\n\n<!-- generated:start read-scope -->\n\n#### Read scope rule\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `any` | list of ([Read scope by role](#read-scope-by-role) \\| [Read scope by member](#read-scope-by-member) \\| [Read scope by option](#read-scope-by-option)) (at least one) | yes | A row is readable when ANY of these holds |\n\n#### Read scope by role\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `member_of` | text | yes | The id of a group (from Settings, or chat): whoever is in it reads the row |\n\n#### Read scope by member\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `through` | list of alias (1\u20133) | no | link aliases from this entity outward, each holding one row (`multi: false`) \u2014 the clause\'s field is on the entity the last hop lands on |\n| `field` | alias | yes | A member field alias on this entity, or on the entity `through` lands on |\n| `is` | `"self"` | yes | The members this column names on a row read that row |\n\n#### Read scope by option\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `through` | list of alias (1\u20133) | no | link aliases from this entity outward, each holding one row (`multi: false`) \u2014 the clause\'s field is on the entity the last hop lands on |\n| `field` | alias | yes | A single-select field alias on this entity, or on the entity `through` lands on |\n| `is` | list of alias (at least one) | yes | Its option aliases whose rows are readable \u2014 naming none would hide every row |\n\n<!-- generated:end read-scope -->\n\n**`read_scope` is a ROW rule, enforced by the platform.** It is resolved into the table\'s own row\nfilters, so an app, a workflow reading for a viewer, and the API all answer the same rows \u2014 a per-record\nvisibility field the app merely honours is a convention, not a gate. A `"self"` clause reads a column\nof one member or several, and every group and option a clause names must exist.\n\n**An app reads every table under the table\'s own `read_scope`**, and a tree has no key to lift it. A desk whose audience\nits sharing already decides is let into the rule by a group that holds that audience (`member_of`), stated on the\ntable.\n\n**A row rule is never inherited.** A child read under a private record states\nits own: the record\'s `member_of` clauses as they are, and its column clauses\n`through` the one-row link the child hangs by \u2014 over one hop or several, each a\nlink with `"multi": false`, since over several rows the rule would admit a\nreader any one of them admits while the editor\'s guard reads the first. Only the\nstatus history takes its record\'s rule.\n\n## Field\n\nEvery field carries the keys below, and its `type`\'s section adds the rest; a\ntype whose section names no `default` takes none. `label` may not contain `{` or\n`}` (formulas reference fields by label at the platform level). Every row in `rows` states each\n`required` field it carries (neither a default nor a `default_from` copy is applied to them), and a required\nLINK is written with its row: the table it names is created first, and tables\nwhose required links name each other are refused, since none of their rows could\never be created.\n\n**A table states who keys nothing.** Whether a person may change a field in an app is that app\'s statement\n(what its tree draws, and `edit: false`, \xA7 App tree): a post\'s id a publishing workflow brings back, a count a sync fills, a status an act moves\nare left out of the apps that do not save them, and written by a workflow, the records API, the chat agent and an\nimport as the table\'s access allows. A formula, a rollup, a lookup and an autonumber are no one\'s to key already.\n\n<!-- generated:start field -->\n\n#### Field\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"text"` \\| `"number"` \\| `"date"` \\| `"boolean"` \\| `"select"` \\| `"member"` \\| `"link"` \\| `"files"` \\| `"formula"` \\| `"rollup"` \\| `"lookup"` \\| `"autonumber"` | yes | What the field holds \u2014 each type takes the further keys its own section lists |\n| `label` | text | yes | The field\'s name in the workspace |\n| `description` | text | no | The field\'s description, written onto the field in the workspace |\n| `required` | boolean | no | Refuse a record whose cell for this field is empty. Every write path \u2014 create, update, an agent\'s tool call, a workflow\'s set \u2014 refuses the row by field name. A stored field is flagged only once every row holds a value. |\n\n<!-- generated:end field -->\n\n### `text`\n\n<!-- generated:start field-text -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | text | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. |\n| `unique` | boolean | no | Unique values required |\n| `format` | `"text"` \\| `"link"` \\| `"markdown"` | no | How the words are drawn \u2014 plain, as a link that opens, or as markdown |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `suggest` | [Suggest](#suggest) | no | A text field whose control offers, as the reader types, the distinct values a text field of a catalog table holds (a damage position\'s code among the codes on file); any text is still written |\n\n<!-- generated:end field-text -->\n\n### `number`\n\n<!-- generated:start field-number -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | number | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. |\n| `notation` | [notation](#number) | no | How the figure reads: { "style": "decimal" }; money, { "style": "currency", "currency": an ISO 4217 code (USD, VND) }; or a quantity, { "style": "unit", "unit": "percent" (10 reads "10%"), or a measured code (g, kg, t, l, m3, cbm, mm, cm, m, km, m2, min, h, day), which converts and scales within its dimension, or any other noun of at most 12 characters (ki\u1EC7n, pallet, TEU), which never does }. A currency or unit read on each row from a single select of that row is { "per_row": that select }. Omitted, a decimal. |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `min` | number \\| text | no | Refuse a create or update whose value is below this: a constant for a number, or a field \u2014 `<field alias>` of the same row, or `<link alias>.<field alias>` of the one row a link names \u2014 of the same type (a return date after the departure, a line\'s day inside its trip). On a figure whose unit or currency is per row (its notation\'s `per_row`) a constant bound is only 0; bound it by a field of the row |\n| `max` | number \\| text | no | Refuse a create or update whose value is above this: a constant for a number, or a field of the same row or of the one row a link names |\n\n<!-- generated:end field-number -->\n\n`notation` is how the number reads, and every surface reads it:\n`{ "style": "currency", "currency": "USD" }` prints as money in its code,\n`{ "style": "unit", "unit": "kg" }` as a quantity with its unit after it,\n`{ "style": "unit", "unit": "percent" }` as a percent \u2014 10 is "10%", never\nmultiplied \u2014 and `{ "style": "decimal" }`, or no notation, as a plain number. One\nkey, so a figure reads one way.\n\nAn absent number is drawn blank, except a `sum` or count rollup read as a meter\'s measure: nothing accumulated yet reads 0.\n\nA unit \u2014 `{ "style": "unit", "unit": "kg" }` \u2014 is what a figure counts or\nmeasures; `percent` aside, it is measured or counted. A **measured** unit is a code of one catalog \u2014 mass `g`\n`kg` `t`, volume `l` `m3` `cbm` (a cubic metre under freight\'s name), length\n`mm` `cm` `m` `km`, area `m2`, duration `min` `h` `day` \u2014 so a figure typed in\nanother unit of its dimension converts (`12,5 t` into a `kg` field is 12 500),\nand a tile or a chart reads it in the largest unit it reaches (12 500 kg as\n12,5 t\u1EA5n; CBM never scales) while a cell, a fact and a column always read in the\nfield\'s own. Any other noun of at most 12 characters (`ki\u1EC7n`, `pallet`, `TEU`) is a\n**counted** unit: a word after the figure that never converts. A measured unit is\nwritten as its code \u2014 `t\u1EA5n`, `KG` or `m\xB3` is refused, naming the code. A formula\nstates its own as its `notation`; a rollup that keeps the value (`sum`, `avg`,\n`median`, `min`, `max`, `range`) and a lookup carry the unit of the figure they\nread, exactly as they carry a currency, and a count carries none. A quantity is a\nnumber with its unit, never words (`3 cartons` in a text field): only a number\nsums, converts and reads down a column.\n\nA field\'s cells always hold figures in its own unit, so moving it between two\nunits of one dimension (`kg` to `t`) converts every stored figure \u2014 a change made\nin the workspace, in the field\'s settings (which say how many first) or through\n`set_tables`. Any other change of unit\nrelabels.\n\nA figure whose unit or currency varies by row names a single select of its own\nrow as `per_row` \u2014 `{ "style": "unit", "unit": { "per_row": "uom" } }`,\n`{ "style": "currency", "currency": { "per_row": "currency" } }` \u2014 or a lookup of\none through a one-link, a line reading its shipment\'s currency. That select\'s\noptions are the vocabulary: each label a unit (`chi\u1EBFc`, `kg`), or an ISO 4217 code\n(`USD`). A label outside it is refused when the figure is written and when the\nselect is \u2014 its options, its type or its deletion while a figure names it. A row\nwhose select is empty reads its figure bare. Every app reads each row\'s figure in\nits own row\'s unit, and a sum never mixes units: totals are one per unit (past three units a total the runtime draws over a set of rows \u2014\nsummary line, group totals and feet, a dashboard\'s table foot \u2014\nstates the count alone; a reading an author states draws every unit), a chart draws one at a time. A rollup that keeps the value (`sum`, `avg`, `min`\u2026)\nover such a figure stands only where the child\'s select is a lookup, through\nthe rollup\'s own link, of a select on this table \u2014 the rollup reads that\nselect\'s unit; otherwise it is refused. A lookup of such a figure stands only\nwhere its table also looks up the figure\'s select through the same one-link,\nand reads in that lookup; otherwise it is refused. A count is unaffected. Moving a field between a\nfixed and a per-row unit relabels.\n\n### `date`\n\n<!-- generated:start field-date -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | text | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. A date string in the field\'s format. |\n| `format` | `"date"` \\| `"datetime"` \\| `"date_range"` \\| `"datetime_range"` | no | Whether the field holds a day or a moment, alone or as a span |\n| `timezone` | text | no | IANA timezone |\n| `derive_from` | `"created_at"` \\| `"updated_at"` | no | Auto-populate from the row\'s system timestamp; the field becomes read-only. |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `min` | number \\| text | no | Refuse a create or update whose value is below this: a constant for a number, or a field \u2014 `<field alias>` of the same row, or `<link alias>.<field alias>` of the one row a link names \u2014 of the same type (a return date after the departure, a line\'s day inside its trip). On a figure whose unit or currency is per row (its notation\'s `per_row`) a constant bound is only 0; bound it by a field of the row |\n| `max` | number \\| text | no | Refuse a create or update whose value is above this: a constant for a number, or a field of the same row or of the one row a link names |\n\n<!-- generated:end field-date -->\n\nA `default` is refused beside `derive_from`: the platform stamps that date.\n\n### `boolean`\n\n<!-- generated:start field-boolean -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | boolean | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n\n<!-- generated:end field-boolean -->\n\n### `select`\n\n<!-- generated:start field-select -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | list of alias | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. Option alias(es) this field declares \u2014 one for single-select. |\n| `options` | list of [Select option](#select-option) (at least one) | yes | The choices, in the order every picker and every ladder lists them |\n| `multi` | boolean | no | Allow multiple selections |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n\n#### Select option\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable local alias, unique within the field |\n| `label` | text | yes | Display label for the option |\n| `color` | [colour](#select) | no | The colour the option\'s badge is drawn in; absent, the platform gives it one no sibling option holds |\n| `mark` | [mark](#select) | no | The option\'s own mark, drawn in place of its colour dot wherever the option is shown: the brand it is ({kind: "brand", name: one of facebook, instagram, threads, meta, tiktok, google-ads, zalo, linkedin, x, google-meet, youtube, telegram, whatsapp, gmail, google-drive, outlook, kiotviet, misa, lark, payos}) a kit glyph ({kind: "icon", name: "wrench"}) or the face of who it is, drawn from its label ({kind: "avatar", name: "person" or "organization"}). Every option of a field has one, or none does. A mark a reader does not draw falls back to the dot |\n\n<!-- generated:end field-select -->\n\nA patch of a select states its whole `options` list: a list replaces whole, so an option it leaves out is deleted. To\nrename or recolour one option, restate every option kept, each with its alias. Rows still holding a deleted option refuse\nthe patch unless `force`.\n\n```json\n{\n "options": [\n { "alias": "draft", "label": "Draft" },\n { "alias": "confirmed", "label": "Confirmed" },\n { "alias": "shipped", "label": "Shipped", "color": "green" }\n ]\n}\n```\n\n`color` is one of: `red`, `orange`, `amber`, `yellow`, `lime`, `green`,\n`emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`,\n`fuchsia`, `pink`, `rose`, `slate`, `gray`, `zinc`, `neutral`, `stone`.\nEvery option is drawn in its colour wherever it shows \u2014 a cell, a row\'s line,\na record, a picker, the bar a reading splits the rows by.\n\nAn option may carry its own `mark`, drawn in place of its colour dot wherever\nthe option is shown \u2014 a stage, a chip, a filter, a fact, an entry of a log, a\nreading\'s part, a lookup of the select on another table. Where the select is its\nrows\' `records` `mark`, a brand or glyph stands on the row\'s lead tile in the option\'s\ncolour, and an option with none leads with its colour dot there. A select a reader scans\ndown a column \u2014 how a payment was made, the channel, the mode \u2014 states one on every\noption, since a glyph reads before its word: the brand it IS\n(`{"kind": "brand", "name": "tiktok"}` \u2014 one of `facebook`, `instagram`,\n`threads`, `meta`, `tiktok`, `google-ads`, `zalo`, `linkedin`, `x`,\n`google-meet`, `youtube`, `telegram`, `whatsapp`, `gmail`, `google-drive`,\n`outlook`, `kiotviet`, `misa`, `lark`, `payos`), a glyph the kit draws\n(`{"kind": "icon", "name": "wrench"}`; any other name is refused), or \u2014 where the\noptions name people or organisations who are no member and no row \u2014 the face of who\nit is, drawn from its label (`{"kind": "avatar", "name": "person"}` or\n`"organization"`). Every option of a select has one, or none does: a run of chips\nwhere one carries no mark reads as the one missing something. `set_tables` writes the\nmarks onto the table. The glyph names are `model/field/option-glyphs`.\n\n```jsonc\n"options": [\n { "alias": "short_video", "label": "Short video", "color": "zinc", "mark": { "kind": "brand", "name": "tiktok" } },\n { "alias": "print", "label": "Print", "color": "amber", "mark": { "kind": "icon", "name": "newspaper" } }\n]\n```\n\n### Option glyphs\n\nThe kit\'s glyph names an option\'s `mark` takes:\n\n<!-- generated:start field-select-glyphs -->\n\nactivity, align-center, align-left, align-right, align-vertical-justify-center, align-vertical-justify-end, align-vertical-justify-start, arrow-down, arrow-down-a-z, arrow-down-up, arrow-down-wide-narrow, arrow-left, arrow-left-from-line, arrow-right, arrow-right-from-line, arrow-right-left, arrow-up, arrow-up-down, arrow-up-wide-narrow, arrow-up-z-a, ban, banknote, bed, bell, between-horizontal-start, between-vertical-start, bold, bolt, book-marked, book-open, book-text, bot, box, brackets, brain, briefcase, building-2, calculator, calendar, calendar-clock, calendar-off, camera, car, chart-column, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down-up, chevrons-up-down, circle-alert, circle-check, circle-x, clipboard-list, clipboard-paste, clock, code, code-xml, columns-3, columns-3-cog, combine, construction, container, copy, credit-card, database, decimals-arrow-left, decimals-arrow-right, dollar-sign, download, ellipsis, eraser, expand, external-link, eye, eye-off, facebook, file, file-csv, file-down, file-question, file-spreadsheet, file-stack, file-text, file-up, folder, folder-closed, folder-open, folder-pen, form, funnel-plus, funnel-x, gauge, globe, gpu, grid-2x2, grip-vertical, group, hand-coins, hash, heading, heading-1, heading-2, heading-3, history, house, image, inbox, info, instagram, italic, keyboard, languages, layout-dashboard, layout-grid, library-big, link-2, link-2-off, linkedin, list, list-checks, list-collapse, list-filter, list-filter-plus, list-ordered, list-tree, loader, lock, lock-keyhole, lock-keyhole-open, lock-open, log-in, log-out, mail, map-pin, maximize-2, megaphone, menu, message-circle, message-circle-question-mark, message-square, messages-square, mic, minimize-2, minus, monitor, mouse, mouse-pointer-click, music, newspaper, notepad-text-dashed, package, paint-bucket, paintbrush-vertical, palette, panel-left, panel-left-close, panel-left-open, panel-right, panel-right-close, panel-right-open, paperclip, pause, pencil, percent, phone, pin, pin-off, plane, play, plug, plus, receipt, rectangle-ellipsis, redo, refresh-cw, repeat, rotate-ccw, rotate-cw, rows-3, scan, scan-line, scissors, search, send, settings, share, share-2, shield, shield-alert, shield-check, shopping-cart, sigma, sliders-horizontal, smile, smile-plus, snowflake, sparkles, split, square, square-check, square-pen, square-sigma, stethoscope, sticky-note, strikethrough, table, table-2, tag, target, text-quote, text-wrap, thumbs-down, thumbs-up, ticket, trash, trending-down, trending-up, triangle-alert, truck, tv-minimal, twitter, type, underline, undo, upload, user, user-check, user-pen, users, utensils, waypoints, workflow, wrench, x, zap, zoom-in, zoom-out\n\n<!-- generated:end field-select-glyphs -->\n\n### `member`\n\nA person picker over the workspace\'s members. No default. With no role it is still drawn \u2014\nface and name, ranked as a `party` \u2014 in a screen\'s `columns` and in the register\na record draws of these rows.\n\n<!-- generated:start field-member -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `multi` | boolean | no | Allow multiple selections |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n\n<!-- generated:end field-member -->\n\n### `link`\n\n<!-- generated:start field-link -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `options_where` | [filter](#views) | no | Which rows of the target this link may point at \u2014 an `and` group of plain conditions over the TARGET table\'s own fields, each `field_key` a field alias. It narrows every picker of the link, and a save naming a row outside it is refused on every path \u2014 an app, the grid, an import, a workflow, the API. |\n| `same` | list of alias (at least one) | no | One-row links this row and each row this link points at must name alike \u2014 a fee\'s invoice is one of its own visit\'s, a box\'s seal one of its own shipping line\'s: each a one-row link of both tables to the same table. It narrows the picker\'s read to the rows naming what this row names, a row made from the picker names it too (set, never asked), and a write is refused wherever it lands \u2014 on the link, or on what the two rows compare. |\n| `table` | text | yes | The table the link points at: its alias, or the `ref` of another item of the call |\n| `multi` | boolean | yes | true: the link holds many rows; false: one. Where the link is paired, at most one of the two sides holds one |\n| `pair` | `true` \\| alias | no | Makes the link two-way: the alias of its partner field on the other table, which names this one back, or `true` to have the alias derived. Stated on one table, the partner is made on the other with the inverse cardinality; a many-to-many link states both sides |\n\n<!-- generated:end field-link -->\n\n**`multi` is stated on every link:** `true` holds several rows, `false` one \u2014 one customer on an\ninvoice, one project on a device. There is no default.\n\nA two-way link states `pair` on one table, as `true` or a free alias, and the partner is made on the\nother table with the inverse cardinality; a many-to-many link states both sides, since the inverse of\nmany is not decided. A pair stated on both sides is made once. Both tables are written in the one\ncall (`table` names the other item\'s `ref`). A link with no `pair` is one-way, and nothing is created on the target. Pair a link when\nthe target\'s own record should list what points at it, and leave it unpaired when it should not: either\nway the record plan draws the relation as a section on the side it points at, so an unpaired link costs\nthe target nothing. Of two paired links at most one holds one row.\n\n```jsonc\n// a field of table "order"\n"customer": { "type": "link", "label": "Customer", "table": "customer", "multi": false, "pair": "orders" }\n```\n\n```jsonc\n// a field of table "customer"\n"orders": { "type": "link", "label": "Orders", "table": "order", "multi": true, "pair": "customer" }\n```\n\n### `files`\n\nA row attaches documents to it (\xA7 Rows). Each file\nis drawn by its kind, with no key to choose: a picture (an image, a video) as its\nthumbnail, a document (a PDF, a sheet) as its type\'s badge and its filename.\n\n### `formula`\n\nA formula states its keys on the field itself, beside `type` and `label`:\n`{ "type": "formula", "label": "Total", "expression": "{quantity} * {unit_price}", "notation": { "style": "currency", "currency": "USD" } }`.\n\n<!-- generated:start field-formula -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `expression` | text | yes | The expression, over fields of THIS entity by alias in braces \u2014 `{quantity} * {unit_price}` |\n| `output_type` | `"number"` \\| `"text"` \\| `"date"` \\| `"datetime"` \\| `"boolean"` \\| `"select"` | no | Absent, it is what the expression yields. Stated, it is checked against that and refused where they differ (a datetime is never stated date) |\n| `notation` | [notation](#number) | no | How the figure reads: { "style": "decimal" }; money, { "style": "currency", "currency": an ISO 4217 code (USD, VND) }; or a quantity, { "style": "unit", "unit": "percent" (10 reads "10%"), or a measured code (g, kg, t, l, m3, cbm, mm, cm, m, km, m2, min, h, day), which converts and scales within its dimension, or any other noun of at most 12 characters (ki\u1EC7n, pallet, TEU), which never does }. A currency or unit read on each row from a single select of that row is { "per_row": that select }. For a formula yielding a number. |\n| `link` | `true` | no | The formula yields a URL as text, drawn as a link that opens. |\n| `options` | list of [Select option](#select-option) (at least one) | no | The categories the formula yields, drawn as a single select\'s options are (read-only). The expression yields one of them as `{this_field:option}`, or null \u2014 `{days_idle} > 30 ? {warmth:cold} : {warmth:hot}`. Omit for a formula yielding a plain value. |\n\n<!-- generated:end field-formula -->\n\n`output_type` is what lets a role or a screen clause accept a computed value: a\ncaption over a derived name (`output_type: "text"`), a period over a settled date\n(`"date"`). A formula declaring no `output_type`, `notation` or `link` says nothing\nabout its result, and every rule that needs one refuses it by name. A formula stating\n`options` is a computed category: read-only, and read as a single select\nwherever it is drawn \u2014 a column, a filter, a reading\'s `by` or `where`.\n\n**A select reaches a formula as the KEYS of its chosen options**, a list \u2014\nnever their labels, and never their aliases \u2014 and a document has no keys: the\nworkspace mints them when the table is made. So an option is named in a formula\nas `{field:option}`, both aliases, and the write puts that option\'s key in its\nplace: `includes({kind}, {kind:crate})` for a select holding one or several,\n`{kind}[0] == {kind:crate}` for a single one. A select compared to its own words\n(`{kind} != "Crate"`) matches no row and computes the other branch everywhere,\nso the check refuses it and names the token. A select looked up from another\ntable is tested there, in a formula of its own, and that result looked up.\n\n**The language.** The check refuses a call or a name the platform cannot run, naming what to write:\n\n<!-- generated:start field-formula-language -->\n\n- Operators: `+ - * / %`, `== != > < >= <=`, `&& || !`; `+` also joins text\n- Numbers: arithmetic is exact decimal (`0.1 * 3` is `0.3`), a division that does not end the nearest double; a comparison between two numbers reads both at 15 significant digits; `round` rounds a half away from zero (`round(-2.5)` is `-3`)\n- Conditionals: a ternary only \u2014 `{amount} > 100 ? "High" : "Low"`\n- Not supported: optional chaining (`?.`), nullish coalescing (`??`), template literals, arrow functions \u2014 use `get(obj, "path", default)`, `coalesce(v1, v2)`\n- Helpers are these names, spelled exactly; a spreadsheet function (`IF`, `SUM`, `LEN`, `DATEDIF`) is none of them, and a formula calling one is refused\n- Math: round(n,decimals?), ceil(n), floor(n), abs(n), min(a,b), max(a,b), sum(arr), mean(arr), clamp(n,min,max), percentage(part,total,decimals?), pow(base,exp), sqrt(n), mod(n,divisor)\n- Strings: upper(s), lower(s), trim(s), capitalize(s), length(s), contains(s,search), join(arr,sep), split(s,sep), replace(s,search,rep), replaceAll(s,search,rep), startsWith(s,prefix), endsWith(s,suffix), substring(s,start,end?), padStart(s,len,char), padEnd(s,len,char), numberToWords(n, lang?) (lang \'vi\' default, or \'en\'), uuidFrom(s) (the same UUID for the same text)\n- Lists: includes(list,value), first(list), last(list), unique(list), compact(list) \u2014 length(list) counts one\n- Dates: now(), formatDate(d,fmt), addDays(d,n), subDays(d,n), addHours(d,n), subHours(d,n), addMinutes(d,n), subMinutes(d,n), startOfDay(d), endOfDay(d), differenceInCalendarDays(later,earlier), differenceInHours(later,earlier), differenceInMinutes(later,earlier), isBefore(d1,d2), isAfter(d1,d2), isSameDay(d1,d2), isToday(d), isWithinRange(d,start,end), meetsBounds(value,kind,{gt,gte,lt,lte},tz?), parseDate(d)\n- Null/type: isNull(v), isEmpty(v) (also true for "" and []), coalesce(v1,v2,...), isString(v), isNumber(v), isBoolean(v), isArray(v), toNumber(v), toFigure(v), toString(v)\n- Other: formatCurrency(amount,locale,currency), formatDecimal(value,decimals,locale) (grouped quantity, no symbol), get(obj,"path",default?)\n- Empty cells: a cell nobody filled is null inside a formula, whatever its type; one holding 0, false or "0" is not empty. Test it with `isEmpty({note})` \u2014 `{note} == ""` and `{done} == false` are false on an unset cell\n- Arithmetic over an empty cell: `+` and `-` read it as 0 beside a value (`{fee} + {surcharge}` is `{fee}` when the surcharge is empty, null when both are); `*`, `/`, `%` and a unary `-` yield null (`{price} * {qty}` is null, not 0, when the quantity is empty)\n- No helper throws on an empty cell: the math helpers and toNumber return null, the string helpers "". A value of the wrong type still errors. When EVERY field a formula reads is empty it is null \u2014 unless it reads each only as the argument of isEmpty, isNull or isNotNull (`!isEmpty({file})` is false there, not null)\n\n<!-- generated:end field-formula-language -->\n\n### `rollup`\n\nAggregates the records reached through a link on this table.\n\n<!-- generated:start field-rollup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `filter` | [filter](#views) group | no | Only linked records matching this filter are aggregated; one condition on its own is a group of one. Every `field_key` in it names a field alias on the linked entity, and a select condition\'s value names an option alias there. A traversal node reaches past that entity, so its `path` hops and inner `field_key` are fully-qualified `entity.field` aliases. |\n| `via` | alias | yes | The alias of a link of this table to roll up from |\n| `op` | [operation](#rollup) | yes | The operation over the linked rows |\n| `field` | alias | no | The alias of the field on the linked table the operation reads; absent for `count` |\n\n<!-- generated:end field-rollup -->\n\nA rollup states `via` (a link of this table), `op` and \u2014 except for `count` \u2014 `field`, an alias on the\nlinked table; `op` is one of `count`,\n`sum`, `avg`, `median`, `min`, `max`, `range`, `empty`, `filled`,\n`percent_empty`, `percent_filled`, `unique`, `percent_unique`, `earliest`,\n`latest`, `date_range`, `checked`, `unchecked`, `percent_checked`,\n`percent_unchecked`. The operation must be one the aggregated field\'s type\nallows \u2014 `sum` over a number, `earliest` over a date, `filled` over any stored or\nformula field. A lookup is never rolled up: roll up the child\'s own field, or a formula\nover it. A rollup states no `notation`: `sum`, `avg`, `median`, `min`, `max` and `range`\nread in the aggregated figure\'s, a count as a plain number, and a `percent_*` as a\npercent (0\u2013100), in the app as in the table.\n\n### `lookup`\n\nDisplays a field from the linked records: `via` names a link of this table, `field` a field of the table it\npoints at, and `order_by` (`field`, `direction`) picks the one row whose value shows.\n\n<!-- generated:start field-lookup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `via` | alias | yes | The alias of a link of this table to look up through |\n| `field` | alias | yes | The alias of the field on the linked table to show |\n| `order_by` | [Lookup order](#lookup-order) | no | Show ONE linked row\'s value \u2014 the first in this order \u2014 rather than every linked row\'s |\n\n#### Lookup order\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A field alias on the linked table the rows are ordered by |\n| `direction` | `"asc"` \\| `"desc"` | yes | Which end of that order the one row is taken from |\n\n<!-- generated:end field-lookup -->\n\nInside a formula, a lookup holding one value is that value (`{due_soon}` is\n`true`, not `[true]`); several values are a list.\n\n### `autonumber`\n\n<!-- generated:start field-autonumber -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `prefix` | text | no | Literal prefix prepended to every display value (e.g. \'KH-\' \u2192 \'KH-001\'). Ignored when `template` is set. |\n| `padding` | integer | no | Zero-pad the integer to this width. Default 1 (no padding). 3 \u2192 \'001\', \'012\', \'123\', \'1234\' (overflow uses the actual width). Ignored when `template` is set. |\n| `template` | text | no | Format template with placeholder tokens evaluated at insert time. Tokens: {N} (raw integer), {N:W} (zero-padded to width W, e.g. {N:3} \u2192 001), {YEAR} (4-digit year), {YEAR:2} (2-digit year), {MONTH} (2-digit month), {DAY} (2-digit day). Date tokens use the workspace timezone. Example: \'HM-{YEAR}-{N:3}\' yields \'HM-2026-001\'. Stored as the composed string; subsequent template edits do NOT re-format existing rows (date tokens would lose the original creation date). |\n| `next` | integer | no | Sets the number the next row takes, once, as the patch is written; never below one past the highest number issued. On a field added to a table with rows, those rows are numbered from it in creation order. It is no setting of the field: a read of the table shows none |\n\n<!-- generated:end field-autonumber -->\n\nA refused write and a dry run take no number, so a table\'s numbers run without a gap; a removed row\'s number is not\nissued again.\n\n<!-- generated:start rules-field -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `field.default` | A default names options its select declares \u2014 one on a single select \u2014 and a date stamped by `derive_from` states none. |\n| `field.default-from` | A field\'s `default_from` reads `<link>.<field>` off the row a one-row link of its table names \u2014 a link no copy fills \u2014 or, through further one-row links, `<link>.<link>.<field>` off the row the last names, into a field of the same type, date format and notation that `derive_from` does not stamp: a lookup only through a one-row link, a link only into one to the same table, a select\'s option as this field\'s of the same alias (one it lacks is not copied), several values only into a field holding several. The server copies it on every add that fills the link and leaves the field empty, so a List\'s `create` sure to fill the link \u2014 a required one, or the record it files the row under \u2014 does not ask it by default. |\n| `field.option-mark` | An option\'s `mark` is one the kit draws, and a select\'s options mark every one or none. |\n| `field.unit-select` | A notation\'s `per_row` names a single select of the same row, or a lookup of one through a one-link, whose every option label is a unit, or an ISO 4217 code. |\n| `field.link` | A link targets a table of the call or the workspace: its `pair` a link on that table naming this one back \u2014 and of two paired links at most one reads one row (`multi: false`). |\n| `field.formula` | A formula parses, reads fields of its own table by alias, names an option as `{field:option}` of one a select declares, and compares a select to its options\' keys, never their words. |\n| `field.formula-type` | A formula yields what it states \u2014 its `output_type` (the type the platform stores: a `datetime` is never stated `date`), else a figure where it states a `notation` and text where it states `link` \u2014 as the platform infers it from the expression when the field is created. |\n| `field.rollup` | A rollup aggregates, through a link of its table (`via`), a field of the linked table (`field`) by an operation (`op`) that field\'s type takes \u2014 never a figure read in each row\'s own unit or currency \u2014 filtered on fields of the linked table. |\n| `field.lookup` | A lookup reads (`field`), through a link of its table (`via`), a field of the linked table \u2014 a figure read in each row\'s own unit or currency only where this table looks up that row\'s select through the same one-row link \u2014 ordered by a field of it (`order_by`). |\n| `field.computed-cycle` | Computed fields never wait on each other in a loop: each is computed after what it reads. |\n\n<!-- generated:end rules-field -->\n\n## Views\n\nSaved views live under the table they belong to. Every field reference is a\nfield ALIAS on that table. A `set_tables` patch of an existing table refuses `views`: they are stated when the table is\ncreated, and change afterwards in chat.\n\n<!-- generated:start views -->\n\n#### View\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable view alias, unique within the entity |\n| `label` | text | yes | Display name of the view |\n| `description` | text | no | The view\'s description, written onto the view in the workspace |\n| `columns` | list of [View column](#view-column) (at least one) | no | The columns the view shows, in this order and no others; absent, every field |\n| `filters` | [filter](#views) | no | The rows the view keeps |\n| `sort` | [sort](#views) | no | The order the view reads its rows in |\n| `summary` | map of text \u2192 text | no | Field alias \u2192 the operation its footer cell states |\n| `frozen_columns` | integer \\| `null` | no | How many leading columns stay in place while the rest scroll |\n\n#### View column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field_alias` | alias | yes | A field of this entity |\n| `visibility` | `"visible"` \\| `"hidden"` | yes | Field visibility state: \'visible\' = shown to everyone, \'hidden\' = not shown by default but members can toggle |\n| `width` | number | no | The column\'s width, in pixels |\n\n<!-- generated:end views -->\n\nA filter is a group \u2014 `{ "node_type": "group", "logic": "and" | "or",\n"children": [ \u2026 ] }` \u2014 or one condition on its own, `{ "node_type":\n"condition", "type": "select", "field_key": "tier", "operator": "has_any_of",\n"value": ["gold"] }`. A sort is a list of `{ "field_key": \u2026, "order": "asc" |\n"desc" | null }`. A condition\'s `type` is the field\'s type and its `operator` is\none that type admits \u2014 `has_any_of` / `has_none_of` / `has_all_of` / `is_empty` /\n`is_not_empty` for a select, `equals` / `greater_than` / `less_than` for a\nnumber, `on` / `before` / `after` / `between` for a date, `contains` /\n`is_any_of` for text. A select condition\'s `value` names option ALIASES.\n\n<!-- generated:start rules-view -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `view.fields` | A view\'s columns, summary, sort and filters name fields of its table. |\n\n<!-- generated:end rules-view -->\n\n## Groups and templates\n\nA member group and a document template are made on their own, outside a table document and an app tree. A tree names a\ntemplate by its alias. A table document names a group by its id, where a `read_scope` clause (`member_of`),\na stage owner (`group`) or a check\'s `member_of("<id>")` takes one. `get_workspace` kind `group` lists the groups with their ids and members; `set_group` creates,\nchanges or deletes one (an admin\'s verb).\n\n`{{name}}` in an `html` or `email` template is filled from the workflow\'s data. Inline content is refused where it holds a\nscript, a frame, a `<link>`, an inline event handler or a `javascript:` URL; an `html` one may be a fragment, kept as\nwritten and printed as a whole document.\n\nA template\'s markup is the `template` of `set_template`, as one JSON string. A paper shaped like a counterparty\'s \u2014 a letterhead, a reference line, a seal, a signature block \u2014 is an `html` template with a shell around the body; `document_templates` covers the types.\n\n**What an act\'s template is handed** is its record\'s own fields by alias: a\nselect, a member and a link as their words (a link as the linked row\'s title), a\nnumber, a date and a computed value that declares its kind raw (`2800000`,\n`2026-09-14`) and a files field as its file cells (`{id, name, mime, url}`, the photos in the order held; a child row\'s\nfiles likewise) \u2014 never a computed select. On one record it is\nhanded, too, the rows of each child List the record draws, listed under that child\'s\nlink\'s name for `{{#each <link>}}` (and under the child entity\'s alias too): every row filed under the record, oldest first, each\nby its own fields as the record\'s are, less its link back to the record. Over\nseveral rows the rows are listed under the table\'s alias, for\n`{{#each <alias>}}`, without their child rows.\n\n**Photos in a paper.** A file cell is drawn only if whoever makes the paper may read that file, by the rule the file\nproxy applies; a file they may not read is never drawn. html: `{{#each photos}}<img src="{{url}}">{{/each}}`, each\nphoto inline, only those the data\'s file cells carry (a url typed into a text field stays a link). Word:\n`{{IMAGE picture(cover, 8)}}` for one file cell, and `{{FOR p IN photos}}{{IMAGE picture($p, 8)}}{{END-FOR p}}` for\nmany. Excel: `{{cover | picture:6}}` as the whole cell, and `{{#each photos}}` with `{{this | picture}}` for many, each\nrow growing to its picture. The number is the width in centimetres (8 by default, 6 in Excel); the height follows the\npicture. `picture` takes one image file cell: a list, a file that is no image and a file that cannot be read are\nrefused by name. Photos are embedded at most 1600 pixels across.\n\nHelpers print a raw value in the template\'s `locale` \u2014 `vi` or `en`, the organization\'s language where it states\nnone; each prints `""` for an empty value:\n\n| Helper | Prints (`vi` / `en`) |\n|---|---|\n| `{{money total "USD"}}` | `1.250,50 US$` / `$1,250.50`, in the currency\'s minor units; no code, a number |\n| `{{number qty}}` | `1.234,5` / `1,234.5`: grouped, up to 2 decimals |\n| `{{date issued_on}}` | `14/09/2026`; a date-fns pattern as the second argument, `{{date at "dd/MM/yyyy HH:mm"}}`, literal text quoted (`"\'Ng\xE0y\' dd"`). A moment prints on the workspace\'s timezone |\n| `{{words total "USD"}}` | `M\u1ED9t ngh\xECn hai tr\u0103m n\u0103m m\u01B0\u01A1i \u0111\xF4 la M\u1EF9 n\u0103m m\u01B0\u01A1i xu` / `One thousand two hundred fifty US dollars and fifty cents`; no code, VND (`\u0111\u1ED3ng`) |\n| `{{qr link}}`, `{{code128 position 6}}` | the value as a QR code, or as a Code 128 barcode (ASCII text only), a picture the width of the optional second argument in centimetres, by default sized to the value (a module prints 0.5 mm in a QR code, 0.33 mm in a barcode); a width too narrow to scan is refused, naming the least that does; not in an email template |\n| `{{date (add_days issued_on -1)}}` | `13/09/2026`: the date shifted by whole days (`-1`: the day before), for `date` to print; a number field as the days, empty shifting none |\n\nA value it cannot read (`{{money "abc"}}`, `{{date "14/09/2026"}}`) fails the render. A field named like a\nhelper and written alone (`{{number}}`) prints its own value.\n\nA field printed as a code in the other types: Excel `{{position | qr}}` or `{{position | code128:6}}` as the whole\ncell and the last helper \u2014 the picture is anchored on the cell and its row grows to hold it; Word `{{IMAGE qr(position)}}` or `{{IMAGE code128(position, 6)}}`. An empty field draws no picture.\n\n## Rows\n\n`rows` of a `set_tables` call are the first records of the tables the call creates, keyed by each one\'s `ref`;\na patch of an existing table seeds none, so write its rows into the table. A sample, not a data set: rows past it go in with `create_records` once the tables exist, in the batches its description bounds.\n\n```jsonc\n"rows": {\n "customer": [\n { "ref": "acme", "fields": { "name": "Acme Trading", "tier": "gold" } }\n ]\n}\n```\n\n`rows` sits beside `tables` in the call, not inside a table.\n\n<!-- generated:start rows -->\n\n#### Row\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `ref` | text | yes | Local handle for this row, referenced by other rows\' link fields |\n| `fields` | map of text \u2192 any value | yes | Field alias \u2192 the value, read against the field\'s declared type |\n\n<!-- generated:end rows -->\n\n<!-- generated:start rules-rows -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `rows.ref` | A row\'s `ref` is unique within its table. |\n| `rows.required` | A row states every field its table requires: it is created with what it states, its fields\' defaults and copies unapplied. |\n| `rows.value` | A row\'s value fits its field: an option alias for a select, `"<table ref>:<row ref>"` of a row of the linked table in this call for a link, `"self"` for a member, a `fil_` id for files, a date in the date grammar (an hour only on a datetime), a text, number, yes/no or null otherwise \u2014 and none on a computed field. |\n\n<!-- generated:end rules-rows -->\n\nA `ref` is lowercase letters, digits and underscores, and is never persisted.\n\n`fields` is keyed by field alias, and every value is read against the field\'s\nDECLARED type:\n\n| Field type | Value |\n|---|---|\n| `text` / `number` / `boolean` | the value itself |\n| `date` | `"2026-03-14"`, or a relative expression (below) |\n| `select` | the option ALIAS \u2014 `"gold"`, or `["gold","vip"]` for a multi-select |\n| `link` | `"<table ref>:<row ref>"` naming another row of this call \u2014 `"customer:acme"`, or an array for several; a paired link is stated on ONE side (the child\'s link to its parent) and its partner fills itself |\n| `member` | `"self"` only \u2014 the member making the call |\n| `files` | the `fil_` id of a file already in this workspace, or a list of them |\n| `formula`, `rollup`, `lookup`, `autonumber` | not allowed \u2014 the platform writes these |\n\n### Relative dates\n\nA date cell holds a literal `YYYY-MM-DD`, or an expression relative to the day\nthe call is made, so a screen that opens on "this month" is not empty a month\nlater:\n\n- `@today` \u2014 the day of the run, in the workspace\'s timezone\n- `@month-start` \u2014 the 1st of that month\n- either with a whole-day offset: `@today-14`, `@month-start+9`\n- on a date that holds its hour (`format: "datetime"`), that hour after the day, and always stated:\n `@today 14:30`, `@today+1 06:00`\n\n`@month-start` exists because `@today-N` cannot promise a month: applied on the\n2nd, `@today-3` lands in the previous one.\n\n## Records\n\n`records` says how a row of a table is RECOGNISED \u2014 stated once in the table\'s document, beside its\n`title` \u2014 and every surface that draws one of its rows reads the same\nstatement: the register\'s row, a picker\'s option, a link\'s chip, a child table\'s\nrow and the record\'s header. Every table an app lists, opens or picks has one. The rarer keys \u2014 `scan`, `cadence`, `place`, `on_site`, `pin`, `plan_place` and `size` \u2014 are stated on `model/record-places-pins-and-cadence`.\n\n```jsonc\n// table "visit"\n"title": "container_no",\n"records": {\n "subtitle": ["customer", "arrived_on"],\n "image": "photos",\n "status": { "field": "stage", "closed": ["gone"], "history": "visit_history" },\n "figure": "total_fees",\n "due": ["free_until"]\n}\n```\n\n```jsonc\n// table "release": a release order read against what it allows \u2014 a meter wherever it shows\n"title": "release_no",\n"records": { "figure": "issued", "limits": { "issued": "units" } }\n```\n\n```jsonc\n// table "dossier": a straight-line process \u2014 the stage a select, each step\'s day the date it stamps\n"title": "applicant",\n"records": { "status": { "field": "stage", "closed": ["signed"], "dates": { "received": "received_on", "appraised": "appraised_on", "signed": "signed_on" } } }\n```\n\n<!-- generated:start records -->\n\n#### Record\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `subtitle` | list of alias (1\u20132) | no | Up to two fields read under the title wherever a row is drawn (a code, a date, a party): one line at a desk, wrapping to a second on a phone |\n| `image` | alias | no | A files field that pictures the row \u2014 never one an act keeps the document it makes in |\n| `mark` | alias | no | A single select whose option leads the row wherever a mark leads it \u2014 a list\'s row, the record\'s head, a link naming the row: the option\'s brand or glyph, else its colour dot, on a tile. The field stays a field: a subtitle run, a column, a section\'s control. Never beside `image` or `party`, and never the status field |\n| `description` | alias | no | A text field saying what the row is about \u2014 a plan\'s brief, a customer\'s note: read in its record\'s head under the title\'s line, its first lines at rest and whole on a press, and written from the head\'s \u270E. Never on a row\'s line |\n| `party` | `"person"` \\| `"organization"` | no | What each row is where the rows are people (a contact, a patient) or organisations (a customer, a supplier, a carrier); absent, the rows are things (an order, an item, a paper) |\n| `status` | [Status](#status) | no | The stage a row moves through, a single select, drawn as a badge beside its title |\n| `figure` | alias | no | The one number the row stands for, read at its right (a total, a quantity left) |\n| `limits` | map of alias \u2192 (alias \\| number) | no | A number read against a bound \u2014 a field of the same row or a constant \u2014 drawn as a meter wherever it shows |\n| `gates` | map of alias \u2192 (alias \\| number) | no | A pass mark on a meter `limits` bounds \u2014 a field of the same row or a constant; a percent field is its share of the bound, any other number an amount. The meter marks it and fills complete once past it |\n| `tolerance` | map of alias \u2192 number | no | How far past its `limits` bound a meter may stand, in percent of the bound: the bound is an amount expected (received against ordered), not a cap. Without one the bound is a cap, and past it is an overrun |\n| `due` | list of alias (at least one) | no | Dates that are deadlines (stored, or computed as a date): each counts down and turns overdue |\n| `frees` | alias | no | A date ending the row\'s span of days on which what the row holds is free again (a check-out, a hire\'s return): a stay from the 3rd to the 5th holds the 3rd and the 4th, and another may start on the 5th \u2014 on a lanes board, a calendar, a roster and in `no_overlap` alike. Absent, a span of days holds its last day; a span of moments always frees its end |\n| `settled` | alias | no | A yes/no of the row (stored, a formula: an invoice paid in full, or looked up through a one-row link: a line settled with its parent) that ends its deadlines: while it is true, no `due` of the row counts down or turns overdue \u2014 in a cell, on its line, in its head, on a calendar or gantt, or in the overdue count. A dashboard\'s or reading\'s `where` reads it as any other yes/no |\n| `task` | `true` | no | A row is work someone finishes: wherever rows of it stand in a table \u2014 a register, a record\'s rows, the rows filed under one \u2014 each is led by a ring ticking it done and unticking it by the one write of the app making that move. Its status is a stored select with `closed` |\n| `applies` | map of alias \u2192 map of alias \u2192 (list of alias (at least one) \\| boolean) | no | Fields that apply only while their conditions hold (a length only on a line whose tariff charges by the metre): where one does not apply no add asks it and no surface shows it. Never the title; a required one starts at its `default` |\n| `starts` | map of alias \u2192 `"today"` \\| `"me"` | no | What a field of a row being added starts at, where that start is certain: `today` a date (a moment starts now), `me` a member, the reader adding it; every other field starts empty unless the reader narrowed the register to a value or the field declares a `default` |\n\n<!-- generated:end records -->\n\n<!-- generated:start rules-records -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `records.title` | A title is the one field naming the row \u2014 any field but a button or a yes/no \u2014 and the subtitle never names the title\'s own field. |\n| `records.image` | `image` is a files field, never one an Act keeps the documents it makes in (`into`). |\n| `records.mark` | `mark` is a single select, never the status field, and never beside `image` or `party` \u2014 a List keeps one lead slot \u2014 nor on the folder an App works inside (`scope`), which its switcher names by a picture or its initials. |\n| `records.description` | `description` is a text field, read in the record\'s head \u2014 never a Field of a part too. |\n| `records.figure` | A figure is a number. |\n| `records.figure-on-line` | *Noted, never refused:* A subtitle entry equal to the figure is shown once, as the headline; the line leaves it out. |\n| `records.applies` | `applies` holds a field of the row other than its title to conditions of the same row \u2014 a yes/no (stored or a formula) by `true` or `false`, a single select by its options, its own or looked up through a one-row link \u2014 never to itself; a required field it holds starts at a `default`. |\n| `records.limits` | `limits` bounds a number by a number field of the row, in its unit or one of the same dimension \u2014 or by a constant, where every row reads the figure in one unit. |\n| `records.gates` | `gates` marks a pass on a meter `limits` bounds: a percent field (its share of the bound), another number in the bound\'s unit, or a constant. |\n| `records.tolerance` | `tolerance` stands on a meter `limits` bounds. |\n| `records.starts` | `starts` starts a field a person writes \u2014 `today` a date or a moment (never a span), `me` a member \u2014 and a `create` that does not ask a field it starts writes that start. |\n| `records.due` | A deadline (`due`) is a date. |\n| `records.frees` | `frees` names a stored day, never a moment: a span of moments already frees its end. |\n| `records.settled` | `settled` names a yes/no of the row (stored, a formula, or looked up through a one-row link) beside a `due`: while it holds, the row\'s deadlines neither count down nor turn overdue. |\n| `records.task` | A task entity (`task`) states a `status` of a stored single select with `closed` options \u2014 its ring ticks a row done by moving it to one. |\n| `records.picture` | *Noted by the designer on a write, never refused:* Rows an App draws that each hold one files field no Act makes documents into, no call is filed into, no files gallery keeps and no List compares as a pair (`compare`) \u2014 and no figure \u2014 are pictured by it (`image`); where the workspace\'s files are known, the field holding images is the picture, a figure or not. |\n\n<!-- generated:end rules-records -->\n\n- **The title names the row** by who or what it is \u2014 a link by the linked row (a job\n read by its customer). Any field but a button or a yes/no may title it, yet a code\n titles a row only where people name the row by it, else it is a `subtitle`; it is the table\'s own setting, which every link shows.\n- **One lead per row.** A row leads with its picture (`image`), a face (`party`) or the\n option of a single select it holds (`mark`) \u2014 never two. A `mark` draws that option\n on a tile: its brand or glyph, else its colour dot; a row holding none, a ring saying\n none is picked. The same tile heads the row\'s record and leads it where a link\n names it; the select stays a field \u2014 a subtitle run, a column, a section\'s control.\n- **A limit** reads a number against a bound \u2014 another field of the row, or a constant.\n A `gates` entry marks a pass on that meter: a percent field is its share of the\n bound, any other number an amount.\n- **A deadline** (`due`) counts down wherever it is drawn and is overdue once past while\n the work is open. A register over the table says how many rows in view are overdue on\n its summary line, and pressing that count narrows to them \u2014 no reading to state.\n- **A span\'s free day** (`frees`) is the end date on which what the row held is free\n again \u2014 a check-out, a hire\'s return. A stay from the 3rd to the 5th holds two nights:\n a lanes board, a calendar and a roster draw it through the 4th, and `no_overlap` lets\n the next stay start on the 5th. Without it a span of days holds its last day; a span of\n moments always ends as the next may begin.\n- **`starts`** is where an add\'s field begins, stated only where that is certain \u2014 a\n log\'s own day at `today`, a request\'s requester at `me`. Nothing else starts a field\n but its declared `default` and what the reader narrowed the register to.\n\n## Record places, pins and cadence\n\nThe rarer keys of `records`, stated beside the ones on `model/records`: a title read off a photo (`scan`), the rows a child owes each day (`cadence`), where a row stands on a map (`place`, `on_site`) or on a picture (`pin`, `plan_place`).\n\n```jsonc\n// table "site": it owes a diary page each day it works, from breaking ground through handover\n"title": "name",\n"records": { "cadence": [{ "entity": "diary_page", "on": ["mon", "tue", "wed", "thu", "fri", "sat"], "from": "started", "until": "handed_over" }] }\n```\n\n<!-- generated:start records-places -->\n\n#### Record places\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `cadence` | list of [Cadence](#cadence) (at least one) | no | The rows of a child each row owes one of a day, on the days of the week listed (a site\'s diary page, Monday to Saturday) \u2014 one entry a child, never owed on a day still to come. A roster of these rows over days marks a past day owed with none missing in the danger tone, counted in the legend; today\'s is due, offered to add and never marked; each row\'s total reads the days it holds of those owed |\n| `scan` | `"text"` \\| `"iso6346"` \\| `"code"` | no | Its title, a text field, is painted or printed on the thing a row is (a container\'s number, a seal\'s) and read off one photo on a phone, beside each search matching it and each field holding or picking it: `iso6346` a container number, refused unless its check digit holds; `text` as it stands; `code` a barcode or QR code on the thing, decoded on the device from the photo with no agent |\n| `place` | [Place](#place) | no | Where a row stands, as plain fields of it, drawn as one place where its record draws the address: how sure it is, a press opening it in the device\'s maps, and one taking it from where the device stands |\n| `plan_place` | [Plan place](#plan-place) | no | Each row is a place on the plan a `plan` register draws: its title the place\'s code, its subtitle a note. A flat list, no tree |\n| `on_site` | [On site](#on-site) | no | Photos taken where a place stands (a site diary\'s): a photo taken through a phone\'s camera here keeps where the device stood, marked at the place, away from it or unknown, and a record holding one taken away says so among its checks \u2014 one with no place (picked on a computer, or kept before places were) is marked and warns nothing; a place reaches only members of the organization, through the apps drawing these photos |\n| `pin` | [Pin](#pin) | no | Each row is a place on a picture (a snag on a flat\'s plan, a spot on a container\'s drawing): a List of these rows draws the picture its `on` row holds with a numbered pin at each row\'s own place, each row led by its number, the row opened lit on it and a pin pressed opening its row; where the List writes rows, a press on the bare picture adds one there, and a pin dragged moves its row. A List of rows that link to these by a single link draws their places the same way |\n| `size` | [Record size](#record-size) | no | The real size of the thing a row is (a carton, a piece of furniture, a pallet): three number fields stating one length unit alike, read as one fact \u2014 "400 \xD7 300 \xD7 250 mm", a part the row lacks "\u2014" \u2014 after the subtitle wherever the row\'s line and head draw facts, never also as the subtitle\'s fields. A plan register drawing these rows draws each box at this size |\n\n#### Cadence\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | text | yes | The table owed: each of its rows stands on one row of this table by its single link to it, and on the first date of its line (`records` title or subtitle) |\n| `on` | list of `"mon"` \\| `"tue"` \\| `"wed"` \\| `"thu"` \\| `"fri"` \\| `"sat"` \\| `"sun"` (at least one) | yes | The days of the week one is owed on, each listed \u2014 `mon` to `sun` |\n| `from` | alias | no | A date of this row: the first day one is owed (a site breaking ground, a contract\'s start); absent, no first day |\n| `until` | alias | no | A date of this row: the last day one is owed (a site\'s handover, a contract\'s end); empty or absent, through today |\n\n#### Place\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `address` | alias | yes | A text field: the address, as people write it |\n| `latitude` | alias | yes | A number field: degrees north, negative south |\n| `longitude` | alias | yes | A number field: degrees east, negative west |\n| `accuracy` | alias | no | A number field: how many metres off the device that gave the place said it may be |\n\n#### On site\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `photos` | list of alias (at least one) | yes | Files fields of this row holding photos taken on site |\n| `place` | alias | no | A single link of this row to the row whose `place` its photos are taken at (a diary page\'s site); absent, this row\'s own `place` |\n| `radius` | number | yes | How many metres from the place a photo still counts as taken there |\n\n#### Pin\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `on` | alias | yes | A single link of this row to the row whose picture (its `records` `image`) the place is on \u2014 a container\'s drawing, a floor\'s plan |\n| `x` | alias | yes | A number field of this row: how far across that picture the place stands, in percent from its left edge |\n| `y` | alias | yes | A number field of this row: how far down that picture the place stands, in percent from its top edge |\n\n#### Record size\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `across` | alias | yes | A number field: the width of the thing a row is, across |\n| `along` | alias | yes | A number field: its depth, along |\n| `up` | alias | yes | A number field: its height, up |\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `records.scan` | A title read off a photo or a code (`scan`) is a text field the row stores. |\n| `records.cadence` | A `cadence` names each child once \u2014 its rows standing on one row of this entity by their single link and on the first date of their line \u2014 lists each day of the week once, and `from` and `until` are two different days of this entity. |\n| `records.place` | A `place` names four different fields of the row: its address a text field, its latitude, longitude and accuracy number fields it stores. |\n| `records.plan_place` | A `plan_place` names `kind` and `reach_from` as single selects, `sheet` as a single select or a one-row link, `stacked` as a boolean and the rest as numbers, each a different field; `kind`, `x`, `y` required. `size` names three numbers (`across`, `along`, `up`) stating one cell in one length `unit` alike. A `sheet` linking to an entity that states an `image` draws that drawing under the places, `scale` (a number field of that entity) its width in cells; `scale` goes with such a `sheet` alone. |\n| `records.on_site` | `on_site` lists files fields of the row, each once, taken at the `place` of the row itself or of the one row a single link of it names (`place`), which states one. |\n| `records.pin` | A `pin` names a single link of the row (`on`) to an entity whose `records` states its `image` (a PDF is drawn as its first page), and two different number fields of the row, how far across (`x`) and down (`y`) that picture in percent. A List of rows whose own entity states a `pin` is drawn over that picture; `on`, `x` and `y` are set by the press that adds a row, never asked. |\n| `records.size` | A `size` names three different number fields of the row (`across`, `along`, `up`) stating one length unit alike (mm, cm, m or km); they read as one fact after the subtitle, so the subtitle never also names one. |\n\n<!-- generated:end records-places -->\n\n## Status\n\nA record\'s `status` is the stage a row moves through \u2014 a stored single select, each\noption\'s moment the date `dates` names \u2014 drawn as a badge beside its title wherever the row is.\n\n<!-- generated:start status -->\n\n#### Status\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The single select that is the row\'s state, its options in the order the work goes through them |\n| `closed` | list of alias (at least one) | no | Options that end the work: a register opens on the rows not at them, and the badge reads muted |\n| `dropped` | list of alias (at least one) | no | Options of `closed` that leave the work undone (cancelled, rejected): whoever holds the row moves it to one from any stage, where every other move forward passes each owned stage between that binds the row (its `when`) and needs each one\'s owner, and a row leaving one enters a stage as a row made there does |\n| `history` | text | no | A table every write that sets or moves the status appends one row to (its link to this table, the new status, the moment and who) \u2014 by the record write path, whoever writes: for reporting on every move |\n| `dates` | map of alias \u2192 alias | no | Option alias \u2192 a date field of this entity the record write path stamps with the moment a row moves into that option, or is made at it \u2014 whoever writes. A value the same write states for that field wins, so past data loads as it happened; the platform never clears a stamp, so each is when the row last entered the option. A stage\'s date is made where none is named |\n| `stages` | map of alias \u2192 [Stage](#stage) | no | Option alias \u2192 who owns that stage and how long it should take: the owner alone takes a row out of it, by any write, and presses the acts offered there; every move forward past it of a row it binds (its `when`), and every such row made past it, is its owner\'s too. Its days run from the moment its `dates` field holds. In an app the status then moves only by acts that `set` one of its options \u2014 no save writes it and no act asks it |\n\n#### Stage\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `owner` | text \\| [Stage group](#stage-group) | yes | Who owns the stage: a member field of the row, `link.field` read through a one-row link (not at the stage a row opens at, whose owner made the row: a member field of the row itself), or `{ group }` |\n| `within` | integer | no | Whole days the stage should take from the moment the row entered it |\n| `when` | map of alias \u2192 (list of alias (at least one) \\| boolean \\| [comparison](#values) \\| `{ "empty": true \\| false }`) | no | The rows that must pass this stage: a row meeting every entry on its own fields, as it stood before a write or as the write leaves it \u2014 a single select holding one of these options, a yes/no this value, a number or date meeting its bounds, a field empty or filled. Absent, every row must. A row at the stage leaves it by its owner either way |\n\n#### Stage group\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `group` | text | yes | A member group\'s id (from Settings, or chat): every member of it owns the stage |\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `records.status` | A status is one single select, its options in the order the work goes through them, `closed` naming the ones that end it and `dropped` those of them reached from any stage. |\n| `records.history` | A declared status `history` has exactly one link to the row it records, one single select holding every option of the status (or a text), exactly one date and at most one member. |\n| `records.stages` | A stage\'s owner (`status.stages`) is a single member field of the row, one read through a one-row link (never at the stage a row opens at: its owner made the row, so it is a member field of the row itself), or a group, on an option that does not end the work; a group\'s stage needs the status `history` with who moved the row. Every write takes a row off a stage as its owner alone, and moves a row past it or makes a row past it as its owner alone where the stage binds the row (its `when`) \u2014 a move to a `dropped` option excepted, which whoever holds the row makes from any stage; a row leaving a dropped option, or holding none, enters a stage as one made there. Each stage\'s moment, and each open option\'s, is the date its `dates` names, made where none is named, which every write that opens or moves the status stamps; `within` runs from it. In an App a status with stages moves only by Acts that `set` one of its options \u2014 no save writes it or a date it stamps, and no Act asks either, sets it otherwise or writes either from a workflow; an Act moving a row forward past a stage another owner owns, into an option not `dropped`, keeps its `when` off every row that stage binds. |\n| `records.stage-when` | A stage\'s `when` names the rows that must pass it, as an Act\'s `when` holds them \u2014 single selects to their options, yes/nos to `true` or `false`, numbers and dates to bounds, any field to `{ "empty": true \\| false }` \u2014 on fields a write stores of the row itself: never the status, a formula, a lookup or a rollup, and never on the stage a row opens at. It binds a row meeting it as it stood before a write or as the write leaves it; absent, it binds every row. |\n| `records.detour-note` | *Noted, never refused:* A stage the work only steps aside into \u2014 not where a row starts, entered from one step alone that also moves the work on without it, and left by its own moves only back to that step (a pause, a repair) \u2014 is a detour: its option and the Section whose `at` holds it stand before that step\'s, else the work reads it as its next step, and the Act moving the work into it leads that Section \u2014 never a correction waiting in a \u22EF. |\n\n<!-- generated:end status -->\n\n- **The status** is one single select. `closed` options end the work: a register opens\n on the rows not in them. A `history` table gets one row per move \u2014 its link to this\n table, the new status (a select holding the same option aliases, or text), the\n moment and who \u2014 appended by the record write path in every write that opens a row at a\n stage or moves it, whoever writes (the member behind the write is its `by`; an automation\n leaves it empty). A `history` the tables do not declare is derived, and needs no\n `records` of its own.\n- **When each step happened** is the date `dates` names for its option, stamped as the row\n enters it, whoever moves it. One way times a status: a straight-line process is a select\n whose options are its steps in order, each with its date \u2014 never a run of dates the stage\n is read off. An Act that moves the stage and `asks` the step\'s date with `starts: "today"`\n lets the reader record a day already past in the same press.\n\n## Write rules\n\nWhat a WRITE of a table\'s rows meets is stated in the table\'s document, where it applies: `min` and\n`max` on a number or date field, `options_where` and `same` on a link, `suggest` on a text field,\nand `no_overlap` and `natural_key` on the table. A key a field\'s type does not take is refused at\nits path, and a document has no `write_rules` key.\nEvery one but `natural_key` and `suggest` holds on every path a record is written by \u2014 the grid, a record\'s form, an import, a workflow, an\nagent, the API, an app \u2014 judged in the write\'s own transaction, every broken rule listed at once; the\nscreen and a picker only mirror them. A row that already breaks one stays editable until a write\ntouches what the rule reads. `natural_key` names the fields an app recognises a row by, to reuse it where an\nadd or a find-or-create meets it; a record tool or the API creates the row it is handed. `suggest` offers\nvalues in a text control and refuses none. An `options_where` link is refused where a write names a row the\nnarrowing does not admit, and every picker of the link offers only the rows it admits.\n\n```json\n{\n "tables": [\n {\n "ref": "customer",\n "label": "Customer",\n "fields": { "email": { "type": "text", "label": "Email", "unique": true } },\n "natural_key": ["email"]\n }\n ]\n}\n```\n\nA customer above is RECOGNISED by their address. An add that names one \u2014 from the customers\nregister or from a picker\'s "new" \u2014 reuses the row it matches and opens one only where nothing does,\nso the book never grows a second Acme.\n\n```json\n{\n "tables": [\n { "ref": "product", "label": "Product", "fields": { "name": { "type": "text", "label": "Name" }, "in_stock": { "type": "number", "label": "In stock" } } },\n {\n "ref": "order",\n "label": "Order",\n "fields": {\n "name": { "type": "text", "label": "Name" },\n "placed_on": { "type": "date", "label": "Placed on" },\n "due_on": { "type": "date", "label": "Due on" },\n "lines": { "type": "link", "label": "Lines", "table": "order_line", "multi": true, "pair": "order" }\n }\n },\n {\n "ref": "order_line",\n "label": "Order line",\n "fields": {\n "name": { "type": "text", "label": "Name" },\n "order": { "type": "link", "label": "Order", "table": "order", "multi": false, "pair": "lines" },\n "quantity": { "type": "number", "label": "Quantity", "min": 1, "max": 9999 },\n "ships_on": { "type": "date", "label": "Ships on", "min": "order.placed_on", "max": "order.due_on" },\n "product": {\n "type": "link",\n "label": "Product",\n "table": "product",\n "multi": false,\n "options_where": {\n "node_type": "group",\n "logic": "and",\n "children": [{ "node_type": "condition", "type": "number", "field_key": "in_stock", "operator": "greater_than", "value": 0 }]\n }\n }\n }\n }\n ]\n}\n```\n\nA line of nothing is not a line: `quantity` is refused on create and on update. A line ships inside\nits order\'s window \u2014 a bound read off the row a link names is judged when the line is written. Nothing is sold off an empty shelf: every picker of `product`\nreads only the rows that answer its `options_where`, and every save refuses the same rows again.\n\n```json\n{\n "tables": [\n {\n "ref": "booking",\n "label": "Booking",\n "fields": {\n "room": { "type": "text", "label": "Room" },\n "state": { "type": "select", "label": "State", "options": [{ "alias": "held", "label": "Held", "color": "blue" }, { "alias": "released", "label": "Released", "color": "gray" }] },\n "starts_on": { "type": "date", "label": "Starts on" },\n "ends_on": { "type": "date", "label": "Ends on", "min": "starts_on" }\n },\n "no_overlap": { "from": "starts_on", "to": "ends_on", "by": ["room"], "while": { "state": ["held"] } }\n }\n ]\n}\n```\n\nOne booking per room at a time, while it is held \u2014 a create or a save whose span overlaps another\nheld booking of the same room is refused.\n\nA table\'s `checks` (the conditions no write of its rows may meet) and `workflows` (what follows every committed write of them) are stated in its document and hold on every path a record is written by; their keys, what they read and an example are on `workflows/table-checks` and `workflows/table-workflows`.\n\n<!-- generated:start write-rules -->\n\n#### Suggest\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `table` | text | yes | The table whose text field is offered from: its id, or its `ref` in this call |\n| `field` | alias | yes | The alias of that table\'s stored text field; the field\'s control offers, as the reader types, the distinct values it holds, and any text is still written |\n\n#### No overlap\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | text | yes | The stored date (or datetime) a row\'s span starts on: `<field alias>` of the row, or `<link alias>.<field alias>` of the one row a one-row link names (a booking line\'s piece held for its booking\'s dates) |\n| `to` | text | yes | The stored date (or datetime) a row\'s span ends on, named like `from` and through the same link \u2014 a day it holds, unless `records` names it the day the row `frees`; a moment another row may start at |\n| `by` | list of alias (at least one) | no | Fields two rows share to compete for a span (the same employee, the same room); absent, every row |\n| `while` | map of text \u2192 list of alias (at least one) | no | Only rows at these options of these selects hold their span (running, not closed), the select named like `from` \u2014 `<field alias>` or `<link alias>.<field alias>`; absent, every row |\n\n<!-- generated:end write-rules -->\n\n<!-- generated:start rules-write -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `write.natural-key` | A `natural_key` is text or a number a person types back, or a one-row link; a text key is unique \u2014 `unique: true`, or the key\'s fields as a set in the table\'s `unique`. |\n| `write.bounds` | `min` and `max` bound a number or a date \u2014 by a constant a number, by a field of this row or of the one row a one-row link names either, of the same type and unit, never by itself \u2014 and `min` is never above `max`. |\n| `write.bound-per-row-unit` | On a figure whose unit or currency is the row\'s own (its notation\'s `per_row`) a constant bound is only 0 \u2014 bound it by a field of the row. |\n| `write.suggest` | `suggest` stands on a stored text field and names a `table` and a `field`, a stored text field of that table; the app reads that table, so it has a `records` entry. |\n| `write.same` | `same` stands on a link and names one-row links this table and the link\'s target both hold to one table. |\n| `write.options-where` | `options_where` narrows a link by an `and` group of plain conditions over the target\'s own fields \u2014 a number compared in one unit on every row, a select by options it declares. |\n| `write.no-overlap` | `no_overlap` spans two date fields, the row\'s own or the one row a one-row link names (`booking.start_on`); `by` names values rows share \u2014 a link, a person, one option, a text or a number; `while` names single selects by their options, the row\'s own or that linked row\'s. |\n\n<!-- generated:end rules-write -->\n\n- A `natural_key` is `text` or `number`, because a person types it back, or a\n one-row link, because a row is also recognised by the one row it belongs with (a\n fee by its visit and its kind). A key holding a `text` field is unique: `unique:\n true` on that field, or the key\'s fields as a set in the table\'s `unique` (a\n site by its customer and its name) \u2014 two rows sharing it would make\n find-or-create pick whichever the read answered first. An add asks the key by\n default.\n- A `same` link names only rows that name what this row names \u2014 a fee\'s invoice\n is one of its own visit\'s \u2014 through a one-row link both tables hold to one\n table; its picker offers nothing until the row names one. A row added under a\n record names what it copies off it (`default_from`) from the start, so a block\n expecting one line per row of the link lists that record\'s rows alone. A save\n of this row moving what the two compare is refused while they would name apart.\n- An `options_where` link may be left empty; a row it names is refused on every\n save that writes the link unless it holds the narrowing, and the refusal names\n the link and the row. A save that leaves the link alone is not judged, so a row\n still naming a row the narrowing no longer admits stays editable. A link naming\n one kind of a book\'s rows (a "Shipping line" among the parties) narrows to that\n kind, or its picker offers every row.\n\nA field\'s own `required` is not here: the field carries it, and every write\npath refuses the row by field name from it. `unique` is the text field\'s own\nclause (\xA7 `text`) \u2014 an add says so at the control before the column does. A\nfield the workspace writes (a formula, a rollup, a lookup, an autonumber, a date\nwith `derive_from`) is never asked, written or edited.\n\n## App tree\n\nAn app is a tree of components, assembled the way a page is written in JSX: pick a component, bind it to a table by\nname, and it draws well with no props, because its defaults come from the table\'s facts (\xA7 Records, \xA7 Status,\n\xA7 Write rules, \xA7 Field). A prop overrides a default at its own node. No prop says how anything looks: the runtime\ndraws each concept one way, and a screen the job needs that no component states is told to the person \u2014 the job,\nwhat the app drew, the word wanted \u2014 never props bent to approximate it.\n\nEach component is a page of its own: `model/app-node`, `model/list-node`, `model/record-node`, `model/section-node`,\n`model/side-node`, `model/page-node`, `model/outside-node`, `model/tab-node`, `model/field-node`, `model/act-node`, `model/check-node`,\n`model/reading-node`, `model/progress-node`, `model/statement-node`, `model/export-node`, `model/book-node`,\n`model/query-node` and `model/workflow-node`. Beside them: `model/changing-an-app` and `model/what-an-app-may-write`, `model/layouts`, `model/time-and-place-layouts` and\n`model/publish-desk` for a List\'s `layout`, `model/composed-page`, `model/tasks`, and the rules, `model/rules-of-the-app`,\n`model/register-rules`, `model/block-rules` and `model/record-and-act-rules`.\n\n<!-- generated:start app-components -->\n\n| Component | Draws | Holds |\n|---|---|---|\n| [App](#app-node) | the launcher tile, the app\'s words, its Lists, and its API | List, Tab, Reading, Query, Workflow |\n| [List](#list-node) | rows of an entity: the register, a record\'s child rows, a roster\'s cells, related rows, a log | Tab, Reading, Progress, Export, Act, Check, Book, Record, List |\n| [Record](#record-node) | what a row opens: the head and the parts | Field, Section, Side, Page, Outside, List, Reading, Progress, Statement, Act |\n| [Section](#section-node) | a titled part of a record, and optionally a step of its path | Field, List, Reading, Progress, Statement, Act |\n| [Side](#side-node) | the part the rest is worked from, beside them | Field, List, Reading, Progress, Statement, Act |\n| [Page](#page-node) | a part on its own page, off the record\'s scroll | Field, List, Reading, Progress, Statement, Act |\n| [Outside](#outside-node) | what one record shows, and which acts it allows, to a person with no seat who opens a link a member made of it | Field, List, Act |\n| [Tab](#tab-node) | one view of a List\'s rows over a period, or one sheet of a dashboard | Reading, List |\n| [Field](#field-node) | one field: a control, a gallery, prose, a link and its picker, the rows filed under a back-link | \u2014 |\n| [Act](#act-node) | a verb: what pressing it does | \u2014 |\n| [Check](#check-node) | a formula of the rows that warns, or refuses the writes and acts it names | \u2014 |\n| [Reading](#reading-node) | a number over rows: one figure, its parts, a grid, per period, steps; or a record\'s own figure | \u2014 |\n| [Progress](#progress-node) | the work against its plan, day by day | \u2014 |\n| [Statement](#statement-node) | a figure built from figures of the record | \u2014 |\n| [Export](#export-node) | a report file of the rows in view | \u2014 |\n| [Book](#book-node) | another table read as part of the register | Record |\n| [Query](#query-node) | nothing: a read for agents | \u2014 |\n| [Workflow](#workflow-node) | nothing: a verb for agents | \u2014 |\n\n<!-- generated:end app-components -->\n\n### A node\n\nA node is `{ "type": <Component>, "key"?, ...props, "children"?: [...] }`. `type` names the component, `key` its\nname among its siblings, the other keys its props, `children` the nodes it holds. An unknown prop is refused.\n\n- **Two shorthands, both for Field.** A bare string child is a Field (`"due_date"`); an object with `field` and no\n `type` is a Field with props (`{ "field": "price", "edit": false }`). Both work in every prop that lists fields:\n `create`, `row.subtitle`, `add`, and the `children` of a Record or a part. A column is a field\'s alias, or\n `{ "field", "edit": true }` for the line\'s own control, and takes no other prop.\n- **Fields and options are named by alias**, never by label. A tagged value is an object, never a magic string.\n- **`false` turns a default off at its node:** `"figure": false` drops the row\'s figure, `"edit": false` makes a\n field read-only, `"create": false` and `"remove": false` take a List\'s add and Delete away, `"readings": false` its\n band, `"row": { "status": { "edit": false } }` the status buttons (another app moves the stage) and\n `"row": { "image": false }` the picture.\n\n### Keys and addresses\n\nA key is unique among its siblings, the way a React key is. A node that binds data takes its key from what it binds:\n\n| Node | Key when none is stated |\n|---|---|\n| Field, Check | its field alias |\n| List, Book | its entity alias |\n| Record | `record` |\n\nEvery other node states its key. Three kinds of key are unique across the whole app, because each is an API name:\nan Act\'s (its workflow is `act_<key>`, or the one it names), a Reading\'s (its query name for `run_app_query`), and a Query\'s or\nWorkflow\'s.\n\nA node\'s **address** is the path of keys from the app, such as `customer/record/orders`; the app itself is `/` (the `in` of a node at the root), and its own key is part of no address. A patch may use any ending\nof that path that names exactly one node (`orders`). The exact path wins where one exists; an ending naming two\nnodes is refused, and the refusal lists both paths. `get_app` prints the tree with each node\'s address.\n\n### Values\n\n| Value | Written |\n|---|---|\n| now, the reader, an asked input | `{ "now": true }`, `{ "me": true }`, `{ "input": "name" }` |\n| a computed value | `{ "formula": "{cost} * 1.2" }` over the acted row\'s fields |\n| a call\'s own value, in a Query | `{ "param": "name" }` |\n| cleared | `null` in an Act\'s `set` |\n| an option | its alias |\n\nA **condition** (`when`) maps a select to a list of its option aliases and a yes/no to `true` or `false`; every entry\nholds. A number (a number, currency or percent field, or a formula or rollup yielding one) takes a comparison with any\nof `gt`, `gte`, `lt`, `lte` (at least one, never both `gt` and `gte` or both `lt` and `lte`), each a number; a date\ntakes the same keys with a day (`"2026-03-01"`), a moment on a datetime (`"2026-03-01T09:30"`) or `{ "now": true }`\n\u2014 today, or the current minute on a datetime, read when the rows are, so overdue is `{ "due_on": { "lt": { "now": true } } }`;\nand any field but a yes/no takes `{ "empty": true }` or `{ "empty": false }`. A comparison on a field it does not fit is\nrefused, naming the field, its type and what it takes. A `when` on an Act, a Section or other block, and a List\'s `where`\n(the register\'s or a child List\'s) take these; a child List that adds, picks or expects rows refuses a comparison in its\n`where`, since a row it holds could stand outside it. A **narrowing** (`opens`, a Reading\'s, a Tab\'s or an Export\'s `where`) takes options, yes/nos and also `{ "me": true }` on a member\nfield, the reader; `where` on the register, a log or a record\'s child rows fixes the rows on the server for every reader, so it refuses\n`{ "me": true }` \u2014 open on it with `opens`; on a record\'s related rows (`same`, `app`) and on a dashboard\'s List it\nnarrows by the reader and takes it, and those two take no comparison.\n\n## Changing an app\n\n1. `get_workspace` with `kind: "app"` names the app. `get_app` prints the `tree:`, the `node addresses:` (the address of\n each node the app draws, `(default)` on those the tree leaves to the platform) and the `version` line, whose id is the `base_version_id` to send. A member who is not the\n app\'s author is shown no tree. Keep the tree: no tool reads an earlier one.\n2. `set_app` with `app_id`, `base_version_id` (that version: a newer write is refused, not overwritten) and `nodes`,\n (a refused change writes nothing, so `dry_run` is for reading the permissions it would grant and remove). Every answer\n states the permissions the change grants and removes.\n3. `screenshot_app` shows the result.\n\n`nodes` maps an address to a patch of that node. Read against the customers List of the account review (\xA7 A complete\nworkspace, abridged), the call below makes `tier` read-only, moves the Orders Section ahead of Account, adds an Act to\nAccount and removes the Reading:\n\n```json\n{\n "type": "List",\n "entity": "customer",\n "children": [\n { "type": "Reading", "key": "ordered_by_tier", "by": "tier", "value": "total_ordered" },\n {\n "type": "Record",\n "children": [\n { "type": "Section", "key": "account", "title": "Account", "children": ["tier"] },\n { "type": "Section", "key": "orders", "title": "Orders", "children": [{ "type": "List", "entity": "order" }] }\n ]\n }\n ]\n}\n```\n\n```json\n{\n "app_id": "<id of the app>",\n "base_version_id": "<current_version_id>",\n "nodes": {\n "tier": { "edit": false },\n "orders": { "before": "account" },\n "mark_gold": { "type": "Act", "in": "account", "label": "Make gold", "when": { "tier": ["standard"] }, "set": { "tier": "gold" } },\n "ordered_by_tier": null\n }\n}\n```\n\nEach entry applies in order:\n\n- **An existing node:** a JSON merge patch (RFC 7396) of its props; `null` drops a prop, and a list (`children`,\n `columns`) replaces the node\'s whole.\n- **`null`:** removes the node and everything under it.\n- **An address naming no node, with `type` and `in`:** adds a node there; its key is the address\'s last word, and a\n Field states `field` in place of `type`.\n- **`in`, `before`, `after`:** move a node into another, or ahead of or behind a sibling.\n\nThe app\'s own props (`name`, `description`, `icon`, `color`, `words`, `scope`, `period`) change\nthrough `body`, a merge patch over the App node. `dry_run` checks a change and writes nothing.\n\n| To change | State |\n|---|---|\n| a field read-only | `"edit": false` on its Field, or on the Section, Side, Page or Record that draws it |\n| a column added or dropped | `columns` of the List, every column kept: a List that never stated `columns` draws none beyond the row\'s title and its `records` line |\n| a button | an Act node (\xA7 Act node); `confirm` makes its press ask first |\n| a label | the table\'s field `label` in `set_tables`, for every app; for this app alone, `body: { "words": { "<table>": { "fields": { "fee": "Consultation" }, "options": { "urgency": { "rush": "Same day" } } } } }`; a List\'s `label`, a Section\'s `title`, an Act\'s `label`, `add_label` on the add. A Field\'s `label` heads only a gallery or prose block |\n| the order of nodes and rows | `before` or `after` a sibling, or `in` another parent, in the node\'s entry (a Section ahead of another, a Field ahead of another in its Section); a List\'s `columns` read in the order stated; its rows read in its `sort`: `{ "sort": { "field": "collected_on", "desc": true } }` |\n| the row\'s second line | `row: { "subtitle": ["mechanic"] }` on the List: up to two fields under the title, in place of the table\'s `records` line, which stays as it is |\n| a picker\'s add | `add` on the link Field: `{ "add": ["species", "microchip"] }` asks those fields of the new row; `false` makes the picker only pick. It holds in every picker of that table in the app |\n| a headline figure | a Reading in the List: `{ "type": "Reading", "in": "orders", "key": "prepaid_owed", "label": "Prepaid owed", "value": "prepaid", "where": { "stage": ["awaiting_payment"] } }` sums the rows in view; without a `value` it counts them |\n| a calendar and its date | `layout: "calendar"` on the List, and the date it places each row on is the first of `row.due`: `{ "layout": "calendar", "row": { "due": ["pickup_on"] } }`; absent, the table\'s `records.due` |\n| a record\'s child lines | a List inside the Record\'s Section over the child table: `{ "type": "List", "in": "details", "entity": "repair_line" }`; the child table (one link back to the record) and the record\'s rollup over its rows come from `set_tables` first. Lines priced off a book: `docs path="design/treatments/moves-and-numbers/priced-lines"` |\n\n**A node the tree leaves to the platform has an address too.** `get_app` lists the nodes the app draws, those the stored\ntree does not state marked `(default)`: the Record with its Sections and Fields, the status buttons, the band of readings.\nPatch one by its address and the call states it first: the nearest node the tree does state gains the default child that\nleads to it, as drawn (the whole Record under a List that states none, with the List\'s `row` picture and the `columns` the\ndefaults added; every Section of a Record that states no part; every status button of the app, each in the Section of its\nstage; every reading of the band, since stating one stands the others down), then the patch applies. Defaults\noutside that subtree stay defaults, and the answer\'s notes say what was stated. Removing every reading, or every status\nbutton, is refused: a set no node states is drawn again by the platform, and only `readings: false` or\n`row.status: { "edit": false }` on the List draws none. **A stated subtree stays as it is**: a\nfield or option the tables gain later is not added to it, so name it in the tree. `{ "due_date": { "edit": false } }`\nover a sparse tree makes that field read-only and takes away `saves <table>: due_date` alone. An address that ends several\ndrawn nodes is refused, as for stated ones.\n\n## What an app may write\n\nAn app states no list of what it writes: the tree derives it, and the generated workflows accept exactly that, so it is\nalso what an agent can do through the app\'s API. `get_app` prints it beside the tree.\n\n1. **A save of field `f` of table `E`** exists when a node draws `f` on a page of `E`\'s rows (a Field in a Record,\n Section, Side or Page, or a part of the head), the table lets a person write it (stored; not computed, stamped or the\n back side of a child\'s link), no Act of the app on `E`\'s rows writes it, and neither the node nor a container above it\n says `edit: false`. A row\'s stage is moved by an Act or the one button per move a status no Act moves gets, by hand only where `row.status` says `{ "edit": true }`, and by none where it says `{ "edit": false }`.\n2. **An add of `E`** exists for a List over `E` that writes its rows unless it says `create: false` (asking what it lists, and\n setting the link back to the record itself), and for an `add` on a Field drawing a picker of `E`. An absent `create`\n stands down where the table states no `singular` (nor the app\'s `words`), or where `where` keeps a select at an option an\n Act sets other than the one a new row starts at; a stated `create` list over such a table is refused. A roster\'s cells and\n a document are added only where `create: true`.\n3. **A Delete of `E`** exists for a List over `E` unless it says `remove: false`, and for the table of every Book of that\n List (a Book has no `remove` of its own). A roster\'s cells, a document and a desk\'s rows are deleted only where `remove: true`.\n `remove: false` takes this app\'s Delete away and no more: the record tools, the API and other apps still delete. A row no\n surface may delete is a table check with `on: ["delete"]`.\n4. **An act** exists for each Act node. Every field it sets, asks, files into, signs or records is the act\'s, and read-only\n everywhere else in the app.\n5. **The table\'s contract holds on every path** and is not restated here: required, unique, no overlap, field rules, table\n checks, stage owners, history and the read scope. (`natural_key` is read by an app\'s add and find-or-create alone.)\n\nDrawing is reading or saving; a List that writes its rows also adds and deletes them, until it says `create: false` or `remove: false`, and a List below a part or a Record that says `edit: false` writes, adds and deletes none.\n\n### What a table gives a component\n\n| Table fact | Feeds |\n|---|---|\n| The title | every row\'s name: Lists, a Record\'s head, link chips, pickers, an add |\n| The row\'s line (`records` subtitle, figure, image, mark, party, description) | the row\'s line and head, cards, a checklist\'s photo |\n| The stage (`records` status) | the stage chips, a List\'s `opens`, the badge, an Act\'s moves, stage owners gating acts, a Section\'s `at` |\n| Deadlines (`records` due, settled, frees) | countdowns and overdue marks, the default sort, attention order, spans on lanes, calendars and rosters |\n| Bounds (`records` limits, gates, tolerance) | the figure\'s meter, wherever it stands |\n| Tasks and cadence (`records` task, cadence) | the ring on task rows, a roster\'s owed days |\n| Applicability and starts (`records` applies, starts) | a field shown and asked only where it applies; an add\'s starting values |\n| A field\'s `required`, `default`, `default_from`, `natural_key`, `unique` | what an add asks, and what it starts at |\n| A field\'s `notation`, `format`, options, `options_where` | money, units, chips, and what a picker offers |\n| The table\'s `read_scope` | which rows each reader reads, in every List |\n\n### Where the rules stand\n\n\xA7 Rules of the app is every rule the check holds over an app, by id, whichever component it concerns. A\n**language** is decided by the app\'s own words: a tree names no locale, so what an app draws itself (its buttons, empty\nstates, refusals) is in the language its labels are written in, and `words` count as labels. Mix two languages in one\napp and it answers in two.\n\n## App node\n\nAn app draws its launcher tile, its words, its Lists and its API. It binds the tables it names: `set_app` fills\n`tables` with the id of every table alias the tree uses, and `templates` with that of every document template alias.\nA relabel on either side is a rename and no tree points at another table by accident.\n\n<!-- generated:start node-app -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The app\'s alias, its name among apps; the App node is addressed `/`, and no address in it starts with this key |\n| `name` | text | yes | The job, in the words of the people who do it |\n| `description` | text | no | What the job is for, in a sentence |\n| `icon` | text | no | A Lucide icon name in kebab-case the launcher tile draws |\n| `color` | [colour](#select) | no | The launcher tile\'s colour |\n| `words` | map of alias \u2192 [Table labels](#table-labels) | no | A table \u2192 the words this app draws for it |\n| `scope` | alias | no | A required one-row link of the first List\'s entity: the app works inside one row of the linked entity |\n| `reads` | `"shared"` | no | Every member reads every row, whatever the entity\'s read scope |\n| `uses` | list of alias (at least one) | no | Tables the app\'s Query and Workflow nodes read without drawing them, by alias: they join the tables the app reaches, so a workflow may name them |\n| `period` | `"month"` \\| `"quarter"` \\| `"year"` \\| [Dashboard period days](#dashboard-period-days) | no | The period a dashboard opens on |\n| `tables` | map of alias \u2192 text | no | The id of each table the tree names, by its alias: filled by set_app, never written |\n| `templates` | map of alias \u2192 text | no | The id of each document template the tree names, by its alias: filled by set_app, never written |\n| `children` | list of ([List](#list-node) \\| [Tab](#tab-node) \\| [Reading](#reading-node) \\| [Query](#query-node) \\| [Workflow](#workflow-node)) (at least one) | no | The first List at the root is the register, and a second is another app; a dashboard holds Tabs, Readings and Lists that open in another app. Queries and Workflows stand here too |\n\n#### Table labels\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | no | The table\'s name in the plural (a register\'s heading, a block\'s, a link\'s picker) |\n| `singular` | text | no | One row of the table, as the add and empty sentences name it |\n| `fields` | map of alias \u2192 text | no | A field alias \u2192 the words this app draws wherever it names the field; the table\'s field keeps its own label |\n| `options` | map of alias \u2192 map of alias \u2192 text | no | A select field alias \u2192 option alias \u2192 its words |\n\n#### Dashboard period days\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `days` | `30` | yes | The last 30 days through today \u2014 the one span of days a dashboard opens on |\n\n<!-- generated:end node-app -->\n\n- **The first List is the register**: the working list of one table. Every further List at the root is a **log** beside\n it, at most 3, each stating its `label`: read as what was brought into or done to the register\'s rows (the files\n imported, the runs of a batch), opened as a register of its own inside the app, its reads, acts and workflows the\n app\'s. Only the organization\'s members are shown a log. A log is a table or cards with no `Tab`, `Book` or `compose`;\n its table is never the register\'s, a Book\'s or another log\'s; none beside `scope`. A list people work every day as\n a desk of their own is another app.\n- **A dashboard** is an App with no register: it holds `Tab`s, `Reading`s and read-only `List`s that open in\n another app (`app`). One `period` windows every reading placed in time: this month by default, or `quarter`, `year`\n or `{ "days": 30 }`. `words` and `scope` belong to a register.\n- **Query and Workflow** nodes stand at the root and are never drawn.\n- **Writes:** nothing itself.\n\n## List node\n\nA List draws rows of one table. What its rows are depends on where it stands:\n\n- at the App root: the register (the first) or a log (each further one);\n- under a Record, Section, Side or Page: the record\'s child rows, filed through the child\'s one link to the record\n (`via` where it has several);\n- under a roster List: the rows that fill the roster\'s cells;\n- with `same` or `app`, or on a dashboard: related rows, read only.\n\n<!-- generated:start node-list -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Absent, the entity alias |\n| `entity` | alias | yes | The table whose rows the List draws |\n| `via` | alias | no | The child\'s link to the record, where it has several |\n| `same` | alias | no | A one-row link of the record: lists the rows sharing that link\'s row, the record left out, read only |\n| `where` | [narrowing](#app-tree) | no | A fixed narrowing on the server, for every reader: a select holds one of these options, a yes/no this value, a number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), a field is `{ "empty": true \\| false }`, and `<link>.<field>` keeps the rows whose one-row link names a row holding a select or yes/no so (a path holds nothing on an add) \u2014 every entry ANDed. A List that only reads (`same`, `app`, a Record with `edit: false`) also bounds a date or number by a field of the record it stands beside, `{ "record": "<field>" }`, read when the rows are: a window of the record\'s own days (`{ "published_on": { "gte": { "record": "period_start" }, "lte": { "record": "period_end" } } }`); a bound the record holds blank keeps no row, and the same rows are handed to an Act\'s paper that names the List. A record bound narrows what is listed; it is never an access bound \u2014 the table\'s row rules are. An add holds a select\'s option and a yes/no\'s value; a bound or a blank holds nothing on an add |\n| `expect` | alias \\| [Expect](#expect) | no | One line per option of this select, or per row of this catalog link |\n| `under` | alias | no | A one-row link to the document that covers the line; lines stand grouped under their document |\n| `app` | text | no | The id of the register app its rows open in |\n| `row` | [List row](#list-row) | no | How this List reads its rows, where it differs from the table\'s `records` |\n| `label` | text | no | What the app\'s lists and the tab strip call the register; absent, the entity\'s label. Never the noun of its rows \u2014 the table\'s own label and singular (this app\'s `words`) are |\n| `columns` | list of (alias \\| [Edit column](#edit-column)) | no | Fields read after the row\'s title; `{ field, edit: true }` is the line\'s own control, at most 2. Absent: none, the row reads its title and its `records` line alone |\n| `filters` | list of (alias \\| [Tiered filter](#tiered-filter)) (at most 3) | no | Fields the reader narrows the rows by every day, at most 3; the stage chips always stand. A field is a select, member, link, date, yes/no or number range; `tiers` bands a number or date |\n| `search` | list of alias (at least one) | no | Fields the search box reads; absent, every word of the row |\n| `group` | alias | no | A field the rows stand grouped by; a gantt draws the groups as its lanes |\n| `sort` | [Sort key](#sort-key) \\| list of [Sort key](#sort-key) (at least one) | no | The order of the rows; absent, the first `records.due` soonest, else newest |\n| `opens` | [narrowing](#app-tree) | no | The view the register opens on, a chip the reader may take away |\n| `period` | `"today"` \\| `"week"` \\| `"month"` \\| `"quarter"` \\| `"year"` \\| [Period days](#period-days) | no | The period the List opens on, with Tabs; absent, this month |\n| `attention` | `false` | no | false: rows keep the list\'s own order; absent, those needing attention open first |\n| `readings` | `false` | no | false: the register leads with no band of figures; absent, rows that move, fall due or carry money lead with one. Never with Readings stated |\n| `layout` | [layout](#layouts) | no | How the rows are drawn: a word, or an object stating one layout with its data; absent, a table |\n| `compare` | [Compare](#compare) | no | Reads each row\'s before against its after: two files fields as a pair of photos, or two values of one type as one column |\n| `current` | alias | no | A child List of revisions: the child\'s date or number field that orders them. The row holding the latest value reads as current and the others fold under "superseded \xB7 N", one press to open; the record shows the current file through a lookup of the child ordered by that field |\n| `create` | boolean \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The fields an add asks. Absent: where this List writes its rows, the table\'s required fields, its natural key and its title; `false`: rows are not added here. Where the table states no `singular` (nor the app\'s `words`), or `where` keeps a select at an option an Act sets other than the one a new row starts at, an absent `create` adds nothing; the table\'s `singular` or a stated list adds. A roster\'s cells and a document add only where `true` |\n| `add_label` | text | no | The add button\'s words; absent, the platform\'s word and the entity\'s singular |\n| `remove` | boolean | no | Rows offer a Delete. Absent: they do where this List writes its rows; `false`: this app\'s Delete is gone, while the record tools, the API and other apps still delete \u2014 a row no surface may delete is a table check with `on: ["delete"]`. A roster\'s cells, a document and a desk\'s rows delete only where `true` |\n| `pick` | `true` | no | Also picks rows of the child that no record holds yet |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `children` | list of ([Tab](#tab-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Export](#export-node) \\| [Act](#act-node) \\| [Check](#check-node) \\| [Book](#book-node) \\| [Record](#record-node) \\| [List](#list-node)) (at least one) | no | What the List holds: its Tabs, Readings, Progress, Exports, Acts and Checks, its Books, the Record a row opens, under a roster the List of its cells, and in a child List with `under` the List of the documents the lines stand under (the one place a document is added or deleted) |\n\n#### List row\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `title` | [List row title](#list-row-title) | no | The row\'s title field, stated for its props alone: `edit: false` reads it, `add` is its picker\'s add, `false` its picker only picks |\n| `subtitle` | list of (alias \\| [Field](#field-node)) (1\u20132) | no | Up to two fields read under the title, in place of the table\'s |\n| `figure` | alias \\| `false` | no | The one number a row stands for, in place of the table\'s; false, none |\n| `image` | alias \\| `false` | no | A files field that pictures a row, in place of the table\'s; false, none |\n| `due` | list of alias (at least one) \\| `false` | no | Dates that count down, in place of the table\'s; false, none |\n| `status` | `false` \\| `{ "edit": true \\| false }` | no | false: the stage is not shown. `{ edit: true }`: the stage is also moved by hand. `{ edit: false }`: nobody moves it in this app, another app does. Absent, the stage\'s moves are buttons |\n\n#### List row title\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `edit` | `false` | no | false: the title is read; absent, it is written where the table lets a person write it |\n| `add` | `false` \\| `true` \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The new row this field\'s picker offers asks these fields; `true`: the target\'s required fields, its natural key and its title. Stated once per target table, and it holds in every picker of that table in the app. `false`: this link only picks, in every picker of it the app draws, not only where it is written |\n\n#### Edit column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The column\'s field |\n| `edit` | `true` | yes | true: the column is the line\'s own control, written in place |\n\n#### Expect\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The child\'s title when the record holds one row per value of it: a single select (one line per option) or a one-row link to a catalog (one line per row) |\n| `of` | alias | no | With a select: the record\'s own multi-select holding the options this record expects a line for, in place of every option |\n| `only` | list of alias (at least one) | no | With a select: the options this List holds a line for; never with `of` |\n\n#### Compare\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `before` | alias | yes | A field of the List\'s rows holding what stood before: a files field (the photo of the place before the work), or a value \u2014 text, a number, a date or a single select (the price a row held before a sheet changed it) |\n| `after` | alias | yes | Another field of the same type holding what stands after: the photo of the same place once the work is done, or the value the row holds now |\n| `edit` | `true` | no | The after is written on the line |\n\n#### Tiered filter\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A number \u2014 stored, or a formula or rollup reading one \u2014 in one unit for every row |\n| `tiers` | list of number (at least one) | yes | The breakpoints between its bands, ascending, in the field\'s stored unit: under the first, from each to the next, from the last up \u2014 each band worded in the field\'s notation; a filter picks several at once |\n\n#### Sort key\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field rows are ordered by |\n| `desc` | `true` | no | Latest or largest first; absent, soonest or smallest first |\n\n<!-- generated:end node-list -->\n\nA List takes only the props its place lists:\n\n<!-- generated:start list-places -->\n\n| A List standing in | Takes |\n|---|---|\n| the register (the first List of the App) and a log (each further one) | `key`, `entity`, `row`, `label`, `columns`, `filters`, `search`, `group`, `sort`, `opens`, `period`, `attention`, `layout`, `create`, `add_label`, `remove`, `readings`, `where` |\n| a record\'s child rows, as a table, a checklist or a desk | `key`, `entity`, `via`, `label`, `columns`, `create`, `add_label`, `where`, `under`, `filters`, `search`, `group`, `sort`, `remove`, `pick`, `layout`, `compare`, `current`, `when`, `expect`, `row` |\n| a record\'s child rows with `layout` an agenda | `key`, `entity`, `via`, `label`, `columns`, `create`, `add_label`, `remove`, `layout`, `when`, `row` |\n| a record\'s child rows with `layout` `"timeline"` | `key`, `entity`, `via`, `label`, `create`, `add_label`, `remove`, `layout`, `when`, `row` |\n| a record\'s related rows, read only (`same`, `app`, or a Record that is `edit: false`) | `key`, `entity`, `via`, `label`, `where`, `columns`, `app`, `same`, `when`, `row` |\n| the documents a child List\'s lines stand under (`under`), a List inside it | `key`, `entity`, `create`, `remove` |\n| the cells of a roster, a List inside it | `key`, `entity`, `expect`, `create`, `remove`, `row` |\n| a dashboard\'s list, read only | `key`, `entity`, `label`, `where`, `columns`, `app` |\n\n<!-- generated:end list-places -->\n\n- **Children:** Tab, Reading, Progress, Export, Act, Check, Book, Record, and a List for a roster\'s cells. A Check or an\n Act stands in the List whose rows it works on: on each row (its \u22EF and the open row\'s), or with `on` on the ticked rows\n or every row in view.\n- **A child List\'s Record** holds `edit` and Fields only: it states which fields the rows\' saves write and draws no page\n (the page a row opens is the register\'s). With `edit: false` the List only reads, as a related-rows List does: it\n states its `label` and takes none of `filters`, `search`, `group`, `sort`, `create`, `remove` or `layout`.\n- **Documents:** in a child List with `under`, a List over the document\'s table (`key`, `entity`, `create`, `remove`,\n Acts and Checks) is the one place the documents are added or deleted; `create` states `true`, and `remove` needs\n `create: true` beside it.\n- **Writes:** a List that writes its rows adds and deletes them \u2014 `create: false` and `remove: false` turn each off \u2014 `pick` links existing rows, an `edit: true` column writes that\n field on the line, and a layout move (a gantt bar, a lane\'s block, a plan\'s item) writes only fields the app already\n writes on the row\'s page.\n- **`create` asks:** `true` asks the title, the natural key and every required field a person writes \u2014 never a computed\n or stamped field, nor the link the place already sets. An `expect` list also asks the key it makes its lines by, and\n their picture. A list of fields overrides it; `false`, rows are not added here.\n- **`row`** overrides what the table\'s `records` say of the register\'s rows in this List alone (a cashier\'s figure is what\n is still owed): the subtitle, figure, image and deadlines stated replace the table\'s. A child List, a related List and\n a roster\'s cells read their rows as their table does, so their `row` states only a picker\'s add (on its title or\n subtitle) or the stage moved by hand (`row.status`).\n- **`filters`** are the fields a reader narrows by every day, none to three \u2014 a quota never; each earns its place by\n daily use. A date filters by a range of days, a number by a range in its unit, `{ "field", "tiers" }` by the bands its\n breakpoints cut. A threshold the business names ("large orders") is a formula yes/no, never a range. The stage chips\n always stand.\n- **`opens`** is the view the register opens on, each entry a chip the reader may take away; absent, the open work\n (rows not at a `closed` stage).\n- **`expect`** makes one line per option of a select, or per row of a catalog link, and an empty line adds its row\n (a checklist of papers per case). **`under`** groups lines under the document that covers them.\n- **`compare`** reads each row\'s before against its after: two files fields as a pair of photos, or two values of one\n type as one column under the after\'s label.\n\n## Layouts\n\nA List\'s `layout` is how it draws its rows: a word (`"table"`, `"cards"`, `"calendar"`, `"roster"`, `"timeline"`) or an\nobject stating one layout and the data it reads (`{ "gantt": \u2026 }`, `{ "lanes": \u2026 }`, `{ "plan": \u2026 }`, `{ "agenda": \u2026 }`,\n`{ "checklist": \u2026 }`, `{ "desk": \u2026 }`). Absent, a table: the word `"table"` is the register\'s, and a child List states a\nchecklist, a desk, an agenda or `"timeline"`, else leaves `layout` out. Each layout reads what the rows already state \u2014 their\n`records` line, stage and dates \u2014 and the keys a layout adds stand on its section: here for an agenda and a checklist, \xA7 Time and place layouts for the rest, \xA7 Publish desk for a desk. Which one a job needs: the\n`design` reference. The register lays out as a table, cards, a calendar, a roster, a gantt, lanes or a plan; a record\'s\nchild rows as a table, a checklist, a desk, an agenda or a timeline.\n\n### Table\n\nAbsent `layout`, each row is one line: the table\'s `records` entry \u2014 its picture or mark, title and the line under it \u2014\nthen the stage, the `columns`, and the figure at the right; the title, the stage and a column of days or amounts sort\nthe rows by their header. A table groups its rows by `group`; a table or cards reads `Tab`s and `Book`s.\n\n### Cards\n\n`"cards"` draws each row as a card led by its picture \u2014 the `records` `image`, or the List\'s `row.image`: its title and\nthe line under it, its stage and what needs the reader (a deadline, a warning), then its figure and the `columns` at its\nfoot. Cards are for rows found by their picture (a product, a vehicle, a damage photo); rows with no picture read better\nas a table.\n\n### Calendar\n\n`"calendar"` places each row on its day \u2014 a month, a week, a day or a list (a phone reads the list or a day): at its\nhour where the date holds one, over its span where the row\'s line holds a second date after it, its line\'s other words\nand the `columns` after its title. The calendar reads the window in view, and its count and the stage chips count that\nwindow, opening on every row.\n\n### Roster\n\n`"roster"` draws the List\'s rows down the side and the days of a week or a month across; the roster\'s cells are a List\ninside it over the table whose rows fill the days \u2014 each stands on one row by its single link to it, and on the first\ndate of its line (a shift on a person\'s day, a run on a vehicle\'s), on every day through the second date where its line\nholds one. A cell reads its row\'s stage, else the first single select on its line (a shift\'s kind), several rows their\ncount. A day holding none stays empty: nothing was due, which is never an absence. A filled cell opens its row; an empty\none adds a row there, the register row and the day set. With `expect` on the cells (a single select of those rows) the\ncolumns are its options in place of days \u2014 a checklist of papers \u2014 and `of` names the ones each row expects: a gap among\nthem reads as one and adds its row, the others stand blank, and each row reads what it holds of them, `2/3`. Over days, a\nroster reads the `cadence` its table\'s `records` states (a site\'s diary page, Monday to Saturday from breaking ground to\nhandover): a past day owed with none is marked missing and counted, and each row reads the days it holds of those owed. A\nroster states no `columns` and needs no readings: its legend counts each state in view.\n\n```json\n{ "type": "List", "entity": "operator", "layout": "roster", "filters": ["depot"], "children": [{ "type": "List", "entity": "run" }] }\n```\n\n### Timeline\n\n`"timeline"` reads a record\'s child rows as a log of dated entries, newest first, with a composer where the app adds\nentries, each corrected and removed where it stands. It is never the record\'s status history: the rail shows each\nmove. A timeline\'s moment is its child\'s first date, and who its member field. Rows each what happened on a day and who \u2014\na date on their line, a member, no stage and no figure \u2014 read best as a timeline.\n\n### Agenda\n\n`{ "agenda": { \u2026 } }` reads a record\'s child rows as planned entries on a time axis: by day, soonest first, each with its\npicture and the day that is today marked. The day is the first date on the child\'s row line (its `records` title or\nsubtitle); within a day entries follow a datetime there, else the first single select after the date. An entry with no day\nyet is a plan, listed first and dated in place. `start` is the record\'s date that is the first day: each day\'s heading\ncounts from it (Day 1, Day 2).\n\n<!-- generated:start layout-agenda -->\n\n#### Agenda keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | no | The record\'s date that is the first day: each day\'s heading counts from it; absent, the weekday and the date alone |\n\n<!-- generated:end layout-agenda -->\n\n### Checklist\n\n`{ "checklist": { \u2026 } }` reads a record\'s child rows, which are tasks, as a checklist of checks: each one\'s mark \u2014 passed\nat a `closed` option, failed at a `failed` one, else still to check \u2014 its title, its columns as its words (a text column\nits note, on its own line), and on a line under its title its photo and its clause. Never grouped nor written in place.\n`passed` alone sets the mark, so rows with no `task` and no status are checks: a measured yes/no is the verdict. With\n`expect` on the standard\'s items, each item is a line marked the same way (a missing one still to check), filled in\nplace by the block\'s `columns` (a measured value, a note, a photo), so `photo` and `clause` are not named.\n\n<!-- generated:start layout-checklist -->\n\n#### Checklist keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `photo` | alias | no | The child\'s files field holding the photo each row is checked by; absent, the child\'s `records.image` |\n| `clause` | alias | no | The child\'s one-row link to what each row is checked against \u2014 a standard\'s clause, a rule |\n| `failed` | list of alias (at least one) | no | Options of the child\'s status a row stands failed at \u2014 none of them closed |\n| `passed` | alias | no | The child\'s yes/no field (stored or a formula) setting each row\'s mark: yes passed, no failed, empty still to check |\n\n<!-- generated:end layout-checklist -->\n\n## Time and place layouts\n\nThe layouts that place a List\'s rows in time or on a plan. Each is a `layout` object stating the data it reads.\n\n### Lanes\n\n`{ "lanes": { "by": \u2026 } }` draws each row of the linked table as a lane (a chair, a machine, a room), empty ones too,\nand each row of the List as a block from the first date on its line to the second: over the hours of one day where the\nfirst holds its hour, else over the days of one day, a week or a month. A block reads the row\'s name and the first of its\n`columns`; a free stretch between blocks adds a row there, its lane and its start set, and its end where the next block\nbounds it. Rows naming no lane stand first, in a lane of their own; where the app writes the link, each block moves to\nanother lane from its menu. Inside a `scope`, the lanes are the ones whose own one-link names that scope\'s row. A span\'s\nend holds its day unless `records` names it the day the row `frees`.\n\n`loads` names what a lane carries: `{ "weight": "payload", "volume": "box" }` sums each block\'s number and reads it\nagainst the lane row\'s own, at most two. A lane holds its blocks while they stand, so it reads the most they add up to at\none time in the window, named, past its bound, by how much and on which day or hour. The two are in one unit, or two of\none dimension. A bound a single row must keep (no parcel heavier than its van) is a `max` on the field through the link,\nwhich refuses the move.\n\n<!-- generated:start layout-lanes -->\n\n#### Lanes keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `by` | alias | yes | A one-link of this entity whose target\'s rows are the lanes (a chair, a machine, a room), each drawn even when empty |\n| `loads` | map of alias \u2192 alias | no | What a lane carries against what it holds, at most 2: a number of this entity each block adds \u2192 a number of the lane\'s row it is held within, in one unit or two of one dimension |\n\n<!-- generated:end layout-lanes -->\n\n### Gantt\n\n`{ "gantt": { \u2026 } }` draws each row as one bar on one axis of time: `{ "start": "etd", "end": "eta", "milestones":\n["cut_off"], "planned": { "end": "booked_eta" }, "progress": "done", "after": "waits_on" }`. `end` defaults to the table\'s\nfirst `due`, and a table with neither is refused; `start` and `end` are two dates of the row, both days or both moments,\nand each planned date is read as `start` is. `progress` is a percent (0\u2013100); absent, a row whose record lists its tasks\nfills to how many are done. `after` is a link to rows of the same table, each ending before the row starts. The bars\nstand in the lanes of the List\'s `group`. Where the app writes the row and a person writes a bar\'s date, the bar is\ndragged through the row\'s save; a computed date, one an Act sets, or a row the app does not write drags nothing.\n\n<!-- generated:start layout-gantt -->\n\n#### Gantt bar\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | yes | A date of this entity each row\'s bar starts at (a sailing, a hire\'s start, a task\'s start) |\n| `end` | alias | no | The date each row\'s bar ends at, read as `start` is \u2014 two days or two moments; absent, the entity\'s first `due` |\n| `milestones` | list of alias (at least one) | no | Dates of the row marked on its bar\'s line as diamonds, each named by its label (a cut-off, a delivery, the end of free time) \u2014 never `start` or `end` |\n| `planned` | [Gantt plan](#gantt-plan) | no | The plan each row is read against, drawn as a thin bar under its own \u2014 each date read as `start` is; a missing one is the bar\'s own |\n| `progress` | alias | no | A percent of this entity (0\u2013100) filling each row\'s bar as far as the work is done; absent, how many of the row\'s tasks are done fills it where its record lists them, else the bar wears its status\'s tone |\n| `after` | alias | no | A link of this entity to its own rows each row waits on \u2014 it starts once they end (finish-to-start), an arrow from each; a row starting before one ends is drawn in the danger tone |\n\n#### Gantt plan\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | no | The date the row was planned to start at |\n| `end` | alias | no | The date the row was planned to end at (a booked arrival) |\n\n<!-- generated:end layout-gantt -->\n\n### Plan\n\n`{ "plan": { \u2026 } }` draws things that stand at a place \u2014 pallets in a warehouse \u2014 on a plan of the places. The places are\nthe rows of a table whose `records` states a `plan_place` (a bay, an open area or a landmark, each at its spot on a sheet\nof the plan); `at` names the one-link of the List\'s rows to a place and, for an item in a bay, the row (`across`) and the\ntier (`up`) it stands in, counted from 1. A bay is drawn as its rows and tiers, an area with a count, a landmark as a\nlabelled frame; the reader sees a plan, one bay enlarged, and the same plan in 3D. The List may read other tables as\n`Book`s: one plan of every table\'s items. Each item carries `__reach` \u2014 how many items stand in the way of taking it out\n(those above it where the place is `stacked`, and those in the rows between it and the side `reach_from` says the bay is\ntaken from) \u2014 so the list, its export and an agent\'s read all carry it; `<table>_plan_load` counts the rows each place\nholds. A move through the app is refused unless it clears the place, row and tier all three, or places the item at\na free cell of a bay \u2014 resting on a present item where the place is `stacked` \u2014 or at an area. Every other write (a grid edit, an import, a tool) holds one item to a cell only where the item\'s table states `unique: [<at>, <across>, <up>]`; a plan over a table stating none is noted. A plan opens on every row\nits `where` keeps, with no stage chip narrowing it. Rows with no place wait in a tray beside the plan. An Act that asks exactly a place (a link to the plan\'s places) and two numbers (the row and the tier) is answered by a pick on the plan, never by a form. On a desk a press opens a row\'s record in the panel beside the plan.\n\n```jsonc\n// table "bay"\n"title": "code",\n"records": { "plan_place": { "kind": "kind", "x": "x", "y": "y", "across": "rows", "up": "tiers", "reach_from": "taken_from", "stacked": "is_stack" } }\n```\n\n```json\n{ "type": "List", "entity": "pallet", "filters": ["line"], "layout": { "plan": { "at": "bay", "across": "row", "up": "tier" } } }\n```\n\n```jsonc\n// table "pallet": one item to a cell on every write, not only through the app\n"unique": [["bay", "row", "tier"]]\n```\n\n<!-- generated:start layout-plan -->\n\n#### Plan keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `at` | alias | yes | A one-link of this entity to an entity whose `records` states a `plan_place`: where each row stands |\n| `across` | alias | yes | A number field: the row of a bay a row stands in, counted from 1; empty at an area |\n| `up` | alias | yes | A number field: the tier of a bay a row stands at, counted from 1 at the ground; empty at an area |\n| `fill` | alias | no | A single select the items are coloured by; absent, the first single select among the register\'s filters |\n| `mark` | [Plan mark](#plan-mark) | no | A row holding the option says the option\'s label on its box and is hatched in the option\'s own colour, and the legend names it by the label; in books, the select is matched by option alias, and a book lacking the select never marks its rows |\n| `size` | [Plan size](#plan-size) | no | Each row\'s box as it stands in the world, in the one length unit its places\' `size` states: the option the row holds of the select names its box\'s width across, depth along and height; in books, matched by option alias, as `mark` is; a row holding an option it does not name is drawn at its place\'s cell |\n\n#### Plan mark\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select of this entity |\n| `option` | alias | yes | An option alias of that select |\n\n#### Plan size\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select of this entity |\n| `options` | map of alias \u2192 [Plan size box](#plan-size-box) | yes | The box of each option alias of that select |\n\n#### Plan size box\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `across` | number | yes | The box\'s width across the bay\'s rows |\n| `along` | number | yes | The box\'s depth along them |\n| `up` | number | yes | The box\'s height |\n\n#### Plan place\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | alias | yes | A single select, options aliased exactly `bay`, `area`, `landmark`: a bay holds rows and tiers, an area neither, a landmark nothing |\n| `sheet` | alias | no | The plan sheet it is on: a single select, or a one-row link to an entity whose `records` states its `image` \u2014 the sheet\'s drawing, which the places are drawn over (with `scale`) |\n| `scale` | alias | no | A number field of the sheet entity `sheet` links to: how many cells wide its drawing is, the plan\'s scale. Required where that entity states an `image`, and only then |\n| `x` | alias | yes | A number field: left edge, in cells |\n| `y` | alias | yes | A number field: top edge, in cells |\n| `width` | alias | no | A number field: an area\'s frame width, in cells |\n| `depth` | alias | no | A number field: an area\'s frame depth, in cells |\n| `across` | alias | yes | A number field: a bay\'s rows |\n| `up` | alias | yes | A number field: a bay\'s most tiers |\n| `reach_from` | alias | no | A single select, options aliased exactly `first`, `last`, `both`: the side boxes leave a bay from; a bay that names none has no reach and no arrow |\n| `stacked` | alias | no | A boolean field: true where items at this place rest on the one below (a stack), so a tier above the first needs a present item under it, shows floating without one, and an item\'s reach counts the items above it; false or empty, items sit at any tier and only the rows between a bay\'s reach side and the item are in the way |\n| `size` | [Plan place size](#plan-place-size) | no | The real size of ONE cell of the place, for 3D: three number fields stating one length `unit` alike. Absent, a cell is drawn at the size most of the sheet\'s places state, a unit cube where none states one |\n\n#### Plan place size\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `across` | alias | yes | A number field: the width of one cell across the rows |\n| `along` | alias | yes | A number field: the depth of one cell along the place |\n| `up` | alias | yes | A number field: the height of one cell, one level |\n\n<!-- generated:end layout-plan -->\n\n## Publish desk\n\nRows that are each a place the record is sent \u2014 a notice\'s boards, a listing\'s portals \u2014 are a record\'s child List with\n`expect` on the child\'s one-row link to the catalog of those places and `layout: { "desk": \u2026 }`:\n\n```json\n{\n "type": "List",\n "entity": "posting",\n "expect": "board",\n "columns": ["posted_link", "refusal"],\n "layout": { "desk": { "bodies": ["wording", "wording_abroad"], "text": "goes_out_as", "media": "pictures", "link": "page", "group": "reach", "network": "kind", "networks": { "company_page": "linkedin" } } }\n}\n```\n\nThe desk is where the post is written. `bodies` are the record\'s plain text fields it is written in (one per language,\nsay), each its editor between the list and the preview, saved as any field of the record is and drawn nowhere else on it.\nThe preview is `text` read over what is on screen: where `text` is a formula of the child, the desk computes that same\nformula over what is typed, so the post moves as it is written, and a place not ticked previews the words its row would\nhold. For that the formula reads only what a screen holds whole: stored values of its row, the record through lookups over\nthe link back to it, the place through lookups over `expect`, and formulas of the row over those \u2014 a rollup, a lookup\nthrough another link or the clock is refused, naming the field.\n\nEvery place the link admits is a line with a tick and that place\'s face: its picture, else the mark of the network it is on\nwhere `networks` names one. `group`, another single select of the place, stands the lines under its options in their\norder: a kind holding a ticked line is open, one holding none folded to its heading and its count. A tick makes the line\'s\nrow from its key alone \u2014 so nothing else the child requires is left without a default \u2014 and an untick deletes it until the\nrow\'s stage is `closed`: what went out is not taken back. It asks first only where a person wrote on the row. One line is\nworked beside the list and read as the post that place\'s feed shows: `text` is the child\'s words for that place, `media`\nand `link` the record\'s, and `network` the place\'s single select whose options `networks` names to a feed; an option not\nnamed reads on a plain card. Under the bodies stand the List\'s `columns` a person writes of the line\'s row; under the post,\nthe press copying its words beside the Acts of the child, then what the line and the `columns` hold that nobody writes\nthere (the link it went out at, why it was refused). A value an Act or a workflow fills is stated as that Act\'s, so the\ndesk never offers it as an empty box. Sending is an ordinary Act of the record.\n\n<!-- generated:start layout-desk -->\n\n#### Desk keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `bodies` | list of alias (1\u20133) | no | The record\'s plain text fields the post is written in, in order \u2014 one per language, say \u2014 at most 3: each its editor on the desk, between the list and the preview, and drawn nowhere else on the record |\n| `text` | alias | yes | A text of the block\'s rows \u2014 stored, or a formula yielding text \u2014 holding the words that go out on that row\'s destination. A formula is computed on the desk too, over what is typed: it reads stored values of its row, the record through lookups over the link back to it, the destination through lookups over `expect`, and formulas of the row over those \u2014 never a rollup, another link or the clock |\n| `media` | alias | yes | A files field of the record: the pictures or the video the post carries |\n| `link` | alias | no | A text field of the record holding a link (`format: "link"`), carried as the post\'s card |\n| `network` | alias | yes | A single select of the entity `expect` links to, saying which network each destination is on |\n| `group` | alias | no | Another single select of the entity `expect` links to \u2014 the destination\'s kind \u2014 the lines stand grouped by: each option a heading in the select\'s order, a group holding no ticked line folded to its heading and its count, destinations holding no option last |\n| `networks` | map of alias \u2192 `"facebook"` \\| `"instagram"` \\| `"threads"` \\| `"x"` \\| `"linkedin"` | yes | That select\'s options, each by its alias, to the network whose feed the post is previewed in \u2014 facebook, instagram, threads, x, linkedin. An option not named has no feed to imitate: its words read as a plain card |\n\n<!-- generated:end layout-desk -->\n\n## Record node\n\nA Record draws what a row opens: the head, then the parts.\n\n- **The head:** title, subtitle, picture, mark, party, stage badge, figure and meter, deadlines, description \u2014 always the\n table\'s `records` facts, overridden by the List\'s `row`. A field the head draws is never also a Field of the record.\n- **No Record node:** under the register, the platform designs the record\'s Sections from the rows: the fields the\n head does not draw, child Lists, papers and Acts. A stated Record stands exactly as written, so it replaces them.\n Under a child List, an absent Record is every field of the child except the link back.\n- **Children:** either parts \u2014 `Section`, `Side`, `Page` \u2014 or, for a one-part record, `Field`, `List`, `Reading`,\n `Progress`, `Statement` and `Act` directly. The two are never mixed. Below the register \u2014 a child List\'s, a Book\'s or\n a roster cell\'s \u2014 a Record holds Fields and `edit` only, stating which fields its saves write.\n\n<!-- generated:start node-record -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `door` | `"page"` \\| `"drawer"` \\| `"beside"` | no | Absent, a page where a part is a step, a Side or a Page or there are more than 3 parts, else a drawer |\n| `comments` | `true` | no | The record carries comments; absent, nobody comments |\n| `edit` | `false` | no | false: nothing the record draws is written |\n| `compose` | [piece](#composed-page) | no | The page stated as a tree of finished pieces; a Record with `compose` holds no children |\n| `children` | list of (alias \\| [Field](#field-node) \\| [Section](#section-node) \\| [Side](#side-node) \\| [Page](#page-node) \\| [Outside](#outside-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | Parts (Section, Side, Page), or for a one-part record its Fields, Lists, Readings, Progress, Statements and Acts directly \u2014 never both; Outside parts stand beside either. Absent, the platform designs the Sections from the rows: the fields the head does not draw, child Lists, papers and Acts. Stated, it stands exactly as written |\n\n<!-- generated:end node-record -->\n\nThe door is a page where a part is a step, a Side or a Page, or where there are more than 3 parts; else a drawer. `door`\nis a judged choice of how a job is worked (one after another over a long record), so it is kept. From three parts on, the\nrecord is navigated by their titles: a page by a rail that jumps to each (a strip of the same names on a phone), a drawer\nby the same strip pinned over its scroll; with fewer, the parts just stack.\n\n`comments: true` lets people comment, and is opt-in: state it only where the job\'s people discuss a record. A door in the\nrecord\'s head then opens the comments in a panel beside it, with one composer. A record shows no activity and no audit\nlog: a history an app shows is table-backed \u2014 `records.status.dates`, `records.status.history`, an event table an act\nmakes \u2014 stated where the app shows it.\n\nA field, a Reading and an Act is each placed once on a record: the head, or one part. A Section or a List with `when` is\ndrawn only while each named select of the record holds one of its options.\n\nA page the parts cannot say is a **composed page** (\xA7 Composed page): `compose` states the record as a tree of finished\npieces, and the Record holds no children.\n\n## Section node\n\nA titled part of a record, and optionally a step of its path. A Section draws its Fields, Lists, Readings, Progress,\nStatements and Acts in reading order; its Acts stand at its foot.\n\n<!-- generated:start node-section -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique among its siblings; part of the node\'s address. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The part\'s heading |\n| `description` | text | no | A line under the heading |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `edit` | `false` | no | false: nothing in this part is written |\n| `at` | list of alias (at least one) | no | Options of the record\'s status during which this part is the work now: a step of its path |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | What the part draws, in reading order; its Acts stand at its foot |\n\n<!-- generated:end node-section -->\n\n- **`at`** names the stage options during which the section is the work now \u2014 only on a record that\n truly moves through those stages: the rail marks it done, now or waiting, and the section stands even holding nothing\n yet. It never hides a field, locks it or gates an act.\n- **Acts:** every Act on a record stands in the Section whose work it is. An Act moving the stage and offered at ONE step\n stands at that step\'s foot when it moves the work forward, to an exit later in the stage\'s options, or back to a step the\n work passes anyway; one offered at several steps, or moving back into a detour, stands in the Section it enters. A\n correction (`danger`, and not one of its step\'s outcomes) waits in the Section heading\'s \u22EF.\n- **A detour** \u2014 a stage the work only steps aside into from one step and leaves only back to it (a pause, a repair) \u2014\n stands before that step, its option and its Section alike.\n- **An exit** stands only once the record is in it, its reason there and its rail item apart from the path; until then the\n Act waits in the header\'s \u22EF.\n- **Writes:** its Fields and its Acts.\n\n## Side node\n\nThe one part drawn beside the rest \u2014 the photos looked at, the pool picked from. At most one Side to a record, on a page\ndoor, and never also a Page.\n\n<!-- generated:start node-side -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique among its siblings; part of the node\'s address. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The part\'s heading |\n| `description` | text | no | A line under the heading |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `edit` | `false` | no | false: nothing in this part is written |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | What the part draws, in reading order; its Acts stand at its foot |\n\n<!-- generated:end node-side -->\n\nIt holds what a Section holds. Writes: its Fields and its Acts.\n\n## Page node\n\nA part on its own page, off the record\'s scroll, with a way back \u2014 rarely, for a long related list or a workspace of its\nown that would make the record worse stacked. Never on a drawer, and never a step: its title names an address no other\npage shares (a letter or a digit at least), and not every part is a Page.\n\n<!-- generated:start node-page -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique among its siblings; part of the node\'s address. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The part\'s heading |\n| `description` | text | no | A line under the heading |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `edit` | `false` | no | false: nothing in this part is written |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | What the part draws, in reading order; its Acts stand at its foot |\n\n<!-- generated:end node-page -->\n\nIt holds what a Section holds. Writes: its Fields and its Acts.\n\n## Outside node\n\nWhat one record shows, and which acts it allows, to a person with no seat who opens a link a member made of it. A member\npresses Create link on a record while its status stands in `at`; the link opens that one record until it expires, is\nrevoked, or its act moves the status out of `at`. The person sees the stated Fields and Lists and presses the stated Acts\nand nothing else. It stands directly in a Record of a table with a stored status select: the register\'s, or the Record of\na List below the register, whose rows the link then opens.\n\n<!-- generated:start node-outside -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique in the app: a link to the record names it. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The heading of the page the person without a seat opens |\n| `description` | text | no | A line under the heading |\n| `at` | list of alias (at least one) | yes | Options of the record\'s status during which a link to it opens; none is owned by a stage, and each Act moves the status out of them |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Act](#act-node)) (at least one) | yes | Exactly what the link shows and allows: Fields read as values, Lists of child rows read as they are, and the Acts the person may press, each moving the status out of `at` |\n\n<!-- generated:end node-outside -->\n\n- **Fields** are read as values, in order; a lookup, a rollup or a formula is computed for the link only if stated here.\n A Field states nothing but its alias.\n- **Lists** read the child rows under the record as they are: `columns`, `label` and `where`.\n- **Acts** are the link\'s alone: no member, visitor or agent presses them. Each states `key`, `label`, `confirm`, `when`,\n `requires`, `asks`, `set`, `template` with `into`, or `sign`. `when` absent is the status held in `at`; `set` moves the\n status to an option outside `at`, so each press ends the visit. `asks` are text, numbers, dates or listed options. A\n `sign` is by a text field the signer types, with no witness: the link is the attestation.\n- `at` names options no stage owns, and no two Outsides of a table open during the same option.\n- **Writes:** its Acts, and nothing else.\n\n## Tab node\n\nA Tab is one view of a List\'s rows over a period, or one sheet of a dashboard.\n\n- **In a List:** a register reporting a period \u2014 what stands at its end beside what came and went during it (the stock,\n the hires out, the tickets open) \u2014 reads its rows as Tabs over one `period` the reader picks above them. A Tab holds\n the rows `dated` within the period, those `open` at its end, or lists every row and draws what a child\'s rows came to\n in the period (`totals`). A Tab narrows its rows and holds no nodes; Readings stand beside it in the List and read the\n open Tab, or the one their `tab` names. Never date chips set by hand, and never a Tab per stage, which the chips split.\n- **On a dashboard:** a Tab holds its Readings and Lists as children. A dashboard with Tabs (2 to 5) draws a strip under\n its title that picks one, the first open.\n\n<!-- generated:start node-tab -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | Unique among its siblings; part of the node\'s address |\n| `label` | text | yes | What the tab holds, in the reader\'s words |\n| `dated` | alias | no | A date: the tab holds the rows dated within the List\'s period |\n| `open` | [Tab open](#tab-open) | no | The tab holds the rows open at the period\'s end: begun, and not yet ended |\n| `totals` | [Tab totals](#tab-totals) | no | Each row\'s figures over a child\'s rows in the period |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `sort` | [Sort key](#sort-key) \\| list of [Sort key](#sort-key) (at least one) | no | The order this tab\'s rows read in; absent, the List\'s |\n| `children` | list of ([Reading](#reading-node) \\| [List](#list-node)) (at least one) | no | On a dashboard: the Readings and Lists this sheet draws. A Tab of a List holds none: Readings stand beside it in the List |\n\n#### Tab open\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | alias | yes | The date a row begins on |\n| `to` | alias | yes | The date a row ends on, empty while it is open |\n\n#### Tab totals\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `rows` | alias | yes | An entity of this model whose rows each belong to one row of the register\'s entity: the rows the columns add |\n| `on` | alias | yes | A date of the child placing each row in time: stored, a formula yielding a date, or a lookup of a date through a one-row link \u2014 never a moment or a span of days |\n| `columns` | list of [Totals column](#totals-column) (1\u20136) | yes | The figures drawn in place of the register\'s columns after each row\'s name, each under its own alias and label |\n\n#### Totals column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | The column\'s name: its address in the totals read |\n| `label` | text | yes | What the column holds, in the reader\'s words |\n| `value` | alias | no | A number of the child (stored, a formula or a rollup) summed over the rows; absent, the rows are counted. A figure read in each row\'s own unit or currency stands only where the child\'s select is a lookup, through `via`, of a single select of the register\'s entity |\n| `where` | map of alias \u2192 (list of alias (at least one) \\| boolean) | no | Only the child\'s rows whose select holds one of these options, or whose yes/no is this value \u2014 every entry ANDed |\n| `window` | `"before"` \\| `"in"` \\| `"through"` | yes | Which of the child\'s rows, by `on`, the column adds: `before` those dated before the period\'s first day, `in` those from its first day through its last, `through` those on or before its last day |\n\n#### Period days\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `days` | integer | yes | How many days through today, at most 366 |\n\n<!-- generated:end node-tab -->\n\nA `totals` Tab adds, per column, the child\'s rows `before` the period\'s first day, `in` the period, or `through` its last\nday, so `before` plus `in` of one value is its `through`:\n\n```json\n{\n "type": "Tab",\n "key": "items",\n "label": "Items",\n "totals": {\n "rows": "ledger_line",\n "on": "moved_on",\n "columns": [\n { "alias": "opening", "label": "Opening", "value": "quantity", "window": "before" },\n { "alias": "received", "label": "Received", "value": "quantity", "where": { "kind": ["receipt"] }, "window": "in" },\n { "alias": "issued", "label": "Issued", "value": "quantity", "where": { "kind": ["issue"] }, "window": "in" },\n { "alias": "closing", "label": "Closing", "value": "quantity", "window": "through" }\n ]\n }\n}\n```\n\n## Field node\n\nA Field draws one field of the record it stands in. How it is drawn follows from the field\'s type and the table\'s facts:\n\n- a control or a value, with its notation, money, units and deadline countdown;\n- a files field as pictures and documents, a long or markdown text as prose written in place \u2014 a gallery or a prose block,\n with a heading (`label`) and a condition (`when`) of its own; a run of files Fields is one gallery;\n- a link as its row\'s chip, with a picker narrowed by `options_where`, and `add` for the new row the picker offers;\n- the back side of a child\'s link as the rows filed under the record, added to with `add`;\n- the fields an Act\'s `sign` writes as one signature.\n\n<!-- generated:start node-field -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field\'s alias |\n| `edit` | `false` | no | false: the field is drawn and read; absent, it is written where the table lets a person write it |\n| `label` | text | no | The heading of the field\'s gallery or prose block, where it draws as one; absent, the field\'s label. A run of files fields takes the first one\'s |\n| `block` | boolean | no | true: a files or text field draws as its own gallery or prose block; false: as a control among the part\'s fields. Absent, files and markdown fields draw as blocks and plain text as a control |\n| `when` | [condition](#app-tree) | no | A gallery or prose block is shown only while the record\'s selects hold these options, its yes/nos these values, its numbers and dates meet their bounds and its `{ "empty": true \\| false }` fields are blank or filled |\n| `add` | `false` \\| `true` \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The new row this field\'s picker offers asks these fields; `true`: the target\'s required fields, its natural key and its title. Stated once per target table, and it holds in every picker of that table in the app. `false`: this link only picks, in every picker of it the app draws, not only where it is written |\n\n<!-- generated:end node-field -->\n\nA field drawn in the head (title, subtitle, picture, figure, stage) is never also a Field node. A Field is drawn once per\npage. **Writes:** the field, under \xA7 App tree, and with `add` the target table\'s create.\n\n## Act node\n\nAn Act is a verb, stated whole where it stands. Its place decides what it works on:\n\n- in a Section, Side or Page, or in a one-part Record: one record, at that part\'s foot;\n- in a List: on each of its rows, offered in the row\'s \u22EF and, once the row is open, in its header\'s \u22EF; in a child List,\n the child\'s rows;\n- in a List with `on`: the ticked rows (`"ticked"`) or every row in view (`"view"`), each checked before any is written;\n- directly in a Record that holds parts: refused.\n\n<!-- generated:start node-act -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The act\'s name; its workflow is `act_<key>`, or the `workflow` it names |\n| `label` | text | yes | The verb, as the reader says it |\n| `on` | `"ticked"` \\| `"view"` | no | In a List: the ticked rows, or every row in view; absent, one row |\n| `when` | [condition](#app-tree) | no | The act works only while each named select holds one of these options, each yes/no this value, each number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and each `{ "empty": true \\| false }` field is blank or filled |\n| `requires` | list of alias (at least one) | no | Fields that must be filled first, each drawn where the act stands; a blocked act names each one missing |\n| `recommends` | list of alias (at least one) | no | Fields the act runs without and reads when filled: while one is empty, the act says what it will leave blank |\n| `asks` | list of (alias \\| `{ "field": alias, "starts": "today" \\| "me" }` \\| [Asked input](#asked-input)) (at least one) | no | What the reader states when pressing it: fields of the row (written by the act) or inputs |\n| `set` | map of alias \u2192 [value](#app-tree) | no | What the act writes; each field is the act\'s, read-only elsewhere in the app |\n| `workflow` | alias | no | The workflow the act makes and runs under this name, for what `set` cannot say; no Workflow node is declared for it, and `set_app_workflow` sets its body |\n| `writes` | list of alias (at least one) | no | With `workflow`, fields of the acted row its body writes (an id kept from a service\'s answer) \u2014 the act\'s, as what `set` states is |\n| `template` | alias | no | A document template the act makes a file from: an html one made a PDF, an excel one a workbook, a word one a .docx; over several rows, one file of them all |\n| `templates` | list of [Act template](#act-template) (at least 2) | no | Several papers the act makes into `into`, instead of one `template`: the reader ticks which to make, one press making those |\n| `into` | alias | no | The record\'s files field the made document is kept in; remade, it replaces its own last paper, never a person\'s file |\n| `paper` | text | no | Its paper, `<child>.<kind>`: the line of the record\'s papers it stands on |\n| `intake` | alias | no | A child the record lists whose rows are its papers: the press takes papers, an agent reads them, and each is filed as a row of this child |\n| `fills` | list of text (at least one) | no | With `intake`, what the papers read may fill: a field of the record, `<link>.<field>` on the row a required one-row link names, or a child whose rows the papers add. With `record`, fields of the new row an agent fills from the transcript |\n| `record` | [Recording](#recording) | no | The press records a call, visit or meeting and files it as a new row of a child the record lists |\n| `sign` | [Signature](#signature) | no | The press opens a pad the signer signs on; the act keeps the signature as a picture with who signed, when and the witness |\n| `open` | text | no | The id of another app that reaches this row\'s table, opened at this row by the press: navigation only \u2014 no workflow runs, and a reader without `use` on it is shown that app\'s own refusal. Stated alone with `label` and `when` |\n| `review` | `true` | no | With `workflow`: the workflow only proposes a changeset of the act\'s table, and the reader ticks the rows to write |\n| `confirm` | `true` \\| text | no | The press asks first: the question, or `true` for the act\'s label over the row |\n| `danger` | `true` | no | The act destroys or cannot be undone |\n\n#### Asked input\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `input` | alias | yes | A value the act asks for that is not a field of the record |\n| `label` | text | yes | What the reader is asked, in their words |\n| `type` | `"text"` \\| `"long_text"` \\| `"number"` \\| `"date"` \\| `"select"` \\| `"link"` \\| `"member"` \\| `"file"` | yes | What the reader states. A `file` is one uploaded file, handed to the act\'s authored `workflow` as its file id and read by nothing else |\n| `options` | list of [Select option](#select-option) (at least one) | no | The choices a `select` input offers, one picked \u2014 written into a select holding the same option aliases |\n| `entity` | alias | no | The entity a `link` input picks one row of |\n| `accepts` | list of `"sheet"` \\| `"pdf"` \\| `"document"` \\| `"photo"` (at least one) | no | The kinds of file a `file` input takes; absent, a sheet, a PDF or a document \u2014 a phone offers its camera only where `photo` is among them |\n| `required` | `true` | no | The act is refused without it |\n| `default` | alias | no | A field of the row whose value the input starts from, still changed at will |\n| `add` | `true` \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The new row this picker offers asks these fields; `true`: the target\'s required fields, its natural key and its title. Stated once per target table, and it holds in every picker of that table in the app |\n\n#### Set formula\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `formula` | text | yes | A formula over the acted row\'s fields, `{alias}` each, read as the entity\'s own formulas read them |\n\n#### Act template\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `template` | alias | yes | A document template the act makes a paper of, kept in `into` \u2014 an html one made a PDF, an excel one a workbook, a word one a .docx |\n| `when` | map of alias \u2192 (list of alias (at least one) \\| boolean) | no | The paper starts ticked while each named select of the record holds one of these options and each named yes/no (stored, or a formula \u2014 how a multi-select is tested: `includes({needs}, {needs:x})`) is this value \u2014 a suggestion the reader changes, never a refusal; absent, it starts ticked |\n\n#### Recording\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `into` | alias | yes | A child the record lists in a timeline or rows block, linking back to it by a single link: each recording is filed as a new row of it |\n| `audio` | alias | yes | The child\'s files field the recording\'s audio is kept in |\n| `transcript` | alias | yes | The child\'s text field the transcript is kept in, a speaker\'s turn per line |\n| `video` | alias | no | The child\'s files field the screen is kept in, where the recording captured it |\n| `at` | alias | no | The child\'s date field stamped with the moment the recording started |\n| `by` | alias | no | The child\'s member field set to the person who recorded |\n\n#### Signature\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `into` | alias | yes | The record\'s files field the signature is kept in, as a picture \u2014 each signing replaces the one before |\n| `by` | alias | yes | Who signed: a text field the signer types their name into (someone with no seat in the workspace \u2014 a driver, the owner\'s supervisor), or a member field set to the person pressing, who signs as themselves |\n| `at` | alias | yes | The record\'s date field stamped with the server\'s time as the act runs |\n| `witness` | alias | no | Beside a text `by`, the member field set to the person pressing, who saw the signer sign |\n\n<!-- generated:end node-act -->\n\nAn Act whose `when` does not hold is not offered. One whose `requires` are empty is offered disabled, naming each; one whose\n`recommends` are empty names what it will leave blank and still presses; one a standing Check blocks is offered disabled in\nthe check\'s words. The generated `act_<key>` workflow re-checks all three on the server, writes `set` and what was asked,\nand appends the status history where the act moves the status. A field an act asks is read-only everywhere else. A `select`\ninput offers its own options and is written into a select holding the same option aliases; a `link` input picks one row of\nits `entity`, a `member` input one person. A `file` input is one uploaded file handed to the act\'s authored `workflow` as its\nfile id; nothing else reads it. A field an act both `requires` and `asks` never disables the press.\n\n- **Writes:** every field it sets, asks, files into, signs or records is the act\'s.\n- **Papers.** A button that makes a paper is `template` (or `templates`) with `into` on the Act, with no workflow. `template` makes a document from the row as the act left it and keeps it in `into`; over several rows it\n makes one document of them all. `templates` names a set of papers kept in `into`, each made one as its file and each not\n made yet as a placeholder, those whose `when` holds ticked to start; the press makes the ticked ones, each replacing the\n file its template made before, and the set downloads as one archive (`archive_<key>`). `intake` reads papers: the press\n takes files, an agent reads them, and each is filed as the record\'s line of its kind in the named child; what they state\n of each field `fills` names is shown beside what the row holds, and the save writes only what differs. `sign` opens a pad\n on the device: the act keeps the picture in `into` in place of any before it, `at` stamped with the server\'s time and the\n person pressing in `witness`; the record reads `into` as one signature, and the act\'s paper is handed the picture under\n `into`\'s name (`{{#each signature}}<img src="{{url}}">{{/each}}` in an html template). `record` records a call or a visit\n through the host and files its audio, transcript and screen as a new row of a child.\n- **`workflow`** is an authored workflow (`workflows`) the act runs after its checks, for what neither `set` nor `template` can say.\n **`review: true`** makes it one that only proposes: its body reads, returns a changeset of the act\'s table in\n `data.review`, and the platform writes the rows the reader ticks in the act\'s dialog, each checked first and all written\n in one transaction or none. Over every row in view the act is handed no rows.\n- **A `when` that compares** splits one stage by an amount or a date, with no formula field behind it. The server refuses a\n press the row\'s numbers and dates do not meet, in the words "Approve works only while Amount is at most 5,000,000", and\n a blank number or date meets no bound. Two acts on an expense claim, the second moving it to a stage the Director group owns\n (stage owners decide who presses):\n\n```json\n{ "type": "Act", "key": "approve", "label": "Approve", "when": { "status": ["submitted"], "amount": { "lte": 5000000 } }, "set": { "status": "approved" } }\n```\n\n```json\n{ "type": "Act", "key": "send_to_director", "label": "Send to the director", "when": { "status": ["submitted"], "amount": { "gt": 5000000 } }, "set": { "status": "director_review" } }\n```\n\n```json\n{ "type": "Act", "key": "gate_in", "label": "Make the gate-in EIR", "template": "eir_in", "into": "eir", "sign": { "into": "driver_signature", "by": "driver_name", "at": "signed_at", "witness": "signed_before" } }\n```\n\n## Check node\n\nA Check is a yes/no or text formula of the rows of the List it stands in: it stands while true or non-empty, and a text\nformula\'s words are what the reader sees. Without `on` it only warns; with it, it refuses the saves and acts it names, on\nthe server, in every write it names.\n\n<!-- generated:start node-check -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A yes/no or text formula of the rows of the List it stands in: true or non-empty means the check stands |\n| `on` | `"all"` \\| list of (alias \\| `"update"` \\| `"delete"`) (at least one) | no | What the standing check refuses: `update` is the rows\' saves, an Act key is that act, `all` is every act and save; absent, it only warns. `delete` is this app\'s removal of the rows, for a rule no check of the table states |\n| `resolve` | alias | no | An Act on the same rows that clears it |\n\n<!-- generated:end node-check -->\n\n`update` is the rows\' saves; an Act key is that Act, on the same rows; `all` is every act and save.\nA rule that refuses a Delete is a check of the table, not a Check: state it in the table\'s `checks` with `on: ["delete"]`\n(\xA7 Write rules), where it holds for every way a row is deleted and a removal that cascades to child rows is refused whole.\nA Check naming `delete` refuses only this app\'s removal of a row, and is kept for a rule the table\'s checks cannot state (a\nformula that is neither a yes/no nor a text).\nA Check in a child List stands on each child row. A warning reading the figure of a meter that `gates` marks against its\nbound or its pass mark is refused: the meter already draws it.\n\nThe check field\'s `label` is its short name \u2014 "Over budget", "Release expired": a row\'s line says it in the check\'s tone\n(the most severe of several, and how many more), while its words whole are the row\'s tooltip. Label it as the reader names\nthe problem, in a few words, and let a text formula say the sentence. A check with no `resolve` links to the first field its formula reads that the record draws.\nA yes/no formula whose yes needs attention (papers the customer still owes) is a Check: its yes reads in the warning tone\nwherever it is drawn, every other yes in the success tone \u2014 and the row\'s line says it, so it is no column of the List too.\n\nA Check that every app over a table states the same way restates the table\'s contract and belongs in the table\'s `checks`\n(\xA7 Write rules).\n\nA Check refusing `update` (or `edit: false` on a record, part or field) makes the rows read-only in this app alone, drawn\nunder the read-only mark, never as a lock. A lock is the table\'s: a table workflow, a person, or an Act whose workflow calls\n`lock_records` locks a row, and every door refuses its writes until a table manager unlocks it \u2014 `unlock_records` in an\napp\'s run is judged as the member who pressed. A locked row reads locked in every app: its lock mark, its fields as values\nunder one line naming who can change it, its saves, Delete and each Act writing it refused \u2014 an Act that writes\nelsewhere, makes a paper it hands back, or unlocks it stays its workflow\'s to answer.\n\n## Reading node\n\nA Reading is a number over rows: one figure, its parts, a grid, a figure per period, steps; or a record\'s own figure. The\npicture is the runtime\'s, chosen from what the reading means \u2014 no prop picks a chart. The question is stated by which\nsplit it names:\n\n| Names | Answers |\n|---|---|\n| nothing | one figure: the rows counted, or `value` summed, `{ "avg": \u2026 }` averaged |\n| `by` | the figure per value of a select, one-row link, member or yes/no, or per band of a number\'s `tiers` |\n| `by` a date field | the figure per period (a trend); `ahead: true` reads forward over the rows still to come |\n| `by` and `across` | a grid, `by` down the side and `across` along the top |\n| `steps` | a funnel: 2 to 8 number fields summed, each kept as a share of the one before; in a record only |\n| no `entity`, in a Record | the record\'s own `value`, a tile under its field\'s label; two to eight consecutive tiles draw as one strip, and a single one is a field of the section |\n\n<!-- generated:start node-reading -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The reading\'s query name, unique in the app; a record\'s own figure is keyed by its field |\n| `label` | text | no | Absent, a split\'s `by` field\'s label, a trend\'s summed `value`\'s (else the rows\'), a grid\'s two fields\'; one figure states its label |\n| `entity` | alias | no | The List\'s rows; in a Record, the child\'s rows under it; absent in a Record with no `by`: the record\'s own `value` |\n| `via` | alias | no | The child\'s link to the record, where it has several |\n| `value` | alias \\| `{ "avg": alias }` | no | A number field summed over the rows, or `{ avg }` averaged; absent, the rows are counted |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, whose number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), whose field is `{ "empty": true \\| false }`, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `by` | alias \\| [Tiered filter](#tiered-filter) | no | Splits the rows into parts; a date field splits them per period |\n| `across` | alias \\| [Tiered filter](#tiered-filter) | no | With `by`: the columns of a grid |\n| `over` | alias | no | The date that places a row in time |\n| `ahead` | `true` | no | With a date `by`: the current period and those after it |\n| `steps` | list of alias (2\u20138) | no | Number fields summed, each a step of a funnel |\n| `on_hand` | [Reading on hand](#reading-on-hand) | no | With `by` (and `across`): the Reading\'s rows are what is owed, per value of those fields; drawn beside what is on hand per the same values, and the shortfall |\n| `target` | number | no | The number the figure is read against; absent, its bound, where the table states one |\n| `better` | `"up"` \\| `"down"` | no | Which way is good, for the figure\'s tone; absent, read from the figure\'s shape |\n| `tab` | alias | no | The Tab it reads, else the open one |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n\n#### Reading on hand\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | alias | yes | The entity whose rows are what is on hand, all of them |\n| `value` | alias | no | A number field summed over those rows; absent, the rows are counted |\n| `by` | alias | no | The field of this entity matching the Reading\'s `by`; absent, the same alias |\n| `across` | alias | no | The field of this entity matching the Reading\'s `across`; absent, the same alias |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, whose number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), whose field is `{ "empty": true \\| false }`, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n\n<!-- generated:end node-reading -->\n\nWhere a Reading stands decides which rows: a dashboard\'s every row, a List\'s the rows in view (so the search, the stage and\nthe filters narrow each one), a Record\'s the child\'s rows under the record. A Reading counts its rows, or sums the number\n`value` names \u2014 never a percent. `over` is a date, the row\'s own or its parent\'s through a lookup: a trend\'s periods, a\nfigure\'s recent periods, and what a dashboard\'s period windows. `where` keeps rows whose select holds one of the options, or\nwhose yes/no is the value, every entry ANDed \u2014 a band leads with the job\'s exception as a formula\'s yes/no (what is\nshort), never a stage the chips already count nor the overdue rows the register\'s summary line counts from `records.due`.\n\n- **A figure\'s change** is green where it moves the good way: a sum\'s rise, a count kept by `where` falling; `better` states\n the other way where that reads wrong (money going out). A value `limits` bounds reads against the sum of its bound\n (`10 / 14`); `target` reads one against a number nothing bounds.\n- **An average** is its sum over the rows that hold a value, divided by how many hold one; a row holding none is no part\n of it, and where none holds one the figure reads a dash, never 0. Under a dashboard\'s period it is the average over the\n period so far, never a sum of averages. A share is an average too: a number formula that is `100` on the rows that count\n and `0` on the others, in percent notation, averaged. Only a single figure averages.\n- **Bands.** `by` and `across` each take, in place of a field, a number by its `tiers` \u2014 `{ "field", "tiers" }` \u2014\n splitting the rows by the bands those breakpoints cut: `[1, 31, 61]` over days late reads not due, 1\u201330, 31\u201360 and over\n 60, the aged report. Every band is drawn in the bands\' own order, one holding no row at zero; rows holding no number\n stand apart. On a List a band is a door: a press narrows the rows to it. In a List\'s band, a split by one or two\n breakpoints stands in it, and one by three or more stands whole under it.\n- **In a List,** Readings stand under the title in one compact band: the job\'s headline number first, then at most the mix\n and the movement. A picture stands only where it splits the rows in view into two parts or more, one holding two rows or\n more. The summary line already totals the figure and the chips count the rows, so a figure summing the figure, or counting\n the rows of a table with a stage, is refused unless its `where` narrows it.\n- **Name.** A Reading\'s `key` is the query it is read by (`run_app_query`).\n\n## Progress node\n\nProgress reads one parent\'s work against its plan \u2014 a List\'s band over a folder, or a record\'s block, never a dashboard\'s\nevery row: the share of the work planned and the share done, cumulative day by day, today\'s two at its foot. Each row is a\npart of the work, its plan spread evenly over its planned days and weighing its `weight` (its value) or, absent, its planned\ndays. Its share done today is `done`, a percent the server computes; on an earlier day, its `done` from its `finished` day\non, before it what its `measured` rows add up to by then.\n\n<!-- generated:start node-progress -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The reading\'s query name, unique in the app |\n| `label` | text | yes | What the progress is of, in the reader\'s words |\n| `entity` | alias | yes | The rows that are the parts of the work: the List\'s, or in a Record the child\'s rows under it |\n| `via` | alias | no | The child\'s link to the record, where it has several |\n| `planned` | [Progress planned](#progress-planned) | yes | The days each row\'s work is planned over, its share planned spread evenly across them \u2014 a row missing either is no part of the plan, and neither line counts it |\n| `done` | alias | yes | A percent field of the row the server computes: its share done today (0\u2013100) |\n| `finished` | alias | no | A date of the row: the day its work was finished, from which it stands at its `done` |\n| `weight` | alias | no | A number of the row: what it weighs in the whole; a row holding none weighs nothing. Absent, each row weighs its planned days |\n| `measured` | [Progress measured](#progress-measured) | no | Before a row is finished, its share done on a day is what these add up to by then, never past its `done`; absent, a row stands at nothing until it is finished, and at its `done` today |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, whose number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), whose field is `{ "empty": true \\| false }`, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `tab` | alias | no | The Tab it reads, else the open one |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n\n#### Progress planned\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | yes | A date of the row: the first day of its planned work |\n| `end` | alias | yes | A date of the row: the last day of its planned work |\n\n#### Progress measured\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `rows` | alias | yes | The entity whose rows record the work as it is done, each through its one-row link to a row of the reading |\n| `on` | alias | yes | A date of those rows (their own, or a lookup): the day each records its work |\n| `share` | alias | yes | A number of those rows the server computes: the share of its row each adds, in percent |\n\n<!-- generated:end node-progress -->\n\n## Statement node\n\nA Statement draws a figure the record works out from its own figures \u2014 a claim\'s payable, an invoice\'s total, a payslip\'s\nnet \u2014 line by line in the order the arithmetic runs: each line a number of the record, the first stating no `op`, the last\nan `equals`. Every figure is a field the server computes or a person writes; the screen adds nothing up. A line\'s `parts`\nlists the rows its figure carries, each read from its own row, and that figure is what they add up to \u2014 a formula, rollup\nor lookup, or the server\'s copy when the record is made (`default_from`), which nothing writes after.\n\n<!-- generated:start node-statement -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The statement\'s name, unique among its siblings |\n| `label` | text | no | Absent, the label of what the statement comes to |\n| `lines` | list of [Statement line](#statement-line) (at least 3) | yes | The figures it is built from, top to bottom, ending in the one it comes to |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n\n#### Statement line\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A number of the record \u2014 stored, a formula, a rollup or a lookup \u2014 read on the line after its label |\n| `op` | `"plus"` \\| `"minus"` \\| `"times"` \\| `"equals"` | no | How the line works on the lines above it \u2014 `plus`, `minus`, `times`, or `equals` for what they come to, a formula, rollup or lookup the server computes; absent on the first line, which the rest work from |\n| `rate` | alias | no | A percent of the record read after the line\'s label: the rate its figure is taken at (retention at 5 %) |\n| `parts` | [Statement parts](#statement-parts) | no | The rows the line\'s figure carries, each listed under it with its words and its own figure \u2014 read from those rows, never a copy of them. The line\'s figure is what they add up to, drawn read: a formula, rollup or lookup, or a value the server copies when the record is made (`default_from`) that nothing writes after |\n\n#### Statement parts\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `rows` | alias | yes | The entity whose rows the line\'s figure carries \u2014 the record\'s own for the earlier rows of the same thing |\n| `same` | alias | yes | A one-link the record and those rows both hold: the rows naming what the record names in it |\n| `at` | alias | no | A moment (a datetime) of those rows no person keys \u2014 when each was certified: with `before`, only the rows whose moment stands before the record\'s, earliest first; absent with it, every row so named, in their own order |\n| `before` | alias | no | A moment (a datetime) of the record no person keys \u2014 when it was made: the moment the rows\' `at` stands before; stated with `at` |\n| `where` | map of alias \u2192 (list of alias (at least one) \\| boolean \\| [comparison](#values) \\| `{ "empty": true \\| false }`) | no | Only the rows whose single select holds one of these options, whose yes/no (stored, or a formula) is this value, whose number or date meets its bounds, or whose field is empty or filled \u2014 read so on the server |\n| `columns` | list of alias (1\u20132) | yes | The rows\' fields read as each one\'s words, in order (its date, its percent) |\n| `figure` | alias | yes | The rows\' number each one reads at the figures\' edge, frozen when the row was certified: a stored number an act fills (`{ "formula": \u2026 }` as it runs) or the server copies when the row is made (`default_from`) \u2014 never one a person keys, nor a formula, rollup or lookup the server recomputes |\n\n<!-- generated:end node-statement -->\n\n```json\n{\n "type": "Statement",\n "key": "claim",\n "lines": [\n { "field": "contract_value" },\n { "field": "done_to_date", "op": "times" },\n { "field": "value_to_date", "op": "equals" },\n { "field": "certified_before", "op": "minus", "parts": { "rows": "claim", "same": "contract", "at": "certified_at", "before": "made_at", "where": { "kind": ["work"] }, "columns": ["claimed_on", "done_to_date"], "figure": "certified_value" } },\n { "field": "claim_value", "op": "equals" },\n { "field": "retention", "op": "minus", "rate": "retention_rate" },\n { "field": "payable", "op": "equals" }\n ],\n "when": { "kind": ["work"] }\n}\n```\n\n## Export node\n\nAn Export is a report file of the rows in view: the title, a line per chip, the search and the moment it was exported, the\nReadings, and every row, at most 20,000. With no props, the one Export is the workbook of the List\'s columns, the Readings\non a sheet before the rows. A List states the reports the business sends \u2014 one a button, several one menu \u2014 each a `label`,\na `description`, and a `template` filled with `title`, `lines`, `readings`, `at` (the moment it was made), `dates` (each\ndate filter\'s `from` and `to` by field) and the rows under the table\'s alias (an html one made a PDF, an excel one a\nworkbook). A report\'s `filename` names its file in the template grammar over the same keys but the rows. Where `per` is a\none-row link, `per_row.<field>` reads a text, number, date or yes/no field of the picked row, and `| add_days:<n>` shifts a\ndate by whole days (`n` a number or such a field, empty for none): `Stock {{per}} {{period.to | add_days:per_row.day_offset | format:"dd.MM.yyyy"}}`. A report of some\nrows (`where`), or of one value the reader picks (`per`: one file per value), is made of the List as those chips would\nnarrow it; the screen keeps the chips and rows it shows.\n\n<!-- generated:start node-export -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | Unique among its siblings; part of the node\'s address |\n| `label` | text | no | The report\'s name; absent on the one Export that stands for the register\'s workbook |\n| `description` | text | no | What the report is for, in a sentence |\n| `template` | alias | no | An html or excel document template the report fills, with `title`, `lines`, `readings`, `at`, `dates`, `period`, `per` and `per_row` (a one-row `per`\'s picked row, the fields `filename` reads); absent, the workbook of the List\'s columns |\n| `filename` | text | no | The file\'s name in the template grammar, over one value each: `title`, `at`, `dates.<field>.from` or `.to`, `period.from` or `.to`, `per`, `per_row.<field>` (a text, number, date or yes/no field of the row a one-row `per` picks), `lines \\| lookup:<n>`; `\\| add_days:<n>` shifts a date by whole days, `n` a number or `per_row.<field>`. Absent, the report\'s label |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `per` | alias | no | A select, member or one-row link the reader picks one value of: one file of that value |\n| `books` | list of alias (at least one) | no | Of the List\'s books, the tables the report reads; absent, all |\n\n<!-- generated:end node-export -->\n\n## Book node\n\nA Book is another table read as part of the register: the same job over its rows \u2014 a legacy ledger, a second branch\'s book.\nA row of the book opens the List\'s Record, lined up by alias; a field the book lacks is blank on its rows and drops from\nwhat they open, and an Act, Check or add needing it is not offered on them. Options line up by alias. With `column: true` the List\ndraws a column naming the table each row is of, and a filter by it. A Book holds the title, the stage, the scope and every Check\nlocking the rows; a table, cards or a plan reads Books.\n\n<!-- generated:start node-book -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | alias | yes | Another table read as part of the register: the same job over its rows |\n| `fields` | map of alias \u2192 alias | no | A field of the List\'s entity \u2192 the field of this entity holding the same fact, where their aliases differ |\n| `column` | `true` | no | The List draws a column naming the table each row is of, and a filter by it |\n| `row` | [Book row](#book-row) | no | The book\'s rows, stated where they differ from the List\'s |\n| `children` | list of [Record](#record-node) (1\u20131) | no | The Fields a save of the book\'s rows writes (and `edit: false` for none), as a child List\'s Record states them; the page a row opens is the List\'s |\n\n#### Book row\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `status` | `{ "edit": true }` | no | `{ edit: true }`: the book\'s stage is also moved by hand |\n\n<!-- generated:end node-book -->\n\n**Writes:** the List\'s `remove`, its Fields and its Acts, on the book\'s own table.\n\n## Query node\n\nA Query is a read for agents, never drawn. An app generates a query for everything its screen reads (`agent_surface` lists them); a Query declares one the screen never asks, called through `run_app_query` or the app\'s API: the\ntable whose rows it reads, the fields each row returns or the figure they add up to, and what narrows them.\n\n<!-- generated:start node-query -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `description` | text | yes | What the query answers, read by whoever calls it by its alias \u2014 at most 300 characters |\n| `params` | map of alias \u2192 [Workflow value](#workflow-value) | no | What a call states, by name, each read by an entry of `where` \u2014 a `number`, `boolean`, `date`, `select`, `record_link`, `member`, holding one. `required: false`: the call may leave it out, and the condition reading it narrows nothing |\n| `rows` | alias | yes | The entity whose rows it reads, under that entity\'s read scope. The app\'s own entity is read as its list reads it: kept by the register\'s `where`, and inside the row the app\'s `scope` names, which the call states |\n| `fields` | list of alias (at least one) | no | The fields each row returns, by alias. A query states `fields` or `reading`, one of the two |\n| `sort` | [Sort key](#sort-key) \\| list of [Sort key](#sort-key) (at least one) | no | The order the rows return in; absent, the order the entity\'s list reads in. Only with `fields` |\n| `limit` | integer | no | The most rows returned. Only with `fields` |\n| `reading` | [Query reading](#query-reading) | no | The rows counted or summed instead of returned: one row for all of them, or one per value of `by` or per day of `over`, each carrying its `by` field or `__day`, `__count`, and `__sum` where `value` is stated |\n| `key` | alias | yes | The query\'s name, what `run_app_query` calls; unique in the app |\n| `where` | map of alias \u2192 [condition](#query-node) | no | What narrows the rows, as a List\'s `where` does: a select\'s options, a yes/no\'s value, a number or date meeting its bounds (`gt`, `gte`, `lt`, `lte`), a field `{ "empty": true \\| false }`, `{ "me": true }` on a member field \u2014 or a value the call states (`{ "param": name }`), or a date or number range of params (`{ "from": \u2026, "to": \u2026 }`) |\n\n#### Query reading\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `value` | alias | no | A number field summed over the rows; absent, the rows are counted |\n| `average` | `true` | no | The `value` is averaged: summed over the rows holding one, beside how many hold one. Never with `by` or `over` |\n| `by` | alias | no | The single select, one-row link, member or yes/no each row is counted under \u2014 one row returned per value |\n| `over` | alias | no | A date: one row returned per day of it. Never with `by` |\n\n#### Query param\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `param` | text | yes | A param of this query: the condition holds what the call states for it, and is left out where the call states none |\n\n#### Query range\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | [Query param](#query-param) | no | The param the field is on or after \u2014 a date\'s first day, a number\'s least |\n| `to` | [Query param](#query-param) | no | The param the field is on or before \u2014 a date\'s last day, a number\'s most |\n\n<!-- generated:end node-query -->\n\n```json\n{\n "type": "Query",\n "key": "fees_unbilled",\n "description": "A customer\'s fees no invoice covers yet, oldest first.",\n "params": {\n "customer": { "type": "record_link", "entity": "customer", "description": "The customer whose fees are read." },\n "since": { "type": "date", "required": false, "description": "Only fees charged on or after this day." }\n },\n "rows": "fee",\n "fields": ["visit", "service", "amount", "charged_on"],\n "where": { "billed": false, "customer": { "param": "customer" }, "charged_on": { "from": { "param": "since" } } },\n "sort": { "field": "charged_on" },\n "limit": 200\n}\n```\n\nAn entry of `where` holds what a Reading\'s `where` holds, or a value the call states: `{ "param": "<name>" }`. A required\nparam is always stated; one stating `required: false` may be left out, and the condition reading it then narrows nothing.\nA query reads under its table\'s read scope, as every read of the app does. A `reading` returns one row for all the rows it\nreads, or one per value of `by` or per day of `over`. `set_app` compiles a Query with the rest of the app: no code is set.\n\nEvery Reading of the app is a query too, returning its groups, called by its key. A Tab\'s `totals` read is called by\n`<table>_totals_<tab>` with the period\'s two days, `from` and `to`, both required.\n\nA query reads one table: on an app that states `Book`s, whose list reads several, it names a book\'s own table, never the\nList\'s. A param compares a field holding one value \u2014 or, as a range, a date or a number \u2014 and never a number read in each\nrow\'s own unit or currency, nor a date holding a span.\n\n## Workflow node\n\nA Workflow is a verb for agents, never drawn: a job no press of the screen starts \u2014 an agent or a service handing the app a\nfile, or a list of rows to reconcile a table against. It states what it takes and what it hands back; a table is named by\nits alias in `tables` and a select by `<table>.<field>`. It is called through the app, by `run_app_workflow` or the app\'s\nAPI. On an app shared by a public link it runs only for someone who holds the app, and a visitor\'s call is refused as an\nalias the app does not have.\n\n<!-- generated:start node-workflow -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `description` | text | yes | What the workflow does, read by whoever calls it by its alias \u2014 at most 300 characters |\n| `inputs` | map of text \u2192 [Workflow value](#workflow-value) | no | What a call hands it, by name \u2014 checked before its body runs |\n| `outputs` | map of text \u2192 [Workflow value](#workflow-value) | no | What its body returns as `data`, by name \u2014 checked as it returns; absent, read off the body |\n| `key` | alias | yes | The workflow\'s name, what `run_app_workflow` calls; unique in the app |\n\n#### Workflow value\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"text"` \\| `"number"` \\| `"boolean"` \\| `"date"` \\| `"datetime"` \\| `"email"` \\| `"json"` \\| `"record_link"` \\| `"select"` \\| `"member"` \\| `"file"` \\| `"object"` \\| `"array"` | yes | What the value is |\n| `description` | text | no | What the value is for, read by whoever calls the workflow |\n| `required` | `false` | no | false: the value may be left out; absent, it is always there |\n| `multi` | `true` | no | A `record_link`, `select`, `member` or `file` holding several |\n| `entity` | alias | no | The entity a `record_link` names a row of |\n| `field` | text | no | `<entity>.<field>`: the select field whose options a `select` takes, as the field holds them when the workflow runs |\n| `options` | list of alias (at least one) | no | The values a `select` takes, where no field holds them |\n| `fields` | map of text \u2192 [Workflow value](#workflow-value) | no | An `object`\'s values, by name |\n| `items` | [Workflow value](#workflow-value) | no | What each item of an `array` is |\n\n<!-- generated:end node-workflow -->\n\n```json\n{\n "type": "Workflow",\n "key": "reconcile_fees",\n "description": "Reconciles a visit\'s fees against the lines of the carrier\'s statement.",\n "inputs": {\n "visit": { "type": "record_link", "entity": "visit" },\n "statement": { "type": "file", "required": false },\n "lines": { "type": "array", "items": { "type": "object", "fields": { "service": { "type": "select", "field": "fee.service" }, "amount": { "type": "number" } } } }\n },\n "outputs": { "added": { "type": "number" }, "changed": { "type": "number" } }\n}\n```\n\nIts code is set on the app, never stated in the tree: `set_app_workflow` with the app, the key and the `source`, leaving\n`inputs` and `outputs` out \u2014 the code is checked against what the node declares. Every `set_app` keeps that code and holds\nwhat it takes, what it hands back and its description to the node. To change what a workflow takes or hands back, change\nthe node first: `set_app` keeps the code where it still meets the change, and where it does not the workflow stands on no\ncode again until `set_app_workflow` sets one that does. With no code it refuses every call, and each `set_app` says so. A\nWorkflow the tree stops declaring is archived with its key.\n\n## Composed page\n\nA Record whose page is its own arrangement of what the app already states \u2014 a piece of writing under its title, a brief\nbeside its figures \u2014 states `compose` instead of parts: a tree of finished pieces, each named by `use`.\n\n```json\n{\n "type": "Record",\n "door": "page",\n "compose": {\n "use": "Column",\n "children": [\n { "use": "Bar", "children": [{ "use": "Status" }, { "use": "Field", "field": "audience" }, { "use": "Act", "act": "send_for_review" }, { "use": "Copy", "field": "body", "as": "plain" }] },\n { "use": "Title" },\n { "use": "Document", "field": "body" },\n { "use": "Group", "title": "Reviews", "children": [{ "use": "Rows", "child": "review", "create": false }, { "use": "Act", "act": "ask_reviewer" }] }\n ]\n }\n}\n```\n\nA node binds by name: a `field` is a field of the record\'s table, an `act` an Act of the app, a `child` a table linking to\nthe record. What a name binds is read and written exactly as a part naming it would be \u2014 a `Field` and a `Document` are\nsaved through the record\'s own save, an `Act` runs its workflow, a `Rows` reads the child\'s rows \u2014 so a tree adds no write\nof its own. It holds no condition and no computed value: a piece that does not apply on a row draws nothing, and anything\nthat must depend on the row is an Act\'s `when` or a field\'s formula, never the tree.\n\nThe first `Act` the page itself draws is its primary and every other one secondary; a `danger` act reads so wherever it\nstands. A `Copy` and a `Document` naming one field copy what is on screen \u2014 the words typed and not yet saved among them. The\nrecord\'s chrome stays the runtime\'s: the way back, its comments door and its \u22EF with Delete share the row over the tree, and the\nhead draws no title and no status of its own \u2014 the tree\'s `Title` and `Status` do.\n\n<!-- generated:start apps-compose -->\n\n#### Column piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Column"` | yes | The page\'s reading column, 680px: its pieces stacked in order from one left edge |\n| `children` | list of [piece](#composed-page) (at least one) | yes | The pieces stacked in it, in the order they are read |\n\n#### Bar piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Bar"` | yes | One row of small things on one control height, in two zones: what the record is \u2014 a status, a field\'s control \u2014 from its left edge, what is done to it \u2014 an act, a copy \u2014 ending on its right edge, taking a row of their own under it where the row runs out of room |\n| `children` | list of [piece](#composed-page) (at least one) | yes | Its pieces, each zone\'s in order; a `Field` directly in it is its control alone on one line, named by its label where it holds nothing; an `Act` that destroys or corrects is not drawn here \u2014 it stands in the record\'s \u22EF |\n\n#### Group piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Group"` | yes | A titled stack, headed once by `title` |\n| `title` | text | yes | Its heading, in the reader\'s words; holding one `Rows`, it carries their count and their add, and the group\'s `Act`s end its line |\n| `children` | list of [piece](#composed-page) (at least one) | yes | The pieces under the heading, in order |\n\n#### Panel piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Panel"` | yes | What a reader does not need while writing: nothing in the page, one entry of the record\'s \u22EF named by `title` that opens its pieces in a panel beside the page \u2014 a sheet on a phone; directly in the root `Column` alone |\n| `title` | text | yes | Its entry in the \u22EF and the panel\'s name, in the reader\'s words; an `Act` directly in it ends that name\'s row |\n| `children` | list of [piece](#composed-page) (at least one) | yes | The pieces in the panel, in order: `Rows`, `Field`, `Act`, `Group`, `Document` \u2014 never `Title`, `Status`, `Bar`, `Copy` or another `Panel` |\n\n#### Title piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Title"` | yes | The record\'s title (`records.<entity>.title`) as the page\'s heading, written in place where a person writes it; one a page |\n\n#### Status piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Status"` | yes | The record\'s status (`records.<entity>.status`) as a record\'s head draws it: moved by hand, by its acts\' moves, or read |\n\n#### Field piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Field"` | yes | A field of the record under its label, as a section\'s `fields` draws it |\n| `field` | alias | yes | The field drawn \u2014 never the title or the status, which `Title` and `Status` draw; once a page |\n\n#### Document piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Document"` | yes | A markdown text field written in place with no box, saved as it is written \u2014 whether it is saved said at its foot |\n| `field` | alias | yes | A text field with `format: "markdown"`; once a page |\n\n#### Act piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Act"` | yes | An act of the app on the record as its button: its `when`, what it lacks, its asks and its confirmation as everywhere; the first `Act` the page itself draws is its primary |\n| `act` | alias | yes | An act of this app on the record \u2014 never one `of` a child\'s rows, which stands on its row; once a page |\n\n#### Rows piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Rows"` | yes | A child entity\'s rows under the record, as a rows block lists them, each row\'s own acts on it |\n| `child` | alias | yes | A child entity linking to the record; once a page |\n| `create` | `false` | no | false, rows are not added here \u2014 an act of the page makes them |\n| `remove` | `true` | no | The rows offer a Delete (open row and \u22EF); absent, none |\n\n#### Copy piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Copy"` | yes | A button copying a field\'s words as the page holds them now \u2014 what is typed in its `Document` and not yet saved among them |\n| `field` | alias | yes | The text field copied |\n| `as` | `"plain"` \\| `"raw"` | yes | `plain`, its markdown marks gone; `raw`, as stored |\n\n<!-- generated:end apps-compose -->\n\n## Tasks\n\nA table whose `records` states `"task": true` \u2014 its status a stored select with `closed` \u2014 is work someone finishes.\nEvery List of its rows draws it alike, each row led by a ring. Ticking the ring moves the row to a closed option by the\none write the app already has for that move: the Act of that table setting a closed option, offered on an open row and\nasking nothing, else its own save where people write the status \u2014 stamping what that act stamps and appending the\nstatus history. Unticking runs the Act moving a closed row back, else the save back to the status\'s default. A move no\nwrite reaches leaves the ring disabled and the check notes it. An Act with `on: "ticked"` completes several at once.\n\nA record listing task rows filed under it by a link holding one row says how much of that work is done \u2014 those at a\nclosed option of all \u2014 wherever its row is drawn: a meter on its head, a column of its register\'s rows, a card\'s foot, a\ncalendar entry, a gantt\'s or a lane\'s bar. No rollup is stated for it.\n\n```json\n{\n "tables": [\n {\n "ref": "project",\n "label": "Projects",\n "singular": "Project",\n "fields": {\n "name": { "label": "Name", "required": true, "type": "text" },\n "lead": { "label": "Lead", "type": "member" }\n },\n "title": "name",\n "records": {}\n },\n {\n "ref": "task",\n "label": "Tasks",\n "singular": "Task",\n "fields": {\n "title": { "label": "Title", "required": true, "type": "text" },\n "project": { "label": "Project", "required": true, "type": "link", "table": "project", "multi": false },\n "assignee": { "label": "Assignee", "type": "member" },\n "status": {\n "label": "Status",\n "required": true,\n "default": ["todo"],\n "type": "select",\n "options": [\n { "alias": "todo", "label": "To do", "color": "slate" },\n { "alias": "done", "label": "Done", "color": "green" }\n ]\n },\n "due": { "label": "Due", "type": "date", "format": "date" },\n "done_on": { "label": "Done on", "type": "date", "format": "date" }\n },\n "title": "title",\n "records": {\n "subtitle": ["assignee"],\n "due": ["due"],\n "task": true,\n "starts": { "assignee": "me" },\n "status": { "field": "status", "closed": ["done"] }\n }\n }\n ]\n}\n```\n\n```json\n{\n "body": {\n "type": "App",\n "key": "projects",\n "name": "Projects",\n "color": "blue",\n "tables": { "project": "<id of the project table>", "task": "<id of the task table>" },\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "project",\n "children": [\n {\n "type": "Record",\n "children": [\n {\n "type": "Section",\n "key": "work",\n "title": "Work",\n "children": [\n "lead",\n {\n "type": "List",\n "entity": "task",\n "columns": ["due"],\n "children": [\n { "type": "Act", "key": "complete", "label": "Complete", "when": { "status": ["todo"] }, "set": { "status": "done", "done_on": { "now": true } } },\n { "type": "Act", "key": "reopen", "label": "Reopen", "when": { "status": ["done"] }, "set": { "status": "todo", "done_on": null } },\n { "type": "Act", "key": "complete_all", "label": "Complete", "on": "ticked", "when": { "status": ["todo"] }, "set": { "status": "done", "done_on": { "now": true } } }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\nThe tasks as a register of their own, for one person: a one-part Record holds its Fields and Acts directly.\n\n```json\n{\n "body": {\n "type": "App",\n "key": "my_tasks",\n "name": "My tasks",\n "color": "teal",\n "tables": { "project": "<id of the project table>", "task": "<id of the task table>" },\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "task",\n "opens": { "assignee": { "me": true } },\n "children": [\n { "type": "Reading", "key": "open_tasks", "label": "Open", "where": { "status": ["todo"] } },\n {\n "type": "Record",\n "door": "drawer",\n "children": [\n "project",\n "due",\n { "type": "Act", "key": "finish", "label": "Finish", "when": { "status": ["todo"] }, "set": { "status": "done", "done_on": { "now": true } } },\n { "type": "Act", "key": "undo", "label": "Undo", "when": { "status": ["done"] }, "set": { "status": "todo", "done_on": null } }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Rules of the app\n\nEvery rule the check enforces over an app, one sentence each under an id; a finding names its rule\'s id\n(`[register.filter]`), and `model/<rule id>` is that one rule\'s page. A sentence names the node and the prop an author writes (`List.create`, a `Section`);\neach area stands in the tree where this table says:\n\n| Area | Stands at |\n|---|---|\n| `app` | the App, and the register List\'s props that belong to the app: `words` (labels), `color`, `scope`, Books, logs |\n| `register` | the register List: its columns, filters, search, group, sort, layout, tabs, period, export |\n| `reading` | a Reading or Progress |\n| `record` | a Record and its parts: Section, Side, Page, Act placement |\n| `block` | what a part holds: a child List, a Field gallery or prose block, a Reading, a desk, a timeline or agenda |\n| `statement` | a Statement |\n| `act` | an Act |\n| `check` | a Check |\n\n### App rules\n\n<!-- generated:start rules-app -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `app.alias-unique` | An App\'s `key` is unique among apps, and an Act\'s among the Acts of its App, every List\'s included. |\n| `app.icon` | An App\'s `icon` is a Lucide icon name in kebab-case, as `receipt` or `trending-up` are. |\n| `app.add-label` | A List\'s `add_label` stands only where it adds rows (`create`), in words that are not empty and hold no `{` or `}`. |\n| `app.labels` | An App\'s `words` name a table it reads and a field or option of it, in words that are not empty and hold no `{` or `}`; no two fields of a table, nor two options of a field, show the same words. |\n| `app.records-entry` | Every table an App draws a List over, opens or picks from \u2014 the register\'s, a child List\'s, a linked row\'s, a documents List\'s \u2014 has a `records` entry. |\n| `app.scope` | The App\'s `scope` is a required one-row link of the first List\'s entity, the same on every row the App lists \u2014 never a `columns`, `filters` or `search` entry, `group`, `where`, `opens`, a Field of a part or a `create` question. |\n| `app.when` | A node\'s `when` holds single selects to options, yes/nos to `true` or `false` (stored or a formula of one; a formula with `options` is a select), numbers and dates to bounds (`gt`, `gte`, `lt`, `lte`; a date\'s bound is a day, a moment on a datetime, or `{ "now": true }`) and any field but a yes/no to `{ "empty": true \\| false }` \u2014 a number read in each row\'s own unit or currency and a lookup take no bound. |\n| `app.asked-written` | What a List\'s `create` asks, what an Act\'s `asks` asks and what `starts` fills is a field a person writes \u2014 never a formula, rollup, lookup, autonumber or a date the workspace stamps. |\n| `app.own-rows` | A many-link to rows that each belong to one row of this entity is never a Field of a part, a `create` question or an Act\'s `asks`: those rows are the record\'s own, read as a child List. |\n| `app.create-asks` | A List\'s `create` asks fields a person writes \u2014 never the link or the folder it files the row under, which the add sets itself. |\n| `app.create-required` | A List\'s `create` asks every field its entity requires that nothing else fills \u2014 a `default`, `starts`, the record or folder it files the row under, or a `default_from` over a link it fills \u2014 or the server refuses every add. |\n| `app.books` | A register App\'s Books each name another entity once (`entity`) \u2014 never the App\'s own \u2014 whose fields line up with the register List\'s by alias, else by `fields` (a field the List reads, or one an Export prints \u2192 one of the Book\'s): read as one column type (a day apart from a moment), one value or several alike, a link to the same entity. Each Book holds the title, the status, the folder (`scope`), what `where` keeps and every Check locking the rows; the List is a table, cards or a plan, whose `at`, `across` and `up` line up like any field. Two Books holding a field of their own under one alias hold it as one kind of value. An Export\'s `books` each name the List\'s entity or one of its Books, once, and one of them holds each field its `where` and `per` narrow by. A Book\'s `column: true` draws the column naming each row\'s table in a List that has Books. |\n| `app.book-option-kin` | *Noted, never refused:* A Book\'s option labelled as one of the App\'s, under another alias, reads apart from it: the List lists both and counts each on its own. |\n| `app.book-lacks` | *Noted, never refused:* A Book lacking a field the Record, an Act or a `create` reads: what reads it drops from the Book\'s rows \u2014 an Act, a child List, a Section, the `create`. A field only an Export prints, held by a Book as another kind of value, prints blank on its rows. |\n| `app.logs` | A register App\'s logs \u2014 further Lists, at most 3 \u2014 each list another entity under a `key` of their own: never the App\'s own entity, one of its Books\' or another log\'s. A log is one List \u2014 a table or cards, with no Tabs, no Book `column` and no composed Record \u2014 over an entity whose status states no owned `stages`, and stands in an App that states no `scope`. An Act\'s `key` is its own among the App\'s Acts, its logs\' included. No Record of the App or of a log lists the rows of an entity another of the App\'s Lists is over: a link to one opens it on its own List. A List people work as a desk of its own is another App. |\n| `app.rates-in-passing` | *Noted, never refused:* An App with no Act, log or Tabs of its own whose rows another App\'s lines copy a number off (`default_from` through a link) \u2014 the rates a quote is priced from \u2014 is worked only while that record is written: a child List, a Section or an Act of that App, never an App of its own. |\n| `app.row` | A List\'s `row` states only what differs from `records`: a `subtitle` that is not the title, a number for its `figure`, dates for its `due`, an `image` that is a files picture no Act makes documents into, or `false` for a figure, deadlines or picture the entity states and the App draws none of. |\n| `app.tasks` | *Noted, never refused:* A task entity an App lists whose rows no write of the App moves to a closed option (its ring stands disabled), or that an Act ticks done and none moves back. |\n| `app.workflow-value` | A value a Workflow takes or hands back states what its type needs and nothing another type does: a `record_link` its `entity`, a `select` its `options` or the `field` holding them \u2014 never both \u2014 an `object` its `fields`, an `array` its `items`; `multi` stands on a `record_link`, `select`, `member` or `file`; an output is never a `member` or a `file`; and a value nests at most 8 deep. |\n| `app.query` | A Query reads one entity\'s rows as `fields` or as a `reading`, one of the two \u2014 never the App\'s own entity where the App has Books, whose List reads several tables; `sort` and `limit` stand with `fields`; a `reading` splits by `by` or by the day of `over`, never both, and averages only with neither; each param is a number, a yes/no, a date, a select, a one-row link or a member holding one, read by an entry of `where` on a field of its type \u2014 a date by a range, a number by either, and never a number read in each row\'s own unit or currency or a date holding a span. |\n| `app.generated-name` | A name the App generates is taken by nothing the App states. No Query, no Reading\'s or Progress\'s `key` \u2014 a log\'s as it is read, `<entity>_<key>` \u2014 and no Tab\'s read (`<entity>_list_<tab>`) is the name of a read the App generates, of a Query or of a Reading. |\n| `app.surface-described` | *Noted, never refused:* A value a Workflow takes, and a param a Query takes, that states no `description`: the one line a caller reads to fill it. |\n| `app.picks-only-entity` | A Field or a row title that states `add: false` stands in an entity the app reaches. |\n| `app.picks-only-field` | `add: false` stands at a link, once, to an entity whose add the app states. |\n| `app.remove` | *Noted by the designer on a write, never refused:* An App that writes its register\'s records offers a Delete of them wherever the List writes them, until the List says `remove: false`. |\n\n<!-- generated:end rules-app -->\n\n### Reading rules\n\n<!-- generated:start rules-reading -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `reading.value` | A Reading sums a number, never a percent \u2014 percents do not add up; a value of `{ avg }` reads one. |\n| `reading.average` | `{ avg }` averages a Reading\'s `value` over the rows holding one, read against a plain `target` \u2014 never a value `limits` bounds, whose bound is a sum. |\n| `reading.split` | `by` and `across` split by one value each row holds \u2014 a single select, a one-row link, one member or a yes/no, stored or a formula, a lookup through one-row links as the field it reads \u2014 or by the bands `tiers` cut a number read in one unit on every row; a date `by` splits them per period, as a trend; a grid crosses two different fields. |\n| `reading.over` | `over` is a date: the row\'s own, stored or a formula, or its parent\'s through a lookup over a one-row link. |\n| `reading.where` | A reading\'s and a Query\'s `where` keep rows by a select\'s options, a yes/no\'s `true` or `false`, a number or date\'s bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }`), `{ "empty": true \\| false }`, or a member field holding the reader (`{ "me": true }`); a List\'s `where` also keeps rows by `<link>.<field>`, a select or yes/no of the row a one-row link names; a Query\'s `where` also holds a field to what the call states (`param`, `from`, `to`). |\n| `reading.target` | `target` reads a value nothing bounds \u2014 a value `limits` bounds reads against the sum of its bound. |\n| `reading.better` | `better` needs `over`: a change is read over the periods its date places the rows in. |\n| `reading.list-app` | A List\'s `app` is the id of an app whose first List is over the List\'s own entity. |\n| `reading.progress` | A Progress reads dates of its rows for `planned` and `finished`, a number for `done` and `weight`, and rows `measured` through a one-row link to them, by a date of theirs and a number of theirs. |\n| `reading.alias` | A Reading\'s or Progress\'s `key` is its own among the App\'s Readings and Queries, a log\'s read by its entity before it (`<entity>_<key>`); a Progress above a List reads the List\'s own rows and states no name of its own: its `key` is the one its place gives it, `register_reading_<position>`. |\n| `reading.tabs` | A dashboard with Tabs holds every Reading and List inside a Tab, and every Tab holds one at least; without Tabs, none sits in one. |\n| `reading.restate` | A dashboard\'s count tile never counts the rows a List beside it lists under the same `where` and tab: the List\'s head counts them. |\n| `reading.period` | A dashboard\'s `period` is the one it opens on, and it stands only over a Reading placed in time by `over`. |\n\n<!-- generated:end rules-reading -->\n\n### Statement rules\n\n<!-- generated:start rules-statement -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `statement.lines` | A Statement\'s `lines` are numbers of the record, each once \u2014 the first stating no `op`, every other one, the last an `equals` \u2014 each `equals` a formula, rollup or lookup, and a `rate` a number of the record. |\n| `statement.rows` | A line\'s `parts` read rows of an entity holding `same`, a one-link of the record\'s to the same entity; `at`, a moment of those rows, and `before`, a moment of the record (datetimes, never days), stated together; their `columns` and `figure` are those rows\' fields, the figure a number. |\n| `statement.parts-keyed` | A line\'s `parts` add up to its figure, so no person keys it: a formula, rollup or lookup, a value the server copies when the record is made (`default_from`) that no `create` asks and no save or Act writes, or a stored number the App\'s saves leave out \u2014 the line draws it read; each part\'s `figure` is frozen when its row was certified \u2014 a stored number an Act fills, the server copies or the App\'s saves leave out, never one a person keys or the server recomputes \u2014 and no person keys the moments `at` and `before` either. |\n\n<!-- generated:end rules-statement -->\n\n### Check rules\n\n<!-- generated:start rules-check -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `check.field` | A Check\'s `field` is a yes/no or text formula of the rows of the List it stands in. |\n| `check.of` | A Check stands in the register List or in a child List the Record lists. |\n| `check.not-placed` | A Check\'s field is never also placed on the record as a Field \u2014 the line under the head says it. |\n| `check.blocks` | A Check\'s `on` names what it refuses: `"update"` (the saves), an Act\'s `key`, or `"all"` (every Act and save); a child List\'s Check refuses that row\'s saves and the Acts of it, never the record\'s. It binds this App alone. `"delete"` refuses this App\'s removal of the rows and is kept for a rule no check of the table states: a Delete is the table\'s checks\' to refuse. |\n| `check.resolve` | A Check\'s `resolve` names an Act of this App on the rows the Check stands on, pressed on one row, not refused by the Check, and drawn as a button \u2014 never a correction. |\n| `check.restates-meter` | A warning reading a gated meter\'s figure against its bound or pass mark is refused \u2014 the meter draws it. |\n\n<!-- generated:end rules-check -->\n\n## Register rules\n\nThe rules of the register List\'s props: columns, filters, search, group, sort, layout, tabs, period and export.\n\n<!-- generated:start rules-register -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `register.export-report` | A List\'s Exports each have their own `label`; `per` picks one option, person or one-link row \u2014 never of the status, the folder or a field its `where` fixes. |\n| `register.export-template` | A template an Export fills reads at its root only `title`, `lines`, `readings`, `at`, `dates` and its rows under the List\'s entity alias \u2014 an entity named none of those \u2014 and is the document of no Act. |\n| `register.column-drawn` | A List\'s `columns` never name what the row draws itself \u2014 its title, subtitle, image, status, figure, or the bound its figure\'s meter reads against \u2014 and name a field once. |\n| `register.columns-budget` | A List\'s `columns`, each at the width of its kind, fit the room a desk leaves beside its name and the row\'s own status, deadline and figure. |\n| `register.filter` | A List\'s `filters` name a select, a member, a link, a date, a yes/no or a number of the List\'s own rows \u2014 a lookup through one-row links as the field it reads \u2014 never the status (its chips), nor one `where` fixes. |\n| `register.filter-split` | *Noted, never refused:* A `filters` entry on a field a Reading of the List with `by` or `across` splits the rows by \u2014 a press on its part narrows by it too; a member\'s filter, which narrows to the reader\'s own rows, is never noted. |\n| `register.filter-unshown` | A `filters` entry, a narrowing the List `opens` on, and a press in its band (a Reading\'s part, a grid\'s cell, a figure\'s `where`) name a field its rows show \u2014 a `columns` entry; the row\'s title, the line under it, its picture, status or figure, a meter\'s bound, or the unit or currency a drawn figure is read in; a deadline; a warning its line says; the group or lane it stands under \u2014 or a formula, drawn where every field of its own row it reads is (through other formulas too; one reading no field is drawn nowhere). A narrowing to the reader (`{ "me": true }`) needs none. A tree leaves out a `filters` entry no row shows and says so (name the field as a column or in the row\'s line to offer it); the other two are refused. |\n| `register.tiers` | `tiers` band a number read in one unit on every row; a figure read in each row\'s own unit or currency is named bare. |\n| `register.search` | A List\'s `search` names text, a code, or a link \u2014 matched by its row\'s title. |\n| `register.sort` | Each `sort` key of a List or a Tab names a field of the List\'s rows that holds an order \u2014 never files \u2014 and no field twice. |\n| `register.tabs` | A List\'s Tabs each have a `key` of their own, none the entity\'s alias or one a report template holds (`title`, `lines`, `readings`, `at`, `dates`, `period`, `per`); a Tab states one of `dated` and `open` (dates of its rows, `open`\'s two different) or `totals` (a child\'s rows windowed by the period); its `where` and `sort` hold as the List\'s do, and never narrow by the status, which its chips split. Only a table or cards List has Tabs, and a Reading\'s `tab` names one of them. |\n| `register.tab-totals` | A Tab\'s `totals` name a child linked to the List\'s entity by a one-row link (a child linking the List\'s entity twice cannot be totalled), a date of the child, never a moment or a span, and 1 to 6 columns each under its own alias and label; a column\'s `value` is a number of the child, its `where` holds over the child\'s selects and yes/nos, and a value read in each row\'s own unit or currency stands only where that select of the child looks up, through its link, a single select of the List\'s entity. Only a table List with no Books has them, and no Tab states `totals` beside `dated` or `open`. |\n| `register.group` | A List\'s `group` names one value per row \u2014 a single select, a one-row link, one member, a date, a yes/no or a text, a lookup through one-row links as the field it reads \u2014 the status too, but never on a task list\'s block, whose chips narrow it; only a table or cards List groups, and a gantt into its lanes; grouping by the title needs a `row.subtitle` to lead each row. |\n| `register.readings-own` | A Reading in a List reads the List\'s own entity; another entity\'s stands on a dashboard. |\n| `register.readings-bands` | A Reading in a List that bands a number by `tiers` has that field with the same tiers among the List\'s `filters`: a press on a band narrows the rows by that filter. |\n| `register.readings-restate` | A Reading with no `by` in a List never sums the row\'s figure or counts the rows without a `where`: the summary line totals the figure and the status chips count the rows. |\n| `register.where-opens` | A List\'s `where` holds its own selects, yes/nos, bounds and blanks (a child List\'s or the register\'s), and `opens` its selects and yes/nos and a member field holding the reader (`{ "me": true }`) \u2014 or, on a status whose `stages` have owners, the rows waiting on the reader; a field `where` fixes to options is never a filter nor opened otherwise, and `opens` stays among the options `where` keeps. A List laid out as a `plan` keeps its items by options and yes/nos alone. |\n| `register.roster` | A List with `layout: "roster"` holds the List of its cells: the cells\' entity has one single link to this one and a date on its line (a field of the row itself, or a lookup of its parent\'s date), a roster draws no `columns`, and an `expect` (with `of`) stands only on the cells List. |\n| `register.roster-expect` | A roster cells List\'s `expect` is a single select of its rows, and its `of` this entity\'s multi-select whose every option is one of `expect`\'s. |\n| `register.gantt` | `layout: { gantt }`: `start` and `end` (absent, the entity\'s first `due`) are two different dates of the row, both days or both moments; each diamond (`milestones`) another date of it, each planned date read as `start` is, `progress` a percent and `after` a link to rows of the same entity. |\n| `register.lanes` | `layout: { lanes }`: `by` is a one-row link to an entity with a `records` entry, the rows name a date on their line (a field of the row itself, or a lookup of its parent\'s date), a lane block\'s two ends are read alike (two days or two moments), and `loads` stands with it. |\n| `register.plan` | `layout: { plan }`: `at` is a one-row link to an entity whose `records` states a `plan_place`, `across` and `up` two different numbers of the row, `fill` a single select; a plan List has no Tabs, `group` or `attention`. A write that sets `at`, `across` or `up` is refused unless it clears all three, or places the row at a free cell of a bay (a row from 1 to the bay\'s `across`, a tier from 1 to its `up`, resting on a present item), or at an area with neither. |\n| `register.plan-unique` | *Noted, never refused:* A plan\'s items (the List\'s table and each Book) state `unique` on their place link, `across` and `up`: the app\'s moves refuse a taken cell, and the unique makes every other write hold it \u2014 the note names a table that states none. |\n| `register.plan-mark` | A plan\'s `mark` names a single select of the row and an option of it by alias: a row holding that option says its label on its box and is hatched in the option\'s colour, which the legend names by its label. A Book that holds the select must hold it as a single select with that option; a Book lacking it never marks its rows (`app.book-lacks`). |\n| `register.plan-size` | A plan\'s `size` names a single select of the row and, by option alias, the box (`across`, `along`, `up` \u2014 positive, in the one length unit the places\' `size` states) of each option: a row holding one is drawn as that box, any other at its place\'s cell. A Book that holds the select must hold it as a single select; a Book lacking it draws its rows at their places\' cells (`app.book-lacks`). |\n| `register.plan-box-size` | *Noted, never refused:* A plan whose places state a `size` and whose rows\' select options are ISO 6346 container size-type codes (`20DC`, `40HC`) states the box of each in `size`, from the ISO 668 dimensions, else every box is drawn at its place\'s cell. |\n| `register.loads` | A lanes layout\'s `loads` names one to 2 numbers a lane block adds \u2014 never a percent \u2014 each against a number of the lane\'s row, in one unit or two of one dimension. |\n| `register.band-pivot` | *Noted, never refused:* A Reading with `across` (a grid) among a List\'s Readings: its grid reads on a dashboard, and the band leads with a figure (a Reading with no `by`). |\n| `register.headline` | *Noted by the designer on a write, never refused:* A List over rows that fall due or carry money and have no status whose steps the chips count, laid out as a table, cards or a gantt, has Readings \u2014 they split the rows by a field they show. |\n| `register.span` | *Noted, never refused:* Rows that each span a run of days or hours \u2014 two dates on their line, a `frees`, a `no_overlap` booking them on what its `by` links to, or one required date and the table\'s `due` where the line names no date \u2014 are drawn in time by some List over them (a `layout` of calendar, lanes, gantt or roster) \u2014 the note names the app to add over them, laid out as lanes on what each one books, else as a gantt (a run held by a date and the `due` also asks for both dates on the line). |\n| `register.remove` | A List\'s Delete stands only where the App writes its rows: `remove: true` over rows it cannot write is refused. Absent, rows offer a Delete wherever the List writes them, and `remove: false` takes it away. |\n| `register.remove-roster` | A roster cells List\'s `remove: true` offers the Delete of the roster\'s rows, so it stands only beside a roster over rows the App writes. |\n| `register.attention` | `attention: false` keeps a table\'s or cards\' rows in the List\'s own order \u2014 a first-come queue; a calendar, lanes, a gantt and a roster stand their rows in time and lift none. |\n| `register.unworked-stage` | *Noted, never refused:* A register lists rows (every open one, else those its `where` keeps) at a stage that no Act of the App is offered at and no Section stands at, though the App states the stages it works: the rows only sit in its list, so keep them out with `where` or leave them to the App that works them. |\n\n<!-- generated:end rules-register -->\n\n## Block rules\n\nThe rules of what a part holds: a child List, a Field gallery or prose block, a Reading, a desk, a timeline or an agenda.\n\n<!-- generated:start rules-block -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `block.child-link` | A child List reads a child linked to the record; `via` names the link where the child links more than once. |\n| `block.column-drawn` | A child List\'s `columns` never name what the child\'s row draws (title, subtitle, image, status, figure, or the bound its figure\'s meter reads against) nor its link back, and name a field once; an `expect` List\'s figure is its filled-in-place column. |\n| `block.columns-budget` | A child List\'s `columns`, each at the width of its kind, fit the room a desk leaves beside its name and the row\'s own status, deadline and figure. |\n| `block.title-back` | Where a child\'s title is its link back to the record a child List lists it under, its first other `row.subtitle` names it there \u2014 a text, a code, a select, a link or a member, never a number or a date. |\n| `block.where` | A child List\'s `where` names single selects the child requires, by options they hold \u2014 one held at a single option is never also a column \u2014 and the child\'s yes/nos by value, numbers and dates by bounds, and fields by `{ "empty": true \\| false }`. A yes/no, a bound or a blank is never held: a child List adds rows only at a stored yes/no\'s start, adds none under a bound or a blank, and never picks, expects or stands lines under documents. |\n| `block.under` | `under` names the child\'s one-row link to a document entity that pairs a many-link back and is read under this record by its one link; the lines\' columns never read the document (its heading does) nor hold long text. |\n| `block.under-once` | Documents drawn over their lines (`under`) are never listed again as a List of their own. |\n| `block.edits` | A child List\'s editable columns (`{ "field", "edit": true }`) name at most two of the child\'s columns or its figure that the App\'s saves of the child write \u2014 a number, a date, a few words, one option or a one-row link, never a files or long-text field, the List\'s `via` nor a link it must name alike (a link\'s `same`) \u2014 never on an `expect` List. |\n| `block.remove` | A child List\'s `remove: true` stands only where the App writes those rows. |\n| `block.remove-documents` | The documents List inside a child List with `under` states `remove: true` only over documents the App writes. |\n| `block.compare` | A child List\'s `compare` names two different fields of the child, `before` and `after`: two files fields, or two values of one type (text but long text, a number, a date, a single select). Never with `expect`. Over files on a checklist layout, `before` is the check\'s `photo`. Over values: neither side is a column or something the row draws already (title, line, figure, status, deadline), two numbers share a notation, no column is long text, never on a checklist layout. |\n| `block.current` | A child List\'s `current` names a date or number of the child (stored, or a formula yielding one) that orders a set of revisions: the row holding the latest value is current, the rest fold as superseded. Never with `group`, `under`, `expect`, `checklist`, `compare`, `post` or `pick`, nor beside a column of long text. |\n| `block.checklist` | A child List with `layout: { checklist }` reads a task entity\'s rows as checks \u2014 never under documents, grouped, edited in place or picked together by an Act over several, its columns no files; `photo` names a files field of the child, `clause` a one-row link of it, `failed` options of its status, none `closed`; `passed` a yes/no of it, stored or a formula, setting each mark (yes passed, no failed, empty still to check) and standing without `task` or a status. With `expect` the lines of a standard are the checks, each marked and filled by the block\'s columns (a measured value, a note, a photo), so `photo` and `clause` are not named. |\n| `block.pick` | A child List\'s `pick` links rows of the child through `via` \u2014 an optional one-row link a person writes, no Act writes, no other rule compares rows by and no `under` List stands lines under \u2014 paired with a many-link of the record its own save writes; the child\'s `read_scope` reads a row naming no record through `via`; never with `expect` or `under`, nor where the App\'s saves leave out the child or the many-link. |\n| `block.narrow` | A child List\'s `filters`, `search`, `group` and `sort` follow the register List\'s rules over the child\'s rows, its status among them but on a task list (its chips); never on the link back to the record or a select its `where` holds at one option, nor on an `expect` List; an `under` List is grouped by its documents. |\n| `block.expect` | `expect` names the child\'s title \u2014 a single select or a one-row link a person writes, never the link back \u2014 on a child added in the List; its `columns` name a field filled in place \u2014 unless the List\'s layout is a desk, whose lines are ticked \u2014 and its `create` takes the file where the child is pictured by one. |\n| `block.expect-fills` | An expected line is made from its key and its first filled column \u2014 a desk\'s from its key alone, by its tick \u2014 so every other field the child requires has a default, a start or a `default_from`, or is read first. |\n| `block.expect-of` | An `expect` object\'s `of` or `only` needs a select: `of` names the record\'s multi-select of the title\'s options; `only` names options of a single select, never with `of` or a `where` on it, one List per option; an Act\'s `paper` is drawn in one. |\n| `block.post` | A child List with `layout: { desk }` needs `expect` on a one-row link \u2014 never a select, nor with `under`, `pick`, a checklist layout, `compare` or a `create` list, its line made by a tick from its key alone: its `bodies` are plain text fields of the record a person writes, drawn nowhere else on it; its `text` is a text of the child, stored or a formula yielding text \u2014 a formula reading only stored values of its row, lookups through the link back to the record or through `expect`, and formulas of the row over those, never the clock; its `media` a files field of the record and its `link` a text field of the record; its `network` a single select of the entity the link names, and `networks` holds options of that select, by alias, each to one of facebook, instagram, threads, x, linkedin; its `group` is another single select of that entity. |\n| `block.funnel` | A Reading\'s `steps` (a funnel) are 2 to 8 numbers of its child, each named once \u2014 stored, a formula or a rollup \u2014 all in one notation, never a percent nor a unit read on each row. |\n| `block.list` | A read-only child List (`same`, `app`, or a Record that is `edit: false`) lists the rows of an entity that link to the record by its one link to it (`via` where it has several); with `same`, a one-row link of the record, it lists the rows sharing that link\'s row with the record \u2014 the record left out \u2014 by the listed entity\'s one link to that row\'s entity (`via` where it has several). `same` stands in a Record, never on a dashboard. Its Record\'s Fields \u2014 at least one, each a field of the listed entity named once, never its title nor one of the `columns` \u2014 are what a row shows, read-only, when it is opened in place. |\n| `block.list-opens` | A read-only child List\'s row opens in the record\'s drawer as a record: its entity is one a List of the App draws \u2014 the register\'s, a Book\'s, or a log\'s \u2014 or the List\'s `app` names the register app that works it. |\n| `block.figures` | A Record\'s own figures (Readings with no `entity` and no `by`, whose `value` names the field) are 2 to 8 numbers of the record no person keys \u2014 a formula, a rollup, a lookup through links to one row, or a stored number the App\'s saves leave out \u2014 each placed once on the record. |\n| `block.agenda` | An agenda List\'s child names a date on its row line (`records` title or subtitle); the layout\'s `start` is a date of the record. |\n| `block.timeline` | A timeline List\'s child holds a date to place its entries by. |\n| `block.field-kind` | A gallery Field names a files field of the record, and a prose Field a text field. |\n| `block.read-scope` | A child read under a record whose rows a `read_scope` narrows states its own \u2014 a row rule is not inherited, save the status history\'s, which takes its record\'s. |\n| `block.agenda-note` | *Noted, never refused:* Rows each on a day and within it (at an hour, in a part of the day) listed as a table: a List with `layout: { agenda }` draws them by day. |\n| `block.under-note` | *Noted, never refused:* Rows holding a paper that cover another List\'s lines, listed apart with no add of their own stated: `under` draws them over the lines they cover. |\n| `block.timeline-note` | *Noted, never refused:* Rows each what happened on a day and who \u2014 a date on their line, a member, no status and no figure \u2014 listed as a table: a List with `layout: "timeline"` draws them as a log, newest first. |\n| `block.expect-note` | *Noted, never refused:* Rows each a kind of paper, listed with no `expect` and no `where` on the kind, that no Act files a call into or `fills`: the lines still missing read as what a record needs. |\n\n<!-- generated:end rules-block -->\n\n## Record and act rules\n\n### Record rules\n\n<!-- generated:start rules-record -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `record.placed-once` | A Field, a child List, a Reading, a Statement and an Act each stand once in a Record: its head draws the title, subtitle, image, status, figure and folder, and a field\'s one other place is a part\'s Fields, a gallery or prose Field (`block`) or a Statement \u2014 save the image, which one files gallery may draw again, whole, the figure, which a Statement draws as what it comes to, and rows one List prices and another compares (`compare`); two places whose `when`s never hold together (a select at options the other\'s lacks) each draw it, and a plain child List (no `expect`, `under`, `pick`, `compare`, nor a `layout` of desk or checklist) likewise. |\n| `record.foot-total` | A child List\'s foot totals the child\'s figure and each summed column over the rows it lists; the record\'s rollup summing that field over those rows (the same `where`) is that foot, never also a Field of a part. |\n| `record.fact-restates` | A Field of a part never looks up what its link already reads where it stands: the linked row\'s title beside the link, the figure or bound its meter reads on the head\'s line. |\n| `record.section-holds` | A part (Section, Side, Page) holds Fields, child Lists, Readings, Progress, Statements or Acts. |\n| `record.section-reads` | A Field a part shows as a value only (`edit: false`) is one that part draws. |\n| `record.compose` | A Record\'s `compose` is stated instead of parts (it holds no children), on a page `door`, by an App with no Books whose records have no owned stages (`status.stages`) and no picture (`image`) \u2014 a composed page draws neither. Each name is a field of the entity, an Act of the app on the record or a child entity linked to it; one `Title`, and each field, Act and child drawn once \u2014 a field by `Field` or `Document`, never the title or the status, which `Title` and `Status` draw. The first `Act` the page itself draws is its next step and every other one secondary; an `Act` is a button alone, never one making a set of papers. A `Bar` is one row on one control height, in two zones \u2014 its `Status` and `Field`s from its left edge, its `Act`s and `Copy`s ending on its right edge \u2014 and holds no `Rows` and no `Document`; a `Field` directly in it is its control alone on one line, named by its label where it holds nothing; an `Act` in it that destroys or corrects what is done (`danger`, or a move back) is not drawn there \u2014 it stands in the record\'s \u22EF. A `Panel` draws nothing in the page: it is one entry of the record\'s \u22EF, named by its `title`, opening its pieces in a panel beside the page; it stands directly in the root `Column`, holds `Rows`, `Field`, `Act`, `Group` and `Document` \u2014 never `Title`, `Status`, `Bar`, `Copy` or another `Panel` \u2014 and an `Act` directly in it ends the panel\'s name, secondary. A `Group` is headed once, by its `title`: holding one `Rows`, that heading carries the rows\' count and their add in place of the rows\' own, and the `Act`s the group holds end its line. A `Copy` takes a text field\'s words as the page holds them now, a `Document`\'s words not yet saved among them; a field a `Copy` alone names is read, never written. |\n| `record.sections` | *Noted by the designer on a write, never refused:* A List laid out as a table, cards or a gantt opens a Record whose Sections hold the fields people write, a files Field per paper, a child List per child and the record\'s Acts. |\n| `record.header-budget` | *Noted by the designer on a write, never refused:* A Record\'s head says what the row is and where it stands \u2014 its title, line, picture and status \u2014 and at most two more facts, one of them a deadline at most; the rest stands in a Section. |\n| `record.children` | *Noted by the designer on a write, never refused:* A table some App records, whose rows other rows each belong to (a one-row link to it, never its status history), draws some of them under its row \u2014 a child List in its Record, a List with `layout` roster or lanes by that link, or an App over them working inside one of its rows at a time (`scope`). |\n| `record.at` | A Section\'s `at` names options of the record\'s status; a record with no status has no steps. |\n| `record.at-once` | A stage is the work of one Section: two Sections naming one option of the status in `at` hold some Field to values apart in their `when` \u2014 a `when` holding the status keeps its Section off the options it leaves out. |\n| `record.section-page` | A Page stands only on a page `door` \u2014 never a drawer\'s or one beside the List \u2014 is never a step (a Page has no `at`), has a `title` naming an address no other Page shares (a letter or a digit at least), and is never every part. |\n| `record.side` | At most one Side stands in a Record, on a page `door`, and it is never also a Page. |\n| `record.beside` | A Record with `door: "beside"` opens beside a table List \u2014 never a board, cards or a calendar, which draw no list to stand beside. |\n| `record.section-acts` | An Act in a part works on the record itself; an Act in a child List stands on the child\'s row, and an Act in the register List with an `on` on the register\'s rows. |\n| `record.act-in-section` | Every Act of the record stands in the part whose work it is; only a correction no part owns \u2014 `danger`, and no other status move offered at the same moment \u2014 or an Act an agent runs for the person (a `workflow` or an `intake` moving no status), placed in the List and in no part, waits in the head\'s \u22EF. |\n| `record.move-home` | A status move offered at one step stands at that step\'s foot when it moves the work forward, to an exit, or back to a step it passes anyway; one offered at several steps, or moving back into a detour, stands in the Section whose `at` holds the status it enters. A tree\'s status move written in a Section with no `at`, or in none, is placed where this puts it and said. |\n| `record.act-staged` | An Act at a staged Section\'s foot is offered only during that Section\'s steps: its `when` holds the status to options the Section\'s `at` names. A tree\'s Act with no `when` at the foot of a Section staged at one step is offered at that step and said; a correction stays offered at every step, and an exit is refused. |\n| `record.act-hidden` | An Act is never offered only while its part\'s `when` hides the part. |\n| `record.outside-place` | An Outside stands directly in a Record whose table states a stored status select, and its `key` is unique in the App: a link to a record names it. |\n| `record.outside-at` | An Outside\'s `at` names options of the record\'s status that no stage owns, and no two Outsides of a record open during the same option. |\n| `record.outside-field` | An Outside\'s Fields are read as values, each a field of the record shown once, never `edit`, `when` or a label of its own. |\n| `record.outside-list` | An Outside\'s Lists read the child rows under the record as they are: their `columns`, `label` and `where` only, never an add, a Delete, a pick, a filter, a search, an `app`, a link that opens, or a bound read from the record. |\n| `record.outside-act` | An Outside\'s Acts are its alone and each ends the visit: `when` holds the status to `at`, `set` moves it to an option outside `at`, `asks` are text, numbers, dates or listed options, `requires` names fields the Outside shows or the act asks, a `sign` is by a text field with no witness, and none states `on`, `of`, `workflow`, `templates`, `paper`, `intake`, `record`, `review`, `open`, `danger`, `recommends` or `me`. |\n| `record.field-edit` | An Act that only writes one field of its record, not the status, from the one input it asks is that field\'s edit: the Field rests in the part as a control. |\n| `record.section-owners` | *Noted, never refused:* A Section holding acts offered at stages that different owners work \u2014 a role apiece, or a member field apiece \u2014 is two jobs in one: each owner\'s part is a Section of its own. |\n\n<!-- generated:end rules-record -->\n\n### Act rules\n\n<!-- generated:start rules-act -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `act.paper-note` | *Noted, never refused:* A made paper kept in a field of a record that lists its papers by kind: it is one of those lines. |\n| `act.moves` | *Noted by the designer on a write, never refused:* Rows a stored status stages towards `closed` options are moved by an Act of some App over them, or by its authored `workflow` \u2014 each open option moves to the next, the last to the first closed one. |\n| `act.set-not-asked` | A field an Act `set`s (an option, a value, `{ "now": true }`, `{ "me": true }`, an input) or `sign`s is the Act\'s outcome: the App\'s `create` never asks it, and a required one takes a `default`. A `formula` only starts a value, which a person may change after. |\n| `act.asks-set` | A field an Act `asks`, `set`s, `sign`s or `writes` is stated by one of them: what the Act writes is its outcome, never also asked, and no field is written by two clauses of one Act. |\n| `act.starts` | `starts` (an ask `{ field, starts }`) starts a field the Act asks \u2014 `today` a date or a moment (never a span), `me` a member \u2014 for the reader pressing it, who changes it at will. |\n| `act.set-formula` | `set: { <field>: { formula } }` reads fields of the acted row and computes what the field holds \u2014 a number, a date, text, a yes/no or a single select\'s option (`{field:option}`), typed as the entity\'s own formulas are \u2014 never the status; a bound a write rule holds the field to is checked on the computed value as the Act runs. |\n| `act.of` | An Act in a child List works on that child: the one row pressed \u2014 or, with `on: "ticked"`, the rows ticked together where the record lists them as tasks \u2014 never the rows in view. |\n| `act.writes-line-key` | An Act never writes the link or the key naming an expected line under its record \u2014 a line\'s key is stated by filling it. |\n| `act.makes-or-runs` | An Act makes a document (`template`, `templates`) or runs an authored `workflow`, never both \u2014 the workflow makes what it needs. |\n| `act.does-nothing` | An Act does something: it `set`s a value, `asks` a field, runs a `workflow`, makes a document, or takes an `intake`, a `record` or a `sign`, or `open`s an app. |\n| `act.view` | An Act over every row in view (`on: "view"`) writes (`set`), makes a document or runs a workflow, and states no `requires`. |\n| `act.requires-drawn` | Every field an Act `requires` is drawn where the Act stands \u2014 its part\'s Fields, a child List\'s, its `asks`, or an unstaged part\'s where its own is unstaged \u2014 never the head alone. |\n| `act.requires-written` | A field an Act requires that only an Act writes is written by an Act offered together with it, or asked by the Act itself. |\n| `act.recommends` | A field an Act `recommends` is not also in its `requires`. |\n| `act.set` | `set` writes each field a value it holds \u2014 an option alias, a number, a yes/no, a text, `{ "now": true }` on a date, `{ "me": true }` on a member, `{ "input": "<name>" }` of an input the Act asks of the same kind \u2014 or clears an optional one with `null`. |\n| `act.into` | `into` is a files field of the one record the Act is pressed on, beside its `template` or `templates`. |\n| `act.paper-list` | A List a made paper names is one it can list: none keeps its rows by the reader, and no name is reached by a child List that writes its rows and one that only reads them, or by two of either, at once. |\n| `act.paper` | `paper` is a kind the record lists its papers by, on one record \u2014 never beside `into` or `templates`; one maker per kind. |\n| `act.templates` | `templates` names each template once, kept in `into`, on one record pressed alone \u2014 never beside `template` or `asks`, in a child List, or with an `on` over several rows. |\n| `act.intake` | `intake` names a child the record lists in a child List with `expect` on its title \u2014 a single select, the paper\'s kind \u2014 and is narrowed by no `where`, holding exactly one files field and needing nothing an added line does not fill; the Act runs on one record, states `fills`, and states nothing but its `label`, `when` and `requires` beside them. |\n| `act.record` | `record` names a child the record draws in a timeline List or a child List with no `expect`, `under` or `where`, linking back by one single link, its row needing nothing the recording does not write: `audio` and `video` files fields, `transcript` text, `at` a date, `by` a member, each its own field; on one record, stating only `label`, `when`, `requires` and `fills` beside it. |\n| `act.sign` | `sign` runs on one record, never a child\'s row: `into` a files field, `by` a text field the signer types or a member field set to the person pressing, `at` a date, `witness` a member field \u2014 stated exactly beside a text `by` \u2014 each a field of its own that the Act does not also ask or set, and no other Act signs into `into`. |\n| `act.open` | `open` opens another app at the record and does nothing else: an Act on one record of the app\'s own, stating only `label` and `when` beside it. |\n| `act.open-app` | An Act\'s `open` is the id of an app of this workspace that reaches the record\'s table: a register over it, or a custom-code app declaring no table or declaring it. |\n| `act.fills` | `fills` names each once, only beside `intake` or `record`. Beside `record`: a field of the recording\'s row the recording does not write, never a link. Beside `intake`: a field of the record, `<link>.<field>` on the row a required one-row link names (never beside that link), or a child the record adds rows of in a child List with no `expect`, `under` or `where`, filled in the fields its `create` asks \u2014 every one a new row needs among them. Each field filled is one a person writes \u2014 text, a number, a date, a yes/no, options or a single link\'s row \u2014 never a status, a field an Act writes or a link `same` narrows; a link\'s row is one the App reads of its entity. |\n| `act.fills-drawn` | *Noted, never refused:* A field `fills` writes that the record draws nowhere: the review shows the change, the record does not. |\n| `act.fills-new` | *Noted, never refused:* A child `fills` adds rows of, whose natural key the papers cannot state: every row read is added, so the same papers read twice list them twice. |\n| `act.input` | An input has a name of its own \u2014 never another ask\'s, a field\'s, or the record input its workflow takes (nor `approved` beside `review`) \u2014 `options` exactly when it is a select, `entity` exactly when it is a link, and a `default` field holding what it states. A `file` input is read by the Act\'s authored `workflow` alone, starts empty, and is never written into a field by `set`. |\n| `act.review` | `review: true` stands beside an authored `workflow` on an Act over the record, the ticked rows or every row in view, in an App over one entity \u2014 never in an App with Books, nor beside `set`, `template`, `templates`, `intake`, `record`, `sign` or `confirm`, nor in a child List, and it asks inputs only, none named `approved`. Its workflow is that Act\'s alone, run by no other Act. The workflow calls only steps that read \u2014 no agent \u2014 and returns a changeset of the entity\'s table in `data.review`, the same for the same inputs and rows; the platform writes the rows `approved` names, each only while the run proposes it as the caller read it. Over every row in view (`on: "view"`) it is handed no rows and states no `when`: its workflow reads the rows it may change, whatever the view is narrowed to, and no bound on a run\'s rows applies. |\n\n<!-- generated:end rules-act -->\n\n## What the workspace remembers\n\nThe workspace holds every table, field and select option under an id that never changes, whatever its\nlabel or alias becomes. `set_tables` answers each table\'s id, and an\napp\'s body names tables and templates by those ids, so a relabel on either side is a rename and no body\npoints at another table by accident.\n\nA table `set_tables` refuses to write, or an id the workspace no longer holds, is a refusal by name. `restore_table` puts a deleted table back.\n\n## A complete workspace\n\nTwo jobs, so two apps: a sales desk, where an order is taken and shipped, and an account review, where an account\nmanager reads each customer by tier. The split is per job, never per table: a table a job reads only in passing, such as\na price book, is a picker on that job\'s lines or an Act of that job\'s app (`first_build`, \xA7 3).\n\nOne `set_tables` call makes both tables: each item carries a `ref`, and a link names the table it points\nat by the `ref` of the other item. Its answer carries each table\'s id. One `set_app` call then makes each\napp, `{ "body": \u2026 }`: the tree\'s root names the app by its `key` and `name`, and the tables it uses in `tables` by those ids,\nshown here as placeholders to replace.\n\n```json\n{\n "tables": [\n {\n "ref": "customer",\n "label": "Customers",\n "singular": "Customer",\n "fields": {\n "name": {"label": "Name", "required": true, "type": "text"},\n "logo": {"label": "Logo", "type": "files"},\n "tier": {\n "label": "Tier",\n "default": ["standard"],\n "type": "select",\n "options": [\n {"alias": "standard", "label": "Standard", "color": "slate"},\n {"alias": "gold", "label": "Gold", "color": "amber"}\n ]\n },\n "orders": {"label": "Orders", "type": "link", "pair": "customer", "table": "order", "multi": true},\n "total_ordered": {"label": "Total ordered", "type": "rollup", "via": "orders", "op": "sum", "field": "amount"}\n },\n "views": [\n {\n "alias": "gold",\n "label": "Gold customers",\n "filters": {\n "node_type": "condition",\n "field_key": "tier",\n "type": "select",\n "operator": "has_any_of",\n "value": ["gold"]\n },\n "sort": [{"field_key": "name", "order": "asc"}]\n }\n ],\n "title": "name",\n "records": {"image": "logo", "party": "organization", "figure": "total_ordered"}\n },\n {\n "ref": "order",\n "label": "Orders",\n "singular": "Order",\n "fields": {\n "code": {"label": "Order no.", "type": "text", "unique": true},\n "placed_on": {"label": "Placed on", "type": "date", "format": "date"},\n "amount": {"label": "Amount", "type": "number", "notation": {"style": "currency", "currency": "VND"}},\n "total": {\n "label": "Total with VAT",\n "type": "formula",\n "expression": "{amount} * 1.1",\n "notation": {"style": "currency", "currency": "VND"}\n },\n "customer": {"label": "Customer", "type": "link", "pair": "orders", "table": "customer", "multi": false},\n "note": {"label": "Note", "type": "text"},\n "stage": {\n "label": "Stage",\n "default": ["draft"],\n "type": "select",\n "options": [\n {"alias": "draft", "label": "Draft", "color": "slate"},\n {"alias": "confirmed", "label": "Confirmed", "color": "blue"},\n {"alias": "shipped", "label": "Shipped", "color": "green"}\n ]\n }\n },\n "title": "customer",\n "records": {\n "subtitle": ["code", "placed_on"],\n "figure": "amount",\n "status": {"field": "stage", "closed": ["shipped"]},\n "starts": {"placed_on": "today"}\n }\n }\n ]\n}\n```\n\n```json\n{\n "body": {\n "type": "App",\n "key": "account_review",\n "name": "Account review",\n "color": "blue",\n "tables": {"customer": "<id of the customer table>", "order": "<id of the order table>"},\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "customer",\n "columns": ["tier"],\n "children": [\n {"type": "Reading", "key": "ordered_by_tier", "by": "tier", "value": "total_ordered"},\n {\n "type": "Record",\n "children": [\n {"type": "Section", "key": "account", "title": "Account", "children": ["tier"]},\n {\n "type": "Section",\n "key": "orders",\n "title": "Orders",\n "children": [{"type": "List", "entity": "order", "columns": ["total"], "create": ["code", "amount"]}]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n```json\n{\n "body": {\n "type": "App",\n "key": "sales_desk",\n "name": "Sales desk",\n "color": "violet",\n "tables": {"customer": "<id of the customer table>", "order": "<id of the order table>"},\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "order",\n "filters": ["customer"],\n "create": ["code", "customer", "placed_on"],\n "children": [\n {"type": "Reading", "key": "amount_per_period", "by": "placed_on", "value": "amount"},\n {\n "type": "Record",\n "door": "drawer",\n "children": [\n {\n "type": "Section",\n "key": "order",\n "title": "Order",\n "children": [\n "note",\n {\n "type": "Act",\n "key": "confirm",\n "label": "Confirm",\n "when": {"stage": ["draft"]},\n "set": {"stage": "confirmed"}\n },\n {\n "type": "Act",\n "key": "ship",\n "label": "Ship",\n "when": {"stage": ["confirmed"]},\n "set": {"stage": "shipped"}\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\nThe account review leaves the door a record opens through and the rows it opens in to the system, each the default the\ntable\'s facts give; the sales desk states a drawer. Each List names what its add asks (`create`). The customer\'s orders are a block in its "Orders" section, never its own `Orders`\nlink as a field as well: one fact, one place. An order is titled by its customer and read by its number under it; the\nList names what its add asks, since a subtitle or a figure is never asked by default. `Total ordered` is a rollup, so it\nis read and never asked; the sales desk\'s record opens in a drawer, because its rows are worked one after another. An\norder\'s `stage` is its status (`records.status`), moved only by the app\'s acts: they set it, so the app\'s saves leave it\nout, each act names the stage it is offered at (`when`) and the one it sets, and the register opens on the orders not yet\nshipped (`closed`).\n';
|
|
1764
|
+
var model_reference_default = '# Tables and apps\n\nThe tables of a workspace and the apps over them, each a JSON document. A table is a document, an item of\nthe tool `set_tables`: its fields, options, views, how a row is recognised, and the rules its writes\nfollow. An app is a tree of components, the `body` of the tool `set_app`: one working register over one table, or a\ndashboard of readings. Each \xA7 is its own page, `model/<section>`.\nWhich treatment each job\'s screen needs: the `design` reference. Each treatment worked through in an\napp: the `examples` reference.\n\n**What the documents compose with**\n\n- **Tables and fields** (\xA7 Tables, \xA7 Read scope, \xA7 Field) \u2014 a table\'s columns, options and links. A table document sits\n in the `tables` list of a `set_tables` call; a field sits under its alias in the table\'s `fields`.\n- **Records** (\xA7 Records, then \xA7 Status) \u2014 how a row of each table is RECOGNISED: its title, the line\n under it, its picture, its status, the one number it stands for. Stated once per table\n and read by every surface that draws one of its rows.\n- **Write rules** (\xA7 Write rules) \u2014 what a write finds, bounds and picks among.\n- **Apps** (\xA7 App tree, then a page each: \xA7 Changing an app, \xA7 What an app may write, \xA7 App node, \xA7 List node, \xA7 Layouts, \xA7 Time and place layouts, \xA7 Publish desk,\n \xA7 Record node, \xA7 Section node, \xA7 Side node, \xA7 Page node, \xA7 Outside node, \xA7 Tab node, \xA7 Field node, \xA7 Act node, \xA7 Check node,\n \xA7 Reading node, \xA7 Progress node, \xA7 Statement node, \xA7 Export node, \xA7 Book node, \xA7 Query node, \xA7 Workflow node,\n \xA7 Composed page, \xA7 Tasks, \xA7 Rules of the app, \xA7 Register rules, \xA7 Block rules, \xA7 Record and act rules) \u2014 one working register over one table and the record each row opens,\n with the logs beside it: which fields go where, the acts, and the checks that guard them \u2014 or a dashboard of readings.\n How each thing LOOKS is the runtime\'s, one treatment per concept; no prop here changes it.\n A screen the job needs and no component states is told to the person \u2014 the job, what the app\n drew, the word wanted \u2014 never props bent to approximate it.\n\n**The working order**\n\n1. The tables and fields the jobs touch, and a `records` entry for every table an\n app lists, opens or picks. Which apps a business needs is `first_build`, \xA7 3.\n2. One `set_tables` call with the tables: a link names the table it points at by the `ref` of another\n item of the call, and the answer carries every table id and the aliases of the fields it added. A\n call carries at most 50 tables, 200 first rows a table, and 2000 rows and 2000 documents in all; more\n tables go in a further call, which names the earlier tables by alias. A call is checked whole and writes\n nothing when refused, listing everything wrong; `dry_run` is for reading the grants a write would make or\n deciding `force`.\n3. One `set_app` call per app. The tree names its tables and document templates by alias wherever it points at one;\n the App node\'s `tables` and `templates` maps are filled by `set_app`, never written.\n `set_app` with the tree as `body` creates the app, named by its `name`; a refused tree writes nothing and is listed whole, and `dry_run` is for reading the permissions the write\n would grant and remove (on the CLI, `lotics app apply <file>`, with `--check` for `dry_run`).\n4. Change an app afterwards one node at a time: `get_app` prints the `tree:`, each node\'s address under `node addresses:`\n and the `version` line, which is the `base_version_id` to send (the tree and addresses go to the app\'s author alone);\n `set_app` with `app_id`, `base_version_id` and `nodes`, a map from a node\'s address to a\n patch, changes it (`model/changing-an-app`), and its answer states the permissions the change grants and removes. A table\n is changed by `set_tables` with its `table_id`, a merge patch over the document `get_table` answers. To go back,\n send the tree you saved before the change as `body` with `app_id`: it replaces `children` but keeps every App prop it\n omits (`words`, `scope`, `period`, `icon`), so null those. (On the CLI, `lotics app pull <app_id>` writes the tree as JSON and `lotics app apply\n <file>` sends an edited file, refused when the app has moved past the version the file was pulled at.) An app\n saved before apps were trees has no tree: `get_app` says so beside what it lets a person write, and `set_app` with\n `app_id` changes it by its whole tree as `body`, which replaces it, or by its name, description, icon and color alone.\n\n## The rules\n\n- **A `set_tables` call is all or nothing.** Every item is checked before any is written; an item\n without `table_id` makes a table, one with it patches that table.\n- **An app\'s labels decide its language.** A body names no locale \u2014 the words an\n app draws itself (its buttons, empty states, refusals) are in the language its\n own labels are written in, so a Vietnamese desk beside English ones answers in\n Vietnamese. The generated screens read the kit\'s\n pack, and a generated WRITE cannot: its refusals run on the server, so they are\n worded in that same language. Mix the two within one app and it answers in two. The app\'s own `words`\n (its words for a table, a field or an option) count as its labels: Vietnamese words over an English table\n make the app answer in Vietnamese.\n- **`set_tables` and `set_app` check all of it before anything is written**, and\n report every problem in one run rather than the first. The tables half of this\n page ends its keys with the rules the check enforces there, and \xA7 Rules of the app holds the app\'s, one sentence each\n under an id; a finding names its rule\'s id (`[register.filter]`), and\n `model/<rule id>` is that one rule\'s page.\n\n<!-- generated:start rules-model -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `model.schema` | Every key is one its table lists, holding the type its row gives, and every required key is stated. |\n| `model.names-declared` | Every alias a key names is declared: a table of the App\'s `tables`, a field of the table the node reads, an option of the select it names, an Act `key` of the App, a template of its `templates`, or an app. |\n| `model.tables` | A call declares at least one table and at most 50; send the rest in a second call. |\n| `model.rows-cap` | Rows are a sample: at most 200 per entity, 2000 per call and 2000 documents attached \u2014 a real data set goes in with `create_records` once the tables exist. |\n| `model.alias-unique` | An alias is unique where it is named: tables, fields and views in their table, options in their select. |\n| `model.label-unique` | A label is unique where it is found by its label: fields and views in their table, options in their select \u2014 and no view takes its table\'s own label, which names the whole-table grid. |\n| `model.template-kind` | An Act\'s `template` is an html, excel or word document template and an Export\'s `template` an html or excel one, never an email or pdf-form one. |\n| `model.template-marker` | Every marker of a Word template an Act makes is filled by that Act: a field of its entity, or a list of the child rows its record lists (the entity\'s own alias over several rows). |\n| `model.template-act` | *Noted, never refused:* In an App none of whose Acts runs an authored `workflow`, every html, excel or word template is made by an Act (`template`, `templates`) or filled by an Export \u2014 the note names the app over the table whose fields it prints most, where the Act belongs. |\n| `model.filter` | A filter \u2014 a view\'s, a rollup\'s \u2014 tests fields of the entity it reads, through links as `entity.field` hops each standing on the entity the last lands on, by options its select declares. |\n\n<!-- generated:end rules-model -->\n\n## Top level\n\nEvery table of keys on this page is generated from the grammar a table document and an app tree are written in, so it is the whole of what a key may hold.\n\n<!-- generated:start top-level -->\n\n#### set_tables call\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `tables` | list of [Table item](#table-item) | yes | One or more, all or nothing; each item is a table document with these keys added. It creates a table (a `label` and `fields` at least) or, with `table_id`, patches one: the document it states merges into the table\'s own |\n| `rows` | map of alias \u2192 list of [Row](#row) | no | First records of the tables the call creates, keyed by each one\'s `ref` |\n| `force` | boolean | no | Consent to losing what rows hold: a field deleted, an option removed |\n| `dry_run` | boolean | no | Answer what the call would change, writing nothing: for reading the grants a write would make or deciding `force`. A refused call writes nothing too and lists every reason, so a dry run checks no more than the call does |\n\n#### Table item\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `ref` | alias | no | A created table\'s handle in this call: other items link to it, and `rows` are keyed by it |\n| `table_id` | text | no | The table to patch; absent, the item creates one. A patch states only what changes: `null` removes a key, a list replaces whole |\n\n<!-- generated:end top-level -->\n\n**A `set_tables` item is a table document** (\xA7 Tables) with `ref` and `table_id` added, and the call\ntakes no other key. **A `set_app` body is an app tree** (\xA7 App tree), whose root is an App node: its `tables` and\n`templates` each map a local alias the tree names to the id of the table or document template it means, from the\n`set_tables` answer or `get_table`. The tree is the only home of the app\'s `name`, `description`, `icon` and `color`;\n`set_app` takes it whole to create, or as a merge patch over the App node with `app_id`, or `nodes` with `app_id` to change one, and with `base_version_id` (the\nversion you read) it refuses a write over a newer one. Nothing is built code. An unknown key is an error, never ignored.\n\nA smallest call, two tables of plain fields, a select and a two-way link \u2014 nothing else is needed to write it:\n\n```json\n{\n "tables": [\n {\n "ref": "customer",\n "label": "Customers",\n "singular": "Customer",\n "fields": {\n "name": {"type": "text", "label": "Name", "required": true},\n "tier": {"type": "select", "label": "Tier", "options": [{"alias": "gold", "label": "Gold", "color": "yellow"}, {"alias": "standard", "label": "Standard", "color": "gray"}]},\n "orders": {"type": "link", "label": "Orders", "table": "order", "multi": true, "pair": "customer"}\n }\n },\n {\n "ref": "order",\n "label": "Orders",\n "singular": "Order",\n "fields": {\n "code": {"type": "text", "label": "Code", "required": true},\n "customer": {"type": "link", "label": "Customer", "table": "customer", "multi": false, "pair": "orders"},\n "amount": {"type": "number", "label": "Amount"}\n }\n }\n ]\n}\n```\n\n### Aliases\n\nEvery `alias` is a lowercase slug \u2014 a letter, then letters, digits and\nunderscores (`unit_price`, `so_1001`). Aliases are how a document and an app tree\nname a field, an option, a view or a table; they are never shown to anyone. `label` is\nwhat a person sees.\n\nLabels must be unique within their namespace \u2014 two fields on one table, two options\non one field, two views on one table cannot share a label.\n\n## Tables\n\n<!-- generated:start table -->\n\n#### Table document\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | The table\'s name in the workspace |\n| `singular` | text | no | One row of this table, in the business\'s own words \u2014 what a create\'s button and panel name |\n| `description` | text | no | The table\'s description, written onto the table in the workspace, where a caller of an app\'s queries and workflows reads it beside them |\n| `fields` | map of alias \u2192 [Field](#field) | yes | The table\'s columns, each under its alias, which is its key here and so is never stated inside it |\n| `read_scope` | [Read scope rule](#read-scope-rule) | no | Which rows a member reads. Absent, every member with access to the table reads every row. |\n| `unique` | list (at least one) of list of alias (at least one) | no | Sets of fields whose values no two live rows share \u2014 each a list of field aliases (text, number, date, a single select, or a link holding one row \u2014 `multi: false`); a single text field states `unique: true` on itself instead of a one-field set. A create or update landing a second row with the same values is refused. |\n| `views` | list of [View](#view) | no | Saved views, in the order they are listed; with none, the table still opens on its default grid |\n| `title` | alias | no | The alias of the field that names a row wherever a link or a record shows one; absent, the first text field |\n| `colors` | any value | no | Row colors: a list of { filters, color }, the filter reading fields of this table by alias and a select\'s options by alias |\n| `records` | any value | no | How a row is recognised: the keys of the Record, Status and Cadence tables below but `title`, which sits above; a table they name is an id or a `ref`. A status `history` that names a `ref` no item of the call declares is made by the call as a table, `dates` add each date field they name, and a status with stages each stage and each open option they name none for its `<option>_at`, unless the document declares it; each is listed in the result\'s notes |\n| `natural_key` | list of alias (at least one) | no | The field aliases a row of this table is RECOGNISED by: an app\'s add and find-or-create reuse the row they find by these values, minting one only where nothing matches. A record tool or the API creates the row it is handed. |\n| `no_overlap` | [No overlap](#no-overlap) | no | No two rows hold overlapping spans \u2014 a create or update that would is refused, and so is a change to the dates or states of the row a span is read through that would make two rows overlap |\n| `checks` | map of alias \u2192 table check (`workflows/table-checks`) | no | The conditions a write of the table\'s rows may not meet, each under its alias: a merge patch edits one, and `null` removes it. Expressions name fields by alias |\n| `workflows` | map of alias \u2192 table workflow (`workflows/table-workflows`) | no | The effects that follow every committed write of the table\'s rows \u2014 the grid\'s, an import\'s, an agent\'s, any app\'s \u2014 each under its alias: a merge patch edits one, and `null` removes it. A body names fields, options and tables by alias |\n\n<!-- generated:end table -->\n\n<!-- generated:start rules-entity -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `entity.singular` | A table a List\'s `create` is mounted over \u2014 the register or a child List that adds rows \u2014 states its `singular`: one row of it, in the business\'s words. |\n| `entity.required-cycle` | Required links never wait on each other in a loop: no row of the loop could be created first. |\n| `entity.link-format` | A text field of `format: "link"` holds web addresses; one whose rows hold a phone or a mail address states no format. |\n| `entity.read-scope` | A `read_scope` clause names a group (`member_of`) or a field of its table reached through one-row links: `"self"` a member field, options a single select declares. |\n| `entity.unique` | A `unique` set names two fields or more of its table once each, each holding one value to compare \u2014 text, a number, a date, a single select, a one-row link \u2014 and is stated once; one field alone is `unique: true` on it. |\n\n<!-- generated:end rules-entity -->\n\n**`unique` is a set of values no two live rows share.** Each entry names fields\nof this table holding ONE value \u2014 text, number, date, a single select, a link\nwith `multi: false` \u2014 and a create or update landing a second row with the\nsame values is refused. A set of one text field is that field\'s own `unique:\ntrue`, so it is refused here. A create carries each set in the names its panel\nsends, so the panel can name the duplicate before the write does.\n\n**`singular` is what a create says** \u2014 `New Order`, `Add Claim line`, `H\u1ED3 s\u01A1\nm\u1EDBi` \u2014 while `label` names the table, so without it the button reads `New\nOrders`. Nothing derives it: English plurals are irregular, and no language is\nexempt. In a language without plural forms it is usually the label itself,\nless any word for the collection. A List that leaves `create` to its default adds nothing over a table that states\nnone (nor does the app\'s `words` state one); a `create` list stated over such a table is refused (`entity.singular`).\n\n**An app that only reads a table\'s rows draws them with `edit: false`** and states `create: false` and `remove: false` \u2014 a log of what the system sent, a copy of\nwhat another system holds. The app keeps the section and opens each row at rest, with no Add and no Delete; a party\nof it is picked, never found or minted; and the app writes no create, update or remove for it. It shapes that app, not who may write: a direct write \u2014 the records\nAPI, the chat agent, an import \u2014 follows the table\'s access, as on any table, and another app over the same rows\nstates its own tree.\n\n**`read_scope`** \u2014 which rows a member reads \u2014 is its own page, `model/read-scope`.\n\n## Read scope\n\n<!-- generated:start read-scope -->\n\n#### Read scope rule\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `any` | list of ([Read scope by role](#read-scope-by-role) \\| [Read scope by member](#read-scope-by-member) \\| [Read scope by option](#read-scope-by-option)) (at least one) | yes | A row is readable when ANY of these holds |\n\n#### Read scope by role\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `member_of` | text | yes | The id of a group (from Settings, or chat): whoever is in it reads the row |\n\n#### Read scope by member\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `through` | list of alias (1\u20133) | no | link aliases from this entity outward, each holding one row (`multi: false`) \u2014 the clause\'s field is on the entity the last hop lands on |\n| `field` | alias | yes | A member field alias on this entity, or on the entity `through` lands on |\n| `is` | `"self"` | yes | The members this column names on a row read that row |\n\n#### Read scope by option\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `through` | list of alias (1\u20133) | no | link aliases from this entity outward, each holding one row (`multi: false`) \u2014 the clause\'s field is on the entity the last hop lands on |\n| `field` | alias | yes | A single-select field alias on this entity, or on the entity `through` lands on |\n| `is` | list of alias (at least one) | yes | Its option aliases whose rows are readable \u2014 naming none would hide every row |\n\n<!-- generated:end read-scope -->\n\n**`read_scope` is a ROW rule, enforced by the platform.** It is resolved into the table\'s own row\nfilters, so an app, a workflow reading for a viewer, and the API all answer the same rows \u2014 a per-record\nvisibility field the app merely honours is a convention, not a gate. A `"self"` clause reads a column\nof one member or several, and every group and option a clause names must exist.\n\n**An app reads every table under the table\'s own `read_scope`**, and a tree has no key to lift it. A desk whose audience\nits sharing already decides is let into the rule by a group that holds that audience (`member_of`), stated on the\ntable.\n\n**A row rule is never inherited.** A child read under a private record states\nits own: the record\'s `member_of` clauses as they are, and its column clauses\n`through` the one-row link the child hangs by \u2014 over one hop or several, each a\nlink with `"multi": false`, since over several rows the rule would admit a\nreader any one of them admits while the editor\'s guard reads the first. Only the\nstatus history takes its record\'s rule.\n\n## Field\n\nEvery field carries the keys below, and its `type`\'s section adds the rest; a\ntype whose section names no `default` takes none. `label` may not contain `{` or\n`}` (formulas reference fields by label at the platform level). Every row in `rows` states each\n`required` field it carries (neither a default nor a `default_from` copy is applied to them), and a required\nLINK is written with its row: the table it names is created first, and tables\nwhose required links name each other are refused, since none of their rows could\never be created.\n\n**A table states who keys nothing.** Whether a person may change a field in an app is that app\'s statement\n(what its tree draws, and `edit: false`, \xA7 App tree): a post\'s id a publishing workflow brings back, a count a sync fills, a status an act moves\nare left out of the apps that do not save them, and written by a workflow, the records API, the chat agent and an\nimport as the table\'s access allows. A formula, a rollup, a lookup and an autonumber are no one\'s to key already.\n\n<!-- generated:start field -->\n\n#### Field\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"text"` \\| `"number"` \\| `"date"` \\| `"boolean"` \\| `"select"` \\| `"member"` \\| `"link"` \\| `"files"` \\| `"formula"` \\| `"rollup"` \\| `"lookup"` \\| `"autonumber"` | yes | What the field holds \u2014 each type takes the further keys its own section lists |\n| `label` | text | yes | The field\'s name in the workspace |\n| `description` | text | no | The field\'s description, written onto the field in the workspace |\n| `required` | boolean | no | Refuse a record whose cell for this field is empty. Every write path \u2014 create, update, an agent\'s tool call, a workflow\'s set \u2014 refuses the row by field name. A stored field is flagged only once every row holds a value. |\n\n<!-- generated:end field -->\n\n### `text`\n\n<!-- generated:start field-text -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | text | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. |\n| `unique` | boolean | no | Unique values required |\n| `format` | `"text"` \\| `"link"` \\| `"markdown"` | no | How the words are drawn \u2014 plain, as a link that opens, or as markdown |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `suggest` | [Suggest](#suggest) | no | A text field whose control offers, as the reader types, the distinct values a text field of a catalog table holds (a damage position\'s code among the codes on file); any text is still written |\n\n<!-- generated:end field-text -->\n\n### `number`\n\n<!-- generated:start field-number -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | number | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. |\n| `notation` | [notation](#number) | no | How the figure reads: { "style": "decimal" }; money, { "style": "currency", "currency": an ISO 4217 code (USD, VND) }; or a quantity, { "style": "unit", "unit": "percent" (10 reads "10%"), or a measured code (g, kg, t, l, m3, cbm, mm, cm, m, km, m2, min, h, day), which converts and scales within its dimension, or any other noun of at most 12 characters (ki\u1EC7n, pallet, TEU), which never does }. A currency or unit read on each row from a single select of that row is { "per_row": that select }. Omitted, a decimal. |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `min` | number \\| text | no | Refuse a create or update whose value is below this: a constant for a number, or a field \u2014 `<field alias>` of the same row, or `<link alias>.<field alias>` of the one row a link names \u2014 of the same type (a return date after the departure, a line\'s day inside its trip). On a figure whose unit or currency is per row (its notation\'s `per_row`) a constant bound is only 0; bound it by a field of the row |\n| `max` | number \\| text | no | Refuse a create or update whose value is above this: a constant for a number, or a field of the same row or of the one row a link names |\n\n<!-- generated:end field-number -->\n\n`notation` is how the number reads, and every surface reads it:\n`{ "style": "currency", "currency": "USD" }` prints as money in its code,\n`{ "style": "unit", "unit": "kg" }` as a quantity with its unit after it,\n`{ "style": "unit", "unit": "percent" }` as a percent \u2014 10 is "10%", never\nmultiplied \u2014 and `{ "style": "decimal" }`, or no notation, as a plain number. One\nkey, so a figure reads one way.\n\nAn absent number is drawn blank, except a `sum` or count rollup read as a meter\'s measure: nothing accumulated yet reads 0.\n\nA unit \u2014 `{ "style": "unit", "unit": "kg" }` \u2014 is what a figure counts or\nmeasures; `percent` aside, it is measured or counted. A **measured** unit is a code of one catalog \u2014 mass `g`\n`kg` `t`, volume `l` `m3` `cbm` (a cubic metre under freight\'s name), length\n`mm` `cm` `m` `km`, area `m2`, duration `min` `h` `day` \u2014 so a figure typed in\nanother unit of its dimension converts (`12,5 t` into a `kg` field is 12 500),\nand a tile or a chart reads it in the largest unit it reaches (12 500 kg as\n12,5 t\u1EA5n; CBM never scales) while a cell, a fact and a column always read in the\nfield\'s own. Any other noun of at most 12 characters (`ki\u1EC7n`, `pallet`, `TEU`) is a\n**counted** unit: a word after the figure that never converts. A measured unit is\nwritten as its code \u2014 `t\u1EA5n`, `KG` or `m\xB3` is refused, naming the code. A formula\nstates its own as its `notation`; a rollup that keeps the value (`sum`, `avg`,\n`median`, `min`, `max`, `range`) and a lookup carry the unit of the figure they\nread, exactly as they carry a currency, and a count carries none. A quantity is a\nnumber with its unit, never words (`3 cartons` in a text field): only a number\nsums, converts and reads down a column.\n\nA field\'s cells always hold figures in its own unit, so moving it between two\nunits of one dimension (`kg` to `t`) converts every stored figure \u2014 a change made\nin the workspace, in the field\'s settings (which say how many first) or through\n`set_tables`. Any other change of unit\nrelabels.\n\nA figure whose unit or currency varies by row names a single select of its own\nrow as `per_row` \u2014 `{ "style": "unit", "unit": { "per_row": "uom" } }`,\n`{ "style": "currency", "currency": { "per_row": "currency" } }` \u2014 or a lookup of\none through a one-link, a line reading its shipment\'s currency. That select\'s\noptions are the vocabulary: each label a unit (`chi\u1EBFc`, `kg`), or an ISO 4217 code\n(`USD`). A label outside it is refused when the figure is written and when the\nselect is \u2014 its options, its type or its deletion while a figure names it. A row\nwhose select is empty reads its figure bare. Every app reads each row\'s figure in\nits own row\'s unit, and a sum never mixes units: totals are one per unit (past three units a total the runtime draws over a set of rows \u2014\nsummary line, group totals and feet, a dashboard\'s table foot \u2014\nstates the count alone; a reading an author states draws every unit), a chart draws one at a time. A rollup that keeps the value (`sum`, `avg`, `min`\u2026)\nover such a figure stands only where the child\'s select is a lookup, through\nthe rollup\'s own link, of a select on this table \u2014 the rollup reads that\nselect\'s unit; otherwise it is refused. A lookup of such a figure stands only\nwhere its table also looks up the figure\'s select through the same one-link,\nand reads in that lookup; otherwise it is refused. A count is unaffected. Moving a field between a\nfixed and a per-row unit relabels.\n\n### `date`\n\n<!-- generated:start field-date -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | text | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. A date string in the field\'s format. |\n| `format` | `"date"` \\| `"datetime"` \\| `"date_range"` \\| `"datetime_range"` | no | Whether the field holds a day or a moment, alone or as a span |\n| `timezone` | text | no | IANA timezone |\n| `derive_from` | `"created_at"` \\| `"updated_at"` | no | Auto-populate from the row\'s system timestamp; the field becomes read-only. |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `min` | number \\| text | no | Refuse a create or update whose value is below this: a constant for a number, or a field \u2014 `<field alias>` of the same row, or `<link alias>.<field alias>` of the one row a link names \u2014 of the same type (a return date after the departure, a line\'s day inside its trip). On a figure whose unit or currency is per row (its notation\'s `per_row`) a constant bound is only 0; bound it by a field of the row |\n| `max` | number \\| text | no | Refuse a create or update whose value is above this: a constant for a number, or a field of the same row or of the one row a link names |\n\n<!-- generated:end field-date -->\n\nA `default` is refused beside `derive_from`: the platform stamps that date.\n\n### `boolean`\n\n<!-- generated:start field-boolean -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | boolean | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n\n<!-- generated:end field-boolean -->\n\n### `select`\n\n<!-- generated:start field-select -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default` | list of alias | no | Value pre-filled into a new record when none is supplied for this field. Applied on create only \u2014 existing records are never backfilled. Option alias(es) this field declares \u2014 one for single-select. |\n| `options` | list of [Select option](#select-option) (at least one) | yes | The choices, in the order every picker and every ladder lists them |\n| `multi` | boolean | no | Allow multiple selections |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n\n#### Select option\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable local alias, unique within the field |\n| `label` | text | yes | Display label for the option |\n| `color` | [colour](#select) | no | The colour the option\'s badge is drawn in; absent, the platform gives it one no sibling option holds |\n| `mark` | [mark](#select) | no | The option\'s own mark, drawn in place of its colour dot wherever the option is shown: the brand it is ({kind: "brand", name: one of facebook, instagram, threads, meta, tiktok, google-ads, zalo, linkedin, x, google-meet, youtube, telegram, whatsapp, gmail, google-drive, outlook, kiotviet, misa, lark, payos}) a kit glyph ({kind: "icon", name: "wrench"}) or the face of who it is, drawn from its label ({kind: "avatar", name: "person" or "organization"}). Every option of a field has one, or none does. A mark a reader does not draw falls back to the dot |\n\n<!-- generated:end field-select -->\n\nA patch of a select states its whole `options` list: a list replaces whole, so an option it leaves out is deleted. To\nrename or recolour one option, restate every option kept, each with its alias. Rows still holding a deleted option refuse\nthe patch unless `force`.\n\n```json\n{\n "options": [\n { "alias": "draft", "label": "Draft" },\n { "alias": "confirmed", "label": "Confirmed" },\n { "alias": "shipped", "label": "Shipped", "color": "green" }\n ]\n}\n```\n\n`color` is one of: `red`, `orange`, `amber`, `yellow`, `lime`, `green`,\n`emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`,\n`fuchsia`, `pink`, `rose`, `slate`, `gray`, `zinc`, `neutral`, `stone`.\nEvery option is drawn in its colour wherever it shows \u2014 a cell, a row\'s line,\na record, a picker, the bar a reading splits the rows by.\n\nAn option may carry its own `mark`, drawn in place of its colour dot wherever\nthe option is shown \u2014 a stage, a chip, a filter, a fact, an entry of a log, a\nreading\'s part, a lookup of the select on another table. Where the select is its\nrows\' `records` `mark`, a brand or glyph stands on the row\'s lead tile in the option\'s\ncolour, and an option with none leads with its colour dot there. A select a reader scans\ndown a column \u2014 how a payment was made, the channel, the mode \u2014 states one on every\noption, since a glyph reads before its word: the brand it IS\n(`{"kind": "brand", "name": "tiktok"}` \u2014 one of `facebook`, `instagram`,\n`threads`, `meta`, `tiktok`, `google-ads`, `zalo`, `linkedin`, `x`,\n`google-meet`, `youtube`, `telegram`, `whatsapp`, `gmail`, `google-drive`,\n`outlook`, `kiotviet`, `misa`, `lark`, `payos`), a glyph the kit draws\n(`{"kind": "icon", "name": "wrench"}`; any other name is refused), or \u2014 where the\noptions name people or organisations who are no member and no row \u2014 the face of who\nit is, drawn from its label (`{"kind": "avatar", "name": "person"}` or\n`"organization"`). Every option of a select has one, or none does: a run of chips\nwhere one carries no mark reads as the one missing something. `set_tables` writes the\nmarks onto the table. The glyph names are `model/field/option-glyphs`.\n\n```jsonc\n"options": [\n { "alias": "short_video", "label": "Short video", "color": "zinc", "mark": { "kind": "brand", "name": "tiktok" } },\n { "alias": "print", "label": "Print", "color": "amber", "mark": { "kind": "icon", "name": "newspaper" } }\n]\n```\n\n### Option glyphs\n\nThe kit\'s glyph names an option\'s `mark` takes:\n\n<!-- generated:start field-select-glyphs -->\n\nactivity, align-center, align-left, align-right, align-vertical-justify-center, align-vertical-justify-end, align-vertical-justify-start, arrow-down, arrow-down-a-z, arrow-down-up, arrow-down-wide-narrow, arrow-left, arrow-left-from-line, arrow-right, arrow-right-from-line, arrow-right-left, arrow-up, arrow-up-down, arrow-up-wide-narrow, arrow-up-z-a, ban, banknote, bed, bell, between-horizontal-start, between-vertical-start, bold, bolt, book-marked, book-open, book-text, bot, box, brackets, brain, briefcase, building-2, calculator, calendar, calendar-clock, calendar-off, camera, car, chart-column, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down-up, chevrons-up-down, circle-alert, circle-check, circle-x, clipboard-list, clipboard-paste, clock, code, code-xml, columns-3, columns-3-cog, combine, construction, container, copy, credit-card, database, decimals-arrow-left, decimals-arrow-right, dollar-sign, download, ellipsis, eraser, expand, external-link, eye, eye-off, facebook, file, file-csv, file-down, file-question, file-spreadsheet, file-stack, file-text, file-up, folder, folder-closed, folder-open, folder-pen, form, funnel-plus, funnel-x, gauge, globe, gpu, grid-2x2, grip-vertical, group, hand-coins, hash, heading, heading-1, heading-2, heading-3, history, house, image, inbox, info, instagram, italic, keyboard, languages, layout-dashboard, layout-grid, library-big, link-2, link-2-off, linkedin, list, list-checks, list-collapse, list-filter, list-filter-plus, list-ordered, list-tree, loader, lock, lock-keyhole, lock-keyhole-open, lock-open, log-in, log-out, mail, map-pin, maximize-2, megaphone, menu, message-circle, message-circle-question-mark, message-square, messages-square, mic, minimize-2, minus, monitor, mouse, mouse-pointer-click, music, newspaper, notepad-text-dashed, package, paint-bucket, paintbrush-vertical, palette, panel-left, panel-left-close, panel-left-open, panel-right, panel-right-close, panel-right-open, paperclip, pause, pencil, percent, phone, pin, pin-off, plane, play, plug, plus, receipt, rectangle-ellipsis, redo, refresh-cw, repeat, rotate-ccw, rotate-cw, rows-3, scan, scan-line, scissors, search, send, settings, share, share-2, shield, shield-alert, shield-check, shopping-cart, sigma, sliders-horizontal, smile, smile-plus, snowflake, sparkles, split, square, square-check, square-pen, square-sigma, stethoscope, sticky-note, strikethrough, table, table-2, tag, target, text-quote, text-wrap, thumbs-down, thumbs-up, ticket, trash, trending-down, trending-up, triangle-alert, truck, tv-minimal, twitter, type, underline, undo, upload, user, user-check, user-pen, users, utensils, waypoints, workflow, wrench, x, zap, zoom-in, zoom-out\n\n<!-- generated:end field-select-glyphs -->\n\n### `member`\n\nA person picker over the workspace\'s members. No default. With no role it is still drawn \u2014\nface and name, ranked as a `party` \u2014 in a screen\'s `columns` and in the register\na record draws of these rows.\n\n<!-- generated:start field-member -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `multi` | boolean | no | Allow multiple selections |\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n\n<!-- generated:end field-member -->\n\n### `link`\n\n<!-- generated:start field-link -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default_from` | text | no | Copied when a record is made off the row a one-row link of this entity names \u2014 `<link alias>.<field alias>` \u2014 or, reading on through one-row links, off the row the last of them names \u2014 `<link alias>.<link alias>.<field alias>` \u2014 and never re-read, so the source changing later leaves the row alone. A value the add states wins; a select copies each option as this field\'s option of the same alias. |\n| `options_where` | [filter](#views) | no | Which rows of the target this link may point at \u2014 an `and` group of plain conditions over the TARGET table\'s own fields, each `field_key` a field alias. It narrows every picker of the link, and a save naming a row outside it is refused on every path \u2014 an app, the grid, an import, a workflow, the API. |\n| `same` | list of alias (at least one) | no | One-row links this row and each row this link points at must name alike \u2014 a fee\'s invoice is one of its own visit\'s, a box\'s seal one of its own shipping line\'s: each a one-row link of both tables to the same table. It narrows the picker\'s read to the rows naming what this row names, a row made from the picker names it too (set, never asked), and a write is refused wherever it lands \u2014 on the link, or on what the two rows compare. |\n| `table` | text | yes | The table the link points at: its alias, or the `ref` of another item of the call |\n| `multi` | boolean | yes | true: the link holds many rows; false: one. Where the link is paired, at most one of the two sides holds one |\n| `pair` | `true` \\| alias | no | Makes the link two-way: the alias of its partner field on the other table, which names this one back, or `true` to have the alias derived. Stated on one table, the partner is made on the other with the inverse cardinality; a many-to-many link states both sides |\n\n<!-- generated:end field-link -->\n\n**`multi` is stated on every link:** `true` holds several rows, `false` one \u2014 one customer on an\ninvoice, one project on a device. There is no default.\n\nA two-way link states `pair` on one table, as `true` or a free alias, and the partner is made on the\nother table with the inverse cardinality; a many-to-many link states both sides, since the inverse of\nmany is not decided. A pair stated on both sides is made once. Both tables are written in the one\ncall (`table` names the other item\'s `ref`). A link with no `pair` is one-way, and nothing is created on the target. Pair a link when\nthe target\'s own record should list what points at it, and leave it unpaired when it should not: either\nway the record plan draws the relation as a section on the side it points at, so an unpaired link costs\nthe target nothing. Of two paired links at most one holds one row.\n\n```jsonc\n// a field of table "order"\n"customer": { "type": "link", "label": "Customer", "table": "customer", "multi": false, "pair": "orders" }\n```\n\n```jsonc\n// a field of table "customer"\n"orders": { "type": "link", "label": "Orders", "table": "order", "multi": true, "pair": "customer" }\n```\n\n### `files`\n\nA row attaches documents to it (\xA7 Rows). Each file\nis drawn by its kind, with no key to choose: a picture (an image, a video) as its\nthumbnail, a document (a PDF, a sheet) as its type\'s badge and its filename.\n\n### `formula`\n\nA formula states its keys on the field itself, beside `type` and `label`:\n`{ "type": "formula", "label": "Total", "expression": "{quantity} * {unit_price}", "notation": { "style": "currency", "currency": "USD" } }`.\n\n<!-- generated:start field-formula -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `expression` | text | yes | The expression, over fields of THIS entity by alias in braces \u2014 `{quantity} * {unit_price}` |\n| `output_type` | `"number"` \\| `"text"` \\| `"date"` \\| `"datetime"` \\| `"boolean"` \\| `"select"` | no | Absent, it is what the expression yields. Stated, it is checked against that and refused where they differ (a datetime is never stated date) |\n| `notation` | [notation](#number) | no | How the figure reads: { "style": "decimal" }; money, { "style": "currency", "currency": an ISO 4217 code (USD, VND) }; or a quantity, { "style": "unit", "unit": "percent" (10 reads "10%"), or a measured code (g, kg, t, l, m3, cbm, mm, cm, m, km, m2, min, h, day), which converts and scales within its dimension, or any other noun of at most 12 characters (ki\u1EC7n, pallet, TEU), which never does }. A currency or unit read on each row from a single select of that row is { "per_row": that select }. For a formula yielding a number. |\n| `link` | `true` | no | The formula yields a URL as text, drawn as a link that opens. |\n| `options` | list of [Select option](#select-option) (at least one) | no | The categories the formula yields, drawn as a single select\'s options are (read-only). The expression yields one of them as `{this_field:option}`, or null \u2014 `{days_idle} > 30 ? {warmth:cold} : {warmth:hot}`. Omit for a formula yielding a plain value. |\n\n<!-- generated:end field-formula -->\n\n`output_type` is what lets a role or a screen clause accept a computed value: a\ncaption over a derived name (`output_type: "text"`), a period over a settled date\n(`"date"`). A formula declaring no `output_type`, `notation` or `link` says nothing\nabout its result, and every rule that needs one refuses it by name. A formula stating\n`options` is a computed category: read-only, and read as a single select\nwherever it is drawn \u2014 a column, a filter, a reading\'s `by` or `where`.\n\n**A select reaches a formula as the KEYS of its chosen options**, a list \u2014\nnever their labels, and never their aliases \u2014 and a document has no keys: the\nworkspace mints them when the table is made. So an option is named in a formula\nas `{field:option}`, both aliases, and the write puts that option\'s key in its\nplace: `includes({kind}, {kind:crate})` for a select holding one or several,\n`{kind}[0] == {kind:crate}` for a single one. A select compared to its own words\n(`{kind} != "Crate"`) matches no row and computes the other branch everywhere,\nso the check refuses it and names the token. A select looked up from another\ntable is tested there, in a formula of its own, and that result looked up.\n\n**The language.** The check refuses a call or a name the platform cannot run, naming what to write:\n\n<!-- generated:start field-formula-language -->\n\n- Operators: `+ - * / %`, `== != > < >= <=`, `&& || !`; `+` also joins text\n- Numbers: arithmetic is exact decimal (`0.1 * 3` is `0.3`), a division that does not end the nearest double; a comparison between two numbers reads both at 15 significant digits; `round` rounds a half away from zero (`round(-2.5)` is `-3`)\n- Conditionals: a ternary only \u2014 `{amount} > 100 ? "High" : "Low"`\n- Not supported: optional chaining (`?.`), nullish coalescing (`??`), template literals, arrow functions \u2014 use `get(obj, "path", default)`, `coalesce(v1, v2)`\n- Helpers are these names, spelled exactly; a spreadsheet function (`IF`, `SUM`, `LEN`, `DATEDIF`) is none of them, and a formula calling one is refused\n- Math: round(n,decimals?), ceil(n), floor(n), abs(n), min(a,b), max(a,b), sum(arr), mean(arr), clamp(n,min,max), percentage(part,total,decimals?), pow(base,exp), sqrt(n), mod(n,divisor)\n- Strings: upper(s), lower(s), trim(s), capitalize(s), length(s), contains(s,search), join(arr,sep), split(s,sep), replace(s,search,rep), replaceAll(s,search,rep), removeChars(s,chars) (s without any character that occurs in chars), removeSpaces(s) (s without any whitespace), startsWith(s,prefix), endsWith(s,suffix), substring(s,start,end?), padStart(s,len,char), padEnd(s,len,char), numberToWords(n, lang?) (lang \'vi\' default, or \'en\'), uuidFrom(s) (the same UUID for the same text)\n- Lists: includes(list,value), first(list), last(list), unique(list), compact(list) \u2014 length(list) counts one\n- Dates: now(), formatDate(d,fmt), addDays(d,n), subDays(d,n), addHours(d,n), subHours(d,n), addMinutes(d,n), subMinutes(d,n), startOfDay(d), endOfDay(d), differenceInCalendarDays(later,earlier), differenceInHours(later,earlier), differenceInMinutes(later,earlier), isBefore(d1,d2), isAfter(d1,d2), isSameDay(d1,d2), isToday(d), isWithinRange(d,start,end), meetsBounds(value,kind,{gt,gte,lt,lte},tz?), parseDate(d)\n- Null/type: isNull(v), isEmpty(v) (also true for "" and []), coalesce(v1,v2,...), isString(v), isNumber(v), isBoolean(v), isArray(v), toNumber(v), toFigure(v), toString(v)\n- Other: formatCurrency(amount,locale,currency), formatDecimal(value,decimals,locale) (grouped quantity, no symbol), get(obj,"path",default?)\n- Empty cells: a cell nobody filled is null inside a formula, whatever its type; one holding 0, false or "0" is not empty. Test it with `isEmpty({note})` \u2014 `{note} == ""` and `{done} == false` are false on an unset cell\n- Arithmetic over an empty cell: `+` and `-` read it as 0 beside a value (`{fee} + {surcharge}` is `{fee}` when the surcharge is empty, null when both are); `*`, `/`, `%` and a unary `-` yield null (`{price} * {qty}` is null, not 0, when the quantity is empty)\n- No helper throws on an empty cell: the math helpers and toNumber return null, the string helpers "". A value of the wrong type still errors. When EVERY field a formula reads is empty it is null \u2014 unless it reads each only as the argument of isEmpty, isNull or isNotNull (`!isEmpty({file})` is false there, not null)\n\n<!-- generated:end field-formula-language -->\n\n### `rollup`\n\nAggregates the records reached through a link on this table.\n\n<!-- generated:start field-rollup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `filter` | [filter](#views) group | no | Only linked records matching this filter are aggregated; one condition on its own is a group of one. Every `field_key` in it names a field alias on the linked entity, and a select condition\'s value names an option alias there. A traversal node reaches past that entity, so its `path` hops and inner `field_key` are fully-qualified `entity.field` aliases. |\n| `via` | alias | yes | The alias of a link of this table to roll up from |\n| `op` | [operation](#rollup) | yes | The operation over the linked rows |\n| `field` | alias | no | The alias of the field on the linked table the operation reads; absent for `count` |\n\n<!-- generated:end field-rollup -->\n\nA rollup states `via` (a link of this table), `op` and \u2014 except for `count` \u2014 `field`, an alias on the\nlinked table; `op` is one of `count`,\n`sum`, `avg`, `median`, `min`, `max`, `range`, `empty`, `filled`,\n`percent_empty`, `percent_filled`, `unique`, `percent_unique`, `earliest`,\n`latest`, `date_range`, `checked`, `unchecked`, `percent_checked`,\n`percent_unchecked`. The operation must be one the aggregated field\'s type\nallows \u2014 `sum` over a number, `earliest` over a date, `filled` over any stored or\nformula field. A lookup is never rolled up: roll up the child\'s own field, or a formula\nover it. A rollup states no `notation`: `sum`, `avg`, `median`, `min`, `max` and `range`\nread in the aggregated figure\'s, a count as a plain number, and a `percent_*` as a\npercent (0\u2013100), in the app as in the table.\n\n### `lookup`\n\nDisplays a field from the linked records: `via` names a link of this table, `field` a field of the table it\npoints at, and `order_by` (`field`, `direction`) picks the one row whose value shows.\n\n<!-- generated:start field-lookup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `via` | alias | yes | The alias of a link of this table to look up through |\n| `field` | alias | yes | The alias of the field on the linked table to show |\n| `order_by` | [Lookup order](#lookup-order) | no | Show ONE linked row\'s value \u2014 the first in this order \u2014 rather than every linked row\'s |\n\n#### Lookup order\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A field alias on the linked table the rows are ordered by |\n| `direction` | `"asc"` \\| `"desc"` | yes | Which end of that order the one row is taken from |\n\n<!-- generated:end field-lookup -->\n\nInside a formula, a lookup holding one value is that value (`{due_soon}` is\n`true`, not `[true]`); several values are a list.\n\n### `autonumber`\n\n<!-- generated:start field-autonumber -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `prefix` | text | no | Literal prefix prepended to every display value (e.g. \'KH-\' \u2192 \'KH-001\'). Ignored when `template` is set. |\n| `padding` | integer | no | Zero-pad the integer to this width. Default 1 (no padding). 3 \u2192 \'001\', \'012\', \'123\', \'1234\' (overflow uses the actual width). Ignored when `template` is set. |\n| `template` | text | no | Format template with placeholder tokens evaluated at insert time. Tokens: {N} (raw integer), {N:W} (zero-padded to width W, e.g. {N:3} \u2192 001), {YEAR} (4-digit year), {YEAR:2} (2-digit year), {MONTH} (2-digit month), {DAY} (2-digit day). Date tokens use the workspace timezone. Example: \'HM-{YEAR}-{N:3}\' yields \'HM-2026-001\'. Stored as the composed string; subsequent template edits do NOT re-format existing rows (date tokens would lose the original creation date). |\n| `next` | integer | no | Sets the number the next row takes, once, as the patch is written; never below one past the highest number issued. On a field added to a table with rows, those rows are numbered from it in creation order. It is no setting of the field: a read of the table shows none |\n\n<!-- generated:end field-autonumber -->\n\nA refused write and a dry run take no number, so a table\'s numbers run without a gap; a removed row\'s number is not\nissued again.\n\n<!-- generated:start rules-field -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `field.default` | A default names options its select declares \u2014 one on a single select \u2014 and a date stamped by `derive_from` states none. |\n| `field.default-from` | A field\'s `default_from` reads `<link>.<field>` off the row a one-row link of its table names \u2014 a link no copy fills \u2014 or, through further one-row links, `<link>.<link>.<field>` off the row the last names, into a field of the same type, date format and notation that `derive_from` does not stamp: a lookup only through a one-row link, a link only into one to the same table, a select\'s option as this field\'s of the same alias (one it lacks is not copied), several values only into a field holding several. The server copies it on every add that fills the link and leaves the field empty, so a List\'s `create` sure to fill the link \u2014 a required one, or the record it files the row under \u2014 does not ask it by default. |\n| `field.option-mark` | An option\'s `mark` is one the kit draws, and a select\'s options mark every one or none. |\n| `field.unit-select` | A notation\'s `per_row` names a single select of the same row, or a lookup of one through a one-link, whose every option label is a unit, or an ISO 4217 code. |\n| `field.link` | A link targets a table of the call or the workspace: its `pair` a link on that table naming this one back \u2014 and of two paired links at most one reads one row (`multi: false`). |\n| `field.formula` | A formula parses, reads fields of its own table by alias, names an option as `{field:option}` of one a select declares, and compares a select to its options\' keys, never their words. |\n| `field.formula-type` | A formula yields what it states \u2014 its `output_type` (the type the platform stores: a `datetime` is never stated `date`), else a figure where it states a `notation` and text where it states `link` \u2014 as the platform infers it from the expression when the field is created. |\n| `field.rollup` | A rollup aggregates, through a link of its table (`via`), a field of the linked table (`field`) by an operation (`op`) that field\'s type takes \u2014 never a figure read in each row\'s own unit or currency \u2014 filtered on fields of the linked table. |\n| `field.lookup` | A lookup reads (`field`), through a link of its table (`via`), a field of the linked table \u2014 a figure read in each row\'s own unit or currency only where this table looks up that row\'s select through the same one-row link \u2014 ordered by a field of it (`order_by`). |\n| `field.computed-cycle` | Computed fields never wait on each other in a loop: each is computed after what it reads. |\n\n<!-- generated:end rules-field -->\n\n## Views\n\nSaved views live under the table they belong to. Every field reference is a\nfield ALIAS on that table. A `set_tables` patch of an existing table refuses `views`: they are stated when the table is\ncreated, and change afterwards in chat.\n\n<!-- generated:start views -->\n\n#### View\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable view alias, unique within the entity |\n| `label` | text | yes | Display name of the view |\n| `description` | text | no | The view\'s description, written onto the view in the workspace |\n| `columns` | list of [View column](#view-column) (at least one) | no | The columns the view shows, in this order and no others; absent, every field |\n| `filters` | [filter](#views) | no | The rows the view keeps |\n| `sort` | [sort](#views) | no | The order the view reads its rows in |\n| `summary` | map of text \u2192 text | no | Field alias \u2192 the operation its footer cell states |\n| `frozen_columns` | integer \\| `null` | no | How many leading columns stay in place while the rest scroll |\n\n#### View column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field_alias` | alias | yes | A field of this entity |\n| `visibility` | `"visible"` \\| `"hidden"` | yes | Field visibility state: \'visible\' = shown to everyone, \'hidden\' = not shown by default but members can toggle |\n| `width` | number | no | The column\'s width, in pixels |\n\n<!-- generated:end views -->\n\nA filter is a group \u2014 `{ "node_type": "group", "logic": "and" | "or",\n"children": [ \u2026 ] }` \u2014 or one condition on its own, `{ "node_type":\n"condition", "type": "select", "field_key": "tier", "operator": "has_any_of",\n"value": ["gold"] }`. A sort is a list of `{ "field_key": \u2026, "order": "asc" |\n"desc" | null }`. A condition\'s `type` is the field\'s type and its `operator` is\none that type admits \u2014 `has_any_of` / `has_none_of` / `has_all_of` / `is_empty` /\n`is_not_empty` for a select, `equals` / `greater_than` / `less_than` for a\nnumber, `on` / `before` / `after` / `between` for a date, `contains` /\n`is_any_of` for text. A select condition\'s `value` names option ALIASES.\n\n<!-- generated:start rules-view -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `view.fields` | A view\'s columns, summary, sort and filters name fields of its table. |\n\n<!-- generated:end rules-view -->\n\n## Groups and templates\n\nA member group and a document template are made on their own, outside a table document and an app tree. A tree names a\ntemplate by its alias. A table document names a group by its id, where a `read_scope` clause (`member_of`),\na stage owner (`group`) or a check\'s `member_of("<id>")` takes one. `get_workspace` kind `group` lists the groups with their ids and members; `set_group` creates,\nchanges or deletes one (an admin\'s verb).\n\n`{{name}}` in an `html` or `email` template is filled from the workflow\'s data. Inline content is refused where it holds a\nscript, a frame, a `<link>`, an inline event handler or a `javascript:` URL; an `html` one may be a fragment, kept as\nwritten and printed as a whole document.\n\nA template\'s markup is the `template` of `set_template`, as one JSON string. A paper shaped like a counterparty\'s \u2014 a letterhead, a reference line, a seal, a signature block \u2014 is an `html` template with a shell around the body; `document_templates` covers the types.\n\n**What an act\'s template is handed** is its record\'s own fields by alias: a\nselect, a member and a link as their words (a link as the linked row\'s title), a\nnumber, a date and a computed value that declares its kind raw (`2800000`,\n`2026-09-14`) and a files field as its file cells (`{id, name, mime, url}`, the photos in the order held; a child row\'s\nfiles likewise) \u2014 never a computed select. On one record it is\nhanded, too, the rows of each child List the record draws, listed under that child\'s\nlink\'s name for `{{#each <link>}}` (and under the child entity\'s alias too): every row filed under the record, oldest first, each\nby its own fields as the record\'s are, less its link back to the record. Over\nseveral rows the rows are listed under the table\'s alias, for\n`{{#each <alias>}}`, without their child rows.\n\n**Photos in a paper.** A file cell is drawn only if whoever makes the paper may read that file, by the rule the file\nproxy applies; a file they may not read is never drawn. html: `{{#each photos}}<img src="{{url}}">{{/each}}`, each\nphoto inline, only those the data\'s file cells carry (a url typed into a text field stays a link). Word:\n`{{IMAGE picture(cover, 8)}}` for one file cell, and `{{FOR p IN photos}}{{IMAGE picture($p, 8)}}{{END-FOR p}}` for\nmany. Excel: `{{cover | picture:6}}` as the whole cell, and `{{#each photos}}` with `{{this | picture}}` for many, each\nrow growing to its picture. The number is the width in centimetres (8 by default, 6 in Excel); the height follows the\npicture. `picture` takes one image file cell: a list, a file that is no image and a file that cannot be read are\nrefused by name. Photos are embedded at most 1600 pixels across.\n\nHelpers print a raw value in the template\'s `locale` \u2014 `vi` or `en`, the organization\'s language where it states\nnone; each prints `""` for an empty value:\n\n| Helper | Prints (`vi` / `en`) |\n|---|---|\n| `{{money total "USD"}}` | `1.250,50 US$` / `$1,250.50`, in the currency\'s minor units; no code, a number |\n| `{{number qty}}` | `1.234,5` / `1,234.5`: grouped, up to 2 decimals |\n| `{{date issued_on}}` | `14/09/2026`; a date-fns pattern as the second argument, `{{date at "dd/MM/yyyy HH:mm"}}`, literal text quoted (`"\'Ng\xE0y\' dd"`). A moment prints on the workspace\'s timezone |\n| `{{words total "USD"}}` | `M\u1ED9t ngh\xECn hai tr\u0103m n\u0103m m\u01B0\u01A1i \u0111\xF4 la M\u1EF9 n\u0103m m\u01B0\u01A1i xu` / `One thousand two hundred fifty US dollars and fifty cents`; no code, VND (`\u0111\u1ED3ng`) |\n| `{{qr link}}`, `{{code128 position 6}}` | the value as a QR code, or as a Code 128 barcode (ASCII text only), a picture the width of the optional second argument in centimetres, by default sized to the value (a module prints 0.5 mm in a QR code, 0.33 mm in a barcode); a width too narrow to scan is refused, naming the least that does; not in an email template |\n| `{{date (add_days issued_on -1)}}` | `13/09/2026`: the date shifted by whole days (`-1`: the day before), for `date` to print; a number field as the days, empty shifting none |\n\nA value it cannot read (`{{money "abc"}}`, `{{date "14/09/2026"}}`) fails the render. A field named like a\nhelper and written alone (`{{number}}`) prints its own value.\n\nA field printed as a code in the other types: Excel `{{position | qr}}` or `{{position | code128:6}}` as the whole\ncell and the last helper \u2014 the picture is anchored on the cell and its row grows to hold it; Word `{{IMAGE qr(position)}}` or `{{IMAGE code128(position, 6)}}`. An empty field draws no picture.\n\n## Rows\n\n`rows` of a `set_tables` call are the first records of the tables the call creates, keyed by each one\'s `ref`;\na patch of an existing table seeds none, so write its rows into the table. A sample, not a data set: rows past it go in with `create_records` once the tables exist, in the batches its description bounds.\n\n```jsonc\n"rows": {\n "customer": [\n { "ref": "acme", "fields": { "name": "Acme Trading", "tier": "gold" } }\n ]\n}\n```\n\n`rows` sits beside `tables` in the call, not inside a table.\n\n<!-- generated:start rows -->\n\n#### Row\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `ref` | text | yes | Local handle for this row, referenced by other rows\' link fields |\n| `fields` | map of text \u2192 any value | yes | Field alias \u2192 the value, read against the field\'s declared type |\n\n<!-- generated:end rows -->\n\n<!-- generated:start rules-rows -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `rows.ref` | A row\'s `ref` is unique within its table. |\n| `rows.required` | A row states every field its table requires: it is created with what it states, its fields\' defaults and copies unapplied. |\n| `rows.value` | A row\'s value fits its field: an option alias for a select, `"<table ref>:<row ref>"` of a row of the linked table in this call for a link, `"self"` for a member, a `fil_` id for files, a date in the date grammar (an hour only on a datetime), a text, number, yes/no or null otherwise \u2014 and none on a computed field. |\n\n<!-- generated:end rules-rows -->\n\nA `ref` is lowercase letters, digits and underscores, and is never persisted.\n\n`fields` is keyed by field alias, and every value is read against the field\'s\nDECLARED type:\n\n| Field type | Value |\n|---|---|\n| `text` / `number` / `boolean` | the value itself |\n| `date` | `"2026-03-14"`, or a relative expression (below) |\n| `select` | the option ALIAS \u2014 `"gold"`, or `["gold","vip"]` for a multi-select |\n| `link` | `"<table ref>:<row ref>"` naming another row of this call \u2014 `"customer:acme"`, or an array for several; a paired link is stated on ONE side (the child\'s link to its parent) and its partner fills itself |\n| `member` | `"self"` only \u2014 the member making the call |\n| `files` | the `fil_` id of a file already in this workspace, or a list of them |\n| `formula`, `rollup`, `lookup`, `autonumber` | not allowed \u2014 the platform writes these |\n\n### Relative dates\n\nA date cell holds a literal `YYYY-MM-DD`, or an expression relative to the day\nthe call is made, so a screen that opens on "this month" is not empty a month\nlater:\n\n- `@today` \u2014 the day of the run, in the workspace\'s timezone\n- `@month-start` \u2014 the 1st of that month\n- either with a whole-day offset: `@today-14`, `@month-start+9`\n- on a date that holds its hour (`format: "datetime"`), that hour after the day, and always stated:\n `@today 14:30`, `@today+1 06:00`\n\n`@month-start` exists because `@today-N` cannot promise a month: applied on the\n2nd, `@today-3` lands in the previous one.\n\n## Records\n\n`records` says how a row of a table is RECOGNISED \u2014 stated once in the table\'s document, beside its\n`title` \u2014 and every surface that draws one of its rows reads the same\nstatement: the register\'s row, a picker\'s option, a link\'s chip, a child table\'s\nrow and the record\'s header. Every table an app lists, opens or picks has one. The rarer keys \u2014 `scan`, `cadence`, `place`, `on_site`, `pin`, `plan_place` and `size` \u2014 are stated on `model/record-places-pins-and-cadence`.\n\n```jsonc\n// table "visit"\n"title": "container_no",\n"records": {\n "subtitle": ["customer", "arrived_on"],\n "image": "photos",\n "status": { "field": "stage", "closed": ["gone"], "history": "visit_history" },\n "figure": "total_fees",\n "due": ["free_until"]\n}\n```\n\n```jsonc\n// table "release": a release order read against what it allows \u2014 a meter wherever it shows\n"title": "release_no",\n"records": { "figure": "issued", "limits": { "issued": "units" } }\n```\n\n```jsonc\n// table "dossier": a straight-line process \u2014 the stage a select, each step\'s day the date it stamps\n"title": "applicant",\n"records": { "status": { "field": "stage", "closed": ["signed"], "dates": { "received": "received_on", "appraised": "appraised_on", "signed": "signed_on" } } }\n```\n\n<!-- generated:start records -->\n\n#### Record\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `subtitle` | list of alias (1\u20132) | no | Up to two fields read under the title wherever a row is drawn (a code, a date, a party): one line at a desk, wrapping to a second on a phone |\n| `image` | alias | no | A files field that pictures the row \u2014 never one an act keeps the document it makes in |\n| `mark` | alias | no | A single select whose option leads the row wherever a mark leads it \u2014 a list\'s row, the record\'s head, a link naming the row: the option\'s brand or glyph, else its colour dot, on a tile. The field stays a field: a subtitle run, a column, a section\'s control. Never beside `image` or `party`, and never the status field |\n| `description` | alias | no | A text field saying what the row is about \u2014 a plan\'s brief, a customer\'s note: read in its record\'s head under the title\'s line, its first lines at rest and whole on a press, and written from the head\'s \u270E. Never on a row\'s line |\n| `party` | `"person"` \\| `"organization"` | no | What each row is where the rows are people (a contact, a patient) or organisations (a customer, a supplier, a carrier); absent, the rows are things (an order, an item, a paper) |\n| `status` | [Status](#status) | no | The stage a row moves through, a single select, drawn as a badge beside its title |\n| `figure` | alias | no | The one number the row stands for, read at its right (a total, a quantity left) |\n| `limits` | map of alias \u2192 (alias \\| number) | no | A number read against a bound \u2014 a field of the same row or a constant \u2014 drawn as a meter wherever it shows |\n| `gates` | map of alias \u2192 (alias \\| number) | no | A pass mark on a meter `limits` bounds \u2014 a field of the same row or a constant; a percent field is its share of the bound, any other number an amount. The meter marks it and fills complete once past it |\n| `tolerance` | map of alias \u2192 number | no | How far past its `limits` bound a meter may stand, in percent of the bound: the bound is an amount expected (received against ordered), not a cap. Without one the bound is a cap, and past it is an overrun |\n| `due` | list of alias (at least one) | no | Dates that are deadlines (stored, or computed as a date): each counts down and turns overdue |\n| `frees` | alias | no | A date ending the row\'s span of days on which what the row holds is free again (a check-out, a hire\'s return): a stay from the 3rd to the 5th holds the 3rd and the 4th, and another may start on the 5th \u2014 on a lanes board, a calendar, a roster and in `no_overlap` alike. Absent, a span of days holds its last day; a span of moments always frees its end |\n| `settled` | alias | no | A yes/no of the row (stored, a formula: an invoice paid in full, or looked up through a one-row link: a line settled with its parent) that ends its deadlines: while it is true, no `due` of the row counts down or turns overdue \u2014 in a cell, on its line, in its head, on a calendar or gantt, or in the overdue count. A dashboard\'s or reading\'s `where` reads it as any other yes/no |\n| `task` | `true` | no | A row is work someone finishes: wherever rows of it stand in a table \u2014 a register, a record\'s rows, the rows filed under one \u2014 each is led by a ring ticking it done and unticking it by the one write of the app making that move. Its status is a stored select with `closed` |\n| `applies` | map of alias \u2192 map of alias \u2192 (list of alias (at least one) \\| boolean) | no | Fields that apply only while their conditions hold (a length only on a line whose tariff charges by the metre): where one does not apply no add asks it and no surface shows it. Never the title; a required one starts at its `default` |\n| `starts` | map of alias \u2192 `"today"` \\| `"me"` | no | What a field of a row being added starts at, where that start is certain: `today` a date (a moment starts now), `me` a member, the reader adding it; every other field starts empty unless the reader narrowed the register to a value or the field declares a `default` |\n\n<!-- generated:end records -->\n\n<!-- generated:start rules-records -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `records.title` | A title is the one field naming the row \u2014 any field but a button or a yes/no \u2014 and the subtitle never names the title\'s own field. |\n| `records.image` | `image` is a files field, never one an Act keeps the documents it makes in (`into`). |\n| `records.mark` | `mark` is a single select, never the status field, and never beside `image` or `party` \u2014 a List keeps one lead slot \u2014 nor on the folder an App works inside (`scope`), which its switcher names by a picture or its initials. |\n| `records.description` | `description` is a text field, read in the record\'s head \u2014 never a Field of a part too. |\n| `records.figure` | A figure is a number. |\n| `records.figure-on-line` | *Noted, never refused:* A subtitle entry equal to the figure is shown once, as the headline; the line leaves it out. |\n| `records.applies` | `applies` holds a field of the row other than its title to conditions of the same row \u2014 a yes/no (stored or a formula) by `true` or `false`, a single select by its options, its own or looked up through a one-row link \u2014 never to itself; a required field it holds starts at a `default`. |\n| `records.limits` | `limits` bounds a number by a number field of the row, in its unit or one of the same dimension \u2014 or by a constant, where every row reads the figure in one unit. |\n| `records.gates` | `gates` marks a pass on a meter `limits` bounds: a percent field (its share of the bound), another number in the bound\'s unit, or a constant. |\n| `records.tolerance` | `tolerance` stands on a meter `limits` bounds. |\n| `records.starts` | `starts` starts a field a person writes \u2014 `today` a date or a moment (never a span), `me` a member \u2014 and a `create` that does not ask a field it starts writes that start. |\n| `records.due` | A deadline (`due`) is a date. |\n| `records.frees` | `frees` names a stored day, never a moment: a span of moments already frees its end. |\n| `records.settled` | `settled` names a yes/no of the row (stored, a formula, or looked up through a one-row link) beside a `due`: while it holds, the row\'s deadlines neither count down nor turn overdue. |\n| `records.task` | A task entity (`task`) states a `status` of a stored single select with `closed` options \u2014 its ring ticks a row done by moving it to one. |\n| `records.picture` | *Noted by the designer on a write, never refused:* Rows an App draws that each hold one files field no Act makes documents into, no call is filed into, no files gallery keeps and no List compares as a pair (`compare`) \u2014 and no figure \u2014 are pictured by it (`image`); where the workspace\'s files are known, the field holding images is the picture, a figure or not. |\n\n<!-- generated:end rules-records -->\n\n- **The title names the row** by who or what it is \u2014 a link by the linked row (a job\n read by its customer). Any field but a button or a yes/no may title it, yet a code\n titles a row only where people name the row by it, else it is a `subtitle`; it is the table\'s own setting, which every link shows.\n- **One lead per row.** A row leads with its picture (`image`), a face (`party`) or the\n option of a single select it holds (`mark`) \u2014 never two. A `mark` draws that option\n on a tile: its brand or glyph, else its colour dot; a row holding none, a ring saying\n none is picked. The same tile heads the row\'s record and leads it where a link\n names it; the select stays a field \u2014 a subtitle run, a column, a section\'s control.\n- **A limit** reads a number against a bound \u2014 another field of the row, or a constant.\n A `gates` entry marks a pass on that meter: a percent field is its share of the\n bound, any other number an amount.\n- **A deadline** (`due`) counts down wherever it is drawn and is overdue once past while\n the work is open. A register over the table says how many rows in view are overdue on\n its summary line, and pressing that count narrows to them \u2014 no reading to state.\n- **A span\'s free day** (`frees`) is the end date on which what the row held is free\n again \u2014 a check-out, a hire\'s return. A stay from the 3rd to the 5th holds two nights:\n a lanes board, a calendar and a roster draw it through the 4th, and `no_overlap` lets\n the next stay start on the 5th. Without it a span of days holds its last day; a span of\n moments always ends as the next may begin.\n- **`starts`** is where an add\'s field begins, stated only where that is certain \u2014 a\n log\'s own day at `today`, a request\'s requester at `me`. Nothing else starts a field\n but its declared `default` and what the reader narrowed the register to.\n\n## Record places, pins and cadence\n\nThe rarer keys of `records`, stated beside the ones on `model/records`: a title read off a photo (`scan`), the rows a child owes each day (`cadence`), where a row stands on a map (`place`, `on_site`) or on a picture (`pin`, `plan_place`).\n\n```jsonc\n// table "site": it owes a diary page each day it works, from breaking ground through handover\n"title": "name",\n"records": { "cadence": [{ "entity": "diary_page", "on": ["mon", "tue", "wed", "thu", "fri", "sat"], "from": "started", "until": "handed_over" }] }\n```\n\n<!-- generated:start records-places -->\n\n#### Record places\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `cadence` | list of [Cadence](#cadence) (at least one) | no | The rows of a child each row owes one of a day, on the days of the week listed (a site\'s diary page, Monday to Saturday) \u2014 one entry a child, never owed on a day still to come. A roster of these rows over days marks a past day owed with none missing in the danger tone, counted in the legend; today\'s is due, offered to add and never marked; each row\'s total reads the days it holds of those owed |\n| `scan` | `"text"` \\| `"iso6346"` \\| `"code"` | no | Its title, a text field, is painted or printed on the thing a row is (a container\'s number, a seal\'s) and read off one photo on a phone, beside each search matching it and each field holding or picking it: `iso6346` a container number, refused unless its check digit holds; `text` as it stands; `code` a barcode or QR code on the thing, decoded on the device from the photo with no agent |\n| `place` | [Place](#place) | no | Where a row stands, as plain fields of it, drawn as one place where its record draws the address: how sure it is, a press opening it in the device\'s maps, and one taking it from where the device stands |\n| `plan_place` | [Plan place](#plan-place) | no | Each row is a place on the plan a `plan` register draws: its title the place\'s code, its subtitle a note. A flat list, no tree |\n| `on_site` | [On site](#on-site) | no | Photos taken where a place stands (a site diary\'s): a photo taken through a phone\'s camera here keeps where the device stood, marked at the place, away from it or unknown, and a record holding one taken away says so among its checks \u2014 one with no place (picked on a computer, or kept before places were) is marked and warns nothing; a place reaches only members of the organization, through the apps drawing these photos |\n| `pin` | [Pin](#pin) | no | Each row is a place on a picture (a snag on a flat\'s plan, a spot on a container\'s drawing): a List of these rows draws the picture its `on` row holds with a numbered pin at each row\'s own place, each row led by its number, the row opened lit on it and a pin pressed opening its row; where the List writes rows, a press on the bare picture adds one there, and a pin dragged moves its row. A List of rows that link to these by a single link draws their places the same way |\n| `size` | [Record size](#record-size) | no | The real size of the thing a row is (a carton, a piece of furniture, a pallet): three number fields stating one length unit alike, read as one fact \u2014 "400 \xD7 300 \xD7 250 mm", a part the row lacks "\u2014" \u2014 after the subtitle wherever the row\'s line and head draw facts, never also as the subtitle\'s fields. A plan register drawing these rows draws each box at this size |\n\n#### Cadence\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | text | yes | The table owed: each of its rows stands on one row of this table by its single link to it, and on the first date of its line (`records` title or subtitle) |\n| `on` | list of `"mon"` \\| `"tue"` \\| `"wed"` \\| `"thu"` \\| `"fri"` \\| `"sat"` \\| `"sun"` (at least one) | yes | The days of the week one is owed on, each listed \u2014 `mon` to `sun` |\n| `from` | alias | no | A date of this row: the first day one is owed (a site breaking ground, a contract\'s start); absent, no first day |\n| `until` | alias | no | A date of this row: the last day one is owed (a site\'s handover, a contract\'s end); empty or absent, through today |\n\n#### Place\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `address` | alias | yes | A text field: the address, as people write it |\n| `latitude` | alias | yes | A number field: degrees north, negative south |\n| `longitude` | alias | yes | A number field: degrees east, negative west |\n| `accuracy` | alias | no | A number field: how many metres off the device that gave the place said it may be |\n\n#### On site\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `photos` | list of alias (at least one) | yes | Files fields of this row holding photos taken on site |\n| `place` | alias | no | A single link of this row to the row whose `place` its photos are taken at (a diary page\'s site); absent, this row\'s own `place` |\n| `radius` | number | yes | How many metres from the place a photo still counts as taken there |\n\n#### Pin\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `on` | alias | yes | A single link of this row to the row whose picture (its `records` `image`) the place is on \u2014 a container\'s drawing, a floor\'s plan |\n| `x` | alias | yes | A number field of this row: how far across that picture the place stands, in percent from its left edge |\n| `y` | alias | yes | A number field of this row: how far down that picture the place stands, in percent from its top edge |\n\n#### Record size\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `across` | alias | yes | A number field: the width of the thing a row is, across |\n| `along` | alias | yes | A number field: its depth, along |\n| `up` | alias | yes | A number field: its height, up |\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `records.scan` | A title read off a photo or a code (`scan`) is a text field the row stores. |\n| `records.cadence` | A `cadence` names each child once \u2014 its rows standing on one row of this entity by their single link and on the first date of their line \u2014 lists each day of the week once, and `from` and `until` are two different days of this entity. |\n| `records.place` | A `place` names four different fields of the row: its address a text field, its latitude, longitude and accuracy number fields it stores. |\n| `records.plan_place` | A `plan_place` names `kind` and `reach_from` as single selects, `sheet` as a single select or a one-row link, `stacked` as a boolean and the rest as numbers, each a different field; `kind`, `x`, `y` required. `size` names three numbers (`across`, `along`, `up`) stating one cell in one length `unit` alike. A `sheet` linking to an entity that states an `image` draws that drawing under the places, `scale` (a number field of that entity) its width in cells; `scale` goes with such a `sheet` alone. |\n| `records.on_site` | `on_site` lists files fields of the row, each once, taken at the `place` of the row itself or of the one row a single link of it names (`place`), which states one. |\n| `records.pin` | A `pin` names a single link of the row (`on`) to an entity whose `records` states its `image` (a PDF is drawn as its first page), and two different number fields of the row, how far across (`x`) and down (`y`) that picture in percent. A List of rows whose own entity states a `pin` is drawn over that picture; `on`, `x` and `y` are set by the press that adds a row, never asked. |\n| `records.size` | A `size` names three different number fields of the row (`across`, `along`, `up`) stating one length unit alike (mm, cm, m or km); they read as one fact after the subtitle, so the subtitle never also names one. |\n\n<!-- generated:end records-places -->\n\n## Status\n\nA record\'s `status` is the stage a row moves through \u2014 a stored single select, each\noption\'s moment the date `dates` names \u2014 drawn as a badge beside its title wherever the row is.\n\n<!-- generated:start status -->\n\n#### Status\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The single select that is the row\'s state, its options in the order the work goes through them |\n| `closed` | list of alias (at least one) | no | Options that end the work: a register opens on the rows not at them, and the badge reads muted |\n| `dropped` | list of alias (at least one) | no | Options of `closed` that leave the work undone (cancelled, rejected): whoever holds the row moves it to one from any stage, where every other move forward passes each owned stage between that binds the row (its `when`) and needs each one\'s owner, and a row leaving one enters a stage as a row made there does |\n| `history` | text | no | A table every write that sets or moves the status appends one row to (its link to this table, the new status, the moment and who) \u2014 by the record write path, whoever writes: for reporting on every move |\n| `dates` | map of alias \u2192 alias | no | Option alias \u2192 a date field of this entity the record write path stamps with the moment a row moves into that option, or is made at it \u2014 whoever writes. A value the same write states for that field wins, so past data loads as it happened; the platform never clears a stamp, so each is when the row last entered the option. A stage\'s date is made where none is named |\n| `stages` | map of alias \u2192 [Stage](#stage) | no | Option alias \u2192 who owns that stage and how long it should take: the owner alone takes a row out of it, by any write, and presses the acts offered there; every move forward past it of a row it binds (its `when`), and every such row made past it, is its owner\'s too. Its days run from the moment its `dates` field holds. In an app the status then moves only by acts that `set` one of its options \u2014 no save writes it and no act asks it |\n\n#### Stage\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `owner` | text \\| [Stage group](#stage-group) | yes | Who owns the stage: a member field of the row, `link.field` read through a one-row link (not at the stage a row opens at, whose owner made the row: a member field of the row itself), or `{ group }` |\n| `within` | integer | no | Whole days the stage should take from the moment the row entered it |\n| `when` | map of alias \u2192 (list of alias (at least one) \\| boolean \\| [comparison](#values) \\| `{ "empty": true \\| false }`) | no | The rows that must pass this stage: a row meeting every entry on its own fields, as it stood before a write or as the write leaves it \u2014 a single select holding one of these options, a yes/no this value, a number or date meeting its bounds, a field empty or filled. Absent, every row must. A row at the stage leaves it by its owner either way |\n\n#### Stage group\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `group` | text | yes | A member group\'s id (from Settings, or chat): every member of it owns the stage |\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `records.status` | A status is one single select, its options in the order the work goes through them, `closed` naming the ones that end it and `dropped` those of them reached from any stage. |\n| `records.history` | A declared status `history` has exactly one link to the row it records, one single select holding every option of the status (or a text), exactly one date and at most one member. |\n| `records.stages` | A stage\'s owner (`status.stages`) is a single member field of the row, one read through a one-row link (never at the stage a row opens at: its owner made the row, so it is a member field of the row itself), or a group, on an option that does not end the work; a group\'s stage needs the status `history` with who moved the row. Every write takes a row off a stage as its owner alone, and moves a row past it or makes a row past it as its owner alone where the stage binds the row (its `when`) \u2014 a move to a `dropped` option excepted, which whoever holds the row makes from any stage; a row leaving a dropped option, or holding none, enters a stage as one made there. Each stage\'s moment, and each open option\'s, is the date its `dates` names, made where none is named, which every write that opens or moves the status stamps; `within` runs from it. In an App a status with stages moves only by Acts that `set` one of its options \u2014 no save writes it or a date it stamps, and no Act asks either, sets it otherwise or writes either from a workflow; an Act moving a row forward past a stage another owner owns, into an option not `dropped`, keeps its `when` off every row that stage binds. |\n| `records.stage-when` | A stage\'s `when` names the rows that must pass it, as an Act\'s `when` holds them \u2014 single selects to their options, yes/nos to `true` or `false`, numbers and dates to bounds, any field to `{ "empty": true \\| false }` \u2014 on fields a write stores of the row itself: never the status, a formula, a lookup or a rollup, and never on the stage a row opens at. It binds a row meeting it as it stood before a write or as the write leaves it; absent, it binds every row. |\n| `records.detour-note` | *Noted, never refused:* A stage the work only steps aside into \u2014 not where a row starts, entered from one step alone that also moves the work on without it, and left by its own moves only back to that step (a pause, a repair) \u2014 is a detour: its option and the Section whose `at` holds it stand before that step\'s, else the work reads it as its next step, and the Act moving the work into it leads that Section \u2014 never a correction waiting in a \u22EF. |\n\n<!-- generated:end status -->\n\n- **The status** is one single select. `closed` options end the work: a register opens\n on the rows not in them. A `history` table gets one row per move \u2014 its link to this\n table, the new status (a select holding the same option aliases, or text), the\n moment and who \u2014 appended by the record write path in every write that opens a row at a\n stage or moves it, whoever writes (the member behind the write is its `by`; an automation\n leaves it empty). A `history` the tables do not declare is derived, and needs no\n `records` of its own.\n- **When each step happened** is the date `dates` names for its option, stamped as the row\n enters it, whoever moves it. One way times a status: a straight-line process is a select\n whose options are its steps in order, each with its date \u2014 never a run of dates the stage\n is read off. An Act that moves the stage and `asks` the step\'s date with `starts: "today"`\n lets the reader record a day already past in the same press.\n\n## Write rules\n\nWhat a WRITE of a table\'s rows meets is stated in the table\'s document, where it applies: `min` and\n`max` on a number or date field, `options_where` and `same` on a link, `suggest` on a text field,\nand `no_overlap` and `natural_key` on the table. A key a field\'s type does not take is refused at\nits path, and a document has no `write_rules` key.\nEvery one but `natural_key` and `suggest` holds on every path a record is written by \u2014 the grid, a record\'s form, an import, a workflow, an\nagent, the API, an app \u2014 judged in the write\'s own transaction, every broken rule listed at once; the\nscreen and a picker only mirror them. A row that already breaks one stays editable until a write\ntouches what the rule reads. `natural_key` names the fields an app recognises a row by, to reuse it where an\nadd or a find-or-create meets it; a record tool or the API creates the row it is handed. `suggest` offers\nvalues in a text control and refuses none. An `options_where` link is refused where a write names a row the\nnarrowing does not admit, and every picker of the link offers only the rows it admits.\n\n```json\n{\n "tables": [\n {\n "ref": "customer",\n "label": "Customer",\n "fields": { "email": { "type": "text", "label": "Email", "unique": true } },\n "natural_key": ["email"]\n }\n ]\n}\n```\n\nA customer above is RECOGNISED by their address. An add that names one \u2014 from the customers\nregister or from a picker\'s "new" \u2014 reuses the row it matches and opens one only where nothing does,\nso the book never grows a second Acme.\n\n```json\n{\n "tables": [\n { "ref": "product", "label": "Product", "fields": { "name": { "type": "text", "label": "Name" }, "in_stock": { "type": "number", "label": "In stock" } } },\n {\n "ref": "order",\n "label": "Order",\n "fields": {\n "name": { "type": "text", "label": "Name" },\n "placed_on": { "type": "date", "label": "Placed on" },\n "due_on": { "type": "date", "label": "Due on" },\n "lines": { "type": "link", "label": "Lines", "table": "order_line", "multi": true, "pair": "order" }\n }\n },\n {\n "ref": "order_line",\n "label": "Order line",\n "fields": {\n "name": { "type": "text", "label": "Name" },\n "order": { "type": "link", "label": "Order", "table": "order", "multi": false, "pair": "lines" },\n "quantity": { "type": "number", "label": "Quantity", "min": 1, "max": 9999 },\n "ships_on": { "type": "date", "label": "Ships on", "min": "order.placed_on", "max": "order.due_on" },\n "product": {\n "type": "link",\n "label": "Product",\n "table": "product",\n "multi": false,\n "options_where": {\n "node_type": "group",\n "logic": "and",\n "children": [{ "node_type": "condition", "type": "number", "field_key": "in_stock", "operator": "greater_than", "value": 0 }]\n }\n }\n }\n }\n ]\n}\n```\n\nA line of nothing is not a line: `quantity` is refused on create and on update. A line ships inside\nits order\'s window \u2014 a bound read off the row a link names is judged when the line is written. Nothing is sold off an empty shelf: every picker of `product`\nreads only the rows that answer its `options_where`, and every save refuses the same rows again.\n\n```json\n{\n "tables": [\n {\n "ref": "booking",\n "label": "Booking",\n "fields": {\n "room": { "type": "text", "label": "Room" },\n "state": { "type": "select", "label": "State", "options": [{ "alias": "held", "label": "Held", "color": "blue" }, { "alias": "released", "label": "Released", "color": "gray" }] },\n "starts_on": { "type": "date", "label": "Starts on" },\n "ends_on": { "type": "date", "label": "Ends on", "min": "starts_on" }\n },\n "no_overlap": { "from": "starts_on", "to": "ends_on", "by": ["room"], "while": { "state": ["held"] } }\n }\n ]\n}\n```\n\nOne booking per room at a time, while it is held \u2014 a create or a save whose span overlaps another\nheld booking of the same room is refused.\n\nA table\'s `checks` (the conditions no write of its rows may meet) and `workflows` (what follows every committed write of them) are stated in its document and hold on every path a record is written by; their keys, what they read and an example are on `workflows/table-checks` and `workflows/table-workflows`.\n\n<!-- generated:start write-rules -->\n\n#### Suggest\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `table` | text | yes | The table whose text field is offered from: its id, or its `ref` in this call |\n| `field` | alias | yes | The alias of that table\'s stored text field; the field\'s control offers, as the reader types, the distinct values it holds, and any text is still written |\n\n#### No overlap\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | text | yes | The stored date (or datetime) a row\'s span starts on: `<field alias>` of the row, or `<link alias>.<field alias>` of the one row a one-row link names (a booking line\'s piece held for its booking\'s dates) |\n| `to` | text | yes | The stored date (or datetime) a row\'s span ends on, named like `from` and through the same link \u2014 a day it holds, unless `records` names it the day the row `frees`; a moment another row may start at |\n| `by` | list of alias (at least one) | no | Fields two rows share to compete for a span (the same employee, the same room); absent, every row |\n| `while` | map of text \u2192 list of alias (at least one) | no | Only rows at these options of these selects hold their span (running, not closed), the select named like `from` \u2014 `<field alias>` or `<link alias>.<field alias>`; absent, every row |\n\n<!-- generated:end write-rules -->\n\n<!-- generated:start rules-write -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `write.natural-key` | A `natural_key` is text or a number a person types back, or a one-row link; a text key is unique \u2014 `unique: true`, or the key\'s fields as a set in the table\'s `unique`. |\n| `write.bounds` | `min` and `max` bound a number or a date \u2014 by a constant a number, by a field of this row or of the one row a one-row link names either, of the same type and unit, never by itself \u2014 and `min` is never above `max`. |\n| `write.bound-per-row-unit` | On a figure whose unit or currency is the row\'s own (its notation\'s `per_row`) a constant bound is only 0 \u2014 bound it by a field of the row. |\n| `write.suggest` | `suggest` stands on a stored text field and names a `table` and a `field`, a stored text field of that table; the app reads that table, so it has a `records` entry. |\n| `write.same` | `same` stands on a link and names one-row links this table and the link\'s target both hold to one table. |\n| `write.options-where` | `options_where` narrows a link by an `and` group of plain conditions over the target\'s own fields \u2014 a number compared in one unit on every row, a select by options it declares. |\n| `write.no-overlap` | `no_overlap` spans two date fields, the row\'s own or the one row a one-row link names (`booking.start_on`); `by` names values rows share \u2014 a link, a person, one option, a text or a number; `while` names single selects by their options, the row\'s own or that linked row\'s. |\n\n<!-- generated:end rules-write -->\n\n- A `natural_key` is `text` or `number`, because a person types it back, or a\n one-row link, because a row is also recognised by the one row it belongs with (a\n fee by its visit and its kind). A key holding a `text` field is unique: `unique:\n true` on that field, or the key\'s fields as a set in the table\'s `unique` (a\n site by its customer and its name) \u2014 two rows sharing it would make\n find-or-create pick whichever the read answered first. An add asks the key by\n default.\n- A `same` link names only rows that name what this row names \u2014 a fee\'s invoice\n is one of its own visit\'s \u2014 through a one-row link both tables hold to one\n table; its picker offers nothing until the row names one. A row added under a\n record names what it copies off it (`default_from`) from the start, so a block\n expecting one line per row of the link lists that record\'s rows alone. A save\n of this row moving what the two compare is refused while they would name apart.\n- An `options_where` link may be left empty; a row it names is refused on every\n save that writes the link unless it holds the narrowing, and the refusal names\n the link and the row. A save that leaves the link alone is not judged, so a row\n still naming a row the narrowing no longer admits stays editable. A link naming\n one kind of a book\'s rows (a "Shipping line" among the parties) narrows to that\n kind, or its picker offers every row.\n\nA field\'s own `required` is not here: the field carries it, and every write\npath refuses the row by field name from it. `unique` is the text field\'s own\nclause (\xA7 `text`) \u2014 an add says so at the control before the column does. A\nfield the workspace writes (a formula, a rollup, a lookup, an autonumber, a date\nwith `derive_from`) is never asked, written or edited.\n\n## App tree\n\nAn app is a tree of components, assembled the way a page is written in JSX: pick a component, bind it to a table by\nname, and it draws well with no props, because its defaults come from the table\'s facts (\xA7 Records, \xA7 Status,\n\xA7 Write rules, \xA7 Field). A prop overrides a default at its own node. No prop says how anything looks: the runtime\ndraws each concept one way, and a screen the job needs that no component states is told to the person \u2014 the job,\nwhat the app drew, the word wanted \u2014 never props bent to approximate it.\n\nEach component is a page of its own: `model/app-node`, `model/list-node`, `model/record-node`, `model/section-node`,\n`model/side-node`, `model/page-node`, `model/outside-node`, `model/tab-node`, `model/field-node`, `model/act-node`, `model/check-node`,\n`model/reading-node`, `model/progress-node`, `model/statement-node`, `model/export-node`, `model/book-node`,\n`model/query-node` and `model/workflow-node`. Beside them: `model/changing-an-app` and `model/what-an-app-may-write`, `model/layouts`, `model/time-and-place-layouts` and\n`model/publish-desk` for a List\'s `layout`, `model/composed-page`, `model/tasks`, and the rules, `model/rules-of-the-app`,\n`model/register-rules`, `model/block-rules` and `model/record-and-act-rules`.\n\n<!-- generated:start app-components -->\n\n| Component | Draws | Holds |\n|---|---|---|\n| [App](#app-node) | the launcher tile, the app\'s words, its Lists, and its API | List, Tab, Reading, Query, Workflow |\n| [List](#list-node) | rows of an entity: the register, a record\'s child rows, a roster\'s cells, related rows, a log | Tab, Reading, Progress, Export, Act, Check, Book, Record, List |\n| [Record](#record-node) | what a row opens: the head and the parts | Field, Section, Side, Page, Outside, List, Reading, Progress, Statement, Act |\n| [Section](#section-node) | a titled part of a record, and optionally a step of its path | Field, List, Reading, Progress, Statement, Act |\n| [Side](#side-node) | the part the rest is worked from, beside them | Field, List, Reading, Progress, Statement, Act |\n| [Page](#page-node) | a part on its own page, off the record\'s scroll | Field, List, Reading, Progress, Statement, Act |\n| [Outside](#outside-node) | what one record shows, and which acts it allows, to a person with no seat who opens a link a member made of it | Field, List, Act |\n| [Tab](#tab-node) | one view of a List\'s rows over a period, or one sheet of a dashboard | Reading, List |\n| [Field](#field-node) | one field: a control, a gallery, prose, a link and its picker, the rows filed under a back-link | \u2014 |\n| [Act](#act-node) | a verb: what pressing it does | \u2014 |\n| [Check](#check-node) | a formula of the rows that warns, or refuses the writes and acts it names | \u2014 |\n| [Reading](#reading-node) | a number over rows: one figure, its parts, a grid, per period, steps; or a record\'s own figure | \u2014 |\n| [Progress](#progress-node) | the work against its plan, day by day | \u2014 |\n| [Statement](#statement-node) | a figure built from figures of the record | \u2014 |\n| [Export](#export-node) | a report file of the rows in view | \u2014 |\n| [Book](#book-node) | another table read as part of the register | Record |\n| [Query](#query-node) | nothing: a read for agents | \u2014 |\n| [Workflow](#workflow-node) | nothing: a verb for agents | \u2014 |\n\n<!-- generated:end app-components -->\n\n### A node\n\nA node is `{ "type": <Component>, "key"?, ...props, "children"?: [...] }`. `type` names the component, `key` its\nname among its siblings, the other keys its props, `children` the nodes it holds. An unknown prop is refused.\n\n- **Two shorthands, both for Field.** A bare string child is a Field (`"due_date"`); an object with `field` and no\n `type` is a Field with props (`{ "field": "price", "edit": false }`). Both work in every prop that lists fields:\n `create`, `row.subtitle`, `add`, and the `children` of a Record or a part. A column is a field\'s alias, or\n `{ "field", "edit": true }` for the line\'s own control, and takes no other prop.\n- **Fields and options are named by alias**, never by label. A tagged value is an object, never a magic string.\n- **`false` turns a default off at its node:** `"figure": false` drops the row\'s figure, `"edit": false` makes a\n field read-only, `"create": false` and `"remove": false` take a List\'s add and Delete away, `"readings": false` its\n band, `"row": { "status": { "edit": false } }` the status buttons (another app moves the stage) and\n `"row": { "image": false }` the picture.\n\n### Keys and addresses\n\nA key is unique among its siblings, the way a React key is. A node that binds data takes its key from what it binds:\n\n| Node | Key when none is stated |\n|---|---|\n| Field, Check | its field alias |\n| List, Book | its entity alias |\n| Record | `record` |\n\nEvery other node states its key. Three kinds of key are unique across the whole app, because each is an API name:\nan Act\'s (its workflow is `act_<key>`, or the one it names), a Reading\'s (its query name for `run_app_query`), and a Query\'s or\nWorkflow\'s.\n\nA node\'s **address** is the path of keys from the app, such as `customer/record/orders`; the app itself is `/` (the `in` of a node at the root), and its own key is part of no address. A patch may use any ending\nof that path that names exactly one node (`orders`). The exact path wins where one exists; an ending naming two\nnodes is refused, and the refusal lists both paths. `get_app` prints the tree with each node\'s address.\n\n### Values\n\n| Value | Written |\n|---|---|\n| now, the reader, an asked input | `{ "now": true }`, `{ "me": true }`, `{ "input": "name" }` |\n| a computed value | `{ "formula": "{cost} * 1.2" }` over the acted row\'s fields |\n| a call\'s own value, in a Query | `{ "param": "name" }` |\n| cleared | `null` in an Act\'s `set` |\n| an option | its alias |\n\nA **condition** (`when`) maps a select to a list of its option aliases and a yes/no to `true` or `false`; every entry\nholds. A number (a number, currency or percent field, or a formula or rollup yielding one) takes a comparison with any\nof `gt`, `gte`, `lt`, `lte` (at least one, never both `gt` and `gte` or both `lt` and `lte`), each a number; a date\ntakes the same keys with a day (`"2026-03-01"`), a moment on a datetime (`"2026-03-01T09:30"`) or `{ "now": true }`\n\u2014 today, or the current minute on a datetime, read when the rows are, so overdue is `{ "due_on": { "lt": { "now": true } } }`;\nand any field but a yes/no takes `{ "empty": true }` or `{ "empty": false }`. A comparison on a field it does not fit is\nrefused, naming the field, its type and what it takes. A `when` on an Act, a Section or other block, and a List\'s `where`\n(the register\'s or a child List\'s) take these; a child List that adds, picks or expects rows refuses a comparison in its\n`where`, since a row it holds could stand outside it. A **narrowing** (`opens`, a Reading\'s, a Tab\'s or an Export\'s `where`) takes options, yes/nos and also `{ "me": true }` on a member\nfield, the reader; `where` on the register, a log or a record\'s child rows fixes the rows on the server for every reader, so it refuses\n`{ "me": true }` \u2014 open on it with `opens`; on a record\'s related rows (`same`, `app`) and on a dashboard\'s List it\nnarrows by the reader and takes it, and those two take no comparison.\n\n## Changing an app\n\n1. `get_workspace` with `kind: "app"` names the app. `get_app` prints the `tree:`, the `node addresses:` (the address of\n each node the app draws, `(default)` on those the tree leaves to the platform) and the `version` line, whose id is the `base_version_id` to send. A member who is not the\n app\'s author is shown no tree. Keep the tree: no tool reads an earlier one.\n2. `set_app` with `app_id`, `base_version_id` (that version: a newer write is refused, not overwritten) and `nodes`,\n (a refused change writes nothing, so `dry_run` is for reading the permissions it would grant and remove). Every answer\n states the permissions the change grants and removes.\n3. `screenshot_app` shows the result.\n\n`nodes` maps an address to a patch of that node. Read against the customers List of the account review (\xA7 A complete\nworkspace, abridged), the call below makes `tier` read-only, moves the Orders Section ahead of Account, adds an Act to\nAccount and removes the Reading:\n\n```json\n{\n "type": "List",\n "entity": "customer",\n "children": [\n { "type": "Reading", "key": "ordered_by_tier", "by": "tier", "value": "total_ordered" },\n {\n "type": "Record",\n "children": [\n { "type": "Section", "key": "account", "title": "Account", "children": ["tier"] },\n { "type": "Section", "key": "orders", "title": "Orders", "children": [{ "type": "List", "entity": "order" }] }\n ]\n }\n ]\n}\n```\n\n```json\n{\n "app_id": "<id of the app>",\n "base_version_id": "<current_version_id>",\n "nodes": {\n "tier": { "edit": false },\n "orders": { "before": "account" },\n "mark_gold": { "type": "Act", "in": "account", "label": "Make gold", "when": { "tier": ["standard"] }, "set": { "tier": "gold" } },\n "ordered_by_tier": null\n }\n}\n```\n\nEach entry applies in order:\n\n- **An existing node:** a JSON merge patch (RFC 7396) of its props; `null` drops a prop, and a list (`children`,\n `columns`) replaces the node\'s whole.\n- **`null`:** removes the node and everything under it.\n- **An address naming no node, with `type` and `in`:** adds a node there; its key is the address\'s last word, and a\n Field states `field` in place of `type`.\n- **`in`, `before`, `after`:** move a node into another, or ahead of or behind a sibling.\n\nThe app\'s own props (`name`, `description`, `icon`, `color`, `words`, `scope`, `period`) change\nthrough `body`, a merge patch over the App node. `dry_run` checks a change and writes nothing.\n\n| To change | State |\n|---|---|\n| a field read-only | `"edit": false` on its Field, or on the Section, Side, Page or Record that draws it |\n| a column added or dropped | `columns` of the List, every column kept: a List that never stated `columns` draws none beyond the row\'s title and its `records` line |\n| a button | an Act node (\xA7 Act node); `confirm` makes its press ask first |\n| a label | the table\'s field `label` in `set_tables`, for every app; for this app alone, `body: { "words": { "<table>": { "fields": { "fee": "Consultation" }, "options": { "urgency": { "rush": "Same day" } } } } }`; a List\'s `label`, a Section\'s `title`, an Act\'s `label`, `add_label` on the add. A Field\'s `label` heads only a gallery or prose block |\n| the order of nodes and rows | `before` or `after` a sibling, or `in` another parent, in the node\'s entry (a Section ahead of another, a Field ahead of another in its Section); a List\'s `columns` read in the order stated; its rows read in its `sort`: `{ "sort": { "field": "collected_on", "desc": true } }` |\n| the row\'s second line | `row: { "subtitle": ["mechanic"] }` on the List: up to two fields under the title, in place of the table\'s `records` line, which stays as it is |\n| a picker\'s add | `add` on the link Field: `{ "add": ["species", "microchip"] }` asks those fields of the new row; `false` makes the picker only pick. It holds in every picker of that table in the app |\n| a headline figure | a Reading in the List: `{ "type": "Reading", "in": "orders", "key": "prepaid_owed", "label": "Prepaid owed", "value": "prepaid", "where": { "stage": ["awaiting_payment"] } }` sums the rows in view; without a `value` it counts them |\n| a calendar and its date | `layout: "calendar"` on the List, and the date it places each row on is the first of `row.due`: `{ "layout": "calendar", "row": { "due": ["pickup_on"] } }`; absent, the table\'s `records.due` |\n| a record\'s child lines | a List inside the Record\'s Section over the child table: `{ "type": "List", "in": "details", "entity": "repair_line" }`; the child table (one link back to the record) and the record\'s rollup over its rows come from `set_tables` first. Lines priced off a book: `docs path="design/treatments/moves-and-numbers/priced-lines"` |\n\n**A node the tree leaves to the platform has an address too.** `get_app` lists the nodes the app draws, those the stored\ntree does not state marked `(default)`: the Record with its Sections and Fields, the status buttons, the band of readings.\nPatch one by its address and the call states it first: the nearest node the tree does state gains the default child that\nleads to it, as drawn (the whole Record under a List that states none, with the List\'s `row` picture and the `columns` the\ndefaults added; every Section of a Record that states no part; every status button of the app, each in the Section of its\nstage; every reading of the band, since stating one stands the others down), then the patch applies. Defaults\noutside that subtree stay defaults, and the answer\'s notes say what was stated. Removing every reading, or every status\nbutton, is refused: a set no node states is drawn again by the platform, and only `readings: false` or\n`row.status: { "edit": false }` on the List draws none. **A stated subtree stays as it is**: a\nfield or option the tables gain later is not added to it, so name it in the tree. `{ "due_date": { "edit": false } }`\nover a sparse tree makes that field read-only and takes away `saves <table>: due_date` alone. An address that ends several\ndrawn nodes is refused, as for stated ones.\n\n## What an app may write\n\nAn app states no list of what it writes: the tree derives it, and the generated workflows accept exactly that, so it is\nalso what an agent can do through the app\'s API. `get_app` prints it beside the tree.\n\n1. **A save of field `f` of table `E`** exists when a node draws `f` on a page of `E`\'s rows (a Field in a Record,\n Section, Side or Page, or a part of the head), the table lets a person write it (stored; not computed, stamped or the\n back side of a child\'s link), no Act of the app on `E`\'s rows writes it, and neither the node nor a container above it\n says `edit: false`. A row\'s stage is moved by an Act or the one button per move a status no Act moves gets, by hand only where `row.status` says `{ "edit": true }`, and by none where it says `{ "edit": false }`.\n2. **An add of `E`** exists for a List over `E` that writes its rows unless it says `create: false` (asking what it lists, and\n setting the link back to the record itself), and for an `add` on a Field drawing a picker of `E`. An absent `create`\n stands down where the table states no `singular` (nor the app\'s `words`), or where `where` keeps a select at an option an\n Act sets other than the one a new row starts at; a stated `create` list over such a table is refused. A roster\'s cells and\n a document are added only where `create: true`.\n3. **A Delete of `E`** exists for a List over `E` unless it says `remove: false`, and for the table of every Book of that\n List (a Book has no `remove` of its own). A roster\'s cells, a document and a desk\'s rows are deleted only where `remove: true`.\n `remove: false` takes this app\'s Delete away and no more: the record tools, the API and other apps still delete. A row no\n surface may delete is a table check with `on: ["delete"]`.\n4. **An act** exists for each Act node. Every field it sets, asks, files into, signs or records is the act\'s, and read-only\n everywhere else in the app.\n5. **The table\'s contract holds on every path** and is not restated here: required, unique, no overlap, field rules, table\n checks, stage owners, history and the read scope. (`natural_key` is read by an app\'s add and find-or-create alone.)\n\nDrawing is reading or saving; a List that writes its rows also adds and deletes them, until it says `create: false` or `remove: false`, and a List below a part or a Record that says `edit: false` writes, adds and deletes none.\n\n### What a table gives a component\n\n| Table fact | Feeds |\n|---|---|\n| The title | every row\'s name: Lists, a Record\'s head, link chips, pickers, an add |\n| The row\'s line (`records` subtitle, figure, image, mark, party, description) | the row\'s line and head, cards, a checklist\'s photo |\n| The stage (`records` status) | the stage chips, a List\'s `opens`, the badge, an Act\'s moves, stage owners gating acts, a Section\'s `at` |\n| Deadlines (`records` due, settled, frees) | countdowns and overdue marks, the default sort, attention order, spans on lanes, calendars and rosters |\n| Bounds (`records` limits, gates, tolerance) | the figure\'s meter, wherever it stands |\n| Tasks and cadence (`records` task, cadence) | the ring on task rows, a roster\'s owed days |\n| Applicability and starts (`records` applies, starts) | a field shown and asked only where it applies; an add\'s starting values |\n| A field\'s `required`, `default`, `default_from`, `natural_key`, `unique` | what an add asks, and what it starts at |\n| A field\'s `notation`, `format`, options, `options_where` | money, units, chips, and what a picker offers |\n| The table\'s `read_scope` | which rows each reader reads, in every List |\n\n### Where the rules stand\n\n\xA7 Rules of the app is every rule the check holds over an app, by id, whichever component it concerns. A\n**language** is decided by the app\'s own words: a tree names no locale, so what an app draws itself (its buttons, empty\nstates, refusals) is in the language its labels are written in, and `words` count as labels. Mix two languages in one\napp and it answers in two.\n\n## App node\n\nAn app draws its launcher tile, its words, its Lists and its API. It binds the tables it names: `set_app` fills\n`tables` with the id of every table alias the tree uses, and `templates` with that of every document template alias.\nA relabel on either side is a rename and no tree points at another table by accident.\n\n<!-- generated:start node-app -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The app\'s alias, its name among apps; the App node is addressed `/`, and no address in it starts with this key |\n| `name` | text | yes | The job, in the words of the people who do it |\n| `description` | text | no | What the job is for, in a sentence |\n| `icon` | text | no | A Lucide icon name in kebab-case the launcher tile draws |\n| `color` | [colour](#select) | no | The launcher tile\'s colour |\n| `words` | map of alias \u2192 [Table labels](#table-labels) | no | A table \u2192 the words this app draws for it |\n| `scope` | alias | no | A required one-row link of the first List\'s entity: the app works inside one row of the linked entity |\n| `reads` | `"shared"` | no | Every member reads every row, whatever the entity\'s read scope |\n| `uses` | list of alias (at least one) | no | Tables the app\'s Query and Workflow nodes read without drawing them, by alias: they join the tables the app reaches, so a workflow may name them |\n| `period` | `"month"` \\| `"quarter"` \\| `"year"` \\| [Dashboard period days](#dashboard-period-days) | no | The period a dashboard opens on |\n| `tables` | map of alias \u2192 text | no | The id of each table the tree names, by its alias: filled by set_app, never written |\n| `templates` | map of alias \u2192 text | no | The id of each document template the tree names, by its alias: filled by set_app, never written |\n| `children` | list of ([List](#list-node) \\| [Tab](#tab-node) \\| [Reading](#reading-node) \\| [Query](#query-node) \\| [Workflow](#workflow-node)) (at least one) | no | The first List at the root is the register, and a second is another app; a dashboard holds Tabs, Readings and Lists that open in another app. Queries and Workflows stand here too |\n\n#### Table labels\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | no | The table\'s name in the plural (a register\'s heading, a block\'s, a link\'s picker) |\n| `singular` | text | no | One row of the table, as the add and empty sentences name it |\n| `fields` | map of alias \u2192 text | no | A field alias \u2192 the words this app draws wherever it names the field; the table\'s field keeps its own label |\n| `options` | map of alias \u2192 map of alias \u2192 text | no | A select field alias \u2192 option alias \u2192 its words |\n\n#### Dashboard period days\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `days` | `30` | yes | The last 30 days through today \u2014 the one span of days a dashboard opens on |\n\n<!-- generated:end node-app -->\n\n- **The first List is the register**: the working list of one table. Every further List at the root is a **log** beside\n it, at most 3, each stating its `label`: read as what was brought into or done to the register\'s rows (the files\n imported, the runs of a batch), opened as a register of its own inside the app, its reads, acts and workflows the\n app\'s. Only the organization\'s members are shown a log. A log is a table or cards with no `Tab`, `Book` or `compose`;\n its table is never the register\'s, a Book\'s or another log\'s; none beside `scope`. A list people work every day as\n a desk of their own is another app.\n- **A dashboard** is an App with no register: it holds `Tab`s, `Reading`s and read-only `List`s that open in\n another app (`app`). One `period` windows every reading placed in time: this month by default, or `quarter`, `year`\n or `{ "days": 30 }`. `words` and `scope` belong to a register.\n- **Query and Workflow** nodes stand at the root and are never drawn.\n- **Writes:** nothing itself.\n\n## List node\n\nA List draws rows of one table. What its rows are depends on where it stands:\n\n- at the App root: the register (the first) or a log (each further one);\n- under a Record, Section, Side or Page: the record\'s child rows, filed through the child\'s one link to the record\n (`via` where it has several);\n- under a roster List: the rows that fill the roster\'s cells;\n- with `same` or `app`, or on a dashboard: related rows, read only.\n\n<!-- generated:start node-list -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Absent, the entity alias |\n| `entity` | alias | yes | The table whose rows the List draws |\n| `via` | alias | no | The child\'s link to the record, where it has several |\n| `same` | alias | no | A one-row link of the record: lists the rows sharing that link\'s row, the record left out, read only |\n| `where` | [narrowing](#app-tree) | no | A fixed narrowing on the server, for every reader: a select holds one of these options, a yes/no this value, a number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), a field is `{ "empty": true \\| false }`, and `<link>.<field>` keeps the rows whose one-row link names a row holding a select or yes/no so (a path holds nothing on an add) \u2014 every entry ANDed. A List that only reads (`same`, `app`, a Record with `edit: false`) also bounds a date or number by a field of the record it stands beside, `{ "record": "<field>" }`, read when the rows are: a window of the record\'s own days (`{ "published_on": { "gte": { "record": "period_start" }, "lte": { "record": "period_end" } } }`); a bound the record holds blank keeps no row, and the same rows are handed to an Act\'s paper that names the List. A record bound narrows what is listed; it is never an access bound \u2014 the table\'s row rules are. An add holds a select\'s option and a yes/no\'s value; a bound or a blank holds nothing on an add |\n| `expect` | alias \\| [Expect](#expect) | no | One line per option of this select, or per row of this catalog link |\n| `under` | alias | no | A one-row link to the document that covers the line; lines stand grouped under their document |\n| `app` | text | no | The id of the register app its rows open in |\n| `row` | [List row](#list-row) | no | How this List reads its rows, where it differs from the table\'s `records` |\n| `label` | text | no | What the app\'s lists and the tab strip call the register; absent, the entity\'s label. Never the noun of its rows \u2014 the table\'s own label and singular (this app\'s `words`) are |\n| `columns` | list of (alias \\| [Edit column](#edit-column)) | no | Fields read after the row\'s title; `{ field, edit: true }` is the line\'s own control, at most 2. Absent: none, the row reads its title and its `records` line alone |\n| `filters` | list of (alias \\| [Tiered filter](#tiered-filter)) (at most 3) | no | Fields the reader narrows the rows by every day, at most 3; the stage chips always stand. A field is a select, member, link, date, yes/no or number range; `tiers` bands a number or date |\n| `search` | list of alias (at least one) | no | Fields the search box reads; absent, every word of the row |\n| `group` | alias | no | A field the rows stand grouped by; a gantt draws the groups as its lanes |\n| `sort` | [Sort key](#sort-key) \\| list of [Sort key](#sort-key) (at least one) | no | The order of the rows; absent, the first `records.due` soonest, else newest |\n| `opens` | [narrowing](#app-tree) | no | The view the register opens on, a chip the reader may take away |\n| `period` | `"today"` \\| `"week"` \\| `"month"` \\| `"quarter"` \\| `"year"` \\| [Period days](#period-days) | no | The period the List opens on, with Tabs; absent, this month |\n| `attention` | `false` | no | false: rows keep the list\'s own order; absent, those needing attention open first |\n| `readings` | `false` | no | false: the register leads with no band of figures; absent, rows that move, fall due or carry money lead with one. Never with Readings stated |\n| `layout` | [layout](#layouts) | no | How the rows are drawn: a word, or an object stating one layout with its data; absent, a table |\n| `compare` | [Compare](#compare) | no | Reads each row\'s before against its after: two files fields as a pair of photos, or two values of one type as one column |\n| `current` | alias | no | A child List of revisions: the child\'s date or number field that orders them. The row holding the latest value reads as current and the others fold under "superseded \xB7 N", one press to open; the record shows the current file through a lookup of the child ordered by that field |\n| `create` | boolean \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The fields an add asks. Absent: where this List writes its rows, the table\'s required fields, its natural key and its title; `false`: rows are not added here. Where the table states no `singular` (nor the app\'s `words`), or `where` keeps a select at an option an Act sets other than the one a new row starts at, an absent `create` adds nothing; the table\'s `singular` or a stated list adds. A roster\'s cells and a document add only where `true` |\n| `add_label` | text | no | The add button\'s words; absent, the platform\'s word and the entity\'s singular |\n| `remove` | boolean | no | Rows offer a Delete. Absent: they do where this List writes its rows; `false`: this app\'s Delete is gone, while the record tools, the API and other apps still delete \u2014 a row no surface may delete is a table check with `on: ["delete"]`. A roster\'s cells, a document and a desk\'s rows delete only where `true` |\n| `pick` | `true` | no | Also picks rows of the child that no record holds yet |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `children` | list of ([Tab](#tab-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Export](#export-node) \\| [Act](#act-node) \\| [Check](#check-node) \\| [Book](#book-node) \\| [Record](#record-node) \\| [List](#list-node)) (at least one) | no | What the List holds: its Tabs, Readings, Progress, Exports, Acts and Checks, its Books, the Record a row opens, under a roster the List of its cells, and in a child List with `under` the List of the documents the lines stand under (the one place a document is added or deleted) |\n\n#### List row\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `title` | [List row title](#list-row-title) | no | The row\'s title field, stated for its props alone: `edit: false` reads it, `add` is its picker\'s add, `false` its picker only picks |\n| `subtitle` | list of (alias \\| [Field](#field-node)) (1\u20132) | no | Up to two fields read under the title, in place of the table\'s |\n| `figure` | alias \\| `false` | no | The one number a row stands for, in place of the table\'s; false, none |\n| `image` | alias \\| `false` | no | A files field that pictures a row, in place of the table\'s; false, none |\n| `due` | list of alias (at least one) \\| `false` | no | Dates that count down, in place of the table\'s; false, none |\n| `status` | `false` \\| `{ "edit": true \\| false }` | no | false: the stage is not shown. `{ edit: true }`: the stage is also moved by hand. `{ edit: false }`: nobody moves it in this app, another app does. Absent, the stage\'s moves are buttons |\n\n#### List row title\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `edit` | `false` | no | false: the title is read; absent, it is written where the table lets a person write it |\n| `add` | `false` \\| `true` \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The new row this field\'s picker offers asks these fields; `true`: the target\'s required fields, its natural key and its title. Stated once per target table, and it holds in every picker of that table in the app. `false`: this link only picks, in every picker of it the app draws, not only where it is written |\n\n#### Edit column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The column\'s field |\n| `edit` | `true` | yes | true: the column is the line\'s own control, written in place |\n\n#### Expect\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The child\'s title when the record holds one row per value of it: a single select (one line per option) or a one-row link to a catalog (one line per row) |\n| `of` | alias | no | With a select: the record\'s own multi-select holding the options this record expects a line for, in place of every option |\n| `only` | list of alias (at least one) | no | With a select: the options this List holds a line for; never with `of` |\n\n#### Compare\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `before` | alias | yes | A field of the List\'s rows holding what stood before: a files field (the photo of the place before the work), or a value \u2014 text, a number, a date or a single select (the price a row held before a sheet changed it) |\n| `after` | alias | yes | Another field of the same type holding what stands after: the photo of the same place once the work is done, or the value the row holds now |\n| `edit` | `true` | no | The after is written on the line |\n\n#### Tiered filter\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A number \u2014 stored, or a formula or rollup reading one \u2014 in one unit for every row |\n| `tiers` | list of number (at least one) | yes | The breakpoints between its bands, ascending, in the field\'s stored unit: under the first, from each to the next, from the last up \u2014 each band worded in the field\'s notation; a filter picks several at once |\n\n#### Sort key\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field rows are ordered by |\n| `desc` | `true` | no | Latest or largest first; absent, soonest or smallest first |\n\n<!-- generated:end node-list -->\n\nA List takes only the props its place lists:\n\n<!-- generated:start list-places -->\n\n| A List standing in | Takes |\n|---|---|\n| the register (the first List of the App) and a log (each further one) | `key`, `entity`, `row`, `label`, `columns`, `filters`, `search`, `group`, `sort`, `opens`, `period`, `attention`, `layout`, `create`, `add_label`, `remove`, `readings`, `where` |\n| a record\'s child rows, as a table, a checklist or a desk | `key`, `entity`, `via`, `label`, `columns`, `create`, `add_label`, `where`, `under`, `filters`, `search`, `group`, `sort`, `remove`, `pick`, `layout`, `compare`, `current`, `when`, `expect`, `row` |\n| a record\'s child rows with `layout` an agenda | `key`, `entity`, `via`, `label`, `columns`, `create`, `add_label`, `remove`, `layout`, `when`, `row` |\n| a record\'s child rows with `layout` `"timeline"` | `key`, `entity`, `via`, `label`, `create`, `add_label`, `remove`, `layout`, `when`, `row` |\n| a record\'s related rows, read only (`same`, `app`, or a Record that is `edit: false`) | `key`, `entity`, `via`, `label`, `where`, `columns`, `app`, `same`, `when`, `row` |\n| the documents a child List\'s lines stand under (`under`), a List inside it | `key`, `entity`, `create`, `remove` |\n| the cells of a roster, a List inside it | `key`, `entity`, `expect`, `create`, `remove`, `row` |\n| a dashboard\'s list, read only | `key`, `entity`, `label`, `where`, `columns`, `app` |\n\n<!-- generated:end list-places -->\n\n- **Children:** Tab, Reading, Progress, Export, Act, Check, Book, Record, and a List for a roster\'s cells. A Check or an\n Act stands in the List whose rows it works on: on each row (its \u22EF and the open row\'s), or with `on` on the ticked rows\n or every row in view.\n- **A child List\'s Record** holds `edit` and Fields only: it states which fields the rows\' saves write and draws no page\n (the page a row opens is the register\'s). With `edit: false` the List only reads, as a related-rows List does: it\n states its `label` and takes none of `filters`, `search`, `group`, `sort`, `create`, `remove` or `layout`.\n- **Documents:** in a child List with `under`, a List over the document\'s table (`key`, `entity`, `create`, `remove`,\n Acts and Checks) is the one place the documents are added or deleted; `create` states `true`, and `remove` needs\n `create: true` beside it.\n- **Writes:** a List that writes its rows adds and deletes them \u2014 `create: false` and `remove: false` turn each off \u2014 `pick` links existing rows, an `edit: true` column writes that\n field on the line, and a layout move (a gantt bar, a lane\'s block, a plan\'s item) writes only fields the app already\n writes on the row\'s page.\n- **`create` asks:** `true` asks the title, the natural key and every required field a person writes \u2014 never a computed\n or stamped field, nor the link the place already sets. An `expect` list also asks the key it makes its lines by, and\n their picture. A list of fields overrides it; `false`, rows are not added here.\n- **`row`** overrides what the table\'s `records` say of the register\'s rows in this List alone (a cashier\'s figure is what\n is still owed): the subtitle, figure, image and deadlines stated replace the table\'s. A child List, a related List and\n a roster\'s cells read their rows as their table does, so their `row` states only a picker\'s add (on its title or\n subtitle) or the stage moved by hand (`row.status`).\n- **`filters`** are the fields a reader narrows by every day, none to three \u2014 a quota never; each earns its place by\n daily use. A date filters by a range of days, a number by a range in its unit, `{ "field", "tiers" }` by the bands its\n breakpoints cut. A threshold the business names ("large orders") is a formula yes/no, never a range. The stage chips\n always stand.\n- **`opens`** is the view the register opens on, each entry a chip the reader may take away; absent, the open work\n (rows not at a `closed` stage).\n- **`expect`** makes one line per option of a select, or per row of a catalog link, and an empty line adds its row\n (a checklist of papers per case). **`under`** groups lines under the document that covers them.\n- **`compare`** reads each row\'s before against its after: two files fields as a pair of photos, or two values of one\n type as one column under the after\'s label.\n\n## Layouts\n\nA List\'s `layout` is how it draws its rows: a word (`"table"`, `"cards"`, `"calendar"`, `"roster"`, `"timeline"`) or an\nobject stating one layout and the data it reads (`{ "gantt": \u2026 }`, `{ "lanes": \u2026 }`, `{ "plan": \u2026 }`, `{ "agenda": \u2026 }`,\n`{ "checklist": \u2026 }`, `{ "desk": \u2026 }`). Absent, a table: the word `"table"` is the register\'s, and a child List states a\nchecklist, a desk, an agenda or `"timeline"`, else leaves `layout` out. Each layout reads what the rows already state \u2014 their\n`records` line, stage and dates \u2014 and the keys a layout adds stand on its section: here for an agenda and a checklist, \xA7 Time and place layouts for the rest, \xA7 Publish desk for a desk. Which one a job needs: the\n`design` reference. The register lays out as a table, cards, a calendar, a roster, a gantt, lanes or a plan; a record\'s\nchild rows as a table, a checklist, a desk, an agenda or a timeline.\n\n### Table\n\nAbsent `layout`, each row is one line: the table\'s `records` entry \u2014 its picture or mark, title and the line under it \u2014\nthen the stage, the `columns`, and the figure at the right; the title, the stage and a column of days or amounts sort\nthe rows by their header. A table groups its rows by `group`; a table or cards reads `Tab`s and `Book`s.\n\n### Cards\n\n`"cards"` draws each row as a card led by its picture \u2014 the `records` `image`, or the List\'s `row.image`: its title and\nthe line under it, its stage and what needs the reader (a deadline, a warning), then its figure and the `columns` at its\nfoot. Cards are for rows found by their picture (a product, a vehicle, a damage photo); rows with no picture read better\nas a table.\n\n### Calendar\n\n`"calendar"` places each row on its day \u2014 a month, a week, a day or a list (a phone reads the list or a day): at its\nhour where the date holds one, over its span where the row\'s line holds a second date after it, its line\'s other words\nand the `columns` after its title. The calendar reads the window in view, and its count and the stage chips count that\nwindow, opening on every row.\n\n### Roster\n\n`"roster"` draws the List\'s rows down the side and the days of a week or a month across; the roster\'s cells are a List\ninside it over the table whose rows fill the days \u2014 each stands on one row by its single link to it, and on the first\ndate of its line (a shift on a person\'s day, a run on a vehicle\'s), on every day through the second date where its line\nholds one. A cell reads its row\'s stage, else the first single select on its line (a shift\'s kind), several rows their\ncount. A day holding none stays empty: nothing was due, which is never an absence. A filled cell opens its row; an empty\none adds a row there, the register row and the day set. With `expect` on the cells (a single select of those rows) the\ncolumns are its options in place of days \u2014 a checklist of papers \u2014 and `of` names the ones each row expects: a gap among\nthem reads as one and adds its row, the others stand blank, and each row reads what it holds of them, `2/3`. Over days, a\nroster reads the `cadence` its table\'s `records` states (a site\'s diary page, Monday to Saturday from breaking ground to\nhandover): a past day owed with none is marked missing and counted, and each row reads the days it holds of those owed. A\nroster states no `columns` and needs no readings: its legend counts each state in view.\n\n```json\n{ "type": "List", "entity": "operator", "layout": "roster", "filters": ["depot"], "children": [{ "type": "List", "entity": "run" }] }\n```\n\n### Timeline\n\n`"timeline"` reads a record\'s child rows as a log of dated entries, newest first, with a composer where the app adds\nentries, each corrected and removed where it stands. It is never the record\'s status history: the rail shows each\nmove. A timeline\'s moment is its child\'s first date, and who its member field. Rows each what happened on a day and who \u2014\na date on their line, a member, no stage and no figure \u2014 read best as a timeline.\n\n### Agenda\n\n`{ "agenda": { \u2026 } }` reads a record\'s child rows as planned entries on a time axis: by day, soonest first, each with its\npicture and the day that is today marked. The day is the first date on the child\'s row line (its `records` title or\nsubtitle); within a day entries follow a datetime there, else the first single select after the date. An entry with no day\nyet is a plan, listed first and dated in place. `start` is the record\'s date that is the first day: each day\'s heading\ncounts from it (Day 1, Day 2).\n\n<!-- generated:start layout-agenda -->\n\n#### Agenda keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | no | The record\'s date that is the first day: each day\'s heading counts from it; absent, the weekday and the date alone |\n\n<!-- generated:end layout-agenda -->\n\n### Checklist\n\n`{ "checklist": { \u2026 } }` reads a record\'s child rows, which are tasks, as a checklist of checks: each one\'s mark \u2014 passed\nat a `closed` option, failed at a `failed` one, else still to check \u2014 its title, its columns as its words (a text column\nits note, on its own line), and on a line under its title its photo and its clause. Never grouped nor written in place.\n`passed` alone sets the mark, so rows with no `task` and no status are checks: a measured yes/no is the verdict. With\n`expect` on the standard\'s items, each item is a line marked the same way (a missing one still to check), filled in\nplace by the block\'s `columns` (a measured value, a note, a photo), so `photo` and `clause` are not named.\n\n<!-- generated:start layout-checklist -->\n\n#### Checklist keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `photo` | alias | no | The child\'s files field holding the photo each row is checked by; absent, the child\'s `records.image` |\n| `clause` | alias | no | The child\'s one-row link to what each row is checked against \u2014 a standard\'s clause, a rule |\n| `failed` | list of alias (at least one) | no | Options of the child\'s status a row stands failed at \u2014 none of them closed |\n| `passed` | alias | no | The child\'s yes/no field (stored or a formula) setting each row\'s mark: yes passed, no failed, empty still to check |\n\n<!-- generated:end layout-checklist -->\n\n## Time and place layouts\n\nThe layouts that place a List\'s rows in time or on a plan. Each is a `layout` object stating the data it reads.\n\n### Lanes\n\n`{ "lanes": { "by": \u2026 } }` draws each row of the linked table as a lane (a chair, a machine, a room), empty ones too,\nand each row of the List as a block from the first date on its line to the second: over the hours of one day where the\nfirst holds its hour, else over the days of one day, a week or a month. A block reads the row\'s name and the first of its\n`columns`; a free stretch between blocks adds a row there, its lane and its start set, and its end where the next block\nbounds it. Rows naming no lane stand first, in a lane of their own; where the app writes the link, each block moves to\nanother lane from its menu. Inside a `scope`, the lanes are the ones whose own one-link names that scope\'s row. A span\'s\nend holds its day unless `records` names it the day the row `frees`.\n\n`loads` names what a lane carries: `{ "weight": "payload", "volume": "box" }` sums each block\'s number and reads it\nagainst the lane row\'s own, at most two. A lane holds its blocks while they stand, so it reads the most they add up to at\none time in the window, named, past its bound, by how much and on which day or hour. The two are in one unit, or two of\none dimension. A bound a single row must keep (no parcel heavier than its van) is a `max` on the field through the link,\nwhich refuses the move.\n\n<!-- generated:start layout-lanes -->\n\n#### Lanes keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `by` | alias | yes | A one-link of this entity whose target\'s rows are the lanes (a chair, a machine, a room), each drawn even when empty |\n| `loads` | map of alias \u2192 alias | no | What a lane carries against what it holds, at most 2: a number of this entity each block adds \u2192 a number of the lane\'s row it is held within, in one unit or two of one dimension |\n\n<!-- generated:end layout-lanes -->\n\n### Gantt\n\n`{ "gantt": { \u2026 } }` draws each row as one bar on one axis of time: `{ "start": "etd", "end": "eta", "milestones":\n["cut_off"], "planned": { "end": "booked_eta" }, "progress": "done", "after": "waits_on" }`. `end` defaults to the table\'s\nfirst `due`, and a table with neither is refused; `start` and `end` are two dates of the row, both days or both moments,\nand each planned date is read as `start` is. `progress` is a percent (0\u2013100); absent, a row whose record lists its tasks\nfills to how many are done. `after` is a link to rows of the same table, each ending before the row starts. The bars\nstand in the lanes of the List\'s `group`. Where the app writes the row and a person writes a bar\'s date, the bar is\ndragged through the row\'s save; a computed date, one an Act sets, or a row the app does not write drags nothing.\n\n<!-- generated:start layout-gantt -->\n\n#### Gantt bar\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | yes | A date of this entity each row\'s bar starts at (a sailing, a hire\'s start, a task\'s start) |\n| `end` | alias | no | The date each row\'s bar ends at, read as `start` is \u2014 two days or two moments; absent, the entity\'s first `due` |\n| `milestones` | list of alias (at least one) | no | Dates of the row marked on its bar\'s line as diamonds, each named by its label (a cut-off, a delivery, the end of free time) \u2014 never `start` or `end` |\n| `planned` | [Gantt plan](#gantt-plan) | no | The plan each row is read against, drawn as a thin bar under its own \u2014 each date read as `start` is; a missing one is the bar\'s own |\n| `progress` | alias | no | A percent of this entity (0\u2013100) filling each row\'s bar as far as the work is done; absent, how many of the row\'s tasks are done fills it where its record lists them, else the bar wears its status\'s tone |\n| `after` | alias | no | A link of this entity to its own rows each row waits on \u2014 it starts once they end (finish-to-start), an arrow from each; a row starting before one ends is drawn in the danger tone |\n\n#### Gantt plan\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | no | The date the row was planned to start at |\n| `end` | alias | no | The date the row was planned to end at (a booked arrival) |\n\n<!-- generated:end layout-gantt -->\n\n### Plan\n\n`{ "plan": { \u2026 } }` draws things that stand at a place \u2014 pallets in a warehouse \u2014 on a plan of the places. The places are\nthe rows of a table whose `records` states a `plan_place` (a bay, an open area or a landmark, each at its spot on a sheet\nof the plan); `at` names the one-link of the List\'s rows to a place and, for an item in a bay, the row (`across`) and the\ntier (`up`) it stands in, counted from 1. A bay is drawn as its rows and tiers, an area with a count, a landmark as a\nlabelled frame; the reader sees a plan, one bay enlarged, and the same plan in 3D. The List may read other tables as\n`Book`s: one plan of every table\'s items. Each item carries `__reach` \u2014 how many items stand in the way of taking it out\n(those above it where the place is `stacked`, and those in the rows between it and the side `reach_from` says the bay is\ntaken from) \u2014 so the list, its export and an agent\'s read all carry it; `<table>_plan_load` counts the rows each place\nholds. A move through the app is refused unless it clears the place, row and tier all three, or places the item at\na free cell of a bay \u2014 resting on a present item where the place is `stacked` \u2014 or at an area. Every other write (a grid edit, an import, a tool) holds one item to a cell only where the item\'s table states `unique: [<at>, <across>, <up>]`; a plan over a table stating none is noted. A plan opens on every row\nits `where` keeps, with no stage chip narrowing it. Rows with no place wait in a tray beside the plan. An Act that asks exactly a place (a link to the plan\'s places) and two numbers (the row and the tier) is answered by a pick on the plan, never by a form. On a desk a press opens a row\'s record in the panel beside the plan.\n\n```jsonc\n// table "bay"\n"title": "code",\n"records": { "plan_place": { "kind": "kind", "x": "x", "y": "y", "across": "rows", "up": "tiers", "reach_from": "taken_from", "stacked": "is_stack" } }\n```\n\n```json\n{ "type": "List", "entity": "pallet", "filters": ["line"], "layout": { "plan": { "at": "bay", "across": "row", "up": "tier" } } }\n```\n\n```jsonc\n// table "pallet": one item to a cell on every write, not only through the app\n"unique": [["bay", "row", "tier"]]\n```\n\n<!-- generated:start layout-plan -->\n\n#### Plan keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `at` | alias | yes | A one-link of this entity to an entity whose `records` states a `plan_place`: where each row stands |\n| `across` | alias | yes | A number field: the row of a bay a row stands in, counted from 1; empty at an area |\n| `up` | alias | yes | A number field: the tier of a bay a row stands at, counted from 1 at the ground; empty at an area |\n| `fill` | alias | no | A single select the items are coloured by; absent, the first single select among the register\'s filters |\n| `mark` | [Plan mark](#plan-mark) | no | A row holding the option says the option\'s label on its box and is hatched in the option\'s own colour, and the legend names it by the label; in books, the select is matched by option alias, and a book lacking the select never marks its rows |\n| `size` | [Plan size](#plan-size) | no | Each row\'s box as it stands in the world, in the one length unit its places\' `size` states: the option the row holds of the select names its box\'s width across, depth along and height; in books, matched by option alias, as `mark` is; a row holding an option it does not name is drawn at its place\'s cell |\n\n#### Plan mark\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select of this entity |\n| `option` | alias | yes | An option alias of that select |\n\n#### Plan size\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select of this entity |\n| `options` | map of alias \u2192 [Plan size box](#plan-size-box) | yes | The box of each option alias of that select |\n\n#### Plan size box\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `across` | number | yes | The box\'s width across the bay\'s rows |\n| `along` | number | yes | The box\'s depth along them |\n| `up` | number | yes | The box\'s height |\n\n#### Plan place\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | alias | yes | A single select, options aliased exactly `bay`, `area`, `landmark`: a bay holds rows and tiers, an area neither, a landmark nothing |\n| `sheet` | alias | no | The plan sheet it is on: a single select, or a one-row link to an entity whose `records` states its `image` \u2014 the sheet\'s drawing, which the places are drawn over (with `scale`) |\n| `scale` | alias | no | A number field of the sheet entity `sheet` links to: how many cells wide its drawing is, the plan\'s scale. Required where that entity states an `image`, and only then |\n| `x` | alias | yes | A number field: left edge, in cells |\n| `y` | alias | yes | A number field: top edge, in cells |\n| `width` | alias | no | A number field: an area\'s frame width, in cells |\n| `depth` | alias | no | A number field: an area\'s frame depth, in cells |\n| `across` | alias | yes | A number field: a bay\'s rows |\n| `up` | alias | yes | A number field: a bay\'s most tiers |\n| `reach_from` | alias | no | A single select, options aliased exactly `first`, `last`, `both`: the side boxes leave a bay from; a bay that names none has no reach and no arrow |\n| `stacked` | alias | no | A boolean field: true where items at this place rest on the one below (a stack), so a tier above the first needs a present item under it, shows floating without one, and an item\'s reach counts the items above it; false or empty, items sit at any tier and only the rows between a bay\'s reach side and the item are in the way |\n| `size` | [Plan place size](#plan-place-size) | no | The real size of ONE cell of the place, for 3D: three number fields stating one length `unit` alike. Absent, a cell is drawn at the size most of the sheet\'s places state, a unit cube where none states one |\n\n#### Plan place size\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `across` | alias | yes | A number field: the width of one cell across the rows |\n| `along` | alias | yes | A number field: the depth of one cell along the place |\n| `up` | alias | yes | A number field: the height of one cell, one level |\n\n<!-- generated:end layout-plan -->\n\n## Publish desk\n\nRows that are each a place the record is sent \u2014 a notice\'s boards, a listing\'s portals \u2014 are a record\'s child List with\n`expect` on the child\'s one-row link to the catalog of those places and `layout: { "desk": \u2026 }`:\n\n```json\n{\n "type": "List",\n "entity": "posting",\n "expect": "board",\n "columns": ["posted_link", "refusal"],\n "layout": { "desk": { "bodies": ["wording", "wording_abroad"], "text": "goes_out_as", "media": "pictures", "link": "page", "group": "reach", "network": "kind", "networks": { "company_page": "linkedin" } } }\n}\n```\n\nThe desk is where the post is written. `bodies` are the record\'s plain text fields it is written in (one per language,\nsay), each its editor between the list and the preview, saved as any field of the record is and drawn nowhere else on it.\nThe preview is `text` read over what is on screen: where `text` is a formula of the child, the desk computes that same\nformula over what is typed, so the post moves as it is written, and a place not ticked previews the words its row would\nhold. For that the formula reads only what a screen holds whole: stored values of its row, the record through lookups over\nthe link back to it, the place through lookups over `expect`, and formulas of the row over those \u2014 a rollup, a lookup\nthrough another link or the clock is refused, naming the field.\n\nEvery place the link admits is a line with a tick and that place\'s face: its picture, else the mark of the network it is on\nwhere `networks` names one. `group`, another single select of the place, stands the lines under its options in their\norder: a kind holding a ticked line is open, one holding none folded to its heading and its count. A tick makes the line\'s\nrow from its key alone \u2014 so nothing else the child requires is left without a default \u2014 and an untick deletes it until the\nrow\'s stage is `closed`: what went out is not taken back. It asks first only where a person wrote on the row. One line is\nworked beside the list and read as the post that place\'s feed shows: `text` is the child\'s words for that place, `media`\nand `link` the record\'s, and `network` the place\'s single select whose options `networks` names to a feed; an option not\nnamed reads on a plain card. Under the bodies stand the List\'s `columns` a person writes of the line\'s row; under the post,\nthe press copying its words beside the Acts of the child, then what the line and the `columns` hold that nobody writes\nthere (the link it went out at, why it was refused). A value an Act or a workflow fills is stated as that Act\'s, so the\ndesk never offers it as an empty box. Sending is an ordinary Act of the record.\n\n<!-- generated:start layout-desk -->\n\n#### Desk keys\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `bodies` | list of alias (1\u20133) | no | The record\'s plain text fields the post is written in, in order \u2014 one per language, say \u2014 at most 3: each its editor on the desk, between the list and the preview, and drawn nowhere else on the record |\n| `text` | alias | yes | A text of the block\'s rows \u2014 stored, or a formula yielding text \u2014 holding the words that go out on that row\'s destination. A formula is computed on the desk too, over what is typed: it reads stored values of its row, the record through lookups over the link back to it, the destination through lookups over `expect`, and formulas of the row over those \u2014 never a rollup, another link or the clock |\n| `media` | alias | yes | A files field of the record: the pictures or the video the post carries |\n| `link` | alias | no | A text field of the record holding a link (`format: "link"`), carried as the post\'s card |\n| `network` | alias | yes | A single select of the entity `expect` links to, saying which network each destination is on |\n| `group` | alias | no | Another single select of the entity `expect` links to \u2014 the destination\'s kind \u2014 the lines stand grouped by: each option a heading in the select\'s order, a group holding no ticked line folded to its heading and its count, destinations holding no option last |\n| `networks` | map of alias \u2192 `"facebook"` \\| `"instagram"` \\| `"threads"` \\| `"x"` \\| `"linkedin"` | yes | That select\'s options, each by its alias, to the network whose feed the post is previewed in \u2014 facebook, instagram, threads, x, linkedin. An option not named has no feed to imitate: its words read as a plain card |\n\n<!-- generated:end layout-desk -->\n\n## Record node\n\nA Record draws what a row opens: the head, then the parts.\n\n- **The head:** title, subtitle, picture, mark, party, stage badge, figure and meter, deadlines, description \u2014 always the\n table\'s `records` facts, overridden by the List\'s `row`. A field the head draws is never also a Field of the record.\n- **No Record node:** under the register, the platform designs the record\'s Sections from the rows: the fields the\n head does not draw, child Lists, papers and Acts. A stated Record stands exactly as written, so it replaces them.\n Under a child List, an absent Record is every field of the child except the link back.\n- **Children:** either parts \u2014 `Section`, `Side`, `Page` \u2014 or, for a one-part record, `Field`, `List`, `Reading`,\n `Progress`, `Statement` and `Act` directly. The two are never mixed. Below the register \u2014 a child List\'s, a Book\'s or\n a roster cell\'s \u2014 a Record holds Fields and `edit` only, stating which fields its saves write.\n\n<!-- generated:start node-record -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `door` | `"page"` \\| `"drawer"` \\| `"beside"` | no | Absent, a page where a part is a step, a Side or a Page or there are more than 3 parts, else a drawer |\n| `comments` | `true` | no | The record carries comments; absent, nobody comments |\n| `edit` | `false` | no | false: nothing the record draws is written |\n| `compose` | [piece](#composed-page) | no | The page stated as a tree of finished pieces; a Record with `compose` holds no children |\n| `children` | list of (alias \\| [Field](#field-node) \\| [Section](#section-node) \\| [Side](#side-node) \\| [Page](#page-node) \\| [Outside](#outside-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | Parts (Section, Side, Page), or for a one-part record its Fields, Lists, Readings, Progress, Statements and Acts directly \u2014 never both; Outside parts stand beside either. Absent, the platform designs the Sections from the rows: the fields the head does not draw, child Lists, papers and Acts. Stated, it stands exactly as written |\n\n<!-- generated:end node-record -->\n\nThe door is a page where a part is a step, a Side or a Page, or where there are more than 3 parts; else a drawer. `door`\nis a judged choice of how a job is worked (one after another over a long record), so it is kept. From three parts on, the\nrecord is navigated by their titles: a page by a rail that jumps to each (a strip of the same names on a phone), a drawer\nby the same strip pinned over its scroll; with fewer, the parts just stack.\n\n`comments: true` lets people comment, and is opt-in: state it only where the job\'s people discuss a record. A door in the\nrecord\'s head then opens the comments in a panel beside it, with one composer. A record shows no activity and no audit\nlog: a history an app shows is table-backed \u2014 `records.status.dates`, `records.status.history`, an event table an act\nmakes \u2014 stated where the app shows it.\n\nA field, a Reading and an Act is each placed once on a record: the head, or one part. A Section or a List with `when` is\ndrawn only while each named select of the record holds one of its options.\n\nA page the parts cannot say is a **composed page** (\xA7 Composed page): `compose` states the record as a tree of finished\npieces, and the Record holds no children.\n\n## Section node\n\nA titled part of a record, and optionally a step of its path. A Section draws its Fields, Lists, Readings, Progress,\nStatements and Acts in reading order; its Acts stand at its foot.\n\n<!-- generated:start node-section -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique among its siblings; part of the node\'s address. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The part\'s heading |\n| `description` | text | no | A line under the heading |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `edit` | `false` | no | false: nothing in this part is written |\n| `at` | list of alias (at least one) | no | Options of the record\'s status during which this part is the work now: a step of its path |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | What the part draws, in reading order; its Acts stand at its foot |\n\n<!-- generated:end node-section -->\n\n- **`at`** names the stage options during which the section is the work now \u2014 only on a record that\n truly moves through those stages: the rail marks it done, now or waiting, and the section stands even holding nothing\n yet. It never hides a field, locks it or gates an act.\n- **Acts:** every Act on a record stands in the Section whose work it is. An Act moving the stage and offered at ONE step\n stands at that step\'s foot when it moves the work forward, to an exit later in the stage\'s options, or back to a step the\n work passes anyway; one offered at several steps, or moving back into a detour, stands in the Section it enters. A\n correction (`danger`, and not one of its step\'s outcomes) waits in the Section heading\'s \u22EF.\n- **A detour** \u2014 a stage the work only steps aside into from one step and leaves only back to it (a pause, a repair) \u2014\n stands before that step, its option and its Section alike.\n- **An exit** stands only once the record is in it, its reason there and its rail item apart from the path; until then the\n Act waits in the header\'s \u22EF.\n- **Writes:** its Fields and its Acts.\n\n## Side node\n\nThe one part drawn beside the rest \u2014 the photos looked at, the pool picked from. At most one Side to a record, on a page\ndoor, and never also a Page.\n\n<!-- generated:start node-side -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique among its siblings; part of the node\'s address. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The part\'s heading |\n| `description` | text | no | A line under the heading |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `edit` | `false` | no | false: nothing in this part is written |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | What the part draws, in reading order; its Acts stand at its foot |\n\n<!-- generated:end node-side -->\n\nIt holds what a Section holds. Writes: its Fields and its Acts.\n\n## Page node\n\nA part on its own page, off the record\'s scroll, with a way back \u2014 rarely, for a long related list or a workspace of its\nown that would make the record worse stacked. Never on a drawer, and never a step: its title names an address no other\npage shares (a letter or a digit at least), and not every part is a Page.\n\n<!-- generated:start node-page -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique among its siblings; part of the node\'s address. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The part\'s heading |\n| `description` | text | no | A line under the heading |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n| `edit` | `false` | no | false: nothing in this part is written |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Reading](#reading-node) \\| [Progress](#progress-node) \\| [Statement](#statement-node) \\| [Act](#act-node)) (at least one) | no | What the part draws, in reading order; its Acts stand at its foot |\n\n<!-- generated:end node-page -->\n\nIt holds what a Section holds. Writes: its Fields and its Acts.\n\n## Outside node\n\nWhat one record shows, and which acts it allows, to a person with no seat who opens a link a member made of it. A member\npresses Create link on a record while its status stands in `at`; the link opens that one record until it expires, is\nrevoked, or its act moves the status out of `at`. The person sees the stated Fields and Lists and presses the stated Acts\nand nothing else. It stands directly in a Record of a table with a stored status select: the register\'s, or the Record of\na List below the register, whose rows the link then opens.\n\n<!-- generated:start node-outside -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | no | Unique in the app: a link to the record names it. Absent, the title\'s words as a slug, written once and kept |\n| `title` | text | yes | The heading of the page the person without a seat opens |\n| `description` | text | no | A line under the heading |\n| `at` | list of alias (at least one) | yes | Options of the record\'s status during which a link to it opens; none is owned by a stage, and each Act moves the status out of them |\n| `children` | list of (alias \\| [Field](#field-node) \\| [List](#list-node) \\| [Act](#act-node)) (at least one) | yes | Exactly what the link shows and allows: Fields read as values, Lists of child rows read as they are, and the Acts the person may press, each moving the status out of `at` |\n\n<!-- generated:end node-outside -->\n\n- **Fields** are read as values, in order; a lookup, a rollup or a formula is computed for the link only if stated here.\n A Field states nothing but its alias.\n- **Lists** read the child rows under the record as they are: `columns`, `label` and `where`.\n- **Acts** are the link\'s alone: no member, visitor or agent presses them. Each states `key`, `label`, `confirm`, `when`,\n `requires`, `asks`, `set`, `template` with `into`, or `sign`. `when` absent is the status held in `at`; `set` moves the\n status to an option outside `at`, so each press ends the visit. `asks` are text, numbers, dates or listed options. A\n `sign` is by a text field the signer types, with no witness: the link is the attestation.\n- `at` names options no stage owns, and no two Outsides of a table open during the same option.\n- **Writes:** its Acts, and nothing else.\n\n## Tab node\n\nA Tab is one view of a List\'s rows over a period, or one sheet of a dashboard.\n\n- **In a List:** a register reporting a period \u2014 what stands at its end beside what came and went during it (the stock,\n the hires out, the tickets open) \u2014 reads its rows as Tabs over one `period` the reader picks above them. A Tab holds\n the rows `dated` within the period, those `open` at its end, or lists every row and draws what a child\'s rows came to\n in the period (`totals`). A Tab narrows its rows and holds no nodes; Readings stand beside it in the List and read the\n open Tab, or the one their `tab` names. Never date chips set by hand, and never a Tab per stage, which the chips split.\n- **On a dashboard:** a Tab holds its Readings and Lists as children. A dashboard with Tabs (2 to 5) draws a strip under\n its title that picks one, the first open.\n\n<!-- generated:start node-tab -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | Unique among its siblings; part of the node\'s address |\n| `label` | text | yes | What the tab holds, in the reader\'s words |\n| `dated` | alias | no | A date: the tab holds the rows dated within the List\'s period |\n| `open` | [Tab open](#tab-open) | no | The tab holds the rows open at the period\'s end: begun, and not yet ended |\n| `totals` | [Tab totals](#tab-totals) | no | Each row\'s figures over a child\'s rows in the period |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `sort` | [Sort key](#sort-key) \\| list of [Sort key](#sort-key) (at least one) | no | The order this tab\'s rows read in; absent, the List\'s |\n| `children` | list of ([Reading](#reading-node) \\| [List](#list-node)) (at least one) | no | On a dashboard: the Readings and Lists this sheet draws. A Tab of a List holds none: Readings stand beside it in the List |\n\n#### Tab open\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | alias | yes | The date a row begins on |\n| `to` | alias | yes | The date a row ends on, empty while it is open |\n\n#### Tab totals\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `rows` | alias | yes | An entity of this model whose rows each belong to one row of the register\'s entity: the rows the columns add |\n| `on` | alias | yes | A date of the child placing each row in time: stored, a formula yielding a date, or a lookup of a date through a one-row link \u2014 never a moment or a span of days |\n| `columns` | list of [Totals column](#totals-column) (1\u20136) | yes | The figures drawn in place of the register\'s columns after each row\'s name, each under its own alias and label |\n\n#### Totals column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | The column\'s name: its address in the totals read |\n| `label` | text | yes | What the column holds, in the reader\'s words |\n| `value` | alias | no | A number of the child (stored, a formula or a rollup) summed over the rows; absent, the rows are counted. A figure read in each row\'s own unit or currency stands only where the child\'s select is a lookup, through `via`, of a single select of the register\'s entity |\n| `where` | map of alias \u2192 (list of alias (at least one) \\| boolean) | no | Only the child\'s rows whose select holds one of these options, or whose yes/no is this value \u2014 every entry ANDed |\n| `window` | `"before"` \\| `"in"` \\| `"through"` | yes | Which of the child\'s rows, by `on`, the column adds: `before` those dated before the period\'s first day, `in` those from its first day through its last, `through` those on or before its last day |\n\n#### Period days\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `days` | integer | yes | How many days through today, at most 366 |\n\n<!-- generated:end node-tab -->\n\nA `totals` Tab adds, per column, the child\'s rows `before` the period\'s first day, `in` the period, or `through` its last\nday, so `before` plus `in` of one value is its `through`:\n\n```json\n{\n "type": "Tab",\n "key": "items",\n "label": "Items",\n "totals": {\n "rows": "ledger_line",\n "on": "moved_on",\n "columns": [\n { "alias": "opening", "label": "Opening", "value": "quantity", "window": "before" },\n { "alias": "received", "label": "Received", "value": "quantity", "where": { "kind": ["receipt"] }, "window": "in" },\n { "alias": "issued", "label": "Issued", "value": "quantity", "where": { "kind": ["issue"] }, "window": "in" },\n { "alias": "closing", "label": "Closing", "value": "quantity", "window": "through" }\n ]\n }\n}\n```\n\n## Field node\n\nA Field draws one field of the record it stands in. How it is drawn follows from the field\'s type and the table\'s facts:\n\n- a control or a value, with its notation, money, units and deadline countdown;\n- a files field as pictures and documents, a long or markdown text as prose written in place \u2014 a gallery or a prose block,\n with a heading (`label`) and a condition (`when`) of its own; a run of files Fields is one gallery;\n- a link as its row\'s chip, with a picker narrowed by `options_where`, and `add` for the new row the picker offers;\n- the back side of a child\'s link as the rows filed under the record, added to with `add`;\n- the fields an Act\'s `sign` writes as one signature.\n\n<!-- generated:start node-field -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field\'s alias |\n| `edit` | `false` | no | false: the field is drawn and read; absent, it is written where the table lets a person write it |\n| `label` | text | no | The heading of the field\'s gallery or prose block, where it draws as one; absent, the field\'s label. A run of files fields takes the first one\'s |\n| `block` | boolean | no | true: a files or text field draws as its own gallery or prose block; false: as a control among the part\'s fields. Absent, files and markdown fields draw as blocks and plain text as a control |\n| `when` | [condition](#app-tree) | no | A gallery or prose block is shown only while the record\'s selects hold these options, its yes/nos these values, its numbers and dates meet their bounds and its `{ "empty": true \\| false }` fields are blank or filled |\n| `add` | `false` \\| `true` \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The new row this field\'s picker offers asks these fields; `true`: the target\'s required fields, its natural key and its title. Stated once per target table, and it holds in every picker of that table in the app. `false`: this link only picks, in every picker of it the app draws, not only where it is written |\n\n<!-- generated:end node-field -->\n\nA field drawn in the head (title, subtitle, picture, figure, stage) is never also a Field node. A Field is drawn once per\npage. **Writes:** the field, under \xA7 App tree, and with `add` the target table\'s create.\n\n## Act node\n\nAn Act is a verb, stated whole where it stands. Its place decides what it works on:\n\n- in a Section, Side or Page, or in a one-part Record: one record, at that part\'s foot;\n- in a List: on each of its rows, offered in the row\'s \u22EF and, once the row is open, in its header\'s \u22EF; in a child List,\n the child\'s rows;\n- in a List with `on`: the ticked rows (`"ticked"`) or every row in view (`"view"`), each checked before any is written;\n- directly in a Record that holds parts: refused.\n\n<!-- generated:start node-act -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The act\'s name; its workflow is `act_<key>`, or the `workflow` it names |\n| `label` | text | yes | The verb, as the reader says it |\n| `on` | `"ticked"` \\| `"view"` | no | In a List: the ticked rows, or every row in view; absent, one row |\n| `when` | [condition](#app-tree) | no | The act works only while each named select holds one of these options, each yes/no this value, each number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and each `{ "empty": true \\| false }` field is blank or filled |\n| `requires` | list of alias (at least one) | no | Fields that must be filled first, each drawn where the act stands; a blocked act names each one missing |\n| `recommends` | list of alias (at least one) | no | Fields the act runs without and reads when filled: while one is empty, the act says what it will leave blank |\n| `asks` | list of (alias \\| `{ "field": alias, "starts": "today" \\| "me" }` \\| [Asked input](#asked-input)) (at least one) | no | What the reader states when pressing it: fields of the row (written by the act) or inputs |\n| `set` | map of alias \u2192 [value](#app-tree) | no | What the act writes; each field is the act\'s, read-only elsewhere in the app |\n| `workflow` | alias | no | The workflow the act makes and runs under this name, for what `set` cannot say; no Workflow node is declared for it, and `set_app_workflow` sets its body |\n| `writes` | list of alias (at least one) | no | With `workflow`, fields of the acted row its body writes (an id kept from a service\'s answer) \u2014 the act\'s, as what `set` states is |\n| `template` | alias | no | A document template the act makes a file from: an html one made a PDF, an excel one a workbook, a word one a .docx; over several rows, one file of them all |\n| `templates` | list of [Act template](#act-template) (at least 2) | no | Several papers the act makes into `into`, instead of one `template`: the reader ticks which to make, one press making those |\n| `into` | alias | no | The record\'s files field the made document is kept in; remade, it replaces its own last paper, never a person\'s file |\n| `paper` | text | no | Its paper, `<child>.<kind>`: the line of the record\'s papers it stands on |\n| `intake` | alias | no | A child the record lists whose rows are its papers: the press takes papers, an agent reads them, and each is filed as a row of this child |\n| `fills` | list of text (at least one) | no | With `intake`, what the papers read may fill: a field of the record, `<link>.<field>` on the row a required one-row link names, or a child whose rows the papers add. With `record`, fields of the new row an agent fills from the transcript |\n| `record` | [Recording](#recording) | no | The press records a call, visit or meeting and files it as a new row of a child the record lists |\n| `sign` | [Signature](#signature) | no | The press opens a pad the signer signs on; the act keeps the signature as a picture with who signed, when and the witness |\n| `open` | text | no | The id of another app that reaches this row\'s table, opened at this row by the press: navigation only \u2014 no workflow runs, and a reader without `use` on it is shown that app\'s own refusal. Stated alone with `label` and `when` |\n| `review` | `true` | no | With `workflow`: the workflow only proposes a changeset of the act\'s table, and the reader ticks the rows to write |\n| `confirm` | `true` \\| text | no | The press asks first: the question, or `true` for the act\'s label over the row |\n| `danger` | `true` | no | The act destroys or cannot be undone |\n\n#### Asked input\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `input` | alias | yes | A value the act asks for that is not a field of the record |\n| `label` | text | yes | What the reader is asked, in their words |\n| `type` | `"text"` \\| `"long_text"` \\| `"number"` \\| `"date"` \\| `"select"` \\| `"link"` \\| `"member"` \\| `"file"` | yes | What the reader states. A `file` is one uploaded file, handed to the act\'s authored `workflow` as its file id and read by nothing else |\n| `options` | list of [Select option](#select-option) (at least one) | no | The choices a `select` input offers, one picked \u2014 written into a select holding the same option aliases |\n| `entity` | alias | no | The entity a `link` input picks one row of |\n| `accepts` | list of `"sheet"` \\| `"pdf"` \\| `"document"` \\| `"photo"` (at least one) | no | The kinds of file a `file` input takes; absent, a sheet, a PDF or a document \u2014 a phone offers its camera only where `photo` is among them |\n| `required` | `true` | no | The act is refused without it |\n| `default` | alias | no | A field of the row whose value the input starts from, still changed at will |\n| `add` | `true` \\| list of (alias \\| [Field](#field-node)) (at least one) | no | The new row this picker offers asks these fields; `true`: the target\'s required fields, its natural key and its title. Stated once per target table, and it holds in every picker of that table in the app |\n\n#### Set formula\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `formula` | text | yes | A formula over the acted row\'s fields, `{alias}` each, read as the entity\'s own formulas read them |\n\n#### Act template\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `template` | alias | yes | A document template the act makes a paper of, kept in `into` \u2014 an html one made a PDF, an excel one a workbook, a word one a .docx |\n| `when` | map of alias \u2192 (list of alias (at least one) \\| boolean) | no | The paper starts ticked while each named select of the record holds one of these options and each named yes/no (stored, or a formula \u2014 how a multi-select is tested: `includes({needs}, {needs:x})`) is this value \u2014 a suggestion the reader changes, never a refusal; absent, it starts ticked |\n\n#### Recording\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `into` | alias | yes | A child the record lists in a timeline or rows block, linking back to it by a single link: each recording is filed as a new row of it |\n| `audio` | alias | yes | The child\'s files field the recording\'s audio is kept in |\n| `transcript` | alias | yes | The child\'s text field the transcript is kept in, a speaker\'s turn per line |\n| `video` | alias | no | The child\'s files field the screen is kept in, where the recording captured it |\n| `at` | alias | no | The child\'s date field stamped with the moment the recording started |\n| `by` | alias | no | The child\'s member field set to the person who recorded |\n\n#### Signature\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `into` | alias | yes | The record\'s files field the signature is kept in, as a picture \u2014 each signing replaces the one before |\n| `by` | alias | yes | Who signed: a text field the signer types their name into (someone with no seat in the workspace \u2014 a driver, the owner\'s supervisor), or a member field set to the person pressing, who signs as themselves |\n| `at` | alias | yes | The record\'s date field stamped with the server\'s time as the act runs |\n| `witness` | alias | no | Beside a text `by`, the member field set to the person pressing, who saw the signer sign |\n\n<!-- generated:end node-act -->\n\nAn Act whose `when` does not hold is not offered. One whose `requires` are empty is offered disabled, naming each; one whose\n`recommends` are empty names what it will leave blank and still presses; one a standing Check blocks is offered disabled in\nthe check\'s words. The generated `act_<key>` workflow re-checks all three on the server, writes `set` and what was asked,\nand appends the status history where the act moves the status. A field an act asks is read-only everywhere else. A `select`\ninput offers its own options and is written into a select holding the same option aliases; a `link` input picks one row of\nits `entity`, a `member` input one person. A `file` input is one uploaded file handed to the act\'s authored `workflow` as its\nfile id; nothing else reads it. A field an act both `requires` and `asks` never disables the press.\n\n- **Writes:** every field it sets, asks, files into, signs or records is the act\'s.\n- **Papers.** A button that makes a paper is `template` (or `templates`) with `into` on the Act, with no workflow. `template` makes a document from the row as the act left it and keeps it in `into`; over several rows it\n makes one document of them all. `templates` names a set of papers kept in `into`, each made one as its file and each not\n made yet as a placeholder, those whose `when` holds ticked to start; the press makes the ticked ones, each replacing the\n file its template made before, and the set downloads as one archive (`archive_<key>`). `intake` reads papers: the press\n takes files, an agent reads them, and each is filed as the record\'s line of its kind in the named child; what they state\n of each field `fills` names is shown beside what the row holds, and the save writes only what differs. `sign` opens a pad\n on the device: the act keeps the picture in `into` in place of any before it, `at` stamped with the server\'s time and the\n person pressing in `witness`; the record reads `into` as one signature, and the act\'s paper is handed the picture under\n `into`\'s name (`{{#each signature}}<img src="{{url}}">{{/each}}` in an html template). `record` records a call or a visit\n through the host and files its audio, transcript and screen as a new row of a child.\n- **`workflow`** is an authored workflow (`workflows`) the act runs after its checks, for what neither `set` nor `template` can say.\n **`review: true`** makes it one that only proposes: its body reads, returns a changeset of the act\'s table in\n `data.review`, and the platform writes the rows the reader ticks in the act\'s dialog, each checked first and all written\n in one transaction or none. Over every row in view the act is handed no rows.\n- **A `when` that compares** splits one stage by an amount or a date, with no formula field behind it. The server refuses a\n press the row\'s numbers and dates do not meet, in the words "Approve works only while Amount is at most 5,000,000", and\n a blank number or date meets no bound. Two acts on an expense claim, the second moving it to a stage the Director group owns\n (stage owners decide who presses):\n\n```json\n{ "type": "Act", "key": "approve", "label": "Approve", "when": { "status": ["submitted"], "amount": { "lte": 5000000 } }, "set": { "status": "approved" } }\n```\n\n```json\n{ "type": "Act", "key": "send_to_director", "label": "Send to the director", "when": { "status": ["submitted"], "amount": { "gt": 5000000 } }, "set": { "status": "director_review" } }\n```\n\n```json\n{ "type": "Act", "key": "gate_in", "label": "Make the gate-in EIR", "template": "eir_in", "into": "eir", "sign": { "into": "driver_signature", "by": "driver_name", "at": "signed_at", "witness": "signed_before" } }\n```\n\n## Check node\n\nA Check is a yes/no or text formula of the rows of the List it stands in: it stands while true or non-empty, and a text\nformula\'s words are what the reader sees. Without `on` it only warns; with it, it refuses the saves and acts it names, on\nthe server, in every write it names.\n\n<!-- generated:start node-check -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A yes/no or text formula of the rows of the List it stands in: true or non-empty means the check stands |\n| `on` | `"all"` \\| list of (alias \\| `"update"` \\| `"delete"`) (at least one) | no | What the standing check refuses: `update` is the rows\' saves, an Act key is that act, `all` is every act and save; absent, it only warns. `delete` is this app\'s removal of the rows, for a rule no check of the table states |\n| `resolve` | alias | no | An Act on the same rows that clears it |\n\n<!-- generated:end node-check -->\n\n`update` is the rows\' saves; an Act key is that Act, on the same rows; `all` is every act and save.\nA rule that refuses a Delete is a check of the table, not a Check: state it in the table\'s `checks` with `on: ["delete"]`\n(\xA7 Write rules), where it holds for every way a row is deleted and a removal that cascades to child rows is refused whole.\nA Check naming `delete` refuses only this app\'s removal of a row, and is kept for a rule the table\'s checks cannot state (a\nformula that is neither a yes/no nor a text).\nA Check in a child List stands on each child row. A warning reading the figure of a meter that `gates` marks against its\nbound or its pass mark is refused: the meter already draws it.\n\nThe check field\'s `label` is its short name \u2014 "Over budget", "Release expired": a row\'s line says it in the check\'s tone\n(the most severe of several, and how many more), while its words whole are the row\'s tooltip. Label it as the reader names\nthe problem, in a few words, and let a text formula say the sentence. A check with no `resolve` links to the first field its formula reads that the record draws.\nA yes/no formula whose yes needs attention (papers the customer still owes) is a Check: its yes reads in the warning tone\nwherever it is drawn, every other yes in the success tone \u2014 and the row\'s line says it, so it is no column of the List too.\n\nA Check that every app over a table states the same way restates the table\'s contract and belongs in the table\'s `checks`\n(\xA7 Write rules).\n\nA Check refusing `update` (or `edit: false` on a record, part or field) makes the rows read-only in this app alone, drawn\nunder the read-only mark, never as a lock. A lock is the table\'s: a table workflow, a person, or an Act whose workflow calls\n`lock_records` locks a row, and every door refuses its writes until a table manager unlocks it \u2014 `unlock_records` in an\napp\'s run is judged as the member who pressed. A locked row reads locked in every app: its lock mark, its fields as values\nunder one line naming who can change it, its saves, Delete and each Act writing it refused \u2014 an Act that writes\nelsewhere, makes a paper it hands back, or unlocks it stays its workflow\'s to answer.\n\n## Reading node\n\nA Reading is a number over rows: one figure, its parts, a grid, a figure per period, steps; or a record\'s own figure. The\npicture is the runtime\'s, chosen from what the reading means \u2014 no prop picks a chart. The question is stated by which\nsplit it names:\n\n| Names | Answers |\n|---|---|\n| nothing | one figure: the rows counted, or `value` summed, `{ "avg": \u2026 }` averaged |\n| `by` | the figure per value of a select, one-row link, member or yes/no, or per band of a number\'s `tiers` |\n| `by` a date field | the figure per period (a trend); `ahead: true` reads forward over the rows still to come |\n| `by` and `across` | a grid, `by` down the side and `across` along the top |\n| `steps` | a funnel: 2 to 8 number fields summed, each kept as a share of the one before; in a record only |\n| no `entity`, in a Record | the record\'s own `value`, a tile under its field\'s label; two to eight consecutive tiles draw as one strip, and a single one is a field of the section |\n\n<!-- generated:start node-reading -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The reading\'s query name, unique in the app; a record\'s own figure is keyed by its field |\n| `label` | text | no | Absent, a split\'s `by` field\'s label, a trend\'s summed `value`\'s (else the rows\'), a grid\'s two fields\'; one figure states its label |\n| `entity` | alias | no | The List\'s rows; in a Record, the child\'s rows under it; absent in a Record with no `by`: the record\'s own `value` |\n| `via` | alias | no | The child\'s link to the record, where it has several |\n| `value` | alias \\| `{ "avg": alias }` | no | A number field summed over the rows, or `{ avg }` averaged; absent, the rows are counted |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, whose number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), whose field is `{ "empty": true \\| false }`, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `by` | alias \\| [Tiered filter](#tiered-filter) | no | Splits the rows into parts; a date field splits them per period |\n| `across` | alias \\| [Tiered filter](#tiered-filter) | no | With `by`: the columns of a grid |\n| `over` | alias | no | The date that places a row in time |\n| `ahead` | `true` | no | With a date `by`: the current period and those after it |\n| `steps` | list of alias (2\u20138) | no | Number fields summed, each a step of a funnel |\n| `on_hand` | [Reading on hand](#reading-on-hand) | no | With `by` (and `across`): the Reading\'s rows are what is owed, per value of those fields; drawn beside what is on hand per the same values, and the shortfall |\n| `target` | number | no | The number the figure is read against; absent, its bound, where the table states one |\n| `better` | `"up"` \\| `"down"` | no | Which way is good, for the figure\'s tone; absent, read from the figure\'s shape |\n| `tab` | alias | no | The Tab it reads, else the open one |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n\n#### Reading on hand\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | alias | yes | The entity whose rows are what is on hand, all of them |\n| `value` | alias | no | A number field summed over those rows; absent, the rows are counted |\n| `by` | alias | no | The field of this entity matching the Reading\'s `by`; absent, the same alias |\n| `across` | alias | no | The field of this entity matching the Reading\'s `across`; absent, the same alias |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, whose number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), whose field is `{ "empty": true \\| false }`, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n\n<!-- generated:end node-reading -->\n\nWhere a Reading stands decides which rows: a dashboard\'s every row, a List\'s the rows in view (so the search, the stage and\nthe filters narrow each one), a Record\'s the child\'s rows under the record. A Reading counts its rows, or sums the number\n`value` names \u2014 never a percent. `over` is a date, the row\'s own or its parent\'s through a lookup: a trend\'s periods, a\nfigure\'s recent periods, and what a dashboard\'s period windows. `where` keeps rows whose select holds one of the options, or\nwhose yes/no is the value, every entry ANDed \u2014 a band leads with the job\'s exception as a formula\'s yes/no (what is\nshort), never a stage the chips already count nor the overdue rows the register\'s summary line counts from `records.due`.\n\n- **A figure\'s change** is green where it moves the good way: a sum\'s rise, a count kept by `where` falling; `better` states\n the other way where that reads wrong (money going out). A value `limits` bounds reads against the sum of its bound\n (`10 / 14`); `target` reads one against a number nothing bounds.\n- **An average** is its sum over the rows that hold a value, divided by how many hold one; a row holding none is no part\n of it, and where none holds one the figure reads a dash, never 0. Under a dashboard\'s period it is the average over the\n period so far, never a sum of averages. A share is an average too: a number formula that is `100` on the rows that count\n and `0` on the others, in percent notation, averaged. Only a single figure averages.\n- **Bands.** `by` and `across` each take, in place of a field, a number by its `tiers` \u2014 `{ "field", "tiers" }` \u2014\n splitting the rows by the bands those breakpoints cut: `[1, 31, 61]` over days late reads not due, 1\u201330, 31\u201360 and over\n 60, the aged report. Every band is drawn in the bands\' own order, one holding no row at zero; rows holding no number\n stand apart. On a List a band is a door: a press narrows the rows to it. In a List\'s band, a split by one or two\n breakpoints stands in it, and one by three or more stands whole under it.\n- **In a List,** Readings stand under the title in one compact band: the job\'s headline number first, then at most the mix\n and the movement. A picture stands only where it splits the rows in view into two parts or more, one holding two rows or\n more. The summary line already totals the figure and the chips count the rows, so a figure summing the figure, or counting\n the rows of a table with a stage, is refused unless its `where` narrows it.\n- **Name.** A Reading\'s `key` is the query it is read by (`run_app_query`).\n\n## Progress node\n\nProgress reads one parent\'s work against its plan \u2014 a List\'s band over a folder, or a record\'s block, never a dashboard\'s\nevery row: the share of the work planned and the share done, cumulative day by day, today\'s two at its foot. Each row is a\npart of the work, its plan spread evenly over its planned days and weighing its `weight` (its value) or, absent, its planned\ndays. Its share done today is `done`, a percent the server computes; on an earlier day, its `done` from its `finished` day\non, before it what its `measured` rows add up to by then.\n\n<!-- generated:start node-progress -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The reading\'s query name, unique in the app |\n| `label` | text | yes | What the progress is of, in the reader\'s words |\n| `entity` | alias | yes | The rows that are the parts of the work: the List\'s, or in a Record the child\'s rows under it |\n| `via` | alias | no | The child\'s link to the record, where it has several |\n| `planned` | [Progress planned](#progress-planned) | yes | The days each row\'s work is planned over, its share planned spread evenly across them \u2014 a row missing either is no part of the plan, and neither line counts it |\n| `done` | alias | yes | A percent field of the row the server computes: its share done today (0\u2013100) |\n| `finished` | alias | no | A date of the row: the day its work was finished, from which it stands at its `done` |\n| `weight` | alias | no | A number of the row: what it weighs in the whole; a row holding none weighs nothing. Absent, each row weighs its planned days |\n| `measured` | [Progress measured](#progress-measured) | no | Before a row is finished, its share done on a day is what these add up to by then, never past its `done`; absent, a row stands at nothing until it is finished, and at its `done` today |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, whose number or date meets its bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is read when the rows are), whose field is `{ "empty": true \\| false }`, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `tab` | alias | no | The Tab it reads, else the open one |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n\n#### Progress planned\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | yes | A date of the row: the first day of its planned work |\n| `end` | alias | yes | A date of the row: the last day of its planned work |\n\n#### Progress measured\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `rows` | alias | yes | The entity whose rows record the work as it is done, each through its one-row link to a row of the reading |\n| `on` | alias | yes | A date of those rows (their own, or a lookup): the day each records its work |\n| `share` | alias | yes | A number of those rows the server computes: the share of its row each adds, in percent |\n\n<!-- generated:end node-progress -->\n\n## Statement node\n\nA Statement draws a figure the record works out from its own figures \u2014 a claim\'s payable, an invoice\'s total, a payslip\'s\nnet \u2014 line by line in the order the arithmetic runs: each line a number of the record, the first stating no `op`, the last\nan `equals`. Every figure is a field the server computes or a person writes; the screen adds nothing up. A line\'s `parts`\nlists the rows its figure carries, each read from its own row, and that figure is what they add up to \u2014 a formula, rollup\nor lookup, or the server\'s copy when the record is made (`default_from`), which nothing writes after.\n\n<!-- generated:start node-statement -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | The statement\'s name, unique among its siblings |\n| `label` | text | no | Absent, the label of what the statement comes to |\n| `lines` | list of [Statement line](#statement-line) (at least 3) | yes | The figures it is built from, top to bottom, ending in the one it comes to |\n| `when` | [condition](#app-tree) | no | Drawn only while the record\'s selects hold one of these options, its yes/nos this value, its numbers and dates meet their bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }` is today) and its `{ "empty": true \\| false }` fields are blank or filled |\n\n#### Statement line\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A number of the record \u2014 stored, a formula, a rollup or a lookup \u2014 read on the line after its label |\n| `op` | `"plus"` \\| `"minus"` \\| `"times"` \\| `"equals"` | no | How the line works on the lines above it \u2014 `plus`, `minus`, `times`, or `equals` for what they come to, a formula, rollup or lookup the server computes; absent on the first line, which the rest work from |\n| `rate` | alias | no | A percent of the record read after the line\'s label: the rate its figure is taken at (retention at 5 %) |\n| `parts` | [Statement parts](#statement-parts) | no | The rows the line\'s figure carries, each listed under it with its words and its own figure \u2014 read from those rows, never a copy of them. The line\'s figure is what they add up to, drawn read: a formula, rollup or lookup, or a value the server copies when the record is made (`default_from`) that nothing writes after |\n\n#### Statement parts\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `rows` | alias | yes | The entity whose rows the line\'s figure carries \u2014 the record\'s own for the earlier rows of the same thing |\n| `same` | alias | yes | A one-link the record and those rows both hold: the rows naming what the record names in it |\n| `at` | alias | no | A moment (a datetime) of those rows no person keys \u2014 when each was certified: with `before`, only the rows whose moment stands before the record\'s, earliest first; absent with it, every row so named, in their own order |\n| `before` | alias | no | A moment (a datetime) of the record no person keys \u2014 when it was made: the moment the rows\' `at` stands before; stated with `at` |\n| `where` | map of alias \u2192 (list of alias (at least one) \\| boolean \\| [comparison](#values) \\| `{ "empty": true \\| false }`) | no | Only the rows whose single select holds one of these options, whose yes/no (stored, or a formula) is this value, whose number or date meets its bounds, or whose field is empty or filled \u2014 read so on the server |\n| `columns` | list of alias (1\u20132) | yes | The rows\' fields read as each one\'s words, in order (its date, its percent) |\n| `figure` | alias | yes | The rows\' number each one reads at the figures\' edge, frozen when the row was certified: a stored number an act fills (`{ "formula": \u2026 }` as it runs) or the server copies when the row is made (`default_from`) \u2014 never one a person keys, nor a formula, rollup or lookup the server recomputes |\n\n<!-- generated:end node-statement -->\n\n```json\n{\n "type": "Statement",\n "key": "claim",\n "lines": [\n { "field": "contract_value" },\n { "field": "done_to_date", "op": "times" },\n { "field": "value_to_date", "op": "equals" },\n { "field": "certified_before", "op": "minus", "parts": { "rows": "claim", "same": "contract", "at": "certified_at", "before": "made_at", "where": { "kind": ["work"] }, "columns": ["claimed_on", "done_to_date"], "figure": "certified_value" } },\n { "field": "claim_value", "op": "equals" },\n { "field": "retention", "op": "minus", "rate": "retention_rate" },\n { "field": "payable", "op": "equals" }\n ],\n "when": { "kind": ["work"] }\n}\n```\n\n## Export node\n\nAn Export is a report file of the rows in view: the title, a line per chip, the search and the moment it was exported, the\nReadings, and every row, at most 20,000. With no props, the one Export is the workbook of the List\'s columns, the Readings\non a sheet before the rows. A List states the reports the business sends \u2014 one a button, several one menu \u2014 each a `label`,\na `description`, and a `template` filled with `title`, `lines`, `readings`, `at` (the moment it was made), `dates` (each\ndate filter\'s `from` and `to` by field) and the rows under the table\'s alias (an html one made a PDF, an excel one a\nworkbook). A report\'s `filename` names its file in the template grammar over the same keys but the rows. Where `per` is a\none-row link, `per_row.<field>` reads a text, number, date or yes/no field of the picked row, and `| add_days:<n>` shifts a\ndate by whole days (`n` a number or such a field, empty for none): `Stock {{per}} {{period.to | add_days:per_row.day_offset | format:"dd.MM.yyyy"}}`. A report of some\nrows (`where`), or of one value the reader picks (`per`: one file per value), is made of the List as those chips would\nnarrow it; the screen keeps the chips and rows it shows.\n\n<!-- generated:start node-export -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `key` | alias | yes | Unique among its siblings; part of the node\'s address |\n| `label` | text | no | The report\'s name; absent on the one Export that stands for the register\'s workbook |\n| `description` | text | no | What the report is for, in a sentence |\n| `template` | alias | no | An html or excel document template the report fills, with `title`, `lines`, `readings`, `at`, `dates`, `period`, `per` and `per_row` (a one-row `per`\'s picked row, the fields `filename` reads); absent, the workbook of the List\'s columns |\n| `filename` | text | no | The file\'s name in the template grammar, over one value each: `title`, `at`, `dates.<field>.from` or `.to`, `period.from` or `.to`, `per`, `per_row.<field>` (a text, number, date or yes/no field of the row a one-row `per` picks), `lines \\| lookup:<n>`; `\\| add_days:<n>` shifts a date by whole days, `n` a number or `per_row.<field>`. Absent, the report\'s label |\n| `where` | [narrowing](#app-tree) | no | Only the rows whose select holds one of these options, whose yes/no is this value, or whose member field holds the reader (`{ "me": true }`) \u2014 every entry ANDed |\n| `per` | alias | no | A select, member or one-row link the reader picks one value of: one file of that value |\n| `books` | list of alias (at least one) | no | Of the List\'s books, the tables the report reads; absent, all |\n\n<!-- generated:end node-export -->\n\n## Book node\n\nA Book is another table read as part of the register: the same job over its rows \u2014 a legacy ledger, a second branch\'s book.\nA row of the book opens the List\'s Record, lined up by alias; a field the book lacks is blank on its rows and drops from\nwhat they open, and an Act, Check or add needing it is not offered on them. Options line up by alias. With `column: true` the List\ndraws a column naming the table each row is of, and a filter by it. A Book holds the title, the stage, the scope and every Check\nlocking the rows; a table, cards or a plan reads Books.\n\n<!-- generated:start node-book -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | alias | yes | Another table read as part of the register: the same job over its rows |\n| `fields` | map of alias \u2192 alias | no | A field of the List\'s entity \u2192 the field of this entity holding the same fact, where their aliases differ |\n| `column` | `true` | no | The List draws a column naming the table each row is of, and a filter by it |\n| `row` | [Book row](#book-row) | no | The book\'s rows, stated where they differ from the List\'s |\n| `children` | list of [Record](#record-node) (1\u20131) | no | The Fields a save of the book\'s rows writes (and `edit: false` for none), as a child List\'s Record states them; the page a row opens is the List\'s |\n\n#### Book row\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `status` | `{ "edit": true }` | no | `{ edit: true }`: the book\'s stage is also moved by hand |\n\n<!-- generated:end node-book -->\n\n**Writes:** the List\'s `remove`, its Fields and its Acts, on the book\'s own table.\n\n## Query node\n\nA Query is a read for agents, never drawn. An app generates a query for everything its screen reads (`agent_surface` lists them); a Query declares one the screen never asks, called through `run_app_query` or the app\'s API: the\ntable whose rows it reads, the fields each row returns or the figure they add up to, and what narrows them.\n\n<!-- generated:start node-query -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `description` | text | yes | What the query answers, read by whoever calls it by its alias \u2014 at most 300 characters |\n| `params` | map of alias \u2192 [Workflow value](#workflow-value) | no | What a call states, by name, each read by an entry of `where` \u2014 a `number`, `boolean`, `date`, `select`, `record_link`, `member`, holding one. `required: false`: the call may leave it out, and the condition reading it narrows nothing |\n| `rows` | alias | yes | The entity whose rows it reads, under that entity\'s read scope. The app\'s own entity is read as its list reads it: kept by the register\'s `where`, and inside the row the app\'s `scope` names, which the call states |\n| `fields` | list of alias (at least one) | no | The fields each row returns, by alias. A query states `fields` or `reading`, one of the two |\n| `sort` | [Sort key](#sort-key) \\| list of [Sort key](#sort-key) (at least one) | no | The order the rows return in; absent, the order the entity\'s list reads in. Only with `fields` |\n| `limit` | integer | no | The most rows returned. Only with `fields` |\n| `reading` | [Query reading](#query-reading) | no | The rows counted or summed instead of returned: one row for all of them, or one per value of `by` or per day of `over`, each carrying its `by` field or `__day`, `__count`, and `__sum` where `value` is stated |\n| `key` | alias | yes | The query\'s name, what `run_app_query` calls; unique in the app |\n| `where` | map of alias \u2192 [condition](#query-node) | no | What narrows the rows, as a List\'s `where` does: a select\'s options, a yes/no\'s value, a number or date meeting its bounds (`gt`, `gte`, `lt`, `lte`), a field `{ "empty": true \\| false }`, `{ "me": true }` on a member field \u2014 or a value the call states (`{ "param": name }`), or a date or number range of params (`{ "from": \u2026, "to": \u2026 }`) |\n\n#### Query reading\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `value` | alias | no | A number field summed over the rows; absent, the rows are counted |\n| `average` | `true` | no | The `value` is averaged: summed over the rows holding one, beside how many hold one. Never with `by` or `over` |\n| `by` | alias | no | The single select, one-row link, member or yes/no each row is counted under \u2014 one row returned per value |\n| `over` | alias | no | A date: one row returned per day of it. Never with `by` |\n\n#### Query param\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `param` | text | yes | A param of this query: the condition holds what the call states for it, and is left out where the call states none |\n\n#### Query range\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | [Query param](#query-param) | no | The param the field is on or after \u2014 a date\'s first day, a number\'s least |\n| `to` | [Query param](#query-param) | no | The param the field is on or before \u2014 a date\'s last day, a number\'s most |\n\n<!-- generated:end node-query -->\n\n```json\n{\n "type": "Query",\n "key": "fees_unbilled",\n "description": "A customer\'s fees no invoice covers yet, oldest first.",\n "params": {\n "customer": { "type": "record_link", "entity": "customer", "description": "The customer whose fees are read." },\n "since": { "type": "date", "required": false, "description": "Only fees charged on or after this day." }\n },\n "rows": "fee",\n "fields": ["visit", "service", "amount", "charged_on"],\n "where": { "billed": false, "customer": { "param": "customer" }, "charged_on": { "from": { "param": "since" } } },\n "sort": { "field": "charged_on" },\n "limit": 200\n}\n```\n\nAn entry of `where` holds what a Reading\'s `where` holds, or a value the call states: `{ "param": "<name>" }`. A required\nparam is always stated; one stating `required: false` may be left out, and the condition reading it then narrows nothing.\nA query reads under its table\'s read scope, as every read of the app does. A `reading` returns one row for all the rows it\nreads, or one per value of `by` or per day of `over`. `set_app` compiles a Query with the rest of the app: no code is set.\n\nEvery Reading of the app is a query too, returning its groups, called by its key. A Tab\'s `totals` read is called by\n`<table>_totals_<tab>` with the period\'s two days, `from` and `to`, both required.\n\nA query reads one table: on an app that states `Book`s, whose list reads several, it names a book\'s own table, never the\nList\'s. A param compares a field holding one value \u2014 or, as a range, a date or a number \u2014 and never a number read in each\nrow\'s own unit or currency, nor a date holding a span.\n\n## Workflow node\n\nA Workflow is a verb for agents, never drawn: a job no press of the screen starts \u2014 an agent or a service handing the app a\nfile, or a list of rows to reconcile a table against. It states what it takes and what it hands back; a table is named by\nits alias in `tables` and a select by `<table>.<field>`. It is called through the app, by `run_app_workflow` or the app\'s\nAPI. On an app shared by a public link it runs only for someone who holds the app, and a visitor\'s call is refused as an\nalias the app does not have.\n\n<!-- generated:start node-workflow -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `description` | text | yes | What the workflow does, read by whoever calls it by its alias \u2014 at most 300 characters |\n| `inputs` | map of text \u2192 [Workflow value](#workflow-value) | no | What a call hands it, by name \u2014 checked before its body runs |\n| `outputs` | map of text \u2192 [Workflow value](#workflow-value) | no | What its body returns as `data`, by name \u2014 checked as it returns; absent, read off the body |\n| `key` | alias | yes | The workflow\'s name, what `run_app_workflow` calls; unique in the app |\n\n#### Workflow value\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"text"` \\| `"number"` \\| `"boolean"` \\| `"date"` \\| `"datetime"` \\| `"email"` \\| `"json"` \\| `"record_link"` \\| `"select"` \\| `"member"` \\| `"file"` \\| `"object"` \\| `"array"` | yes | What the value is |\n| `description` | text | no | What the value is for, read by whoever calls the workflow |\n| `required` | `false` | no | false: the value may be left out; absent, it is always there |\n| `multi` | `true` | no | A `record_link`, `select`, `member` or `file` holding several |\n| `entity` | alias | no | The entity a `record_link` names a row of |\n| `field` | text | no | `<entity>.<field>`: the select field whose options a `select` takes, as the field holds them when the workflow runs |\n| `options` | list of alias (at least one) | no | The values a `select` takes, where no field holds them |\n| `fields` | map of text \u2192 [Workflow value](#workflow-value) | no | An `object`\'s values, by name |\n| `items` | [Workflow value](#workflow-value) | no | What each item of an `array` is |\n\n<!-- generated:end node-workflow -->\n\n```json\n{\n "type": "Workflow",\n "key": "reconcile_fees",\n "description": "Reconciles a visit\'s fees against the lines of the carrier\'s statement.",\n "inputs": {\n "visit": { "type": "record_link", "entity": "visit" },\n "statement": { "type": "file", "required": false },\n "lines": { "type": "array", "items": { "type": "object", "fields": { "service": { "type": "select", "field": "fee.service" }, "amount": { "type": "number" } } } }\n },\n "outputs": { "added": { "type": "number" }, "changed": { "type": "number" } }\n}\n```\n\nIts code is set on the app, never stated in the tree: `set_app_workflow` with the app, the key and the `source`, leaving\n`inputs` and `outputs` out \u2014 the code is checked against what the node declares. Every `set_app` keeps that code and holds\nwhat it takes, what it hands back and its description to the node. To change what a workflow takes or hands back, change\nthe node first: `set_app` keeps the code where it still meets the change, and where it does not the workflow stands on no\ncode again until `set_app_workflow` sets one that does. With no code it refuses every call, and each `set_app` says so. A\nWorkflow the tree stops declaring is archived with its key.\n\n## Composed page\n\nA Record whose page is its own arrangement of what the app already states \u2014 a piece of writing under its title, a brief\nbeside its figures \u2014 states `compose` instead of parts: a tree of finished pieces, each named by `use`.\n\n```json\n{\n "type": "Record",\n "door": "page",\n "compose": {\n "use": "Column",\n "children": [\n { "use": "Bar", "children": [{ "use": "Status" }, { "use": "Field", "field": "audience" }, { "use": "Act", "act": "send_for_review" }, { "use": "Copy", "field": "body", "as": "plain" }] },\n { "use": "Title" },\n { "use": "Document", "field": "body" },\n { "use": "Group", "title": "Reviews", "children": [{ "use": "Rows", "child": "review", "create": false }, { "use": "Act", "act": "ask_reviewer" }] }\n ]\n }\n}\n```\n\nA node binds by name: a `field` is a field of the record\'s table, an `act` an Act of the app, a `child` a table linking to\nthe record. What a name binds is read and written exactly as a part naming it would be \u2014 a `Field` and a `Document` are\nsaved through the record\'s own save, an `Act` runs its workflow, a `Rows` reads the child\'s rows \u2014 so a tree adds no write\nof its own. It holds no condition and no computed value: a piece that does not apply on a row draws nothing, and anything\nthat must depend on the row is an Act\'s `when` or a field\'s formula, never the tree.\n\nThe first `Act` the page itself draws is its primary and every other one secondary; a `danger` act reads so wherever it\nstands. A `Copy` and a `Document` naming one field copy what is on screen \u2014 the words typed and not yet saved among them. The\nrecord\'s chrome stays the runtime\'s: the way back, its comments door and its \u22EF with Delete share the row over the tree, and the\nhead draws no title and no status of its own \u2014 the tree\'s `Title` and `Status` do.\n\n<!-- generated:start apps-compose -->\n\n#### Column piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Column"` | yes | The page\'s reading column, 680px: its pieces stacked in order from one left edge |\n| `children` | list of [piece](#composed-page) (at least one) | yes | The pieces stacked in it, in the order they are read |\n\n#### Bar piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Bar"` | yes | One row of small things on one control height, in two zones: what the record is \u2014 a status, a field\'s control \u2014 from its left edge, what is done to it \u2014 an act, a copy \u2014 ending on its right edge, taking a row of their own under it where the row runs out of room |\n| `children` | list of [piece](#composed-page) (at least one) | yes | Its pieces, each zone\'s in order; a `Field` directly in it is its control alone on one line, named by its label where it holds nothing; an `Act` that destroys or corrects is not drawn here \u2014 it stands in the record\'s \u22EF |\n\n#### Group piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Group"` | yes | A titled stack, headed once by `title` |\n| `title` | text | yes | Its heading, in the reader\'s words; holding one `Rows`, it carries their count and their add, and the group\'s `Act`s end its line |\n| `children` | list of [piece](#composed-page) (at least one) | yes | The pieces under the heading, in order |\n\n#### Panel piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Panel"` | yes | What a reader does not need while writing: nothing in the page, one entry of the record\'s \u22EF named by `title` that opens its pieces in a panel beside the page \u2014 a sheet on a phone; directly in the root `Column` alone |\n| `title` | text | yes | Its entry in the \u22EF and the panel\'s name, in the reader\'s words; an `Act` directly in it ends that name\'s row |\n| `children` | list of [piece](#composed-page) (at least one) | yes | The pieces in the panel, in order: `Rows`, `Field`, `Act`, `Group`, `Document` \u2014 never `Title`, `Status`, `Bar`, `Copy` or another `Panel` |\n\n#### Title piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Title"` | yes | The record\'s title (`records.<entity>.title`) as the page\'s heading, written in place where a person writes it; one a page |\n\n#### Status piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Status"` | yes | The record\'s status (`records.<entity>.status`) as a record\'s head draws it: moved by hand, by its acts\' moves, or read |\n\n#### Field piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Field"` | yes | A field of the record under its label, as a section\'s `fields` draws it |\n| `field` | alias | yes | The field drawn \u2014 never the title or the status, which `Title` and `Status` draw; once a page |\n\n#### Document piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Document"` | yes | A markdown text field written in place with no box, saved as it is written \u2014 whether it is saved said at its foot |\n| `field` | alias | yes | A text field with `format: "markdown"`; once a page |\n\n#### Act piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Act"` | yes | An act of the app on the record as its button: its `when`, what it lacks, its asks and its confirmation as everywhere; the first `Act` the page itself draws is its primary |\n| `act` | alias | yes | An act of this app on the record \u2014 never one `of` a child\'s rows, which stands on its row; once a page |\n\n#### Rows piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Rows"` | yes | A child entity\'s rows under the record, as a rows block lists them, each row\'s own acts on it |\n| `child` | alias | yes | A child entity linking to the record; once a page |\n| `create` | `false` | no | false, rows are not added here \u2014 an act of the page makes them |\n| `remove` | `true` | no | The rows offer a Delete (open row and \u22EF); absent, none |\n\n#### Copy piece\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `use` | `"Copy"` | yes | A button copying a field\'s words as the page holds them now \u2014 what is typed in its `Document` and not yet saved among them |\n| `field` | alias | yes | The text field copied |\n| `as` | `"plain"` \\| `"raw"` | yes | `plain`, its markdown marks gone; `raw`, as stored |\n\n<!-- generated:end apps-compose -->\n\n## Tasks\n\nA table whose `records` states `"task": true` \u2014 its status a stored select with `closed` \u2014 is work someone finishes.\nEvery List of its rows draws it alike, each row led by a ring. Ticking the ring moves the row to a closed option by the\none write the app already has for that move: the Act of that table setting a closed option, offered on an open row and\nasking nothing, else its own save where people write the status \u2014 stamping what that act stamps and appending the\nstatus history. Unticking runs the Act moving a closed row back, else the save back to the status\'s default. A move no\nwrite reaches leaves the ring disabled and the check notes it. An Act with `on: "ticked"` completes several at once.\n\nA record listing task rows filed under it by a link holding one row says how much of that work is done \u2014 those at a\nclosed option of all \u2014 wherever its row is drawn: a meter on its head, a column of its register\'s rows, a card\'s foot, a\ncalendar entry, a gantt\'s or a lane\'s bar. No rollup is stated for it.\n\n```json\n{\n "tables": [\n {\n "ref": "project",\n "label": "Projects",\n "singular": "Project",\n "fields": {\n "name": { "label": "Name", "required": true, "type": "text" },\n "lead": { "label": "Lead", "type": "member" }\n },\n "title": "name",\n "records": {}\n },\n {\n "ref": "task",\n "label": "Tasks",\n "singular": "Task",\n "fields": {\n "title": { "label": "Title", "required": true, "type": "text" },\n "project": { "label": "Project", "required": true, "type": "link", "table": "project", "multi": false },\n "assignee": { "label": "Assignee", "type": "member" },\n "status": {\n "label": "Status",\n "required": true,\n "default": ["todo"],\n "type": "select",\n "options": [\n { "alias": "todo", "label": "To do", "color": "slate" },\n { "alias": "done", "label": "Done", "color": "green" }\n ]\n },\n "due": { "label": "Due", "type": "date", "format": "date" },\n "done_on": { "label": "Done on", "type": "date", "format": "date" }\n },\n "title": "title",\n "records": {\n "subtitle": ["assignee"],\n "due": ["due"],\n "task": true,\n "starts": { "assignee": "me" },\n "status": { "field": "status", "closed": ["done"] }\n }\n }\n ]\n}\n```\n\n```json\n{\n "body": {\n "type": "App",\n "key": "projects",\n "name": "Projects",\n "color": "blue",\n "tables": { "project": "<id of the project table>", "task": "<id of the task table>" },\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "project",\n "children": [\n {\n "type": "Record",\n "children": [\n {\n "type": "Section",\n "key": "work",\n "title": "Work",\n "children": [\n "lead",\n {\n "type": "List",\n "entity": "task",\n "columns": ["due"],\n "children": [\n { "type": "Act", "key": "complete", "label": "Complete", "when": { "status": ["todo"] }, "set": { "status": "done", "done_on": { "now": true } } },\n { "type": "Act", "key": "reopen", "label": "Reopen", "when": { "status": ["done"] }, "set": { "status": "todo", "done_on": null } },\n { "type": "Act", "key": "complete_all", "label": "Complete", "on": "ticked", "when": { "status": ["todo"] }, "set": { "status": "done", "done_on": { "now": true } } }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\nThe tasks as a register of their own, for one person: a one-part Record holds its Fields and Acts directly.\n\n```json\n{\n "body": {\n "type": "App",\n "key": "my_tasks",\n "name": "My tasks",\n "color": "teal",\n "tables": { "project": "<id of the project table>", "task": "<id of the task table>" },\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "task",\n "opens": { "assignee": { "me": true } },\n "children": [\n { "type": "Reading", "key": "open_tasks", "label": "Open", "where": { "status": ["todo"] } },\n {\n "type": "Record",\n "door": "drawer",\n "children": [\n "project",\n "due",\n { "type": "Act", "key": "finish", "label": "Finish", "when": { "status": ["todo"] }, "set": { "status": "done", "done_on": { "now": true } } },\n { "type": "Act", "key": "undo", "label": "Undo", "when": { "status": ["done"] }, "set": { "status": "todo", "done_on": null } }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Rules of the app\n\nEvery rule the check enforces over an app, one sentence each under an id; a finding names its rule\'s id\n(`[register.filter]`), and `model/<rule id>` is that one rule\'s page. A sentence names the node and the prop an author writes (`List.create`, a `Section`);\neach area stands in the tree where this table says:\n\n| Area | Stands at |\n|---|---|\n| `app` | the App, and the register List\'s props that belong to the app: `words` (labels), `color`, `scope`, Books, logs |\n| `register` | the register List: its columns, filters, search, group, sort, layout, tabs, period, export |\n| `reading` | a Reading or Progress |\n| `record` | a Record and its parts: Section, Side, Page, Act placement |\n| `block` | what a part holds: a child List, a Field gallery or prose block, a Reading, a desk, a timeline or agenda |\n| `statement` | a Statement |\n| `act` | an Act |\n| `check` | a Check |\n\n### App rules\n\n<!-- generated:start rules-app -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `app.alias-unique` | An App\'s `key` is unique among apps, and an Act\'s among the Acts of its App, every List\'s included. |\n| `app.icon` | An App\'s `icon` is a Lucide icon name in kebab-case, as `receipt` or `trending-up` are. |\n| `app.add-label` | A List\'s `add_label` stands only where it adds rows (`create`), in words that are not empty and hold no `{` or `}`. |\n| `app.labels` | An App\'s `words` name a table it reads and a field or option of it, in words that are not empty and hold no `{` or `}`; no two fields of a table, nor two options of a field, show the same words. |\n| `app.records-entry` | Every table an App draws a List over, opens or picks from \u2014 the register\'s, a child List\'s, a linked row\'s, a documents List\'s \u2014 has a `records` entry. |\n| `app.scope` | The App\'s `scope` is a required one-row link of the first List\'s entity, the same on every row the App lists \u2014 never a `columns`, `filters` or `search` entry, `group`, `where`, `opens`, a Field of a part or a `create` question. |\n| `app.when` | A node\'s `when` holds single selects to options, yes/nos to `true` or `false` (stored or a formula of one; a formula with `options` is a select), numbers and dates to bounds (`gt`, `gte`, `lt`, `lte`; a date\'s bound is a day, a moment on a datetime, or `{ "now": true }`) and any field but a yes/no to `{ "empty": true \\| false }` \u2014 a number read in each row\'s own unit or currency and a lookup take no bound. |\n| `app.asked-written` | What a List\'s `create` asks, what an Act\'s `asks` asks and what `starts` fills is a field a person writes \u2014 never a formula, rollup, lookup, autonumber or a date the workspace stamps. |\n| `app.own-rows` | A many-link to rows that each belong to one row of this entity is never a Field of a part, a `create` question or an Act\'s `asks`: those rows are the record\'s own, read as a child List. |\n| `app.create-asks` | A List\'s `create` asks fields a person writes \u2014 never the link or the folder it files the row under, which the add sets itself. |\n| `app.create-required` | A List\'s `create` asks every field its entity requires that nothing else fills \u2014 a `default`, `starts`, the record or folder it files the row under, or a `default_from` over a link it fills \u2014 or the server refuses every add. |\n| `app.books` | A register App\'s Books each name another entity once (`entity`) \u2014 never the App\'s own \u2014 whose fields line up with the register List\'s by alias, else by `fields` (a field the List reads, or one an Export prints \u2192 one of the Book\'s): read as one column type (a day apart from a moment), one value or several alike, a link to the same entity. Each Book holds the title, the status where the List shows one, the folder (`scope`), what `where` keeps and every Check locking the rows; the List is a table, cards or a plan, whose `at`, `across` and `up` line up like any field. Two Books holding a field of their own under one alias hold it as one kind of value. An Export\'s `books` each name the List\'s entity or one of its Books, once, and one of them holds each field its `where` and `per` narrow by. A Book\'s `column: true` draws the column naming each row\'s table in a List that has Books. |\n| `app.book-option-kin` | *Noted, never refused:* A Book\'s option labelled as one of the App\'s, under another alias, reads apart from it: the List lists both and counts each on its own. |\n| `app.book-lacks` | *Noted, never refused:* A Book lacking a field the Record, an Act or a `create` reads: what reads it drops from the Book\'s rows \u2014 an Act, a child List, a Section, the `create`. A field only an Export prints, held by a Book as another kind of value, prints blank on its rows. |\n| `app.logs` | A register App\'s logs \u2014 further Lists, at most 3 \u2014 each list another entity under a `key` of their own: never the App\'s own entity, one of its Books\' or another log\'s. A log is one List \u2014 a table or cards, with no Tabs, no Book `column` and no composed Record \u2014 over an entity whose status states no owned `stages`, and stands in an App that states no `scope`. An Act\'s `key` is its own among the App\'s Acts, its logs\' included. No Record of the App or of a log lists the rows of an entity another of the App\'s Lists is over: a link to one opens it on its own List. A List people work as a desk of its own is another App. |\n| `app.rates-in-passing` | *Noted, never refused:* An App with no Act, log or Tabs of its own whose rows another App\'s lines copy a number off (`default_from` through a link) \u2014 the rates a quote is priced from \u2014 is worked only while that record is written: a child List, a Section or an Act of that App, never an App of its own. |\n| `app.row` | A List\'s `row` states only what differs from `records`: a `subtitle` that is not the title, a number for its `figure`, dates for its `due`, an `image` that is a files picture no Act makes documents into, or `false` for a figure, deadlines or picture the entity states and the App draws none of. |\n| `app.tasks` | *Noted, never refused:* A task entity an App lists whose rows no write of the App moves to a closed option (its ring stands disabled), or that an Act ticks done and none moves back. |\n| `app.workflow-value` | A value a Workflow takes or hands back states what its type needs and nothing another type does: a `record_link` its `entity`, a `select` its `options` or the `field` holding them \u2014 never both \u2014 an `object` its `fields`, an `array` its `items`; `multi` stands on a `record_link`, `select`, `member` or `file`; an output is never a `member` or a `file`; and a value nests at most 8 deep. |\n| `app.query` | A Query reads one entity\'s rows as `fields` or as a `reading`, one of the two \u2014 never the App\'s own entity where the App has Books, whose List reads several tables; `sort` and `limit` stand with `fields`; a `reading` splits by `by` or by the day of `over`, never both, and averages only with neither; each param is a number, a yes/no, a date, a select, a one-row link or a member holding one, read by an entry of `where` on a field of its type \u2014 a date by a range, a number by either, and never a number read in each row\'s own unit or currency or a date holding a span. |\n| `app.generated-name` | A name the App generates is taken by nothing the App states. No Query, no Reading\'s or Progress\'s `key` \u2014 a log\'s as it is read, `<entity>_<key>` \u2014 and no Tab\'s read (`<entity>_list_<tab>`) is the name of a read the App generates, of a Query or of a Reading. |\n| `app.surface-described` | *Noted, never refused:* A value a Workflow takes, and a param a Query takes, that states no `description`: the one line a caller reads to fill it. |\n| `app.picks-only-entity` | A Field or a row title that states `add: false` stands in an entity the app reaches. |\n| `app.picks-only-field` | `add: false` stands at a link, once, to an entity whose add the app states. |\n| `app.remove` | *Noted by the designer on a write, never refused:* An App that writes its register\'s records offers a Delete of them wherever the List writes them, until the List says `remove: false`. |\n\n<!-- generated:end rules-app -->\n\n### Reading rules\n\n<!-- generated:start rules-reading -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `reading.value` | A Reading sums a number, never a percent \u2014 percents do not add up; a value of `{ avg }` reads one. |\n| `reading.average` | `{ avg }` averages a Reading\'s `value` over the rows holding one, read against a plain `target` \u2014 never a value `limits` bounds, whose bound is a sum. |\n| `reading.split` | `by` and `across` split by one value each row holds \u2014 a single select, a one-row link, one member or a yes/no, stored or a formula, a lookup through one-row links as the field it reads \u2014 or by the bands `tiers` cut a number read in one unit on every row; a date `by` splits them per period, as a trend; a grid crosses two different fields. |\n| `reading.over` | `over` is a date: the row\'s own, stored or a formula, or its parent\'s through a lookup over a one-row link. |\n| `reading.where` | A reading\'s and a Query\'s `where` keep rows by a select\'s options, a yes/no\'s `true` or `false`, a number or date\'s bounds (`gt`, `gte`, `lt`, `lte`; a date\'s `{ "now": true }`), `{ "empty": true \\| false }`, or a member field holding the reader (`{ "me": true }`); a List\'s `where` also keeps rows by `<link>.<field>`, a select or yes/no of the row a one-row link names; a Query\'s `where` also holds a field to what the call states (`param`, `from`, `to`). |\n| `reading.target` | `target` reads a value nothing bounds \u2014 a value `limits` bounds reads against the sum of its bound. |\n| `reading.better` | `better` needs `over`: a change is read over the periods its date places the rows in. |\n| `reading.list-app` | A List\'s `app` is the id of an app whose first List is over the List\'s own entity. |\n| `reading.progress` | A Progress reads dates of its rows for `planned` and `finished`, a number for `done` and `weight`, and rows `measured` through a one-row link to them, by a date of theirs and a number of theirs. |\n| `reading.alias` | A Reading\'s or Progress\'s `key` is its own among the App\'s Readings and Queries, a log\'s read by its entity before it (`<entity>_<key>`); a Progress above a List reads the List\'s own rows and states no name of its own: its `key` is the one its place gives it, `register_reading_<position>`. |\n| `reading.tabs` | A dashboard with Tabs holds every Reading and List inside a Tab, and every Tab holds one at least; without Tabs, none sits in one. |\n| `reading.restate` | A dashboard\'s count tile never counts the rows a List beside it lists under the same `where` and tab: the List\'s head counts them. |\n| `reading.period` | A dashboard\'s `period` is the one it opens on, and it stands only over a Reading placed in time by `over`. |\n\n<!-- generated:end rules-reading -->\n\n### Statement rules\n\n<!-- generated:start rules-statement -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `statement.lines` | A Statement\'s `lines` are numbers of the record, each once \u2014 the first stating no `op`, every other one, the last an `equals` \u2014 each `equals` a formula, rollup or lookup, and a `rate` a number of the record. |\n| `statement.rows` | A line\'s `parts` read rows of an entity holding `same`, a one-link of the record\'s to the same entity; `at`, a moment of those rows, and `before`, a moment of the record (datetimes, never days), stated together; their `columns` and `figure` are those rows\' fields, the figure a number. |\n| `statement.parts-keyed` | A line\'s `parts` add up to its figure, so no person keys it: a formula, rollup or lookup, a value the server copies when the record is made (`default_from`) that no `create` asks and no save or Act writes, or a stored number the App\'s saves leave out \u2014 the line draws it read; each part\'s `figure` is frozen when its row was certified \u2014 a stored number an Act fills, the server copies or the App\'s saves leave out, never one a person keys or the server recomputes \u2014 and no person keys the moments `at` and `before` either. |\n\n<!-- generated:end rules-statement -->\n\n### Check rules\n\n<!-- generated:start rules-check -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `check.field` | A Check\'s `field` is a yes/no or text formula of the rows of the List it stands in. |\n| `check.of` | A Check stands in the register List or in a child List the Record lists. |\n| `check.not-placed` | A Check\'s field is never also placed on the record as a Field \u2014 the line under the head says it. |\n| `check.blocks` | A Check\'s `on` names what it refuses: `"update"` (the saves), an Act\'s `key`, or `"all"` (every Act and save); a child List\'s Check refuses that row\'s saves and the Acts of it, never the record\'s. It binds this App alone. `"delete"` refuses this App\'s removal of the rows and is kept for a rule no check of the table states: a Delete is the table\'s checks\' to refuse. |\n| `check.resolve` | A Check\'s `resolve` names an Act of this App on the rows the Check stands on, pressed on one row, not refused by the Check, and drawn as a button \u2014 never a correction. |\n| `check.restates-meter` | A warning reading a gated meter\'s figure against its bound or pass mark is refused \u2014 the meter draws it. |\n\n<!-- generated:end rules-check -->\n\n## Register rules\n\nThe rules of the register List\'s props: columns, filters, search, group, sort, layout, tabs, period and export.\n\n<!-- generated:start rules-register -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `register.export-report` | A List\'s Exports each have their own `label`; `per` picks one option, person or one-link row \u2014 never of the status, the folder or a field its `where` fixes. |\n| `register.export-template` | A template an Export fills reads at its root only `title`, `lines`, `readings`, `at`, `dates` and its rows under the List\'s entity alias \u2014 an entity named none of those \u2014 and is the document of no Act. |\n| `register.column-drawn` | A List\'s `columns` never name what the row draws itself \u2014 its title, subtitle, image, status, figure, or the bound its figure\'s meter reads against \u2014 and name a field once. |\n| `register.columns-budget` | A List\'s `columns`, each at the width of its kind, fit the room a desk leaves beside its name and the row\'s own status, deadline and figure. |\n| `register.filter` | A List\'s `filters` name a select, a member, a link, a date, a yes/no or a number of the List\'s own rows \u2014 a lookup through one-row links as the field it reads \u2014 never the status (its chips), nor one `where` fixes. |\n| `register.filter-split` | *Noted, never refused:* A `filters` entry on a field a Reading of the List with `by` or `across` splits the rows by \u2014 a press on its part narrows by it too; a member\'s filter, which narrows to the reader\'s own rows, is never noted. |\n| `register.filter-unshown` | A `filters` entry, a narrowing the List `opens` on, and a press in its band (a Reading\'s part, a grid\'s cell, a figure\'s `where`) name a field its rows show \u2014 a `columns` entry; the row\'s title, the line under it, its picture, status or figure, a meter\'s bound, or the unit or currency a drawn figure is read in; a deadline; a warning its line says; the group or lane it stands under \u2014 or a formula, drawn where every field of its own row it reads is (through other formulas too; one reading no field is drawn nowhere). A narrowing to the reader (`{ "me": true }`) needs none. A tree leaves out a `filters` entry no row shows and says so (name the field as a column or in the row\'s line to offer it); the other two are refused. |\n| `register.tiers` | `tiers` band a number read in one unit on every row; a figure read in each row\'s own unit or currency is named bare. |\n| `register.search` | A List\'s `search` names text, a code, or a link \u2014 matched by its row\'s title. |\n| `register.sort` | Each `sort` key of a List or a Tab names a field of the List\'s rows that holds an order \u2014 never files \u2014 and no field twice. |\n| `register.tabs` | A List\'s Tabs each have a `key` of their own, none the entity\'s alias or one a report template holds (`title`, `lines`, `readings`, `at`, `dates`, `period`, `per`); a Tab states one of `dated` and `open` (dates of its rows, `open`\'s two different) or `totals` (a child\'s rows windowed by the period); its `where` and `sort` hold as the List\'s do, and never narrow by the status, which its chips split. Only a table or cards List has Tabs, and a Reading\'s `tab` names one of them. |\n| `register.tab-totals` | A Tab\'s `totals` name a child linked to the List\'s entity by a one-row link (a child linking the List\'s entity twice cannot be totalled), a date of the child, never a moment or a span, and 1 to 6 columns each under its own alias and label; a column\'s `value` is a number of the child, its `where` holds over the child\'s selects and yes/nos, and a value read in each row\'s own unit or currency stands only where that select of the child looks up, through its link, a single select of the List\'s entity. Only a table List with no Books has them, and no Tab states `totals` beside `dated` or `open`. |\n| `register.group` | A List\'s `group` names one value per row \u2014 a single select, a one-row link, one member, a date, a yes/no or a text, a lookup through one-row links as the field it reads \u2014 the status too, but never on a task list\'s block, whose chips narrow it; only a table or cards List groups, and a gantt into its lanes; grouping by the title needs a `row.subtitle` to lead each row. |\n| `register.readings-own` | A Reading in a List reads the List\'s own entity; another entity\'s stands on a dashboard. |\n| `register.readings-bands` | A Reading in a List that bands a number by `tiers` has that field with the same tiers among the List\'s `filters`: a press on a band narrows the rows by that filter. |\n| `register.readings-restate` | A Reading with no `by` in a List never sums the row\'s figure or counts the rows without a `where`: the summary line totals the figure and the status chips count the rows. |\n| `register.where-opens` | A List\'s `where` holds its own selects, yes/nos, bounds and blanks (a child List\'s or the register\'s), and `opens` its selects and yes/nos and a member field holding the reader (`{ "me": true }`) \u2014 or, on a status whose `stages` have owners, the rows waiting on the reader; a field `where` fixes to options is never a filter nor opened otherwise, and `opens` stays among the options `where` keeps. A List laid out as a `plan` keeps its items by options and yes/nos alone. |\n| `register.roster` | A List with `layout: "roster"` holds the List of its cells: the cells\' entity has one single link to this one and a date on its line (a field of the row itself, or a lookup of its parent\'s date), a roster draws no `columns`, and an `expect` (with `of`) stands only on the cells List. |\n| `register.roster-expect` | A roster cells List\'s `expect` is a single select of its rows, and its `of` this entity\'s multi-select whose every option is one of `expect`\'s. |\n| `register.gantt` | `layout: { gantt }`: `start` and `end` (absent, the entity\'s first `due`) are two different dates of the row, both days or both moments; each diamond (`milestones`) another date of it, each planned date read as `start` is, `progress` a percent and `after` a link to rows of the same entity. |\n| `register.lanes` | `layout: { lanes }`: `by` is a one-row link to an entity with a `records` entry, the rows name a date on their line (a field of the row itself, or a lookup of its parent\'s date), a lane block\'s two ends are read alike (two days or two moments), and `loads` stands with it. |\n| `register.plan` | `layout: { plan }`: `at` is a one-row link to an entity whose `records` states a `plan_place`, `across` and `up` two different numbers of the row, `fill` a single select; a plan List has no Tabs, `group` or `attention`. A write that sets `at`, `across` or `up` is refused unless it clears all three, or places the row at a free cell of a bay (a row from 1 to the bay\'s `across`, a tier from 1 to its `up`, resting on a present item), or at an area with neither. |\n| `register.plan-unique` | *Noted, never refused:* A plan\'s items (the List\'s table and each Book) state `unique` on their place link, `across` and `up`: the app\'s moves refuse a taken cell, and the unique makes every other write hold it \u2014 the note names a table that states none. |\n| `register.plan-mark` | A plan\'s `mark` names a single select of the row and an option of it by alias: a row holding that option says its label on its box and is hatched in the option\'s colour, which the legend names by its label. A Book that holds the select must hold it as a single select with that option; a Book lacking it never marks its rows (`app.book-lacks`). |\n| `register.plan-size` | A plan\'s `size` names a single select of the row and, by option alias, the box (`across`, `along`, `up` \u2014 positive, in the one length unit the places\' `size` states) of each option: a row holding one is drawn as that box, any other at its place\'s cell. A Book that holds the select must hold it as a single select; a Book lacking it draws its rows at their places\' cells (`app.book-lacks`). |\n| `register.plan-box-size` | *Noted, never refused:* A plan whose places state a `size` and whose rows\' select options are ISO 6346 container size-type codes (`20DC`, `40HC`) states the box of each in `size`, from the ISO 668 dimensions, else every box is drawn at its place\'s cell. |\n| `register.loads` | A lanes layout\'s `loads` names one to 2 numbers a lane block adds \u2014 never a percent \u2014 each against a number of the lane\'s row, in one unit or two of one dimension. |\n| `register.band-pivot` | *Noted, never refused:* A Reading with `across` (a grid) among a List\'s Readings: its grid reads on a dashboard, and the band leads with a figure (a Reading with no `by`). |\n| `register.headline` | *Noted by the designer on a write, never refused:* A List over rows that fall due or carry money and have no status whose steps the chips count, laid out as a table, cards or a gantt, has Readings \u2014 they split the rows by a field they show. |\n| `register.span` | *Noted, never refused:* Rows that each span a run of days or hours \u2014 two dates on their line, a `frees`, a `no_overlap` booking them on what its `by` links to, or one required date and the table\'s `due` where the line names no date \u2014 are drawn in time by some List over them (a `layout` of calendar, lanes, gantt or roster) \u2014 the note names the app to add over them, laid out as lanes on what each one books, else as a gantt (a run held by a date and the `due` also asks for both dates on the line). |\n| `register.remove` | A List\'s Delete stands only where the App writes its rows: `remove: true` over rows it cannot write is refused. Absent, rows offer a Delete wherever the List writes them, and `remove: false` takes it away. |\n| `register.remove-roster` | A roster cells List\'s `remove: true` offers the Delete of the roster\'s rows, so it stands only beside a roster over rows the App writes. |\n| `register.attention` | `attention: false` keeps a table\'s or cards\' rows in the List\'s own order \u2014 a first-come queue; a calendar, lanes, a gantt and a roster stand their rows in time and lift none. |\n| `register.unworked-stage` | *Noted, never refused:* A register lists rows (every open one, else those its `where` keeps) at a stage that no Act of the App is offered at and no Section stands at, though the App states the stages it works: the rows only sit in its list, so keep them out with `where` or leave them to the App that works them. |\n\n<!-- generated:end rules-register -->\n\n## Block rules\n\nThe rules of what a part holds: a child List, a Field gallery or prose block, a Reading, a desk, a timeline or an agenda.\n\n<!-- generated:start rules-block -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `block.child-link` | A child List reads a child linked to the record; `via` names the link where the child links more than once. |\n| `block.column-drawn` | A child List\'s `columns` never name what the child\'s row draws (title, subtitle, image, status, figure, or the bound its figure\'s meter reads against) nor its link back, and name a field once; an `expect` List\'s figure is its filled-in-place column. |\n| `block.columns-budget` | A child List\'s `columns`, each at the width of its kind, fit the room a desk leaves beside its name and the row\'s own status, deadline and figure. |\n| `block.title-back` | Where a child\'s title is its link back to the record a child List lists it under, its first other `row.subtitle` names it there \u2014 a text, a code, a select, a link or a member, never a number or a date. |\n| `block.where` | A child List\'s `where` names single selects the child requires, by options they hold \u2014 one held at a single option is never also a column \u2014 and the child\'s yes/nos by value, numbers and dates by bounds, and fields by `{ "empty": true \\| false }`. A yes/no, a bound or a blank is never held: a child List adds rows only at a stored yes/no\'s start, adds none under a bound or a blank, and never picks, expects or stands lines under documents. |\n| `block.under` | `under` names the child\'s one-row link to a document entity that pairs a many-link back and is read under this record by its one link; the lines\' columns never read the document (its heading does) nor hold long text. |\n| `block.under-once` | Documents drawn over their lines (`under`) are never listed again as a List of their own. |\n| `block.edits` | A child List\'s editable columns (`{ "field", "edit": true }`) name at most two of the child\'s columns or its figure that the App\'s saves of the child write \u2014 a number, a date, a few words, one option or a one-row link, never a files or long-text field, the List\'s `via` nor a link it must name alike (a link\'s `same`) \u2014 never on an `expect` List. |\n| `block.remove` | A child List\'s `remove: true` stands only where the App writes those rows. |\n| `block.remove-documents` | The documents List inside a child List with `under` states `remove: true` only over documents the App writes. |\n| `block.compare` | A child List\'s `compare` names two different fields of the child, `before` and `after`: two files fields, or two values of one type (text but long text, a number, a date, a single select). Never with `expect`. Over files on a checklist layout, `before` is the check\'s `photo`. Over values: neither side is a column or something the row draws already (title, line, figure, status, deadline), two numbers share a notation, no column is long text, never on a checklist layout. |\n| `block.current` | A child List\'s `current` names a date or number of the child (stored, or a formula yielding one) that orders a set of revisions: the row holding the latest value is current, the rest fold as superseded. Never with `group`, `under`, `expect`, `checklist`, `compare`, `post` or `pick`, nor beside a column of long text. |\n| `block.checklist` | A child List with `layout: { checklist }` reads a task entity\'s rows as checks \u2014 never under documents, grouped, edited in place or picked together by an Act over several, its columns no files; `photo` names a files field of the child, `clause` a one-row link of it, `failed` options of its status, none `closed`; `passed` a yes/no of it, stored or a formula, setting each mark (yes passed, no failed, empty still to check) and standing without `task` or a status. With `expect` the lines of a standard are the checks, each marked and filled by the block\'s columns (a measured value, a note, a photo), so `photo` and `clause` are not named. |\n| `block.pick` | A child List\'s `pick` links rows of the child through `via` \u2014 an optional one-row link a person writes, no Act writes, no other rule compares rows by and no `under` List stands lines under \u2014 paired with a many-link of the record its own save writes; the child\'s `read_scope` reads a row naming no record through `via`; never with `expect` or `under`, nor where the App\'s saves leave out the child or the many-link. |\n| `block.narrow` | A child List\'s `filters`, `search`, `group` and `sort` follow the register List\'s rules over the child\'s rows, its status among them but on a task list (its chips); never on the link back to the record or a select its `where` holds at one option, nor on an `expect` List; an `under` List is grouped by its documents. |\n| `block.expect` | `expect` names the child\'s title \u2014 a single select or a one-row link a person writes, never the link back \u2014 on a child added in the List; its `columns` name a field filled in place \u2014 unless the List\'s layout is a desk, whose lines are ticked \u2014 and its `create` takes the file where the child is pictured by one. |\n| `block.expect-fills` | An expected line is made from its key and its first filled column \u2014 a desk\'s from its key alone, by its tick \u2014 so every other field the child requires has a default, a start or a `default_from`, or is read first. |\n| `block.expect-of` | An `expect` object\'s `of` or `only` needs a select: `of` names the record\'s multi-select of the title\'s options; `only` names options of a single select, never with `of` or a `where` on it, one List per option; an Act\'s `paper` is drawn in one. |\n| `block.post` | A child List with `layout: { desk }` needs `expect` on a one-row link \u2014 never a select, nor with `under`, `pick`, a checklist layout, `compare` or a `create` list, its line made by a tick from its key alone: its `bodies` are plain text fields of the record a person writes, drawn nowhere else on it; its `text` is a text of the child, stored or a formula yielding text \u2014 a formula reading only stored values of its row, lookups through the link back to the record or through `expect`, and formulas of the row over those, never the clock; its `media` a files field of the record and its `link` a text field of the record; its `network` a single select of the entity the link names, and `networks` holds options of that select, by alias, each to one of facebook, instagram, threads, x, linkedin; its `group` is another single select of that entity. |\n| `block.funnel` | A Reading\'s `steps` (a funnel) are 2 to 8 numbers of its child, each named once \u2014 stored, a formula or a rollup \u2014 all in one notation, never a percent nor a unit read on each row. |\n| `block.list` | A read-only child List (`same`, `app`, or a Record that is `edit: false`) lists the rows of an entity that link to the record by its one link to it (`via` where it has several); with `same`, a one-row link of the record, it lists the rows sharing that link\'s row with the record \u2014 the record left out \u2014 by the listed entity\'s one link to that row\'s entity (`via` where it has several). `same` stands in a Record, never on a dashboard. Its Record\'s Fields \u2014 at least one, each a field of the listed entity named once, never its title nor one of the `columns` \u2014 are what a row shows, read-only, when it is opened in place. |\n| `block.list-opens` | A read-only child List\'s row opens in the record\'s drawer as a record: its entity is one a List of the App draws \u2014 the register\'s, a Book\'s, or a log\'s \u2014 or the List\'s `app` names the register app that works it. |\n| `block.figures` | A Record\'s own figures (Readings with no `entity` and no `by`, whose `value` names the field) are 2 to 8 numbers of the record no person keys \u2014 a formula, a rollup, a lookup through links to one row, or a stored number the App\'s saves leave out \u2014 each placed once on the record. |\n| `block.agenda` | An agenda List\'s child names a date on its row line (`records` title or subtitle); the layout\'s `start` is a date of the record. |\n| `block.timeline` | A timeline List\'s child holds a date to place its entries by. |\n| `block.field-kind` | A gallery Field names a files field of the record, and a prose Field a text field. |\n| `block.read-scope` | A child read under a record whose rows a `read_scope` narrows states its own \u2014 a row rule is not inherited, save the status history\'s, which takes its record\'s. |\n| `block.agenda-note` | *Noted, never refused:* Rows each on a day and within it (at an hour, in a part of the day) listed as a table: a List with `layout: { agenda }` draws them by day. |\n| `block.under-note` | *Noted, never refused:* Rows holding a paper that cover another List\'s lines, listed apart with no add of their own stated: `under` draws them over the lines they cover. |\n| `block.timeline-note` | *Noted, never refused:* Rows each what happened on a day and who \u2014 a date on their line, a member, no status and no figure \u2014 listed as a table: a List with `layout: "timeline"` draws them as a log, newest first. |\n| `block.expect-note` | *Noted, never refused:* Rows each a kind of paper, listed with no `expect` and no `where` on the kind, that no Act files a call into or `fills`: the lines still missing read as what a record needs. |\n\n<!-- generated:end rules-block -->\n\n## Record and act rules\n\n### Record rules\n\n<!-- generated:start rules-record -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `record.placed-once` | A Field, a child List, a Reading, a Statement and an Act each stand once in a Record: its head draws the title, subtitle, image, status, figure and folder, and a field\'s one other place is a part\'s Fields, a gallery or prose Field (`block`) or a Statement \u2014 save the image, which one files gallery may draw again, whole, the figure, which a Statement draws as what it comes to, and rows one List prices and another compares (`compare`); two places whose `when`s never hold together (a select at options the other\'s lacks) each draw it, and a plain child List (no `expect`, `under`, `pick`, `compare`, nor a `layout` of desk or checklist) likewise. |\n| `record.foot-total` | A child List\'s foot totals the child\'s figure and each summed column over the rows it lists; the record\'s rollup summing that field over those rows (the same `where`) is that foot, never also a Field of a part. |\n| `record.fact-restates` | A Field of a part never looks up what its link already reads where it stands: the linked row\'s title beside the link, the figure or bound its meter reads on the head\'s line. |\n| `record.section-holds` | A part (Section, Side, Page) holds Fields, child Lists, Readings, Progress, Statements or Acts. |\n| `record.section-reads` | A Field a part shows as a value only (`edit: false`) is one that part draws. |\n| `record.compose` | A Record\'s `compose` is stated instead of parts (it holds no children), on a page `door`, by an App with no Books whose records have no owned stages (`status.stages`) and no picture (`image`) \u2014 a composed page draws neither. Each name is a field of the entity, an Act of the app on the record or a child entity linked to it; one `Title`, and each field, Act and child drawn once \u2014 a field by `Field` or `Document`, never the title or the status, which `Title` and `Status` draw. The first `Act` the page itself draws is its next step and every other one secondary; an `Act` is a button alone, never one making a set of papers. A `Bar` is one row on one control height, in two zones \u2014 its `Status` and `Field`s from its left edge, its `Act`s and `Copy`s ending on its right edge \u2014 and holds no `Rows` and no `Document`; a `Field` directly in it is its control alone on one line, named by its label where it holds nothing; an `Act` in it that destroys or corrects what is done (`danger`, or a move back) is not drawn there \u2014 it stands in the record\'s \u22EF. A `Panel` draws nothing in the page: it is one entry of the record\'s \u22EF, named by its `title`, opening its pieces in a panel beside the page; it stands directly in the root `Column`, holds `Rows`, `Field`, `Act`, `Group` and `Document` \u2014 never `Title`, `Status`, `Bar`, `Copy` or another `Panel` \u2014 and an `Act` directly in it ends the panel\'s name, secondary. A `Group` is headed once, by its `title`: holding one `Rows`, that heading carries the rows\' count and their add in place of the rows\' own, and the `Act`s the group holds end its line. A `Copy` takes a text field\'s words as the page holds them now, a `Document`\'s words not yet saved among them; a field a `Copy` alone names is read, never written. |\n| `record.sections` | *Noted by the designer on a write, never refused:* A List laid out as a table, cards or a gantt opens a Record whose Sections hold the fields people write, a files Field per paper, a child List per child and the record\'s Acts. |\n| `record.header-budget` | *Noted by the designer on a write, never refused:* A Record\'s head says what the row is and where it stands \u2014 its title, line, picture and status \u2014 and at most two more facts, one of them a deadline at most; the rest stands in a Section. |\n| `record.children` | *Noted by the designer on a write, never refused:* A table some App records, whose rows other rows each belong to (a one-row link to it, never its status history), draws some of them under its row \u2014 a child List in its Record, a List with `layout` roster or lanes by that link, or an App over them working inside one of its rows at a time (`scope`). |\n| `record.at` | A Section\'s `at` names options of the record\'s status; a record with no status has no steps. |\n| `record.at-once` | A stage is the work of one Section: two Sections naming one option of the status in `at` hold some Field to values apart in their `when` \u2014 a `when` holding the status keeps its Section off the options it leaves out. |\n| `record.section-page` | A Page stands only on a page `door` \u2014 never a drawer\'s or one beside the List \u2014 is never a step (a Page has no `at`), has a `title` naming an address no other Page shares (a letter or a digit at least), and is never every part. |\n| `record.side` | At most one Side stands in a Record, on a page `door`, and it is never also a Page. |\n| `record.beside` | A Record with `door: "beside"` opens beside a table List \u2014 never a board, cards or a calendar, which draw no list to stand beside. |\n| `record.section-acts` | An Act in a part works on the record itself; an Act in a child List stands on the child\'s row, and an Act in the register List with an `on` on the register\'s rows. |\n| `record.act-in-section` | Every Act of the record stands in the part whose work it is; only a correction no part owns \u2014 `danger`, and no other status move offered at the same moment \u2014 or an Act an agent runs for the person (a `workflow` or an `intake` moving no status), placed in the List and in no part, waits in the head\'s \u22EF. |\n| `record.move-home` | A status move offered at one step stands at that step\'s foot when it moves the work forward, to an exit, or back to a step it passes anyway; one offered at several steps, or moving back into a detour, stands in the Section whose `at` holds the status it enters. A tree\'s status move written in a Section with no `at`, or in none, is placed where this puts it and said. |\n| `record.act-staged` | An Act at a staged Section\'s foot is offered only during that Section\'s steps: its `when` holds the status to options the Section\'s `at` names. A tree\'s Act with no `when` at the foot of a Section staged at one step is offered at that step and said; a correction stays offered at every step, and an exit is refused. |\n| `record.act-hidden` | An Act is never offered only while its part\'s `when` hides the part. |\n| `record.outside-place` | An Outside stands directly in a Record whose table states a stored status select, and its `key` is unique in the App: a link to a record names it. |\n| `record.outside-at` | An Outside\'s `at` names options of the record\'s status that no stage owns, and no two Outsides of a record open during the same option. |\n| `record.outside-field` | An Outside\'s Fields are read as values, each a field of the record shown once, never `edit`, `when` or a label of its own. |\n| `record.outside-list` | An Outside\'s Lists read the child rows under the record as they are: their `columns`, `label` and `where` only, never an add, a Delete, a pick, a filter, a search, an `app`, a link that opens, or a bound read from the record. |\n| `record.outside-act` | An Outside\'s Acts are its alone and each ends the visit: `when` holds the status to `at`, `set` moves it to an option outside `at`, `asks` are text, numbers, dates or listed options, `requires` names fields the Outside shows or the act asks, a `sign` is by a text field with no witness, and none states `on`, `of`, `workflow`, `templates`, `paper`, `intake`, `record`, `review`, `open`, `danger`, `recommends` or `me`. |\n| `record.field-edit` | An Act that only writes one field of its record, not the status, from the one input it asks is that field\'s edit: the Field rests in the part as a control. |\n| `record.section-owners` | *Noted, never refused:* A Section holding acts offered at stages that different owners work \u2014 a role apiece, or a member field apiece \u2014 is two jobs in one: each owner\'s part is a Section of its own. |\n\n<!-- generated:end rules-record -->\n\n### Act rules\n\n<!-- generated:start rules-act -->\n\n#### Rules the check enforces\n\n| Rule | |\n|---|---|\n| `act.paper-note` | *Noted, never refused:* A made paper kept in a field of a record that lists its papers by kind: it is one of those lines. |\n| `act.moves` | *Noted by the designer on a write, never refused:* Rows a stored status stages towards `closed` options are moved by an Act of some App over them, or by its authored `workflow` \u2014 each open option moves to the next, the last to the first closed one. |\n| `act.set-not-asked` | A field an Act `set`s (an option, a value, `{ "now": true }`, `{ "me": true }`, an input) or `sign`s is the Act\'s outcome: the App\'s `create` never asks it, and a required one takes a `default`. A `formula` only starts a value, which a person may change after. |\n| `act.asks-set` | A field an Act `asks`, `set`s, `sign`s or `writes` is stated by one of them: what the Act writes is its outcome, never also asked, and no field is written by two clauses of one Act. |\n| `act.starts` | `starts` (an ask `{ field, starts }`) starts a field the Act asks \u2014 `today` a date or a moment (never a span), `me` a member \u2014 for the reader pressing it, who changes it at will. |\n| `act.set-formula` | `set: { <field>: { formula } }` reads fields of the acted row and computes what the field holds \u2014 a number, a date, text, a yes/no or a single select\'s option (`{field:option}`), typed as the entity\'s own formulas are \u2014 never the status; a bound a write rule holds the field to is checked on the computed value as the Act runs. |\n| `act.of` | An Act in a child List works on that child: the one row pressed \u2014 or, with `on: "ticked"`, the rows ticked together where the record lists them as tasks \u2014 never the rows in view. |\n| `act.writes-line-key` | An Act never writes the link or the key naming an expected line under its record \u2014 a line\'s key is stated by filling it. |\n| `act.makes-or-runs` | An Act makes a document (`template`, `templates`) or runs an authored `workflow`, never both \u2014 the workflow makes what it needs. |\n| `act.does-nothing` | An Act does something: it `set`s a value, `asks` a field, runs a `workflow`, makes a document, or takes an `intake`, a `record` or a `sign`, or `open`s an app. |\n| `act.view` | An Act over every row in view (`on: "view"`) writes (`set`), makes a document or runs a workflow, and states no `requires`. |\n| `act.requires-drawn` | Every field an Act `requires` is drawn where the Act stands \u2014 its part\'s Fields, a child List\'s, its `asks`, or an unstaged part\'s where its own is unstaged \u2014 never the head alone. |\n| `act.requires-written` | A field an Act requires that only an Act writes is written by an Act offered together with it, or asked by the Act itself. |\n| `act.recommends` | A field an Act `recommends` is not also in its `requires`. |\n| `act.set` | `set` writes each field a value it holds \u2014 an option alias, a number, a yes/no, a text, `{ "now": true }` on a date, `{ "me": true }` on a member, `{ "input": "<name>" }` of an input the Act asks of the same kind \u2014 or clears an optional one with `null`. |\n| `act.into` | `into` is a files field of the one record the Act is pressed on, beside its `template` or `templates`. |\n| `act.paper-list` | A List a made paper names is one it can list: none keeps its rows by the reader, and no name is reached by a child List that writes its rows and one that only reads them, or by two of either, at once. |\n| `act.paper` | `paper` is a kind the record lists its papers by, on one record \u2014 never beside `into` or `templates`; one maker per kind. |\n| `act.templates` | `templates` names each template once, kept in `into`, on one record pressed alone \u2014 never beside `template` or `asks`, in a child List, or with an `on` over several rows. |\n| `act.intake` | `intake` names a child the record lists in a child List with `expect` on its title \u2014 a single select, the paper\'s kind \u2014 and is narrowed by no `where`, holding exactly one files field and needing nothing an added line does not fill; the Act runs on one record, states `fills`, and states nothing but its `label`, `when` and `requires` beside them. |\n| `act.record` | `record` names a child the record draws in a timeline List or a child List with no `expect`, `under` or `where`, linking back by one single link, its row needing nothing the recording does not write: `audio` and `video` files fields, `transcript` text, `at` a date, `by` a member, each its own field; on one record, stating only `label`, `when`, `requires` and `fills` beside it. |\n| `act.sign` | `sign` runs on one record, never a child\'s row: `into` a files field, `by` a text field the signer types or a member field set to the person pressing, `at` a date, `witness` a member field \u2014 stated exactly beside a text `by` \u2014 each a field of its own that the Act does not also ask or set, and no other Act signs into `into`. |\n| `act.open` | `open` opens another app at the record and does nothing else: an Act on one record of the app\'s own, stating only `label` and `when` beside it. |\n| `act.open-app` | An Act\'s `open` is the id of an app of this workspace that reaches the record\'s table: a register over it, or a custom-code app declaring no table or declaring it. |\n| `act.fills` | `fills` names each once, only beside `intake` or `record`. Beside `record`: a field of the recording\'s row the recording does not write, never a link. Beside `intake`: a field of the record, `<link>.<field>` on the row a required one-row link names (never beside that link), or a child the record adds rows of in a child List with no `expect`, `under` or `where`, filled in the fields its `create` asks \u2014 every one a new row needs among them. Each field filled is one a person writes \u2014 text, a number, a date, a yes/no, options or a single link\'s row \u2014 never a status, a field an Act writes or a link `same` narrows; a link\'s row is one the App reads of its entity. |\n| `act.fills-drawn` | *Noted, never refused:* A field `fills` writes that the record draws nowhere: the review shows the change, the record does not. |\n| `act.fills-new` | *Noted, never refused:* A child `fills` adds rows of, whose natural key the papers cannot state: every row read is added, so the same papers read twice list them twice. |\n| `act.input` | An input has a name of its own \u2014 never another ask\'s, a field\'s, or the record input its workflow takes (nor `approved` beside `review`) \u2014 `options` exactly when it is a select, `entity` exactly when it is a link, and a `default` field holding what it states. A `file` input is read by the Act\'s authored `workflow` alone, starts empty, and is never written into a field by `set`. |\n| `act.review` | `review: true` stands beside an authored `workflow` on an Act over the record, the ticked rows or every row in view, in an App over one entity \u2014 never in an App with Books, nor beside `set`, `template`, `templates`, `intake`, `record`, `sign` or `confirm`, nor in a child List, and it asks inputs only, none named `approved`. Its workflow is that Act\'s alone, run by no other Act. The workflow calls only steps that read \u2014 no agent \u2014 and returns a changeset of the entity\'s table in `data.review`, the same for the same inputs and rows; the platform writes the rows `approved` names, each only while the run proposes it as the caller read it. Over every row in view (`on: "view"`) it is handed no rows and states no `when`: its workflow reads the rows it may change, whatever the view is narrowed to, and no bound on a run\'s rows applies. |\n\n<!-- generated:end rules-act -->\n\n## What the workspace remembers\n\nThe workspace holds every table, field and select option under an id that never changes, whatever its\nlabel or alias becomes. `set_tables` answers each table\'s id, and an\napp\'s body names tables and templates by those ids, so a relabel on either side is a rename and no body\npoints at another table by accident.\n\nA table `set_tables` refuses to write, or an id the workspace no longer holds, is a refusal by name. `restore_table` puts a deleted table back.\n\n## A complete workspace\n\nTwo jobs, so two apps: a sales desk, where an order is taken and shipped, and an account review, where an account\nmanager reads each customer by tier. The split is per job, never per table: a table a job reads only in passing, such as\na price book, is a picker on that job\'s lines or an Act of that job\'s app (`first_build`, \xA7 3).\n\nOne `set_tables` call makes both tables: each item carries a `ref`, and a link names the table it points\nat by the `ref` of the other item. Its answer carries each table\'s id. One `set_app` call then makes each\napp, `{ "body": \u2026 }`: the tree\'s root names the app by its `key` and `name`, and the tables it uses in `tables` by those ids,\nshown here as placeholders to replace.\n\n```json\n{\n "tables": [\n {\n "ref": "customer",\n "label": "Customers",\n "singular": "Customer",\n "fields": {\n "name": {"label": "Name", "required": true, "type": "text"},\n "logo": {"label": "Logo", "type": "files"},\n "tier": {\n "label": "Tier",\n "default": ["standard"],\n "type": "select",\n "options": [\n {"alias": "standard", "label": "Standard", "color": "slate"},\n {"alias": "gold", "label": "Gold", "color": "amber"}\n ]\n },\n "orders": {"label": "Orders", "type": "link", "pair": "customer", "table": "order", "multi": true},\n "total_ordered": {"label": "Total ordered", "type": "rollup", "via": "orders", "op": "sum", "field": "amount"}\n },\n "views": [\n {\n "alias": "gold",\n "label": "Gold customers",\n "filters": {\n "node_type": "condition",\n "field_key": "tier",\n "type": "select",\n "operator": "has_any_of",\n "value": ["gold"]\n },\n "sort": [{"field_key": "name", "order": "asc"}]\n }\n ],\n "title": "name",\n "records": {"image": "logo", "party": "organization", "figure": "total_ordered"}\n },\n {\n "ref": "order",\n "label": "Orders",\n "singular": "Order",\n "fields": {\n "code": {"label": "Order no.", "type": "text", "unique": true},\n "placed_on": {"label": "Placed on", "type": "date", "format": "date"},\n "amount": {"label": "Amount", "type": "number", "notation": {"style": "currency", "currency": "VND"}},\n "total": {\n "label": "Total with VAT",\n "type": "formula",\n "expression": "{amount} * 1.1",\n "notation": {"style": "currency", "currency": "VND"}\n },\n "customer": {"label": "Customer", "type": "link", "pair": "orders", "table": "customer", "multi": false},\n "note": {"label": "Note", "type": "text"},\n "stage": {\n "label": "Stage",\n "default": ["draft"],\n "type": "select",\n "options": [\n {"alias": "draft", "label": "Draft", "color": "slate"},\n {"alias": "confirmed", "label": "Confirmed", "color": "blue"},\n {"alias": "shipped", "label": "Shipped", "color": "green"}\n ]\n }\n },\n "title": "customer",\n "records": {\n "subtitle": ["code", "placed_on"],\n "figure": "amount",\n "status": {"field": "stage", "closed": ["shipped"]},\n "starts": {"placed_on": "today"}\n }\n }\n ]\n}\n```\n\n```json\n{\n "body": {\n "type": "App",\n "key": "account_review",\n "name": "Account review",\n "color": "blue",\n "tables": {"customer": "<id of the customer table>", "order": "<id of the order table>"},\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "customer",\n "columns": ["tier"],\n "children": [\n {"type": "Reading", "key": "ordered_by_tier", "by": "tier", "value": "total_ordered"},\n {\n "type": "Record",\n "children": [\n {"type": "Section", "key": "account", "title": "Account", "children": ["tier"]},\n {\n "type": "Section",\n "key": "orders",\n "title": "Orders",\n "children": [{"type": "List", "entity": "order", "columns": ["total"], "create": ["code", "amount"]}]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n```json\n{\n "body": {\n "type": "App",\n "key": "sales_desk",\n "name": "Sales desk",\n "color": "violet",\n "tables": {"customer": "<id of the customer table>", "order": "<id of the order table>"},\n "templates": {},\n "children": [\n {\n "type": "List",\n "entity": "order",\n "filters": ["customer"],\n "create": ["code", "customer", "placed_on"],\n "children": [\n {"type": "Reading", "key": "amount_per_period", "by": "placed_on", "value": "amount"},\n {\n "type": "Record",\n "door": "drawer",\n "children": [\n {\n "type": "Section",\n "key": "order",\n "title": "Order",\n "children": [\n "note",\n {\n "type": "Act",\n "key": "confirm",\n "label": "Confirm",\n "when": {"stage": ["draft"]},\n "set": {"stage": "confirmed"}\n },\n {\n "type": "Act",\n "key": "ship",\n "label": "Ship",\n "when": {"stage": ["confirmed"]},\n "set": {"stage": "shipped"}\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\nThe account review leaves the door a record opens through and the rows it opens in to the system, each the default the\ntable\'s facts give; the sales desk states a drawer. Each List names what its add asks (`create`). The customer\'s orders are a block in its "Orders" section, never its own `Orders`\nlink as a field as well: one fact, one place. An order is titled by its customer and read by its number under it; the\nList names what its add asks, since a subtitle or a figure is never asked by default. `Total ordered` is a rollup, so it\nis read and never asked; the sales desk\'s record opens in a drawer, because its rows are worked one after another. An\norder\'s `stage` is its status (`records.status`), moved only by the app\'s acts: they set it, so the app\'s saves leave it\nout, each act names the stage it is offered at (`when`) and the one it sets, and the register opens on the orders not yet\nshipped (`closed`).\n';
|
|
1765
1765
|
|
|
1766
1766
|
// docs/examples.md
|
|
1767
1767
|
var examples_default = '# Worked examples\n\nEach section is one treatment, cut from a complete workspace of an invented business: the job, the keys to\nread, where the treatment is wrong, and the excerpt \u2014 the app\'s tree, the App node `set_app` takes as `body`,\nwhich the check passes with no finding; the tables it draws as the items of a `set_tables` call, each with its\n`title` and `records`; those tables\' fields it names, by what each holds and how it is drawn; and the templates it makes.\nLeft out, for the page\'s size: a Section that carries nothing of the treatment, every `label` and `description` of a table or field, `required`, `unique`, `default`, a link\'s\n`pair`, how a computed field computes, the `tables` and `templates` maps an App adds (ids from the\n`set_tables` answer), and template contents \u2014 a call states each table\'s and field\'s `label`. Choosing\namong them: the `design` reference. Every key: the `model` reference, at the pages each section names under\n**Keys**.\n\n## Calendar\n\n**The job.** Book visits on the days they happen, and see a week\'s work at a glance.\n\n**Read.** `layout: "calendar"` on the List; each visit stands over its span because its `records` line holds two dates (`scheduled_for`, `ends_at`).\n\n**Keys.** `model/calendar`\n\n**Not when.** Rows booked on a resource that two may not share at once \u2014 that is `lanes`. Rows with a date nobody plans by (an invoice\'s date) stay a table.\n\n```json\n{\n "tables": [\n {\n "ref": "service_visit",\n "title": "customer",\n "fields": {\n "customer": {"type": "link", "table": "customer", "multi": false},\n "scheduled_for": {"type": "date", "format": "datetime"},\n "ends_at": {"type": "date", "format": "datetime"},\n "engineer": {"type": "link", "table": "engineer", "multi": false},\n "job": {\n "type": "select",\n "options": [{"alias": "inspection", "color": "sky"}, {"alias": "repair", "color": "orange"}]\n },\n "stage": {\n "type": "select",\n "options": [\n {"alias": "booked_on", "color": "blue"},\n {"alias": "parts_ready_on", "color": "blue"},\n {"alias": "arrived_on", "color": "blue"},\n {"alias": "completed_on", "color": "green"}\n ]\n },\n "booked_on": {"type": "date"},\n "parts_ready_on": {"type": "date"},\n "arrived_on": {"type": "date"},\n "hours": {"type": "number", "notation": {"style": "unit", "unit": "h"}},\n "report": {"type": "text", "format": "markdown"},\n "completed_on": {"type": "date"}\n },\n "records": {\n "subtitle": ["scheduled_for", "ends_at"],\n "figure": "hours",\n "due": ["scheduled_for"],\n "status": {\n "field": "stage",\n "closed": ["completed_on"],\n "dates": {\n "booked_on": "booked_on",\n "parts_ready_on": "parts_ready_on",\n "arrived_on": "arrived_on",\n "completed_on": "completed_on"\n }\n }\n }\n }\n ],\n "body": {\n "type": "App",\n "key": "visits",\n "name": "Service visits",\n "icon": "calendar",\n "color": "emerald",\n "children": [\n {\n "type": "List",\n "entity": "service_visit",\n "row": {"title": {"add": ["name", "credit_limit"]}},\n "columns": ["engineer", "job"],\n "filters": ["customer"],\n "sort": {"field": "scheduled_for"},\n "layout": "calendar",\n "create": [\n "customer",\n "job",\n "scheduled_for",\n "ends_at",\n {"field": "engineer", "add": ["name", {"field": "base", "add": ["name", "city"]}]}\n ],\n "children": [\n {"type": "Reading", "key": "register_reading_0", "label": "Repairs", "where": {"job": ["repair"]}},\n {"type": "Reading", "key": "register_reading_1", "value": "hours", "by": "engineer"},\n {\n "type": "Record",\n "children": [\n {\n "type": "Section",\n "key": "visit",\n "title": "Visit",\n "at": ["booked_on", "parts_ready_on"],\n "children": [\n "job",\n "booked_on",\n "parts_ready_on",\n {\n "type": "Act",\n "key": "reschedule",\n "label": "Reschedule",\n "asks": ["scheduled_for", "ends_at"],\n "when": {"stage": ["booked_on", "parts_ready_on"]}\n },\n {\n "type": "Act",\n "key": "to_booked_on",\n "label": "Booked",\n "when": {"stage": {"empty": true}},\n "asks": [{"field": "booked_on", "starts": "today"}],\n "set": {"stage": "booked_on"}\n },\n {\n "type": "Act",\n "key": "to_parts_ready_on",\n "label": "Parts ready",\n "when": {"stage": ["booked_on"], "job": ["repair"]},\n "asks": [{"field": "parts_ready_on", "starts": "today"}],\n "set": {"stage": "parts_ready_on"}\n },\n {\n "type": "Act",\n "key": "to_arrived_on",\n "label": "On site",\n "when": {"stage": ["booked_on"], "job": ["inspection"]},\n "asks": [{"field": "arrived_on", "starts": "today"}],\n "set": {"stage": "arrived_on"}\n },\n {\n "type": "Act",\n "key": "to_arrived_on_2",\n "label": "On site",\n "when": {"stage": ["parts_ready_on"]},\n "asks": [{"field": "arrived_on", "starts": "today"}],\n "set": {"stage": "arrived_on"}\n }\n ]\n },\n {\n "type": "Section",\n "key": "report",\n "title": "Report",\n "at": ["arrived_on"],\n "children": [\n "engineer",\n "arrived_on",\n {"field": "report", "block": false},\n {\n "type": "Act",\n "key": "complete",\n "label": "Complete visit",\n "requires": ["engineer"],\n "asks": [\n "hours",\n {"input": "summary", "label": "What was done", "type": "long_text", "required": true}\n ],\n "set": {"report": {"input": "summary"}, "stage": "completed_on"},\n "when": {"stage": ["arrived_on"]}\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Lanes\n\n**The job.** Put each service visit on an engineer\'s day by the hour, and see at once who is free between visits.\n\n**Read.** `layout` `lanes` with `by` naming the one-row link to the resource (the engineer); every engineer stands as a lane, empty or not.\n\n**Keys.** `model/lanes`\n\n**Not when.** The resource is not a row of the model (a free-text room name), or rows never compete for one \u2014 a `calendar` reads them by day.\n\n```json\n{\n "tables": [\n {\n "ref": "service_visit",\n "title": "customer",\n "fields": {\n "customer": {"type": "link", "table": "customer", "multi": false},\n "scheduled_for": {"type": "date", "format": "datetime"},\n "ends_at": {"type": "date", "format": "datetime"},\n "engineer": {"type": "link", "table": "engineer", "multi": false},\n "job": {\n "type": "select",\n "options": [{"alias": "inspection", "color": "sky"}, {"alias": "repair", "color": "orange"}]\n },\n "stage": {\n "type": "select",\n "options": [\n {"alias": "booked_on", "color": "blue"},\n {"alias": "parts_ready_on", "color": "blue"},\n {"alias": "arrived_on", "color": "blue"},\n {"alias": "completed_on", "color": "green"}\n ]\n },\n "booked_on": {"type": "date"},\n "parts_ready_on": {"type": "date"},\n "arrived_on": {"type": "date"},\n "hours": {"type": "number", "notation": {"style": "unit", "unit": "h"}},\n "completed_on": {"type": "date"}\n },\n "records": {\n "subtitle": ["scheduled_for", "ends_at"],\n "figure": "hours",\n "due": ["scheduled_for"],\n "status": {\n "field": "stage",\n "closed": ["completed_on"],\n "dates": {\n "booked_on": "booked_on",\n "parts_ready_on": "parts_ready_on",\n "arrived_on": "arrived_on",\n "completed_on": "completed_on"\n }\n }\n }\n },\n {\n "ref": "engineer",\n "title": "name",\n "fields": {"name": {"type": "text"}, "base": {"type": "link", "table": "warehouse", "multi": false}},\n "records": {"subtitle": ["base"], "party": "person"}\n }\n ],\n "body": {\n "type": "App",\n "key": "dispatch",\n "name": "Dispatch",\n "icon": "wrench",\n "color": "sky",\n "children": [\n {\n "type": "List",\n "entity": "service_visit",\n "row": {\n "title": {"add": ["name", "credit_limit"]},\n "subtitle": [{"field": "scheduled_for", "edit": false}, {"field": "ends_at", "edit": false}]\n },\n "columns": ["job"],\n "search": ["customer"],\n "layout": {"lanes": {"by": "engineer"}},\n "create": [\n {"field": "engineer", "add": ["name", {"field": "base", "add": ["name", "city"]}]},\n "scheduled_for",\n "ends_at",\n "customer",\n "job"\n ],\n "children": [\n {\n "type": "Record",\n "door": "drawer",\n "children": [\n {\n "type": "Section",\n "key": "booking",\n "title": "Booking",\n "children": [\n "engineer",\n "job",\n {\n "type": "Act",\n "key": "to_booked_on",\n "label": "Booked",\n "when": {"stage": {"empty": true}},\n "asks": [{"field": "booked_on", "starts": "today"}],\n "set": {"stage": "booked_on"}\n },\n {\n "type": "Act",\n "key": "to_parts_ready_on",\n "label": "Parts ready",\n "when": {"stage": ["booked_on"], "job": ["repair"]},\n "asks": [{"field": "parts_ready_on", "starts": "today"}],\n "set": {"stage": "parts_ready_on"}\n },\n {\n "type": "Act",\n "key": "to_arrived_on",\n "label": "On site",\n "when": {"stage": ["booked_on"], "job": ["inspection"]},\n "asks": [{"field": "arrived_on", "starts": "today"}],\n "set": {"stage": "arrived_on"}\n },\n {\n "type": "Act",\n "key": "to_arrived_on_2",\n "label": "On site",\n "when": {"stage": ["parts_ready_on"]},\n "asks": [{"field": "arrived_on", "starts": "today"}],\n "set": {"stage": "arrived_on"}\n },\n {\n "type": "Act",\n "key": "to_completed_on",\n "label": "Completed",\n "when": {"stage": ["arrived_on"]},\n "asks": [{"field": "completed_on", "starts": "today"}],\n "set": {"stage": "completed_on"}\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Gantt\n\n**The job.** Read every hire as a bar from the day the machine goes out to the day it is due back, against today.\n\n**Read.** `layout` `gantt` with its `start`; with no end stated, the bar ends at the entity\'s first `due`. The day a hire comes back (`ngay_tra`) is stamped by the status\'s `dates`, so no act sets it.\n\n**Keys.** `model/gantt`\n\n**Not when.** Rows that last a day or less (a visit, a shift) \u2014 `calendar` or `lanes`; a plan with no dates set yet.\n\n```json\n{\n "tables": [\n {\n "ref": "phieu_thue",\n "title": "khach_hang",\n "fields": {\n "khach_hang": {"type": "link", "table": "khach_hang", "multi": false},\n "so_phieu": {"type": "text"},\n "trang_thai": {\n "type": "select",\n "options": [\n {"alias": "moi", "color": "slate"},\n {"alias": "dang_thue", "color": "blue"},\n {"alias": "da_tra", "color": "green"},\n {"alias": "huy", "color": "gray"}\n ]\n },\n "phu_trach": {"type": "member"},\n "ngay_giao": {"type": "date"},\n "han_tra": {"type": "date"},\n "ngay_tra": {"type": "date"},\n "tinh_trang_tra": {\n "type": "select",\n "options": [\n {"alias": "tot", "color": "green"},\n {"alias": "tray_xuoc", "color": "amber"},\n {"alias": "hu_hong", "color": "red"}\n ]\n },\n "so_ngay": {"type": "formula"},\n "tong_tien": {"type": "rollup"}\n },\n "records": {\n "subtitle": ["so_phieu"],\n "figure": "tong_tien",\n "due": ["han_tra"],\n "status": {"field": "trang_thai", "closed": ["da_tra", "huy"], "dates": {"da_tra": "ngay_tra"}}\n }\n }\n ],\n "body": {\n "type": "App",\n "key": "tien_do_thue",\n "name": "Ti\u1EBFn \u0111\u1ED9 cho thu\xEA",\n "icon": "calendar-range",\n "color": "teal",\n "children": [\n {\n "type": "List",\n "entity": "phieu_thue",\n "row": {"title": {"add": true}},\n "columns": ["phu_trach"],\n "filters": ["phu_trach", "khach_hang"],\n "layout": {"gantt": {"start": "ngay_giao"}},\n "create": false,\n "remove": false,\n "children": [\n {\n "type": "Reading",\n "key": "register_reading_0",\n "label": "Phi\u1EBFu \u0111ang cho thu\xEA",\n "where": {"trang_thai": ["dang_thue"]}\n },\n {\n "type": "Reading",\n "key": "register_reading_1",\n "label": "Ti\u1EC1n thu\xEA \u0111ang ch\u1EA1y",\n "value": "tong_tien",\n "where": {"trang_thai": ["dang_thue"]}\n },\n {\n "type": "Record",\n "children": [\n {\n "type": "Section",\n "key": "phieu_thue",\n "title": "Phi\u1EBFu thu\xEA",\n "children": ["ngay_giao", "han_tra"]\n },\n {\n "type": "Section",\n "key": "dang_thue",\n "title": "\u0110ang thu\xEA",\n "at": ["dang_thue"],\n "children": [\n "so_ngay",\n "ngay_tra",\n {\n "type": "Act",\n "key": "nhan_tra",\n "label": "Nh\u1EADn m\xE1y tr\u1EA3",\n "when": {"trang_thai": ["dang_thue"]},\n "asks": ["tinh_trang_tra"],\n "set": {"trang_thai": "da_tra"},\n "confirm": true\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Roster\n\n**The job.** See who works which shift on each day of the week or the month.\n\n**Read.** `layout: "roster"` over the people; the child List (`lich_truc`) is the rows that fill the days \u2014 one link back and a date on its line.\n\n**Keys.** `model/roster`\n\n**Not when.** Rows that are themselves the bookings (a stay over several days) \u2014 a `calendar` or `lanes` draws them; a roster\'s row is what the days are filled for.\n\n```json\n{\n "tables": [\n {\n "ref": "nha_si",\n "title": "ho_ten",\n "fields": {\n "ho_ten": {"type": "text"},\n "chuyen_mon": {\n "type": "select",\n "options": [\n {"alias": "tong_quat", "color": "blue"},\n {"alias": "chinh_nha", "color": "purple"},\n {"alias": "nha_chu", "color": "green"},\n {"alias": "phuc_hinh", "color": "orange"}\n ]\n },\n "dien_thoai": {"type": "text"}\n },\n "records": {"subtitle": ["chuyen_mon"], "party": "person"}\n },\n {\n "ref": "lich_truc",\n "title": "ca",\n "fields": {\n "nha_si": {"type": "link", "table": "nha_si", "multi": false},\n "ca": {\n "type": "select",\n "options": [\n {"alias": "sang", "color": "blue"},\n {"alias": "chieu", "color": "purple"},\n {"alias": "ca_ngay", "color": "green"},\n {"alias": "nghi", "color": "gray"}\n ]\n },\n "ngay": {"type": "date"},\n "ghe": {"type": "link", "table": "ghe", "multi": false}\n },\n "records": {"subtitle": ["ngay", "ghe"]}\n }\n ],\n "body": {\n "type": "App",\n "key": "lich_truc",\n "name": "L\u1ECBch tr\u1EF1c",\n "icon": "users",\n "color": "indigo",\n "children": [\n {\n "type": "List",\n "entity": "nha_si",\n "filters": ["chuyen_mon"],\n "layout": "roster",\n "create": ["ho_ten", "chuyen_mon", "dien_thoai"],\n "children": [\n {\n "type": "List",\n "entity": "lich_truc",\n "row": {"subtitle": ["ngay", {"field": "ghe", "add": ["ten", "phong"]}]},\n "create": true,\n "remove": true\n }\n ]\n }\n ]\n }\n}\n```\n\n## Cards\n\n**The job.** Find an item by its picture, and count what the shelf holds.\n\n**Read.** `layout: "cards"` over rows whose `records.image` is a photo; `scope` keeps the app inside one warehouse at a time.\n\n**Keys.** `model/cards`, `model/records`, `model/app-node`\n\n**Not when.** Rows with no picture \u2014 cards of words are a table drawn worse.\n\n```json\n{\n "tables": [\n {\n "ref": "stock_item",\n "title": "name",\n "fields": {\n "name": {"type": "text"},\n "warehouse": {"type": "link", "table": "warehouse", "multi": false},\n "photo": {"type": "files"},\n "sku": {"type": "text"},\n "category": {\n "type": "select",\n "options": [\n {"alias": "pumps", "color": "blue"},\n {"alias": "valves", "color": "teal"},\n {"alias": "seals", "color": "amber"},\n {"alias": "motors", "color": "violet"}\n ]\n },\n "on_hand": {"type": "number"},\n "minimum": {"type": "number"},\n "price": {"type": "number", "notation": {"style": "currency", "currency": "USD"}},\n "supplier_page": {"type": "text", "format": "link"},\n "last_counted": {"type": "date"},\n "counted_by": {"type": "member"},\n "count_note": {"type": "text"}\n },\n "records": {\n "subtitle": ["sku", "category"],\n "image": "photo",\n "figure": "on_hand",\n "limits": {"on_hand": 400},\n "gates": {"on_hand": "minimum"}\n }\n },\n {\n "ref": "order_line",\n "title": "item",\n "fields": {\n "item": {"type": "link", "table": "stock_item", "multi": false},\n "order": {"type": "link", "table": "order", "multi": false},\n "quantity": {"type": "number"},\n "shipped": {"type": "number"},\n "picked": {"type": "number"},\n "unit_price": {"type": "number", "notation": {"style": "currency", "currency": "USD"}},\n "amount": {"type": "formula", "notation": {"style": "currency", "currency": "USD"}},\n "settled": {"type": "formula"},\n "category": {"type": "lookup"}\n },\n "records": {\n "subtitle": ["order", "unit_price"],\n "figure": "amount",\n "limits": {"shipped": "quantity", "picked": "quantity"}\n }\n }\n ],\n "body": {\n "type": "App",\n "key": "stock",\n "name": "Stock",\n "icon": "boxes",\n "color": "teal",\n "scope": "warehouse",\n "children": [\n {\n "type": "List",\n "entity": "stock_item",\n "columns": ["price"],\n "filters": ["on_hand"],\n "search": ["name", "sku"],\n "layout": "cards",\n "children": [\n {"type": "Reading", "key": "register_reading_0", "value": "on_hand", "by": "category"},\n {"type": "Export", "key": "export"},\n {\n "type": "Record",\n "children": [\n {\n "type": "Section",\n "key": "item",\n "title": "Item",\n "children": ["price", "minimum", "supplier_page"]\n },\n {\n "type": "Section",\n "key": "last_count",\n "title": "Last count",\n "children": [\n "last_counted",\n "counted_by",\n "count_note",\n {\n "type": "Act",\n "key": "count",\n "label": "Count stock",\n "asks": [\n {"input": "counted", "label": "Units on the shelf", "type": "number", "required": true},\n {"input": "remark", "label": "Remark", "type": "long_text"}\n ],\n "set": {\n "on_hand": {"input": "counted"},\n "last_counted": {"now": true},\n "counted_by": {"me": true},\n "count_note": {"input": "remark"}\n }\n }\n ]\n },\n {\n "type": "Section",\n "key": "on_order",\n "title": "On order",\n "children": [\n {\n "type": "List",\n "entity": "order_line",\n "row": {\n "subtitle": [\n {\n "field": "order",\n "add": [{"field": "customer", "add": ["name", "credit_limit"]}, "reference"]\n },\n "unit_price"\n ]\n },\n "columns": ["quantity"],\n "children": [\n {"type": "Check", "field": "settled", "on": ["update"]},\n {"type": "Record", "children": ["item", "order", "quantity", "shipped", "unit_price"]}\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Party\n\n**The job.** Keep each customer account \u2014 who they are, what they ordered, what was said to them.\n\n**Read.** `records.customer.party: "organization"` and its `image` (the logo); a List with `layout: "timeline"` for the touches, a Reading `by` the day placed for its orders, and its papers `expect`ed by kind.\n\n**Keys.** `model/records`, `model/layouts`, `model/list-node`, `model/reading-node`\n\n**Not when.** A list of customers is not a job by itself: the customer is a link on the desk whose rows name it, unless the job is the account itself.\n\n```json\n{\n "tables": [\n {\n "ref": "customer",\n "title": "name",\n "fields": {\n "name": {"type": "text"},\n "logo": {"type": "files"},\n "code": {"type": "text"},\n "tier": {\n "type": "select",\n "options": [\n {"alias": "key", "color": "violet"},\n {"alias": "standard", "color": "sky"},\n {"alias": "lapsed", "color": "gray"}\n ]\n },\n "phone": {"type": "text"},\n "website": {"type": "text", "format": "link"},\n "account_manager": {"type": "member"},\n "credit_limit": {"type": "number", "notation": {"style": "currency", "currency": "USD"}},\n "orders": {"type": "link", "table": "order", "multi": true},\n "outstanding": {"type": "rollup"},\n "over_limit": {"type": "formula"},\n "papers_needed": {\n "type": "select",\n "options": [\n {"alias": "credit_application", "color": "blue"},\n {"alias": "trade_reference", "color": "violet"},\n {"alias": "vat_certificate", "color": "teal"}\n ],\n "multi": true\n },\n "notes": {"type": "text", "format": "markdown"}\n },\n "records": {\n "subtitle": ["tier"],\n "image": "logo",\n "party": "organization",\n "figure": "outstanding",\n "limits": {"outstanding": "credit_limit"}\n }\n },\n {\n "ref": "order",\n "title": "reference",\n "fields": {\n "customer": {"type": "link", "table": "customer", "multi": false},\n "reference": {"type": "text"},\n "stage": {\n "type": "select",\n "options": [\n {"alias": "draft", "color": "slate"},\n {"alias": "confirmed", "color": "blue"},\n {"alias": "picking", "color": "amber"},\n {"alias": "shipped", "color": "indigo"},\n {"alias": "received", "color": "green"},\n {"alias": "cancelled", "color": "rose"}\n ]\n },\n "placed": {"type": "date"},\n "due_date": {"type": "date"},\n "handler": {"type": "member"},\n "priority": {"type": "boolean"},\n "lines": {"type": "link", "table": "order_line", "multi": true},\n "payments": {"type": "link", "table": "payment", "multi": true},\n "total": {"type": "rollup"},\n "balance": {"type": "formula", "notation": {"style": "currency", "currency": "USD"}},\n "units_ordered": {"type": "rollup"},\n "units_shipped": {"type": "rollup"},\n "units_picked": {"type": "rollup"},\n "carrier": {"type": "text"},\n "tracking_no": {"type": "text"},\n "shipped_on": {"type": "date"},\n "cancellation_reason": {"type": "text"},\n "cancelled_by": {"type": "member"},\n "replaced_by": {"type": "link", "table": "order", "multi": false},\n "attachments": {"type": "files"},\n "delivery_note": {"type": "files"},\n "notes": {"type": "text", "format": "markdown"},\n "paid_in_full": {"type": "formula"},\n "has_payments": {"type": "formula"},\n "reminder_ref": {"type": "text"}\n },\n "records": {\n "subtitle": ["customer", "placed"],\n "figure": "total",\n "limits": {"units_shipped": "units_ordered", "units_picked": "units_ordered"},\n "due": ["due_date"],\n "status": {\n "field": "stage",\n "closed": ["received", "cancelled"],\n "dates": {"shipped": "shipped_on"},\n "history": "order_stage"\n }\n }\n },\n {\n "ref": "customer_paper",\n "title": "kind",\n "fields": {\n "kind": {\n "type": "select",\n "options": [\n {"alias": "credit_application", "color": "blue"},\n {"alias": "trade_reference", "color": "violet"},\n {"alias": "vat_certificate", "color": "teal"}\n ]\n },\n "customer": {"type": "link", "table": "customer", "multi": false},\n "received_on": {"type": "date"},\n "file": {"type": "files"}\n },\n "records": {"image": "file"}\n },\n {\n "ref": "touch",\n "title": "said",\n "fields": {\n "said": {"type": "text"},\n "customer": {"type": "link", "table": "customer", "multi": false},\n "on": {"type": "date"},\n "by": {"type": "member"}\n }\n },\n {\n "ref": "service_visit",\n "title": "customer",\n "fields": {\n "customer": {"type": "link", "table": "customer", "multi": false},\n "scheduled_for": {"type": "date", "format": "datetime"},\n "ends_at": {"type": "date", "format": "datetime"},\n "engineer": {"type": "link", "table": "engineer", "multi": false},\n "job": {\n "type": "select",\n "options": [{"alias": "inspection", "color": "sky"}, {"alias": "repair", "color": "orange"}]\n },\n "stage": {\n "type": "select",\n "options": [\n {"alias": "booked_on", "color": "blue"},\n {"alias": "parts_ready_on", "color": "blue"},\n {"alias": "arrived_on", "color": "blue"},\n {"alias": "completed_on", "color": "green"}\n ]\n },\n "booked_on": {"type": "date"},\n "parts_ready_on": {"type": "date"},\n "arrived_on": {"type": "date"},\n "hours": {"type": "number", "notation": {"style": "unit", "unit": "h"}},\n "report": {"type": "text", "format": "markdown"},\n "completed_on": {"type": "date"}\n },\n "records": {\n "subtitle": ["scheduled_for", "ends_at"],\n "figure": "hours",\n "due": ["scheduled_for"],\n "status": {\n "field": "stage",\n "closed": ["completed_on"],\n "dates": {\n "booked_on": "booked_on",\n "parts_ready_on": "parts_ready_on",\n "arrived_on": "arrived_on",\n "completed_on": "completed_on"\n }\n }\n }\n }\n ],\n "body": {\n "type": "App",\n "key": "customers",\n "name": "Customers",\n "icon": "users",\n "color": "violet",\n "children": [\n {\n "type": "List",\n "entity": "customer",\n "columns": ["phone", "account_manager"],\n "filters": ["account_manager"],\n "sort": {"field": "name"},\n "opens": {"tier": ["key", "standard"]},\n "create": ["name", "code", "credit_limit"],\n "children": [\n {"type": "Reading", "key": "register_reading_0", "value": "outstanding", "by": "tier"},\n {"type": "Reading", "key": "register_reading_1", "value": "outstanding", "by": "account_manager"},\n {"type": "Check", "field": "over_limit"},\n {\n "type": "Record",\n "door": "drawer",\n "children": [\n {"type": "Section", "key": "contact", "title": "Contact", "children": ["phone", "website"]},\n {\n "type": "Section",\n "key": "account",\n "title": "Account",\n "children": [\n "code",\n "account_manager",\n "credit_limit",\n {\n "type": "Act",\n "key": "reassign",\n "label": "Reassign",\n "asks": [{"input": "to", "label": "Account manager", "type": "member", "required": true}],\n "set": {"account_manager": {"input": "to"}}\n },\n {\n "type": "Act",\n "key": "lapse",\n "label": "Mark lapsed",\n "when": {"tier": ["key", "standard"]},\n "set": {"tier": "lapsed"},\n "confirm": true\n },\n {\n "type": "Act",\n "key": "reinstate",\n "label": "Reinstate",\n "when": {"tier": ["lapsed"]},\n "asks": [\n {\n "input": "back_to",\n "label": "Back to",\n "type": "select",\n "options": [\n {"alias": "key", "label": "Key account", "color": "violet"},\n {"alias": "standard", "label": "Standard", "color": "sky"}\n ],\n "required": true\n }\n ],\n "set": {"tier": {"input": "back_to"}}\n }\n ]\n },\n {\n "type": "Section",\n "key": "orders",\n "title": "Orders",\n "children": [\n {\n "type": "Reading",\n "key": "block_reading_0",\n "label": "Order value",\n "entity": "order",\n "value": "total",\n "where": {"stage": ["confirmed", "picking", "shipped", "received"]},\n "by": "placed"\n },\n {\n "type": "List",\n "entity": "order",\n "where": {"stage": ["draft", "confirmed", "picking", "shipped"]},\n "label": "Open orders",\n "columns": ["due_date"],\n "create": false,\n "children": [\n {"type": "Check", "field": "paid_in_full", "on": ["update"]},\n {"type": "Check", "field": "has_payments"},\n {\n "type": "Record",\n "children": [\n "due_date",\n "handler",\n {"field": "priority", "edit": false},\n {\n "field": "lines",\n "add": [\n {\n "field": "item",\n "add": ["name", {"field": "warehouse", "add": ["name", "city"]}, "sku", "category"]\n },\n "quantity"\n ]\n },\n {"field": "payments", "add": true},\n {"field": "carrier", "edit": false},\n {"field": "tracking_no", "edit": false},\n {"field": "shipped_on", "edit": false},\n {"field": "cancellation_reason", "edit": false},\n {"field": "cancelled_by", "edit": false},\n {"field": "replaced_by", "edit": false},\n {"field": "attachments", "edit": false},\n "delivery_note",\n "notes",\n {"field": "reminder_ref", "edit": false}\n ]\n }\n ]\n },\n {\n "type": "List",\n "key": "order_2",\n "entity": "order",\n "where": {"stage": ["shipped", "received"], "paid_in_full": false},\n "label": "Shipped, still owed",\n "columns": ["balance"],\n "create": false,\n "children": [{"type": "Record", "children": ["due_date"]}]\n },\n {\n "type": "List",\n "key": "block_reading_3",\n "entity": "service_visit",\n "app": "visits",\n "label": "Service visits",\n "columns": ["engineer", "job"],\n "children": [\n {\n "type": "Act",\n "key": "clear_visit_hours",\n "label": "Clear hours",\n "set": {"hours": null},\n "confirm": true\n },\n {"type": "Record", "edit": false, "children": ["report", "arrived_on", "completed_on"]}\n ]\n }\n ]\n },\n {\n "type": "Section",\n "key": "account_papers",\n "title": "Account papers",\n "children": [\n {\n "type": "List",\n "entity": "customer_paper",\n "expect": {"field": "kind", "of": "papers_needed"},\n "columns": ["received_on"]\n }\n ]\n },\n {\n "type": "Section",\n "key": "contact_log",\n "title": "Contact log",\n "children": [{"type": "List", "entity": "touch", "layout": "timeline"}]\n },\n {"type": "Section", "key": "notes", "title": "Notes", "children": ["notes"]}\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Case\n\n**The job.** Gather an applicant\'s papers into one case, make the forms it needs, and file it on time.\n\n**Read.** Lists with `expect` for the papers brought (one line per kind, the missing ones empty), an Act\'s `templates` for the forms made (ticked and made at once, kept in `into`), and `intake` reading the papers brought.\n\n**Keys.** `model/list-node`, `model/act-node`\n\n**Not when.** A record that needs no papers, or one paper made once \u2014 a single `template` act.\n\n```json\n{\n "tables": [\n {\n "ref": "ho_so",\n "title": "khach_hang",\n "fields": {\n "khach_hang": {"type": "link", "table": "khach_hang", "multi": false},\n "du_an": {"type": "link", "table": "du_an", "multi": false},\n "ma_ho_so": {"type": "autonumber"},\n "dich_vu": {\n "type": "select",\n "options": [{"alias": "tron_goi", "color": "blue"}, {"alias": "lam_ho_so", "color": "violet"}]\n },\n "doi_tuong": {\n "type": "select",\n "options": [\n {"alias": "thu_nhap_thap", "color": "sky"},\n {"alias": "cong_nhan", "color": "teal"},\n {"alias": "can_bo", "color": "indigo"},\n {"alias": "luc_luong", "color": "green"},\n {"alias": "ho_ngheo", "color": "amber"},\n {"alias": "nguoi_co_cong", "color": "rose"}\n ]\n },\n "tinh_trang_nha_o": {\n "type": "select",\n "options": [{"alias": "chua_co_nha", "color": "gray"}, {"alias": "nha_chat", "color": "orange"}]\n },\n "giay_chung_nhan_so": {"type": "text"},\n "dien_tich_nha": {"type": "number"},\n "sale": {"type": "member"},\n "xu_ly": {"type": "member"},\n "can_bo_sung": {"type": "boolean"},\n "phi_dich_vu": {"type": "number", "notation": {"style": "currency", "currency": "VND"}},\n "loai_can": {\n "type": "select",\n "options": [\n {"alias": "studio", "color": "sky"},\n {"alias": "mot_pn", "color": "blue"},\n {"alias": "hai_pn", "color": "indigo"},\n {"alias": "ba_pn", "color": "violet"}\n ]\n },\n "giai_doan": {\n "type": "select",\n "options": [\n {"alias": "ngay_thu_phi", "color": "blue"},\n {"alias": "ngay_nhan_giay_to", "color": "blue"},\n {"alias": "ngay_lap_ho_so", "color": "blue"},\n {"alias": "ngay_nop_cdt", "color": "blue"},\n {"alias": "ngay_cdt_duyet", "color": "blue"},\n {"alias": "ngay_so_xd_duyet", "color": "blue"},\n {"alias": "ngay_boc_tham", "color": "blue"},\n {"alias": "ngay_ky_hdmb", "color": "green"},\n {"alias": "ngay_ban_giao", "color": "green"}\n ]\n },\n "ngay_thu_phi": {"type": "date"},\n "ngay_nhan_giay_to": {"type": "date"},\n "ngay_lap_ho_so": {"type": "date"},\n "ngay_nop_cdt": {"type": "date"},\n "ngay_cdt_duyet": {"type": "date"},\n "ngay_so_xd_duyet": {"type": "date"},\n "ngay_boc_tham": {"type": "date"},\n "ngay_ky_hdmb": {"type": "date"},\n "ngay_ban_giao": {"type": "date"},\n "bo_ho_so": {"type": "files"},\n "giay_to": {"type": "link", "table": "giay_to", "multi": true},\n "thanh_vien": {"type": "link", "table": "thanh_vien", "multi": true},\n "phieu_thu": {"type": "link", "table": "phieu_thu", "multi": true},\n "dien_thoai": {"type": "lookup"},\n "cccd": {"type": "lookup"},\n "ngay_sinh": {"type": "lookup"},\n "dia_chi": {"type": "lookup"},\n "da_thu": {"type": "rollup"},\n "chua_thu_phi": {"type": "formula"},\n "con_no_phi": {"type": "formula"},\n "vuot_thu_nhap": {"type": "formula"},\n "da_hoan_tat": {"type": "formula"}\n },\n "records": {\n "subtitle": ["dich_vu"],\n "figure": "phi_dich_vu",\n "status": {\n "field": "giai_doan",\n "closed": ["ngay_ky_hdmb", "ngay_ban_giao"],\n "dates": {\n "ngay_thu_phi": "ngay_thu_phi",\n "ngay_nhan_giay_to": "ngay_nhan_giay_to",\n "ngay_lap_ho_so": "ngay_lap_ho_so",\n "ngay_nop_cdt": "ngay_nop_cdt",\n "ngay_cdt_duyet": "ngay_cdt_duyet",\n "ngay_so_xd_duyet": "ngay_so_xd_duyet",\n "ngay_boc_tham": "ngay_boc_tham",\n "ngay_ky_hdmb": "ngay_ky_hdmb",\n "ngay_ban_giao": "ngay_ban_giao"\n }\n }\n }\n },\n {\n "ref": "thanh_vien",\n "title": "ho_ten",\n "fields": {\n "ho_ten": {"type": "text"},\n "ho_so": {"type": "link", "table": "ho_so", "multi": false},\n "quan_he": {\n "type": "select",\n "options": [\n {"alias": "nguoi_dung_don", "color": "blue"},\n {"alias": "vo_chong", "color": "violet"},\n {"alias": "con", "color": "teal"},\n {"alias": "bo_me", "color": "amber"},\n {"alias": "khac", "color": "gray"}\n ]\n },\n "ngay_sinh": {"type": "date"},\n "cccd": {"type": "text"},\n "thu_nhap": {"type": "number", "notation": {"style": "currency", "currency": "VND"}}\n },\n "records": {"subtitle": ["quan_he", "cccd"], "party": "person", "figure": "thu_nhap"}\n },\n {\n "ref": "giay_to",\n "title": "loai",\n "fields": {\n "loai": {\n "type": "select",\n "options": [\n {"alias": "don_dang_ky", "color": "blue"},\n {"alias": "can_cuoc", "color": "sky"},\n {"alias": "giay_doi_tuong", "color": "violet"},\n {"alias": "xac_nhan_nha_o", "color": "teal"},\n {"alias": "xac_nhan_thu_nhap", "color": "amber"},\n {"alias": "hon_nhan", "color": "indigo"}\n ]\n },\n "ho_so": {"type": "link", "table": "ho_so", "multi": false},\n "ngay_nhan": {"type": "date"},\n "tep": {"type": "files"},\n "phai_sua": {"type": "formula"}\n },\n "records": {"image": "tep", "starts": {"ngay_nhan": "today"}}\n },\n {\n "ref": "phieu_thu",\n "title": "khoan",\n "fields": {\n "khoan": {\n "type": "select",\n "options": [\n {"alias": "dat_coc", "color": "blue"},\n {"alias": "tat_toan", "color": "indigo"},\n {"alias": "phi_ho_so", "color": "violet"}\n ]\n },\n "ho_so": {"type": "link", "table": "ho_so", "multi": false},\n "so_tien": {"type": "number", "notation": {"style": "currency", "currency": "VND"}},\n "hinh_thuc": {\n "type": "select",\n "options": [\n {"alias": "tien_mat", "color": "green", "mark": {"kind": "icon", "name": "banknote"}},\n {"alias": "chuyen_khoan", "color": "blue", "mark": {"kind": "icon", "name": "arrow-right-left"}}\n ]\n },\n "trang_thai": {\n "type": "select",\n "options": [\n {"alias": "cho_thu", "color": "amber"},\n {"alias": "da_thu", "color": "green"},\n {"alias": "huy", "color": "gray"}\n ]\n },\n "ngay_thu": {"type": "date"}\n },\n "records": {\n "subtitle": ["hinh_thuc", "ngay_thu"],\n "figure": "so_tien",\n "status": {"field": "trang_thai", "closed": ["da_thu", "huy"]}\n }\n }\n ],\n "templates": [\n {"alias": "don_dang_ky", "type": "html", "label": "\u0110\u01A1n \u0111\u0103ng k\xFD mua nh\xE0 \u1EDF x\xE3 h\u1ED9i"},\n {"alias": "mau_02_chua_co_nha", "type": "html", "label": "M\u1EABu 02 \u2014 X\xE1c nh\u1EADn ch\u01B0a c\xF3 nh\xE0 \u1EDF"},\n {"alias": "mau_03_nha_chat", "type": "html", "label": "M\u1EABu 03 \u2014 X\xE1c nh\u1EADn nh\xE0 \u1EDF d\u01B0\u1EDBi 15 m\xB2/ng\u01B0\u1EDDi"},\n {"alias": "mau_05_thu_nhap", "type": "html", "label": "M\u1EABu 05 \u2014 X\xE1c nh\u1EADn thu nh\u1EADp"},\n {"alias": "danh_sach_nop", "type": "html", "label": "Danh s\xE1ch h\u1ED3 s\u01A1 n\u1ED9p ch\u1EE7 \u0111\u1EA7u t\u01B0"}\n ],\n "body": {\n "type": "App",\n "key": "ho_so",\n "name": "H\u1ED3 s\u01A1 nh\xE0 \u1EDF x\xE3 h\u1ED9i",\n "icon": "folder-open",\n "color": "blue",\n "scope": "du_an",\n "children": [\n {\n "type": "List",\n "entity": "ho_so",\n "row": {"title": {"add": true}},\n "columns": ["xu_ly", "can_bo_sung", "doi_tuong"],\n "filters": ["xu_ly", "can_bo_sung"],\n "search": ["khach_hang", "ma_ho_so", "dien_thoai"],\n "create": ["khach_hang", "dich_vu", "doi_tuong", "tinh_trang_nha_o", "loai_can", "phi_dich_vu", "sale"],\n "children": [\n {\n "type": "Reading",\n "key": "register_reading_0",\n "label": "Ch\u1EDD kh\xE1ch b\u1ED5 sung",\n "where": {"can_bo_sung": true}\n },\n {\n "type": "Reading",\n "key": "register_reading_1",\n "label": "Ph\xED \u0111\xE3 thu",\n "value": "da_thu",\n "over": "ngay_nhan_giay_to"\n },\n {"type": "Reading", "key": "register_reading_2", "by": "doi_tuong"},\n {"type": "Export", "key": "export"},\n {"type": "Check", "field": "chua_thu_phi", "on": ["nop_cdt"]},\n {"type": "Check", "field": "con_no_phi"},\n {"type": "Check", "field": "vuot_thu_nhap"},\n {"type": "Check", "field": "da_hoan_tat", "on": ["doc_giay_to"]},\n {\n "type": "Record",\n "children": [\n {\n "type": "Section",\n "key": "nguoi_dung_don",\n "title": "Ng\u01B0\u1EDDi \u0111\u1EE9ng \u0111\u01A1n",\n "children": [\n "ma_ho_so",\n "dien_thoai",\n "cccd",\n "ngay_sinh",\n "dia_chi",\n "doi_tuong",\n "tinh_trang_nha_o",\n "sale",\n "xu_ly",\n "can_bo_sung",\n "ngay_ky_hdmb",\n "ngay_ban_giao",\n {\n "type": "List",\n "entity": "thanh_vien",\n "label": "H\u1ED9 gia \u0111\xECnh",\n "columns": ["ngay_sinh"],\n "create": ["ho_ten", "quan_he", "ngay_sinh", "cccd", "thu_nhap"]\n },\n {\n "type": "List",\n "entity": "giay_to",\n "expect": {"field": "loai", "only": ["can_cuoc", "hon_nhan"]},\n "label": "Gi\u1EA5y t\u1EDD c\xE1 nh\xE2n",\n "columns": ["ngay_nhan"],\n "children": [{"type": "Check", "field": "phai_sua"}]\n },\n {\n "type": "Act",\n "key": "doc_giay_to",\n "label": "\u0110\u1ECDc gi\u1EA5y t\u1EDD",\n "intake": "giay_to",\n "fills": [\n "doi_tuong",\n "tinh_trang_nha_o",\n "giay_chung_nhan_so",\n "dien_tich_nha",\n "khach_hang.ho_ten",\n "khach_hang.cccd",\n "khach_hang.ngay_sinh",\n "khach_hang.dia_chi",\n "thanh_vien"\n ]\n },\n {\n "type": "Act",\n "key": "giao_xu_ly",\n "label": "Giao ng\u01B0\u1EDDi x\u1EED l\xFD",\n "asks": [{"input": "nguoi", "label": "Ng\u01B0\u1EDDi x\u1EED l\xFD", "type": "member", "required": true}],\n "set": {"xu_ly": {"input": "nguoi"}}\n },\n {\n "type": "Act",\n "key": "chuyen_du_an",\n "label": "Chuy\u1EC3n d\u1EF1 \xE1n",\n "asks": [\n {\n "input": "du_an_moi",\n "label": "Sang d\u1EF1 \xE1n",\n "type": "link",\n "entity": "du_an",\n "required": true,\n "add": ["ten_du_an", "chu_dau_tu", "so_can"]\n }\n ],\n "set": {"du_an": {"input": "du_an_moi"}},\n "confirm": true\n }\n ]\n },\n {\n "type": "Section",\n "key": "bo_ho_so",\n "title": "B\u1ED9 h\u1ED3 s\u01A1",\n "at": ["ngay_nhan_giay_to", "ngay_lap_ho_so"],\n "children": [\n "ngay_nhan_giay_to",\n "ngay_lap_ho_so",\n {\n "type": "Act",\n "key": "tao_giay_to",\n "label": "T\u1EA1o gi\u1EA5y t\u1EDD",\n "templates": [\n {"template": "don_dang_ky"},\n {"template": "mau_02_chua_co_nha", "when": {"tinh_trang_nha_o": ["chua_co_nha"]}},\n {"template": "mau_03_nha_chat", "when": {"tinh_trang_nha_o": ["nha_chat"]}},\n {"template": "mau_05_thu_nhap", "when": {"doi_tuong": ["thu_nhap_thap"]}}\n ],\n "into": "bo_ho_so",\n "when": {"giai_doan": ["ngay_nhan_giay_to", "ngay_lap_ho_so"]}\n },\n {\n "type": "Act",\n "key": "nop_cdt",\n "label": "N\u1ED9p ch\u1EE7 \u0111\u1EA7u t\u01B0",\n "when": {"dich_vu": ["tron_goi"], "giai_doan": ["ngay_lap_ho_so"]},\n "requires": ["bo_ho_so", "loai_can"],\n "asks": ["loai_can"],\n "set": {"giai_doan": "ngay_nop_cdt"},\n "confirm": true\n },\n {\n "type": "Act",\n "key": "to_ngay_nhan_giay_to",\n "label": "\u0110\xE3 nh\u1EADn gi\u1EA5y t\u1EDD",\n "when": {"giai_doan": {"empty": true}, "dich_vu": ["tron_goi"]},\n "asks": [{"field": "ngay_nhan_giay_to", "starts": "today"}],\n "set": {"giai_doan": "ngay_nhan_giay_to"}\n },\n {\n "type": "Act",\n "key": "to_ngay_lap_ho_so",\n "label": "\u0110\xE3 l\u1EADp h\u1ED3 s\u01A1",\n "when": {"giai_doan": ["ngay_nhan_giay_to"], "dich_vu": ["tron_goi"]},\n "asks": [{"field": "ngay_lap_ho_so", "starts": "today"}],\n "set": {"giai_doan": "ngay_lap_ho_so"}\n },\n {\n "type": "Act",\n "key": "to_ngay_ban_giao",\n "label": "\u0110\xE3 b\xE0n giao h\u1ED3 s\u01A1",\n "when": {"giai_doan": ["ngay_nhan_giay_to"], "dich_vu": ["lam_ho_so"]},\n "asks": [{"field": "ngay_ban_giao", "starts": "today"}],\n "set": {"giai_doan": "ngay_ban_giao"}\n }\n ]\n }\n ]\n },\n {\n "type": "Act",\n "key": "in_danh_sach",\n "label": "In danh s\xE1ch n\u1ED9p ch\u1EE7 \u0111\u1EA7u t\u01B0",\n "on": "view",\n "template": "danh_sach_nop"\n }\n ]\n }\n ]\n }\n}\n```\n\n## Calls\n\n**The job.** Call a lead from their record, and keep what was said as a touch on its timeline.\n\n**Read.** An Act\'s `record` (into the `timeline` child: audio, transcript, when and who) and `fills` (the touch\'s fields an agent fills from the transcript).\n\n**Keys.** `model/act-node`, `model/layouts`\n\n**Not when.** Calls made and logged elsewhere \u2014 a `timeline` with its own add keeps the note.\n\n```json\n{\n "tables": [\n {\n "ref": "khach_tiem_nang",\n "title": "ho_ten",\n "fields": {\n "ho_ten": {"type": "text"},\n "ten_cong_ty": {"type": "text"},\n "vai_tro": {\n "type": "select",\n "options": [\n {"alias": "chu_doanh_nghiep", "color": "emerald"},\n {"alias": "ke_toan_truong", "color": "blue"},\n {"alias": "quan_ly", "color": "teal"},\n {"alias": "nhan_vien", "color": "sky"},\n {"alias": "chua_ro", "color": "zinc"}\n ]\n },\n "loai_hinh": {\n "type": "select",\n "options": [\n {"alias": "thuong_mai", "color": "blue"},\n {"alias": "san_xuat", "color": "amber"},\n {"alias": "dich_vu", "color": "violet"},\n {"alias": "xay_dung", "color": "orange"},\n {"alias": "ho_kinh_doanh", "color": "teal"},\n {"alias": "chua_ro", "color": "zinc"}\n ]\n },\n "quy_mo": {\n "type": "select",\n "options": [\n {"alias": "q_1_10", "color": "sky"},\n {"alias": "q_11_50", "color": "blue"},\n {"alias": "q_51_200", "color": "indigo"},\n {"alias": "q_200", "color": "violet"},\n {"alias": "chua_ro", "color": "zinc"}\n ]\n },\n "so_dien_thoai": {"type": "text"},\n "zalo": {"type": "text"},\n "email": {"type": "text"},\n "trang_ca_nhan": {"type": "text", "format": "link"},\n "anh": {"type": "files"},\n "trang_thai": {\n "type": "select",\n "options": [\n {"alias": "moi", "color": "gray"},\n {"alias": "du_dieu_kien", "color": "blue"},\n {"alias": "da_lien_he", "color": "amber"},\n {"alias": "da_chuyen_doi", "color": "emerald"},\n {"alias": "loai", "color": "red"}\n ]\n },\n "ly_do_loai": {\n "type": "select",\n "options": [\n {"alias": "khong_phu_hop", "color": "orange"},\n {"alias": "qua_nho", "color": "amber"},\n {"alias": "chua_co_ngan_sach", "color": "yellow"},\n {"alias": "sai_nguoi", "color": "blue"},\n {"alias": "yeu_cau_dung", "color": "red"},\n {"alias": "trung", "color": "zinc"}\n ]\n },\n "vi_sao_du_dieu_kien": {"type": "text"},\n "phu_trach": {"type": "member"},\n "nguon": {"type": "link", "table": "kenh", "multi": false},\n "lien_he": {"type": "link", "table": "lien_he", "multi": false},\n "cong_ty": {"type": "link", "table": "cong_ty", "multi": false},\n "ngay_chuyen_doi": {"type": "date", "format": "datetime", "timezone": "Asia/Ho_Chi_Minh"},\n "ghi_chu": {"type": "text", "format": "markdown"},\n "tin_hieu": {"type": "link", "table": "tin_hieu", "multi": true},\n "buoc_tiep": {"type": "link", "table": "buoc_tiep", "multi": true},\n "tuong_tac": {"type": "link", "table": "tuong_tac", "multi": true},\n "do_nong": {\n "type": "formula",\n "options": [\n {"alias": "nong", "label": "N\xF3ng", "color": "orange"},\n {"alias": "nguoi", "label": "Ngu\u1ED9i", "color": "sky"}\n ]\n },\n "han_buoc_tiep": {"type": "rollup"},\n "khong_lien_he": {"type": "formula"}\n },\n "records": {\n "subtitle": ["ten_cong_ty"],\n "image": "anh",\n "party": "person",\n "due": ["han_buoc_tiep"],\n "status": {\n "field": "trang_thai",\n "closed": ["da_chuyen_doi", "loai"],\n "dates": {"da_chuyen_doi": "ngay_chuyen_doi"}\n }\n }\n },\n {\n "ref": "tin_hieu",\n "title": "tom_tat",\n "fields": {\n "tom_tat": {"type": "text"},\n "noi_dung": {"type": "text", "format": "markdown"},\n "anh_chup": {"type": "files"},\n "lien_ket": {"type": "text", "format": "link"},\n "kenh": {"type": "link", "table": "kenh", "multi": false},\n "loai": {\n "type": "select",\n "options": [\n {"alias": "hoi_dich_vu", "color": "emerald"},\n {"alias": "than_phien", "color": "blue"},\n {"alias": "tuyen_ke_toan", "color": "amber"},\n {"alias": "moi_thanh_lap", "color": "violet"},\n {"alias": "gioi_thieu", "color": "teal"},\n {"alias": "danh_ba", "color": "zinc"},\n {"alias": "nhieu", "color": "stone"}\n ]\n },\n "ngay_dang": {"type": "date", "timezone": "Asia/Ho_Chi_Minh"},\n "ngay_xay_ra": {"type": "formula"}\n },\n "records": {"subtitle": ["kenh", "ngay_xay_ra"], "image": "anh_chup"}\n },\n {\n "ref": "buoc_tiep",\n "title": "vi_sao_luc_nay",\n "fields": {\n "vi_sao_luc_nay": {"type": "text"},\n "viec": {\n "type": "select",\n "options": [\n {"alias": "binh_luan", "color": "blue", "mark": {"kind": "icon", "name": "message-square"}},\n {"alias": "nhan_facebook", "color": "violet", "mark": {"kind": "brand", "name": "facebook"}},\n {"alias": "nhan_zalo", "color": "sky", "mark": {"kind": "brand", "name": "zalo"}},\n {"alias": "goi_dien", "color": "teal", "mark": {"kind": "icon", "name": "phone"}},\n {"alias": "gui_email", "color": "indigo", "mark": {"kind": "icon", "name": "mail"}},\n {"alias": "gui_bang_gia", "color": "amber", "mark": {"kind": "icon", "name": "receipt"}}\n ]\n },\n "ban_nhap": {"type": "text", "format": "markdown"},\n "han": {"type": "date", "format": "datetime", "timezone": "Asia/Ho_Chi_Minh"},\n "tinh_trang": {\n "type": "select",\n "options": [\n {"alias": "cho", "color": "blue"},\n {"alias": "xong", "color": "emerald"},\n {"alias": "bo_qua", "color": "zinc"}\n ]\n }\n },\n "records": {\n "subtitle": ["viec"],\n "mark": "viec",\n "due": ["han"],\n "status": {"field": "tinh_trang", "closed": ["xong", "bo_qua"]}\n }\n },\n {\n "ref": "tuong_tac",\n "title": "tom_tat",\n "fields": {\n "tom_tat": {"type": "text"},\n "thoi_diem": {"type": "date", "format": "datetime", "timezone": "Asia/Ho_Chi_Minh"},\n "qua": {\n "type": "select",\n "options": [\n {"alias": "goi_dien", "color": "teal", "mark": {"kind": "icon", "name": "phone"}},\n {"alias": "zalo", "color": "sky", "mark": {"kind": "brand", "name": "zalo"}},\n {"alias": "facebook", "color": "blue", "mark": {"kind": "brand", "name": "facebook"}},\n {"alias": "email", "color": "indigo", "mark": {"kind": "icon", "name": "mail"}},\n {"alias": "gap_truc_tiep", "color": "emerald", "mark": {"kind": "icon", "name": "users"}},\n {"alias": "khac", "color": "zinc", "mark": {"kind": "icon", "name": "ellipsis"}}\n ]\n },\n "phan_hoi": {\n "type": "select",\n "options": [\n {"alias": "dang_cho", "color": "blue"},\n {"alias": "da_tra_loi", "color": "green"},\n {"alias": "quan_tam", "color": "amber"},\n {"alias": "khong_phu_hop", "color": "red"},\n {"alias": "khong_phan_hoi", "color": "zinc"},\n {"alias": "sai_so", "color": "stone"}\n ]\n },\n "ghi_am": {"type": "files"},\n "nguyen_van": {"type": "text", "format": "markdown"},\n "hen_lai": {"type": "date", "timezone": "Asia/Ho_Chi_Minh"},\n "nguoi_thuc_hien": {"type": "member"}\n },\n "records": {"subtitle": ["qua", "phan_hoi"]}\n }\n ],\n "body": {\n "type": "App",\n "key": "khach_tiem_nang",\n "name": "Kh\xE1ch ti\u1EC1m n\u0103ng",\n "icon": "radar",\n "color": "violet",\n "children": [\n {\n "type": "List",\n "entity": "khach_tiem_nang",\n "columns": ["phu_trach", "han_buoc_tiep"],\n "filters": ["phu_trach"],\n "create": [\n "ho_ten",\n "trang_ca_nhan",\n "so_dien_thoai",\n "ten_cong_ty",\n {"field": "nguon", "add": ["ten", "loai"]}\n ],\n "children": [\n {"type": "Reading", "key": "register_reading_0", "by": "phu_trach"},\n {\n "type": "Reading",\n "key": "register_reading_1",\n "label": "Kh\xE1ch chuy\u1EC3n \u0111\u1ED5i",\n "by": "ngay_chuyen_doi"\n },\n {"type": "Check", "field": "khong_lien_he", "resolve": "da_lien_he"},\n {\n "type": "Record",\n "children": [\n {\n "type": "Section",\n "key": "danh_gia",\n "title": "\u0110\xE1nh gi\xE1",\n "at": ["moi"],\n "children": [\n "vai_tro",\n "loai_hinh",\n "quy_mo",\n "vi_sao_du_dieu_kien",\n "do_nong",\n {\n "type": "List",\n "entity": "tin_hieu",\n "label": "T\xEDn hi\u1EC7u",\n "columns": ["loai", "noi_dung"],\n "create": ["tom_tat", "anh_chup", "loai", "noi_dung", "kenh", "lien_ket", "ngay_dang"]\n },\n {\n "type": "Act",\n "key": "danh_gia_dat",\n "label": "\u0110\u1EE7 \u0111i\u1EC1u ki\u1EC7n",\n "when": {"trang_thai": ["moi"]},\n "requires": ["vi_sao_du_dieu_kien"],\n "set": {"trang_thai": "du_dieu_kien"}\n }\n ]\n },\n {\n "type": "Section",\n "key": "lien_he",\n "title": "Li\xEAn h\u1EC7",\n "at": ["du_dieu_kien"],\n "children": [\n "so_dien_thoai",\n "phu_trach",\n "trang_ca_nhan",\n "zalo",\n "email",\n {"field": "ghi_chu", "block": false},\n {\n "type": "List",\n "entity": "buoc_tiep",\n "label": "B\u01B0\u1EDBc ti\u1EBFp theo",\n "columns": ["ban_nhap"],\n "create": ["vi_sao_luc_nay", "viec", "han", "ban_nhap"],\n "children": [\n {\n "type": "Act",\n "key": "lam_xong",\n "label": "\u0110\xE3 l\xE0m",\n "when": {"tinh_trang": ["cho"]},\n "set": {"tinh_trang": "xong"}\n }\n ]\n },\n {\n "type": "List",\n "entity": "tuong_tac",\n "label": "T\u01B0\u01A1ng t\xE1c",\n "layout": "timeline",\n "create": ["tom_tat", "qua", "phan_hoi", "thoi_diem", "hen_lai"]\n },\n {\n "type": "Act",\n "key": "ghi_cuoc_goi",\n "label": "Ghi \xE2m cu\u1ED9c g\u1ECDi",\n "when": {"trang_thai": ["du_dieu_kien"]},\n "fills": ["tom_tat", "phan_hoi"],\n "record": {\n "into": "tuong_tac",\n "audio": "ghi_am",\n "transcript": "nguyen_van",\n "at": "thoi_diem",\n "by": "nguoi_thuc_hien"\n }\n },\n {\n "type": "Act",\n "key": "da_lien_he",\n "label": "\u0110\xE1nh d\u1EA5u \u0111\xE3 li\xEAn h\u1EC7",\n "when": {"trang_thai": ["du_dieu_kien"]},\n "set": {"trang_thai": "da_lien_he"}\n }\n ]\n },\n {\n "type": "Section",\n "key": "chuyen_doi",\n "title": "Chuy\u1EC3n \u0111\u1ED5i",\n "at": ["da_lien_he"],\n "children": [\n "nguon",\n {"field": "lien_he", "edit": false},\n {"field": "cong_ty", "edit": false},\n {\n "type": "Act",\n "key": "chuyen_doi",\n "label": "Chuy\u1EC3n \u0111\u1ED5i",\n "when": {"trang_thai": ["da_lien_he"]},\n "workflow": "chuyen_doi_khach",\n "confirm": true\n }\n ]\n },\n {\n "type": "Section",\n "key": "loai",\n "title": "Lo\u1EA1i",\n "at": ["loai"],\n "children": [\n "ly_do_loai",\n {\n "type": "Act",\n "key": "loai",\n "label": "Lo\u1EA1i",\n "when": {"trang_thai": ["moi", "du_dieu_kien", "da_lien_he"]},\n "asks": ["ly_do_loai"],\n "set": {"trang_thai": "loai"},\n "danger": true\n }\n ]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Approval\n\n**The job.** Raise a payment request, see it through approval, and read what is waiting at the top of the list.\n\n**Read.** `records.de_nghi.status` with its `history` (each move: when and who), Sections staged by `at`, and the List\'s Readings leading with the headline number. The history stays because the business reads who signed each move; where only the day a stage was reached matters, the status stamps a date on the row instead (the Gantt above).\n\n**Keys.** `model/status`, `model/section-node`, `model/reading-node`\n\n**Not when.** Rows that never move \u2014 a reference list (a price list, a catalog) has no status and no headline number.\n\n```json\n{\n "tables": [\n {\n "ref": "de_nghi",\n "title": "noi_dung",\n "fields": {\n "noi_dung": {"type": "text"},\n "so_de_nghi": {"type": "text"},\n "loai": {\n "type": "select",\n "options": [\n {"alias": "thanh_toan_ncc", "color": "violet"},\n {"alias": "nhan_cong", "color": "indigo"},\n {"alias": "chi_phi_cong_truong", "color": "sky"},\n {"alias": "tam_ung", "color": "amber"},\n {"alias": "hoan_ung", "color": "teal"}\n ]\n },\n "du_an": {"type": "link", "table": "du_an", "multi": false},\n "nha_cung_cap": {"type": "link", "table": "nha_cung_cap", "multi": false},\n "so_tien": {"type": "number", "notation": {"style": "currency", "currency": "VND"}},\n "han_thanh_toan": {"type": "date"},\n "trang_thai": {\n "type": "select",\n "options": [\n {"alias": "nhap", "color": "slate"},\n {"alias": "cho_duyet", "color": "amber"},\n {"alias": "cho_chu_tich", "color": "violet"},\n {"alias": "da_duyet", "color": "blue"},\n {"alias": "da_chi", "color": "green"},\n {"alias": "tu_choi", "color": "rose"}\n ]\n },\n "nguoi_de_nghi": {"type": "member"},\n "chung_tu": {"type": "files"},\n "nguoi_duyet": {"type": "member"},\n "duyet_luc": {"type": "date", "format": "datetime"},\n "ly_do_tu_choi": {"type": "text"},\n "phieu_chi": {"type": "files"},\n "ngan_sach_du_an": {"type": "lookup"},\n "da_duyet_du_an": {"type": "lookup"},\n "vuot_ngan_sach": {"type": "formula"},\n "sat_ngan_sach": {"type": "formula"},\n "da_gui": {"type": "formula"},\n "nhap_at": {"type": "date", "format": "datetime"},\n "cho_duyet_at": {"type": "date", "format": "datetime"},\n "cho_chu_tich_at": {"type": "date", "format": "datetime"},\n "da_duyet_at": {"type": "date", "format": "datetime"}\n },\n "records": {\n "subtitle": ["du_an"],\n "figure": "so_tien",\n "limits": {"da_duyet_du_an": "ngan_sach_du_an"},\n "due": ["han_thanh_toan"],\n "starts": {"nguoi_de_nghi": "me"},\n "status": {\n "field": "trang_thai",\n "closed": ["da_chi", "tu_choi"],\n "dropped": ["tu_choi"],\n "dates": {\n "nhap": "nhap_at",\n "cho_duyet": "cho_duyet_at",\n "cho_chu_tich": "cho_chu_tich_at",\n "da_duyet": "da_duyet_at"\n },\n "history": "lich_su_duyet",\n "stages": {\n "nhap": {"owner": "nguoi_de_nghi"},\n "cho_duyet": {"owner": {"group": "<id of the giam_doc group>"}, "within": 1},\n "cho_chu_tich": {\n "owner": {"group": "<id of the chu_tich group>"},\n "within": 2,\n "when": {"so_tien": {"gt": 200000000}}\n },\n "da_duyet": {"owner": {"group": "<id of the ke_toan group>"}, "within": 2}\n }\n }\n }\n }\n ],\n "body": {\n "type": "App",\n "key": "de_nghi_chi",\n "name": "\u0110\u1EC1 ngh\u1ECB thanh to\xE1n",\n "icon": "hand-coins",\n "color": "amber",\n "children": [\n {\n "type": "List",\n "entity": "de_nghi",\n "row": {"subtitle": [{"field": "du_an", "add": ["ten", "ma_du_an", "chu_dau_tu", "ngan_sach"]}]},\n "columns": ["nguoi_de_nghi"],\n "filters": ["nguoi_de_nghi", "du_an"],\n "group": "du_an",\n "create": [\n "noi_dung",\n "loai",\n "du_an",\n {"field": "nha_cung_cap", "add": true},\n "so_tien",\n "han_thanh_toan",\n "nguoi_de_nghi"\n ],\n "children": [\n {\n "type": "Reading",\n "key": "register_reading_0",\n "label": "\u0110\xE3 duy\u1EC7t, ch\u1EDD chi",\n "value": "so_tien",\n "where": {"trang_thai": ["da_duyet"]}\n },\n {"type": "Reading", "key": "register_reading_1", "value": "so_tien", "by": "han_thanh_toan"},\n {"type": "Check", "field": "da_gui", "on": ["update"]},\n {"type": "Check", "field": "vuot_ngan_sach"},\n {"type": "Check", "field": "sat_ngan_sach"},\n {\n "type": "Record",\n "door": "drawer",\n "children": [\n {\n "type": "Section",\n "key": "de_nghi",\n "title": "\u0110\u1EC1 ngh\u1ECB",\n "at": ["nhap"],\n "children": [\n "so_de_nghi",\n "loai",\n "nha_cung_cap",\n "han_thanh_toan",\n "nguoi_de_nghi",\n {"field": "chung_tu", "label": "Ch\u1EE9ng t\u1EEB k\xE8m"},\n {\n "type": "Act",\n "key": "gui_duyet",\n "label": "G\u1EEDi duy\u1EC7t",\n "when": {"trang_thai": ["nhap"]},\n "requires": ["chung_tu", "nguoi_de_nghi"],\n "set": {"trang_thai": "cho_duyet"}\n }\n ]\n },\n {\n "type": "Section",\n "key": "duyet",\n "title": "Duy\u1EC7t",\n "at": ["cho_duyet", "cho_chu_tich"],\n "children": [\n {"field": "nguoi_duyet", "edit": false},\n {"field": "duyet_luc", "edit": false},\n {"field": "ly_do_tu_choi", "edit": false},\n {\n "type": "Act",\n "key": "rut_lai",\n "label": "R\xFAt l\u1EA1i",\n "when": {"trang_thai": ["cho_duyet", "cho_chu_tich"]},\n "set": {"trang_thai": "nhap"},\n "confirm": true\n }\n ]\n },\n {\n "type": "Section",\n "key": "da_chi",\n "title": "\u0110\xE3 chi",\n "at": ["da_chi"],\n "children": [{"field": "phieu_chi", "label": "Phi\u1EBFu chi", "edit": false}]\n }\n ]\n }\n ]\n }\n ]\n }\n}\n```\n\n## Dashboard\n\n**The job.** The owner\'s question \u2014 what came in, what went out, where it went, and what is still unmatched \u2014 answered over one period.\n\n**Read.** An App of Readings and no List: Readings with `over` (the period windows them), a Reading `by` the day, Readings `by` a field, and a List of rows to act on that opens in its `app`.\n\n**Keys.** `model/app-node`, `model/reading-node`\n\n**Not when.** One job\'s headline numbers \u2014 those lead that job\'s List as its Readings.\n\n```json\n{\n "tables": [\n {\n "ref": "giao_dich",\n "title": "noi_dung",\n "fields": {\n "noi_dung": {"type": "text"},\n "ngay": {"type": "date"},\n "loai": {"type": "select", "options": [{"alias": "thu", "color": "green"}, {"alias": "chi", "color": "rose"}]},\n "so_tien": {"type": "number", "notation": {"style": "currency", "currency": "VND"}},\n "bien_dong": {"type": "formula", "notation": {"style": "currency", "currency": "VND"}},\n "doi_tuong": {"type": "link", "table": "doi_tuong", "multi": false},\n "danh_muc": {\n "type": "select",\n "options": [\n {"alias": "doanh_thu", "color": "green"},\n {"alias": "hang_hoa", "color": "blue"},\n {"alias": "van_chuyen", "color": "indigo"},\n {"alias": "nhan_su", "color": "violet"},\n {"alias": "van_phong", "color": "sky"},\n {"alias": "thue_phi", "color": "slate"},\n {"alias": "ngan_hang", "color": "teal"},\n {"alias": "khac", "color": "gray"}\n ]\n },\n "sao_ke": {"type": "link", "table": "sao_ke", "multi": false},\n "hoa_don": {"type": "link", "table": "hoa_don", "multi": true},\n "trang_thai": {\n "type": "select",\n "options": [\n {"alias": "chua_khop", "color": "amber"},\n {"alias": "da_khop", "color": "green"},\n {"alias": "khong_can_hd", "color": "slate"}\n ]\n },\n "khop_luc": {"type": "date", "format": "datetime"}\n },\n "records": {\n "subtitle": ["ngay"],\n "figure": "bien_dong",\n "status": {"field": "trang_thai", "closed": ["da_khop", "khong_can_hd"], "dates": {"da_khop": "khop_luc"}}\n }\n },\n {\n "ref": "hoa_don",\n "title": "doi_tuong",\n "fields": {\n "doi_tuong": {"type": "link", "table": "doi_tuong", "multi": false},\n "so_hoa_don": {"type": "text"},\n "ngay_hoa_don": {"type": "date"},\n "so_ngay_qua_han": {"type": "formula", "notation": {"style": "unit", "unit": "day"}},\n "chieu": {\n "type": "select",\n "options": [{"alias": "ban_ra", "color": "green"}, {"alias": "mua_vao", "color": "violet"}]\n },\n "tong_cong": {"type": "formula", "notation": {"style": "currency", "currency": "VND"}},\n "thue_suat": {"type": "formula", "notation": {"style": "unit", "unit": "percent"}},\n "tep": {"type": "files"},\n "giao_dich": {"type": "link", "table": "giao_dich", "multi": true}\n },\n "records": {"subtitle": ["so_hoa_don", "ngay_hoa_don"], "image": "tep", "figure": "tong_cong"}\n }\n ],\n "body": {\n "type": "App",\n "key": "dong_tien",\n "name": "D\xF2ng ti\u1EC1n",\n "icon": "chart-line",\n "color": "emerald",\n "children": [\n {\n "type": "Reading",\n "key": "dashboard_reading_0",\n "label": "Ti\u1EC1n v\xE0o",\n "entity": "giao_dich",\n "value": "so_tien",\n "where": {"loai": ["thu"]},\n "over": "ngay"\n },\n {\n "type": "Reading",\n "key": "dashboard_reading_1",\n "label": "Ti\u1EC1n ra",\n "entity": "giao_dich",\n "value": "so_tien",\n "where": {"loai": ["chi"]},\n "over": "ngay",\n "better": "down"\n },\n {\n "type": "Reading",\n "key": "dashboard_reading_2",\n "label": "D\xF2ng ti\u1EC1n r\xF2ng",\n "entity": "giao_dich",\n "value": "bien_dong",\n "by": "ngay"\n },\n {\n "type": "Reading",\n "key": "dashboard_reading_3",\n "label": "Chi theo danh m\u1EE5c",\n "entity": "giao_dich",\n "value": "so_tien",\n "where": {"loai": ["chi"]},\n "by": "danh_muc",\n "over": "ngay"\n },\n {\n "type": "Reading",\n "key": "dashboard_reading_4",\n "label": "Thu theo kh\xE1ch h\xE0ng",\n "entity": "giao_dich",\n "value": "so_tien",\n "where": {"loai": ["thu"]},\n "by": "doi_tuong",\n "over": "ngay"\n },\n {\n "type": "Reading",\n "key": "dashboard_reading_5",\n "label": "C\xF4ng n\u1EE3 b\xE1n ra theo tu\u1ED5i n\u1EE3",\n "entity": "hoa_don",\n "value": "tong_cong",\n "where": {"chieu": ["ban_ra"]},\n "by": "doi_tuong",\n "across": {"field": "so_ngay_qua_han", "tiers": [1, 8, 16, 31]}\n },\n {\n "type": "Reading",\n "key": "dashboard_reading_6",\n "label": "S\u1ED1 ng\xE0y qu\xE1 h\u1EA1n b\xECnh qu\xE2n",\n "entity": "hoa_don",\n "value": {"avg": "so_ngay_qua_han"},\n "where": {"chieu": ["ban_ra"]}\n },\n {\n "type": "Reading",\n "key": "dashboard_reading_7",\n "label": "Thu\u1EBF su\u1EA5t b\xECnh qu\xE2n",\n "entity": "hoa_don",\n "value": {"avg": "thue_suat"}\n },\n {\n "type": "List",\n "key": "dashboard_reading_8",\n "entity": "giao_dich",\n "where": {"trang_thai": ["chua_khop"]},\n "app": "doi_chieu",\n "label": "D\xF2ng ch\u01B0a kh\u1EDBp",\n "columns": ["sao_ke"]\n }\n ]\n }\n}\n```\n';
|
|
@@ -1788,7 +1788,7 @@ var filters_default = '# Filters \u2014 choosing records by their fields\n\nOne
|
|
|
1788
1788
|
var field_values_default = '# Field values \u2014 what a write takes for each field type\n\nThe value `create_records` and `update_records` take for a field. A field is named by its alias, as `get_table` shows it, and a select option the same way. `dry_run: true` on `create_records`, `update_records` and `delete_records` runs the write in a transaction that is rolled back: every check, rule and stage owner is judged, no number is consumed, and the answer says what would be written or why it is refused. Every write meets the table\'s rules (`model/write-rules`): a refusal lists every broken rule at once, and a locked record answers under `blocked` in `update_records`\' result.\n\n## Values by type\n\n| Type | Value |\n|---|---|\n| `text` | `"hello"` |\n| `number` | `42` |\n| `boolean` | `true` |\n| `date` | `"2025-03-14"`, or a month `"2025-03"` or a year `"2025"`, stored as written and compared as its first day |\n| `datetime` | a `date` of this format: `"2025-03-14T09:00"`, never a month or a year |\n| `date_range` | a `date` of this format: `"2025-03-01/2025-03-15"`, both halves full dates |\n| `datetime_range` | a `date` of this format: `"2025-03-01T09:00/2025-03-15T17:00"`, both halves full |\n| `select` | `["open"]`, the option\'s alias |\n| `member` | `["mbr_\u2026"]` |\n| `link` | `["rec_\u2026"]` |\n| `files` | `["fil_\u2026"]`, ids of uploaded files |\n| `formula`, `rollup`, `lookup`, `autonumber`, a `date` with `derive_from` | none \u2014 the platform writes them, and a write naming one is refused |\n\n## Lists\n\n`select`, `member`, `link` and `files` store an ARRAY, even where the field holds\none value.\n\n- A single-select is a ONE-element array; a bare `"open"` is accepted and wrapped. A single\n `member` takes a bare `"mbr_\u2026"` the same way.\n- A single `link` takes a bare `"rec_\u2026"` the same way; a multi `link` and `files` take an array only.\n- A `files` id the write adds must name a live file of this workspace, or the whole write is refused;\n an id that cell already holds stays, though its file was archived since.\n- A file previews by its kind: an image zooms; a PDF scrolls, zooms sharp and turns by page, its text selectable; a DXF\n drawing shows read-only in 2D with a toggle per layer. A DWG stays a stored file with no preview; attach its plotted\n PDF beside it.\n- Two options on a single-select, two members on a single `member`, or two records on a single `link`, are refused.\n- `update_records`\' `add_to`, `remove_from` and `replace` take arrays of the same items: option aliases\n for a select, member ids for a `member`, record ids for a `link`, file ids for\n `files`.\n';
|
|
1789
1789
|
|
|
1790
1790
|
// docs/workflows.md
|
|
1791
|
-
var workflows_default = '# Workflows \u2014 the steps a workflow runs\n\nA workflow is written as a strict subset of JavaScript. The source is never run as JavaScript: a\nsave parses it into steps, type-checks it against what its trigger supplies, and stores the steps.\nOnly the forms below are accepted; anything else is refused at save with the line, the column and\nwhat to write instead. A save that succeeds may still return `warnings` \u2014 advisory hints such as a\nloop that may never end. Read them.\n\nA workflow changes records, calls integrations, notifies and makes papers. A tool for anything else \u2014 views, history,\ntables and apps, members, knowledge, skills, editing a file \u2014 is an agent\'s, on chat, MCP or the CLI, and a body that\ncalls one is refused at save with that reason. One saved before is refused at run, before its first step.\n\nThe same grammar serves an app\'s workflow, which `set_app_workflow` saves, an automation and the expressions of a table\ncheck. An app workflow takes its trigger from the app that calls it; only an automation\'s source opens with a trigger\ndeclaration.\n\n## Triggers\n\nAn app workflow reads its call as `trigger.app_workflow.inputs.<name>`, or `inputs.<name>`, which is the same read. A Workflow node receives the inputs it declares;\nan Act\'s `workflow` receives `record_id` (`record_ids` on an act over several rows) and what the Act asks.\n\n### Automations\n\nAn automation answers an event outside the records: a schedule, a webhook, an inbound email. `set_automation` saves one\nfrom its source \u2014 the `on({...})` declaration, then its steps \u2014 and, given its `automation_id`, replaces it: the\ntrigger config is the one the header states, a key it leaves out removed, and steps left as it runs them are checked again\nonly when the call turns it on, so one a later rule refuses can still be switched off. `get_automation` reads that source back with its latest\nruns, and `dry_run_workflow` rehearses a source before it is saved. It runs with its owner\'s current permissions, a connected service it names is its owner\'s account, and only its\nowner or an admin reads, changes or deletes it.\n\n```\non({ type: "recurring_schedule", cron_expression: "0 9 * * MON" });\n```\n\nEvery source below is typed, so a misspelled member is refused at save rather than read as nothing.\nAll three carry `trigger.trigger_id`.\n\n| trigger | required config | what the body reads |\n|---|---|---|\n| `recurring_schedule` | `cron_expression` | `runtime.timezone`; the current time is `now()` |\n| `receive_webhook` | `secret`? | `trigger.method`, `trigger.headers[...]`, `trigger.query[...]`, `trigger.body` (any JSON, or raw text when the request is not JSON), `trigger.received_at` |\n| `receive_gmail_email` / `receive_outlook_email` | `connected_account_id`, `filter` | `trigger.email_id`, `trigger.from`, `trigger.to`, `trigger.cc`, `trigger.bcc`, `trigger.subject`, `trigger.date`, `trigger.body` (plain text), `trigger.reply_to`, `trigger.in_reply_to`, `trigger.attachments` (file refs \u2014 assign them straight to a files field). One type serves both providers, so `labels`, `thread_id`, `importance` and `conversation_id` are not readable. |\n\n## Steps\n\n| step | syntax |\n|---|---|\n| tool call, kept | `const <id> = await <tool>({ ...inputs });` \u2014 also `let x = await \u2026` and `x = await \u2026` |\n| tool call | `await <tool>({ ...inputs });` \u2014 the platform\'s own steps (`send_notification`, `generate_document`, \u2026) and a connection\'s tools, each a workflow step (`bkav_submit_invoices`, `kiotviet_create_order`, \u2026); a step that fails ends the run with its reason unless a `try`/`catch` around it handles it |\n| agent | `const <id> = await agent({ instructions, input, model, output });` \u2014 see **An agent step** |\n| app agent | `const <id> = await app_agent({ alias: "<agent alias>", input: { ... } });` \u2014 runs one of the app\'s declared agents, in an app workflow only, and resolves to its declared outputs or its final text. `wait: false` only starts the run and resolves to `{ run_id }`; the run\'s failure is then its own, not the workflow\'s. Refused in a run an agent started. |\n| bind | `const <id> = <expression>;` \u2014 evaluated once; later steps read `<id>`. Bind any expression used twice. |\n| if / else | `if (<expr>) { ... } else { ... }` \u2014 `else` optional, `else if` chains allowed |\n| switch | `switch (<expr>) { case "X": { ... } default: { ... } }` \u2014 string cases only, no fallthrough |\n| for | `for (const <name> of <expr>) { ... }` |\n| wait | `await wait({ duration_in_minutes: 5 });` |\n| wait for an event | `await wait_for_event({ event_type: "webhook", event_ref: <expr>, timeout_in_minutes: 60 });` |\n| wait for an approval | `await wait_for_approval({ approvers: <expr>, prompt: <expr> });` \u2014 see **Waiting for an approval** |\n| return | `return({ status: "success" \\| "error", message: <expr>, field_errors: { ... }?, data: { ... }? });` \u2014 `data` holds the outputs a Workflow node declares |\n| validate | `validate({ checks: [{ fail_when: <expr>, field_key: "...", message: <expr> }, ...] });` |\n\nA delayed run is never cancelled from a workflow: after a `wait`, it reads the row\'s state and acts only if it still holds.\n\nA tool\'s inputs and answer are in its own description; the steps only a body runs \u2014 a connection\'s, a mail\'s \u2014 are each\nan entry of `workflow_steps`. A person connects a service in Lotics under Settings \u2192 Connections; a step names that\nconnection by its `connected_account_id`, which `get_workspace` with `kind: "connection"` lists for the connections the\ncaller can use. The app\'s owner and whoever saves the body must both be able to use that connection.\n\nIn a workflow, email reaches a member of the workspace through the step `send_notification`: Lotics shows it to them and\nmails it to their own address, with no connection. Its recipients are members by `member_id` (`get_workspace` with\n`kind: "member"` lists them), member groups or the whole organization. Mail to anyone else goes from a person\'s\nconnected Gmail or Outlook (`gmail_send_email`, `outlook_send_email`), which leaves an unsent draft unless `draft` is\n`false`.\n\n`return` is a call, not a JavaScript `return` statement. `validate` ends the workflow with an error\nwhen a check\'s `fail_when` is truthy; the check\'s `field_key` names the control its message lands on \u2014\nin an app workflow a declared input, or the alias of a field on a table the body names; in an\nautomation it is not checked.\n\nThe name in `const x = await tool({...})` is the step\'s id; later expressions read its output as\n`x.records`, `x.id` and so on. A `// id: my_step` comment directly above a statement sets an explicit\nid; any other `//` comment there becomes the step\'s description.\n\n**`await` inside a statement** runs the call as its own step first, then reads its output where it is\nwritten: `size((await query_records({...})).records)`, an `if` test, a `return` value, a `for-of`\niterable, a tool input. `const x = c ? await t({...}) : null;` is stored as the `if` it means. An\n`await` that would run on some paths only \u2014 inside `&&`, `||`, `??`, `?.`, a nested ternary, a loop\ntest, a `validate` check \u2014 is refused; write the `if`.\n\n**Names are block-scoped, as in JavaScript** \u2014 `const`, `let`, loop items and `catch (e)`: sibling\nblocks may reuse a name and an inner block may shadow an outer one. A local may take a helper\'s name\n(`const size = 3;`), but calling `size(...)` while it is in scope is refused. The names of the tools a\nworkflow calls, the reserved roots, `linked` and `_s` followed by digits cannot be bound.\n\n### Waiting for an approval\n\n`wait_for_approval` is the wait worth binding \u2014 `wait` and `wait_for_event` carry nothing to read.\nBind it to branch on the decision:\n\n```\nconst approval = await wait_for_approval({\n approvers: trigger.app_workflow.inputs.approvers,\n prompt: `Approve this quote?`,\n});\n\nif (approval.status == "approved") {\n // the approved path\n} else {\n // the rejected or timed-out path; approval.decision_comment says why\n}\n```\n\nIt resolves to `{ status: "approved" | "rejected" | "timed_out", decided_by: MemberId | null,\ndecided_at: ISO string, decision_comment: string | null }`.\n\n`approvers` takes a bare member id (`"mbr_\u2026"`), a bare group id (`"grp_\u2026"`) or a full principal\n(`{ type: "member_group", id: "grp_x" }`), alone or in a list \u2014 so a member field\'s value passes as it\nis.\n\n### An agent step\n\n`const x = await agent({ instructions, input, model, output });` runs a model as one step \u2014 read a paper, classify,\ndraft text. It calls no tool: the workflow\'s own steps write, send and notify with what it returns, and a step that\nstates `tools` is refused.\n\n- `instructions` \u2014 a fixed string.\n- `input` \u2014 an object literal; its fields are expressions, evaluated and handed to the model (`{}` for\n none). It is the ONLY workflow data the agent sees: read the trigger, prior steps, `runtime` or the loop\n item here, never inside `instructions`, which is a fixed literal and reads no binding. A field holding a file, or a\n list of files (a files field, a mail\'s attachments), hands the agent the files themselves: a picture or a PDF it\n sees, any other paper read into text.\n- `model` \u2014 optional; omit it to follow the platform\'s default model.\n- `output` \u2014 `{ mode: "text" }` resolves to a string; `{ mode: "object", schema }` to a typed object.\n\nOnly `x` comes back: in text mode `x` is the string itself, in object mode `x.field` reads a field the\n`schema` declares.\n\n## Naming tables and templates\n\nA body names a table or document template by its alias (`query_records({ table_id: "orders" })`); ids still work. `get_app_workflow` and `get_automation` show aliases. An app\'s workflow reaches only the tables the app uses.\n\n## Fields and options\n\n1. **A body names a table\'s fields and select options by alias**, the names the table\'s document states: a field reads\n as `record["status"]`, an option as `"done"`, a linked field as `linked(record["customer"])[0]["name"]`, in the\n body of a Workflow node, of an Act\'s own `workflow`, of an automation (`set_automation`) and of a table\'s workflow. It is stored by key and `get_app_workflow`, `get_automation` and `get_table` show it by alias\n again. A field\'s or option\'s key is accepted where its alias is and is kept as written; a display name is refused with the\n aliases there are, and a row that names one field by both its alias and its key is refused. A row written to a table is\n stored by field key wherever the save can follow it to the record tool that writes it: a row or list written in the call,\n a conditional of them, a name holding them, pushed to a `let`, and through `map`, `flatMap`, `concat`, `filter`, `sortBy`,\n `slice` and `reverse`. A field of such rows is read by alias as well (`picks.toSorted((a, b) => a.quantity - b.quantity)`,\n `sortBy(picks, "quantity")`, `rows[0].note`) and stored by key. A body that cannot be stored by key is refused at save,\n naming the statement.\n2. **An Act\'s own `workflow`** has the app\'s guards placed at the top of the body, between `<lotics:guards>` tags,\n replaced on every save: a save that leaves them out or edits them gets the app\'s again, at the top wherever the tags\n sat, so the act\'s row rule, `when`, `requires`, checks and locks always run first.\n3. **A value is read as the record tools store it.** A single select reads as its option and a member as its id; an\n option is written by its alias wherever it meets a select \u2014 compared, listed, switched on, written \u2014 also beside a\n name set from the field (`const s = record.status; s == "done"`), and a select\n value handed on whole (returned, written to a text field) is its key. An option\'s alias searched for in a list the\n save cannot place it against (`includes(filter(record.tags, \u2026), "hot")`) is refused: search the field itself. A link reads as the list of its row ids, a\n one-row link too: `first(record.customer)` is the one id, and `record.customer` used as one id is refused at save,\n naming `first(record.customer)`. A one-row link is written with its one id or a list of one; a many-row link with a list. A lookup reads as one flat list of the values it looks up: `includes(record.owner,\n me)`. A select\'s value is one of another select\'s options only where that table shares its keys, by having been\n duplicated from it, not where an option merely shares its alias. A row that takes its parent\'s option gets it from\n `default_from`, and a lookup shows it.\n4. **What an app sends takes an option by alias or key.** An input or output drawn from a field (`field`, written\n `<table id>.<alias>`) compares and returns by alias in the body, and the run hands it on as the option\'s key. A\n refusal\'s `field_errors` and a `validate` check\'s `field_key` name a declared input or a field\'s alias.\n5. **`==` against a multi-select means "includes".** `record["tags"] == "urgent"` on a multi-select is stored as\n `includes(record["tags"], "urgent")`; either spelling works.\n6. **Text renders labels.** Inside a template or a text tool input, a select, member or link value renders its display\n text: `` `${record["status"]}` `` writes "Done", not `done`.\n\n## What an expression reads\n\n- `record`, `prev_record`, `changes` \u2014 in a table check, the write it checks (see **Table checks**).\n `changes` is a PARTIAL map: `changes["x"]?.next_value` and `?.prev_value`, the `?.` required.\n- `trigger` \u2014 what the trigger carries (see **Triggers**).\n- a loop\'s item name, and `index`, the iteration\'s 0-based index.\n- `<step id>` \u2014 an earlier step\'s output (`dup.records`).\n- `runtime.timezone`, `runtime.workflow_id`, `runtime.execution_id`, `runtime.workspace_id`,\n `runtime.organization_id`, `runtime.change_origin`, `runtime.triggered_by_member_id`. The current\n time is `now()`.\n\n## Paths\n\n- `record.x` and `record["x"]` read a field; `[n]` reads a list\'s n-th item.\n- A path may start at any expression: an awaited call `(await get_record({...})).data["x"]`, a helper\'s\n result `first(found.records).id`, a filter `rows.filter((r) => r.ok)[0].n`, a `(a ?? b).c`. `?.` reads\n through a null as usual, except on a value drawn at random (`sample(xs)?.id` is refused: bind it first).\n- A record reads in its declared shape wherever it comes from \u2014 the trigger, a tool result, a `let`, a\n callback parameter: a single select as its option alias or null, a member as its id, a link as its ids. (The record\n tools called outside a workflow return the stored arrays instead.)\n- `linked(record["link"])[n]["x"]` fetches the n-th linked row and reads a field of it; bare\n `linked(record["link"])` is every linked row, one fetch per row. Its argument is a path ending on\n a link field and carries the guard: `linked(record?.["link"])[0]`.\n- `changes["link"]?.next_value` is the same list of ids; `linked()` over a change is refused \u2014\n read `linked(record["link"])` or `linked(prev_record["link"])`.\n\n## Operators and statements\n\nOperators in JavaScript precedence: `? :`, `||`, `??`, `&&`, `== != === !==`, `< <= > >=`, `+ -`, `* / %`,\nprefix `! -`.\n\n- Arithmetic is exact decimal (`0.1 + 0.2` is `0.3`). A result computed from a division that does not end\n is the short decimal it reads as (`1 / 3 * 3` is `1`), and stands as computed where it reads long\n (`1 / 3 * 100` is `33.33333333333333`). A comparison between two numbers reads both at 15 significant\n digits. `round` and `formatNumber` round the decimal, a half away from zero: `round(-2.5)` is `-3`. A\n table filter compares stored values exactly.\n- `??` is `coalesce(left, right)`; `?.` reads through a null (`a?.b`, `a?.[0]`, and `s?.trim()` for the\n method names listed under **Helpers**). `x?.()` is refused: helpers and tools are not values.\n- Templates use backticks and `${...}`.\n- Destructuring \u2014 `const { status, name } = record;` binds each name. Renames\n (`{ status: s }`), quoted keys (`{ "ref": ref }`), array patterns with holes\n (`const [first, , third] = xs;`) and defaults (`{ note = "" }`, which apply on null too) work on\n `const`, `let` and `for (const { id } of rows)`. A tool result destructures directly:\n `const { records } = await query_records({...});`. `const a = 1, b = 2;` declares both. Nested\n patterns and rest are refused.\n- Spread \u2014 `[...a, b]` and `{ ...a, b: 1 }` (later keys win). Refused inside a tool input: bind the\n merged value first and pass the binding.\n- Shorthand \u2014 `{ table_id }` is `{ table_id: table_id }`.\n- `xs.push(a, b);` appends, also on a key (`o.items.push(v)`); `o.a.b = v;` sets a nested key;\n `x ??= v`, `x ||= v` and `x &&= v` assign. A `const` may be pushed to or have a key set, as in\n JavaScript; a loop item is read-only.\n- `undefined` is the same value as `null`, so `x === undefined` is `isNull(x)` and `x !== undefined` is\n `isNotNull(x)`; a key set to null and a key never sent are not told apart (`"k" in o` tells a key that\n is there).\n- `typeof x` answers JavaScript\'s words, and a null (a missing value too) is `"object"`. `k in o` tests an\n object\'s own keys, or a list\'s indexes and `length`.\n- A filter node may leave out `node_type` when its keys say which it is: `field_key` / `operator` /\n `value` a condition, `logic` / `children` a group, `path` / `condition` a traversal.\n- `function name(p = <default>) { return <expr>; }` \u2014 top level only, expanded at every call (a call\n may come before it). Every parameter needs a default, which gives it its type; the body is one\n `return <expr>;` reading only its parameters, helpers and the reserved roots \u2014 never the caller\'s\n names, nor `index`. No recursion; a function never called is refused. A body read back shows the\n expression at each call, not the `function`.\n\n**try / catch.** `try { ... } catch (e) { ... }` catches a tool error or an expression error inside the\nbody; `e` is `{ message, type, step_id?, detail? }`. A failed `validate` and a `return` are not\nerrors \u2014 they end the workflow \u2014 and a step that runs after a wait inside the `try` is outside it.\n`finally` is refused: put always-run steps after the `try`.\n\n**Loops.** `for-of`, `while (cond) { ... }`, `do { ... } while (cond);` and\n`for (let i = 0; i < n; i++) { ... }`, each capped at 10,000 iterations. `break;` and `continue;` act\non the innermost loop; labels and `for-in` are refused.\n\n**`let`.** `let x = <expr>;` declares a block-scoped variable; the initializer is required\n(`let x = null;`). Reassign with `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `??=`, `++` and `--`. Re-declaring in\nthe same block is refused; shadowing in a nested block is allowed. A `let` keeps its value across a\n`wait`, `wait_for_event` or `wait_for_approval`.\n\n**Refused, each because a save must know what runs and a run must give the same answer twice:** regex\n(unbounded matching: use the string methods), `Math.random`, `Date.now()` and `new Date()` (use `now()` and\n`parseDate`), a tool or helper as a value or a computed callee (callees are named), `new` of anything,\n`instanceof`, `delete`, `void`, rest elements, computed keys, classes, `throw`, `finally`, `import`,\n`export`, `eval`, labels, and a function as a value (`const f = (x) => ...`: pass the arrow to the helper\nthat takes it). `xs.sort(...)` on a name or path is refused too, since a value is never changed in place:\nwrite `xs.toSorted(...)` or `[...xs].sort(...)`.\n\n## Typing\n\n- The body is type-checked as TypeScript before it is read: annotations, `as`, `satisfies` and a non-null `!`\n are accepted and erased, and the checker is strict about null. The stored body keeps none of them and is checked again as\n stored, by field key, as every later change to its app or tables checks it: a body that types only through one of them is\n refused at save.\n- Narrow what is not typed (a connector\'s JSON is `unknown`) with `typeof`, `in`, `Array.isArray` and `isNull`.\n- `first(xs)` may be undefined; `requireFirst(xs)` is the list\'s first item or an error.\n- `obj[key]` needs `key` to be a key of `obj`, or `obj` to be a record with a string index.\n\n## Helpers\n\nOrdinary JavaScript works \u2014 the standard library below, the array and string methods, `Math`, `JSON`,\n`Object`, `Array`, `Number`, `parseInt` \u2014 and means what it means in JavaScript, with the differences\nlisted here. Where JavaScript answers NaN, Infinity or undefined, a workflow has none of them and the\nanswer is `null`.\n\n- Standard library: `Math.abs/ceil/floor/trunc/sign/min/max/pow/sqrt`, `Number(x)`, `String(x)`,\n `Boolean(x)`, `parseInt(s, radix?)`, `parseFloat(s)`, `Number.isFinite/isInteger`,\n `Object.keys/values/entries/fromEntries`, `Array.isArray/from` (a list, text, or `{ length: n }`),\n `JSON.parse/stringify`.\n- Arrays: `length, map, filter, find, findIndex, findLast, some, every, reduce, includes, indexOf, join,\n slice, concat, flat, flatMap, at, reverse, toReversed, toSorted` and `[...xs].sort(compare?)`.\n- Strings: `length, toLowerCase, toUpperCase, trim, trimStart, trimEnd, startsWith, endsWith, includes,\n indexOf, slice, substring, split, replace, replaceAll, padStart, padEnd, repeat, charAt, at,\n localeCompare` (en-US order, wherever it runs). Numbers: `toFixed, toString`.\n- Differences from JavaScript: `Math.round` is the engine\'s `round` (a half goes away from zero:\n `Math.round(-2.5)` is `-3`); `Number(x)` and `parseFloat(x)` are `toNumber`, which refuses text that is no\n number; `String(null)` is `""`; `join` writes a null item as `null`; `concat` skips a null; `toFixed`\n rounds the decimal, not the binary double; `replaceAll` writes the replacement as text\n (`$&` stays as typed), while `replace` expands `$&`, `$$`, `` $` `` and `$\'` in it as JavaScript does; `split` takes no limit; `sort` without a comparator orders by text, as JavaScript does. `==`, `!=`, `===` and `!==` between two list values (files, a multi-select, a link of any cardinality, a lookup) are refused at save, since a list compares by identity, not by its items: `!isNull(changes.<field>)` asks whether one changed.\n\nThe same work can be written with the helpers below, which are additions to JavaScript. They are called as\n`size(arr)`; the method form works only where the name is also a JavaScript method \u2014\n`x.trim()`, `arr.includes(v)`, `arr.at(-1)`, `arr.map(fn)`, `s.split(",")`; `arr.size()` is refused.\n\n- **Null and type**: `isNull`, `isNotNull`, `isEmpty`, `isString`, `isNumber`, `isBoolean`, `isArray`,\n `isObject`, `coalesce`, `get`, `toNumber`, `toFigure`, `toString` (a text, number, yes/no or date; a record, file or list is refused at save, an object at run), `typeOf`, `parseJson`, `toJson`. `isNull` is strict (`null` and `undefined` only), but on a list field of the record (a link, files, a multi-select) it asks whether the list holds nothing, since a cleared list is `[]`: `!isNull(record.vehicle)` reads as `!isEmpty(record.vehicle)`. `isNull(changes.<field>)` stays strict: null there means unchanged\n- **Lists**: `size`, `first`, `requireFirst` (the first item, refusing an empty list \u2014 after a\n `validate` on the size it saves an `if`), `last`, `nth`, `at` (`at(arr, -1)` counts from the end),\n `includes`, `filter`, `find`, `some`, `every`, `pluck`, `sortBy`, `groupBy`, `countBy`, `unique`,\n `uniqueBy`, `compact` (drops falsy items), `flatten`, `reverse`, `slice`, `concat`, `difference`,\n `differenceBy`, `intersection`, `intersectionBy`, `list`, `reduce(arr, (acc, x) => ..., initial)`,\n `range(end)` / `range(start, end)`\n- **Math**: `sum`, `sumBy`, `mean`, `meanBy`, `minBy`, `maxBy`, `round`, `ceil`, `floor`, `min`, `max`,\n `abs`, `mod`, `pow`, `sqrt`, `clamp`, `percentage`\n- **Text**: `upper`, `lower`, `capitalize`, `trim`, `contains`, `startsWith`, `endsWith`, `replace`,\n `replaceAll`, `substring`, `length`, `split`, `join`, `padStart(str, length, char)`,\n `padEnd(str, length, char)`, `formatNumber(x, decimals)` (ungrouped; rounds the decimal, a half away\n from zero: `formatNumber(1.005, 2)` is `1.01`),\n `formatDecimal(x, decimals, locale)` (grouped: `formatDecimal(151000, 0, "vi-VN")` is `151.000`),\n `numberToWords(x, lang?)` (an integer in words: Vietnamese, or English with `"en"`),\n `uuidFrom(text)` (the same UUID for the same text, refusing null and empty text: `uuidFrom(record.id)`)\n- **Objects**: `keys`, `values`, `entries`, `nonNullKeys`, `pick`, `omit`, `merge`\n- **Dates**, in the workspace\'s timezone: `now`, `formatDate`, `parseDate`, `addDays`, `subDays`,\n `addHours`, `subHours`, `addMinutes`, `subMinutes`, `startOfDay`, `endOfDay`,\n `differenceInCalendarDays`, `differenceInHours`, `differenceInMinutes`, `isBefore`, `isAfter`,\n `isSameDay`, `isToday`, `isWithinRange`, `meetsBounds`\n- **Other**: `formatCurrency(amount, locale, currency)`, `randomNumber(len)`,\n `randomAlphaNumeric(len)`, `sample(items)` (a random item)\n\nOver a null or empty list `mean`, `meanBy`, `min`, `max`, `minBy` and `maxBy` answer null, while `sum`\nand `sumBy` answer 0: `maxBy(pieces, "reach")?.Name` prints a blank for a month with no pieces.\n\nA date helper given a null or empty date returns null, so guard its result before comparing or\nwriting it: `differenceInCalendarDays(a, b) ?? 0`.\n\n`filter`, `find`, `some`, `every`, `sortBy`, `pluck`, `sumBy`, `meanBy`, `minBy`, `maxBy`, `groupBy`,\n`countBy`, `uniqueBy`, `differenceBy` and `intersectionBy` take a path string\n(`filter(record.items, "Status", "open")`) or a callback\n(`filter(record.items, x => x.Status == "open" && x.Amount > 100)`); `reduce` takes a callback and an\ninitial value. A callback gets `(item, idx)` (`reduce`: `(acc, item, idx)`) and reads `record`,\n`runtime`, the names in scope, loop items and an enclosing callback\'s parameters. Its body is one\nexpression \u2014 `(x) => <expr>`, `(x) => { return <expr>; }` or `function (x) { return <expr>; }`;\nseveral statements, `async` and named function expressions are refused.\n\nLook a row up by key rather than filtering for it: a `filter` inside a callback walks its whole list again\nfor every item, so a thousand items against a few hundred rows is hundreds of thousands of calls. Group the\nrows once, then read each group by its key. `groupBy` answers an object of lists, and `byCode[code] ?? []`\nis the group a code names, or an empty list:\n\n```\nconst parties = await query_records({ table_id: "parties" });\nconst lines = await query_records({ table_id: "sheet_lines" });\nconst byCode = groupBy(parties.records, (p) => p.data["code"] ?? "");\nconst holders = lines.records.map((line) => ({\n line: line.id,\n parties: (byCode[line.data["code"] ?? ""] ?? []).map((p) => p.id),\n}));\n```\n\nA key is read exactly: a code holding a dot, a slash or a bracket reads its own group, and a name every\nobject inherits (`constructor`) reads nothing unless a row made a group of that name. `get(o, "a.b")`\nfollows a dotted path instead, so a key that holds a dot is read with `o[key]`.\n\n## Writing a field: null and undefined\n\nIn `update_records`\' `set` and `create_records`\' `records`:\n\n- `null` clears the field.\n- `undefined`, or leaving the key out, leaves the field as it is.\n\nSo passing a read that may be null (`record["x"]`) keeps a value when there is one and clears the\nfield when there is none. Use `coalesce(x, fallback)` only for a real fallback value.\n\n## Examples\n\nCompare a link by id. Display text is not unique, and a link reads as a list of ids \u2014 `==` against a\nstring is a type error:\n\n```\nconst found = await query_records({\n table_id: "customers",\n filters: { node_type: "condition", field_key: "name", operator: "equals", value: "ACME Corp" },\n});\nconst customer = first(found.records);\nconst order = await get_record({ table_id: "orders", record_id: trigger.app_workflow.inputs.order_id });\n\nif (customer && includes(order.data["customer"], customer.id)) {\n // \u2026\n}\n```\n\nRead an uploaded sheet and add the rows a table lacks. `excel_get_rows`, a workflow step, answers every\nrow of an `.xlsx`, `.xls` or `.csv` sheet in one call, each an object keyed by the header row\'s text. A cell is a number,\n`true` or `false`, text, a date as ISO text, or null when blank, so `row["Header"]` is typed\n`string | number | boolean | null` and a helper narrows it: `toString` for text, `toNumber` for a number,\n`isString` before a date. Every cell of a `.csv` is text. `columns` keeps only the ones the body reads.\nA sheet past what one call reads is refused with its size, never cut short: the step\'s entry in\n`workflow_steps` states the bounds.\n\n`import_rows`, a workflow step, matches those rows to a table by a key and writes what differs. `match`\nnames the fields that identify a row, compared as trimmed text with case ignored: a row matching no live\nrow is added, one matching a row changes only the cells that differ, and one that changes nothing is\ncounted and not written, so the same file run twice writes nothing the second time. `values` holds a\nrow\'s cells by field alias, as `create_records` takes them; a key the row omits is left as the table holds\nit, and `null` is an empty cell, which differs from a filled one as any other value does. `dry_run: true` judges the import as a real one would be, rolls it back and answers what it would write or why it is refused.\n\nA `match` field may also be a link field holding one row, compared by the row linked: lines keyed by\ntheir parent and a line number state `match: ["invoice", "line"]`, the parent given as its\nrecord id or as `{ lookup: "<text>" }`.\n\nWhat the step is not certain of is held \u2014 answered with why, and not written: a row carrying `hold`\n(its text is the message); a row whose key several live rows carry, or an earlier row of the same call;\na row the table\'s checks refuse, or whose row is locked; with `create: false`, a row matching none. A\nheld row never fails the step, and the rows beside it are written. A row whose key names a row outside\nthe rows the run may write is held `refused` with nothing of that row: no `record_id`, no `changes`.\n\nThe step removes no row, and a row the source leaves out is never touched. A row carrying\n`remove: true`, with its key in `values`, proposes moving the row its key names to the trash: it is\nalways held, whatever `create` and `conflicts` say, and a key naming no row is counted unchanged.\n\n`conflicts` states what a matched row does where a live cell holds another value: `"hold"` writes the\nrow\'s empty cells and holds the differing ones, `"overwrite"` writes over them, `"fill_empty"` leaves\nthem. `conflicts.fields` gives one field its own. A number and the field its unit or currency is read\nfrom on each row are one cell, under the number\'s policy: both are written, or both held.\n\nA link cell takes `{ lookup: "<text>" }` in place of record ids: the linked row is the one row holding\nthat text in the linked table\'s unique or autonumber field, else the one titled it, among the rows the\nrun reads of that table. A text naming no row, or several, holds the row, and the rows it could be are\nanswered beside it. A link holding many rows takes a list, `[{ lookup: "<text>" }, \u2026]`: each text is\nresolved by itself.\n\nThe step keeps nothing of its own: its answer is everything it did. `written` lists each record it\nwrote \u2014 a changed one with each field from old to new as text, a created one by its id alone \u2014 and\n`held_rows` each row it held: its `kind`, `reason` and `message`, the `record_id` of the row it matched,\nits `key`, the `changes` it would make, the `links` it could not resolve with their `candidates`, and\n`values` \u2014 its key and the cells it would write, as the call gave them. A later call given a held row\'s\n`values` under `conflicts: { default: "overwrite" }` writes it. Both lists are read under the run\'s own\nreach of the table, and a workflow hands its caller only what it returns. Two calls matching one table\nby the same fields take turns, so the second matches the rows the first added. The step\'s entry in\n`workflow_steps` states its bounds.\n\nA workflow does not put an import back: that reads the history of what the run wrote, which a member\'s agent reads\nunder the member\'s own access to the table and no workflow does. On chat, MCP or the CLI, `undo_record_changes` takes the\ntable, up to 500 record ids and the run \u2014 what `runtime.execution_id` held in it, which a body keeps on a row of its own \u2014\nand puts back what that run wrote, as the member\'s own writes. A field goes back where the row still holds what the run\nwrote. A row the run created goes to the trash where nothing else has written it or linked a row to it since; in a table\nwhere a workflow writes each new row, every row created reads as written since, and is kept. A row the run moved to the\ntrash comes back where nothing has touched it since. A row the run brought back from the trash stays out of it. What the\ntool leaves as it stands is answered in `kept`, one entry a record and reason. `undo_record_change` puts back one edit\nnamed by its audit-log entry, the same way.\n\nWhat a business keeps of an import \u2014 the file, its counts, the lines waiting for a person \u2014 is rows of\nits own tables, written by the workflow that ran the step. A garden centre brings its grower\'s price\nsheet into its plants, and keeps each sheet and its lines. A line names its plant through a link\nholding many rows: one holding a single row would make the line that plant\'s own, and a plant its\nlines belong to is not moved to the trash.\n\nThese tables are shown beside the plants, in the same app: the sheets as a log of the plants\' register\n(a further List at the root, `model/app-node`), a sheet\'s lines on the page it opens. A line keeping what a cell held\nand what it holds now in two fields reads one against the other (`compare`, `model/list-node`).\n\n```\nconst sheet = await excel_get_rows({ // a workflow step\n file_id: trigger.app_workflow.inputs.file,\n columns: ["Code", "Name", "Price"],\n});\n\nconst imported = await import_rows({ // a workflow step\n table_id: "plants",\n match: ["code"],\n rows: sheet.rows.map((row, at) => ({\n line: at + 2,\n hold: isNull(row["Price"]) ? "The sheet states no price" : null,\n values: {\n code: toString(row["Code"]),\n name: toString(row["Name"]),\n price: toNumber(row["Price"]),\n },\n })),\n});\n\nconst kept = await create_records({\n table_id: "price_sheets",\n records: [{\n file: [trigger.app_workflow.inputs.file],\n run: runtime.execution_id,\n read: imported.read,\n created: imported.created,\n changed: imported.changed,\n held: imported.held,\n }],\n});\nconst sheet_id = kept.record_ids[0];\n\nfor (let start = 0; start < size(imported.written); start += 50) {\n await create_records({\n table_id: "sheet_lines",\n records: slice(imported.written, start, start + 50).map((one) => ({\n sheet: [sheet_id],\n plant: [one.record_id],\n line: one.line,\n outcome: one.kind,\n changes: join(one.changes.map((change) => `${change.label}: ${change.before ?? "\u2014"} \u2192 ${change.after ?? "\u2014"}`), "; "),\n })),\n });\n}\nfor (let start = 0; start < size(imported.held_rows); start += 50) {\n await create_records({\n table_id: "sheet_lines",\n records: slice(imported.held_rows, start, start + 50).map((one) => ({\n sheet: [sheet_id],\n plant: isNull(one.record_id) ? [] : [one.record_id],\n line: one.line,\n outcome: "held",\n why: one.message,\n code: toString(sheet.rows[coalesce(one.line, 2) - 2]["Code"]),\n name: toString(sheet.rows[coalesce(one.line, 2) - 2]["Name"]),\n price: toNumber(sheet.rows[coalesce(one.line, 2) - 2]["Price"]),\n changes: join(one.changes.map((change) => `${change.label}: ${change.before ?? "\u2014"} \u2192 ${change.after ?? "\u2014"}`), "; "),\n })),\n });\n}\n\nreturn({ status: "success", message: `${imported.created} added, ${imported.changed} changed, ${imported.held} held` });\n```\n\nA held line keeps the cells the sheet states for it, found by the `line` the step answered, in\nfields of its own. A held row\'s `key` and `values` name fields by their keys, as every record a step answers a body does, so\nthe body reads the sheet again rather than those names. It is applied by the act on it, whose workflow hands the step those values again and\nlets it write over what the plant holds. A cell the line holds none of is left out, so a held line\nnever empties a cell of the plant:\n\n```\nconst line = await get_record({ table_id: "sheet_lines", record_id: trigger.app_workflow.inputs.record_id });\nconst values = {\n code: line.data["code"],\n ...(isEmpty(line.data["name"]) ? {} : { name: line.data["name"] }),\n ...(isNull(line.data["price"]) ? {} : { price: line.data["price"] }),\n};\n\nconst applied = await import_rows({ // a workflow step\n table_id: "plants",\n match: ["code"],\n conflicts: { default: "overwrite" },\n rows: [{ values }],\n});\nif (applied.held > 0) {\n return({ status: "error", message: applied.held_rows[0].message });\n}\n\nawait update_records({ table_id: "sheet_lines", record_ids: [line.id], set: { outcome: "applied" } });\nreturn({ status: "success", message: "Applied." });\n```\n\nA sheet is put back by a member\'s agent on chat, MCP or the CLI: `undo_record_changes` over the plants its lines name,\n500 at a time, with the run the sheet kept.\n\nA shop matches a bank statement to the notes it is owed by an act that states `review`. That act\'s workflow only\nproposes: its body calls steps that read and no other, and runs no agent \u2014 either is refused when the body is saved \u2014\nand returns in `data.review` one changeset of the act\'s table, which the platform writes the approved rows of. Called\nwith what the act asks, it answers the proposal and nothing is written; called again with\n`approved: { updates: [{ record_id, sha }], creates: [{ key, sha }] }`, the body runs again and the platform writes\nthose rows as this second run proposes them, so the body never reads `approved`. The platform adds a `sha` to every row\nof the proposal it answers \u2014 the body states none, and one it states is dropped \u2014 and the caller echoes the `sha` of\neach row it approves. A row the second run proposes with other values than the `sha` was read from is left unwritten\n(`changed`), so the body must propose the same for the same inputs and the rows it reads.\nAn update is of a record the act is run over (`record_id`, or one of `record_ids`): one of any other record is left\nunwritten. An act over every row in view is handed no rows: its body reads the rows it may change, and any of them may\nbe proposed. A create carries a `key`, a text of the body\'s own that names the same line on every run\n(`key: toString(line["Reference"])`), unique among the creates; where a confirm repeated must not add a second row,\nthe body also writes the key into a field the table keeps unique, so the repeat\'s create is left as a `conflict`.\nEach row\'s `data` holds only stored fields that change \u2014 never one the platform computes or stamps, nor a file \u2014 with\na `note` and a `ticked` the reader starts from, and `unplaced` lists what was read and matched nothing. Every approved\nrow is checked against its fields before any is written, and they are written in one transaction, so a flawed one\nwrites none. The status history of every row written that moved, or was made holding a status, is recorded in the\nsame run. The answer\'s `applied.previous` holds what each updated record\'s written fields held before, so one `update_records` of it puts the values back, and `delete_records` of a `created` record takes that row away. A body that stops proposing a row once it is\nwritten \u2014 it matches against what the rows hold now \u2014 makes approving twice write once.\n\n```\nconst inputs = trigger.app_workflow.inputs;\n\nconst sheet = await excel_get_rows({ // a workflow step\n file_id: inputs.statement,\n columns: ["Reference", "Amount", "Paid on"],\n});\nconst unpaid = await query_records({\n table_id: "notes",\n filters: { field_key: "paid_on", operator: "is_empty" },\n pagination: { offset: 0, limit: 500 },\n});\nconst acted = groupBy(inputs.record_ids, (id) => toString(id));\nconst owed = filter(unpaid.records, (one) => size(acted[toString(one.id)] ?? []) > 0);\nconst byRef = groupBy(owed, (one) => toString(one.data["ref"]));\n\nconst lines = sheet.rows.map((line) => ({\n line,\n note: first(byRef[toString(line["Reference"])] ?? []),\n}));\nconst updates = filter(lines, (one) => !isNull(one.note)).map((one) => ({\n record_id: toString(one.note?.id),\n note: `Transfer of ${toString(one.line["Amount"])}`,\n ticked: toNumber(one.line["Amount"]) == sumBy(filter(owed, (owing) => owing.id == one.note?.id), (owing) => coalesce(owing.data["amount"], 0)),\n data: { paid_on: toString(one.line["Paid on"]) },\n}));\nconst unplaced = filter(lines, (one) => isNull(one.note)).map((one) => ({\n title: `${toString(one.line["Reference"])}: ${toString(one.line["Amount"])}`,\n note: "Matches no unpaid note",\n}));\n\nreturn({\n status: "success",\n message: `${size(updates)} of ${size(lines)} transfers match a note`,\n data: { review: { version: 1, table_id: "notes", creates: [], updates, unplaced } },\n});\n```\n\n### Saving a record with its lines\n\n`save_record`, a workflow step, saves one record and the rows of other tables that hang off it, all or\nnothing, and recognises what exists by the identity the input carries \u2014 so a call sent twice updates and\nadds nothing. `match` is the record\'s identity, `{ field_key: value }`, and must be exactly a uniqueness\nits table declares: one field marked `unique`, or the fields of one of the table\'s `unique` sets together\n(text, number, date, single select and one-row link fields). A `match` that is no declared uniqueness is\nrefused, naming what the table does declare. A live record carrying the match is found and only the cells\nof `set` that differ are written; none is found and it is added with `match` and `set`. `add_to` appends\nto a files, multi-select or many-link field and keeps what it holds.\n\n`lines` saves rows of other tables that link the record. Each entry names the child `table_id`, its\n`link` \u2014 a field of the child holding one row of this table, which the step fills and no row states \u2014\nthe child fields that, with `link`, are one uniqueness of the child table (`match`), and `rows`, each\nstating every `match` field. A line found by its match changes only the cells that differ, one not found\nis added, and a line the call does not list is left as it is: the step deletes nothing. Two rows of one\nentry cannot share a match.\n\nEverything is checked before the first write, and everything is written in one transaction: a table check\nthat refuses one line, a locked record or line the call would change, a value a field does not take, or a\ntable the run may not write refuses the call and saves nothing, naming the row. Two calls of one identity\ntake turns, so the second finds what the first saved. The answer is `record_id`, `created`,\n`updated_field_keys` (the cells changed on a record that existed) and, per entry of `lines`, the ids it\nadded, changed and left unchanged; these ids are typed for their tables, so a declared `record_link`\noutput or a follow-up write links straight to them.\n\n```\nconst inputs = trigger.app_workflow.inputs;\n\nconst sheet = await excel_get_rows({ // a workflow step\n file_id: inputs.release_order,\n columns: ["Size", "Quantity"],\n});\n\nconst saved = await save_record({ // a workflow step\n table_id: "release_orders",\n match: { number: inputs.number },\n set: { note: "Read from the paper" },\n lines: [{\n table_id: "release_lines",\n link: "order",\n match: ["size"],\n rows: sheet.rows.map((row) => ({\n size: toString(row["Size"]),\n quantity: toNumber(row["Quantity"]),\n })),\n }],\n});\n\nreturn({\n status: "success",\n message: `${size(saved.lines[0].created_ids)} lines added`,\n data: { record_id: saved.record_id },\n});\n```\n\n## Reaching a connected service\n\nA service is reached only by a workflow step, and a step is called inside an app\'s workflow or an automation. From\nnothing to an app\'s run:\n\n1. `get_workspace` with `kind: "connection"` lists each `connected_account_id`.\n2. `set_app` declares a Workflow node that states what a call takes and hands back:\n\n```json\n{\n "type": "Workflow",\n "key": "invoice_order",\n "description": "Issues the KiotViet invoice for a confirmed order and hands back its code.",\n "inputs": { "order": { "type": "record_link", "entity": "order" } },\n "outputs": { "invoice_code": { "type": "text" } }\n}\n```\n\n3. `set_app_workflow` with the app, the alias `invoice_order` and this `source` (the step\'s entry is in `workflow_steps`):\n\n```\nconst order = await get_record({ table_id: "orders", record_id: trigger.app_workflow.inputs.order });\nconst made = await kiotviet_create_invoice({ // a workflow step\n connected_account_id: "cac_\u2026",\n branch_id: 1,\n sold_by_id: 7,\n lines: [{ product_id: 42, quantity: toNumber(order.data["quantity"]), price: toNumber(order.data["price"]) }],\n});\nreturn({ status: "success", message: "Invoiced", data: { invoice_code: made.invoice.code } });\n```\n\n4. `run_app_workflow` with `dry_run: true`, then the same call without it.\n\nA step wants the provider\'s own ids (`branch_id`, `sold_by_id`, `product_id`) and no tool lists them: declare a Workflow\nwhose body calls the provider\'s `list_*` workflow step (`kiotviet_list_branches`), run it, and read the ids from its answer.\nNo step lists KiotViet\'s users: read `sold_by_id` off a row `kiotviet_list_invoices` returns (KiotViet\'s `soldById`), or ask the owner.\n\n## Table checks\n\nA table check is one condition its table\'s record writes must not meet, declared beside the table\'s\n`unique`. It is not a workflow: it reads the write and a few lookups and answers yes or no, so it\ncannot change data. It runs inside the write, on every create, update or delete it is `on`, whichever\nsurface makes it \u2014 a person, the API, the CLI, an agent, a workflow. A refusal writes nothing and\nanswers with the check\'s message, under its field when the check names one, and names the check.\n\nA check is stated in its table\'s document, under `checks`, each under an alias of its own, and\n`set_tables` writes it with the rest of the table: a merge patch edits one, `null` removes it, and a\ncall that makes a table can state its checks, a lookup naming another table of the call by its `ref`.\n`get_table` states a table\'s checks in its document.\n\n| part | what it holds |\n|---|---|\n| `on` | the writes it checks: any of `create`, `update`, `delete` |\n| `when` | optional expression; the check runs only on a write where it is truthy. It cannot read lookups. |\n| `lookups` | optional, by name, the rows to read: `{ table, filter, limit }`, `table` an id or the `ref` of a table of the call, the filter as `query_records` takes it with fields and select options named by alias (`id` matches a row by its own record id), `limit` from 1 to 50 (50 when left out). A filter value that reads the record is `{ "$expr": "record.code" }`. |\n| `fail_when` | expression; truthy refuses the write |\n| `message` | expression for the refusal\'s text \u2014 a fixed text is a quoted string |\n| `field` | optional; the alias of the field the refusal is shown under |\n| `enabled` | `false` keeps the check without deciding writes; absent, it decides |\n\nEvery expression is written in the grammar above, names a table\'s fields and select options by\nalias (`record.status == "closed"`), and reads:\n\n- `record` \u2014 the record as it will be stored; on a delete, the record being deleted.\n- `prev_record` \u2014 on an update or a delete, the record before.\n- `changes` \u2014 on an update, the fields it changes. `!isNull(changes.photos)` asks whether a field changed.\n- `lookups.<name>` \u2014 in `fail_when` and `message`, the rows that lookup read. A row reads as `record` does: its fields by alias beside `id` (`row.start_on`), and a lookup field as the flat list of what it looks up (`first(row.booking_start)` through a one-row link). A lookup over the written table never returns the row being checked, and finds its other rows as the write leaves them, this call\'s other rows included. A field given to a helper as text names it by alias too (`sumBy(lookups.paid, "amount")`); text that names no field of the rows\' table is refused when the check is saved.\n- `runtime.timezone`, `runtime.workspace_id`, `runtime.organization_id`, `runtime.change_origin` (the\n write\'s origin), `runtime.triggered_by_member_id` (the member writing; null for a write no member\n made), `now()`, and `member_of("grp_\u2026")` \u2014 true when the member writing belongs to the group.\n\nIn a check a select reads as its one value (`record.kind == "retail"`), and a link (a one-row link too) and a lookup read as lists (`first(record.customer)`).\n\nA check on several operations reads only what all of them carry, so one on creates and updates reads\nneither `prev_record` nor `changes`.\n\nA write that recomputes another table\'s rows (a rollup over lines, a lookup or a formula over a parent) judges each recomputed row as an update, by the checks `on` `update` that read a field the recompute changed; `changes` is what the recompute changed. A check that refuses on `changes` or on a status the row holds can therefore refuse the write that moved the parent, so it is gated on the field it means (`!isNull(changes.status)`).\n\nA lookup reads with the access of the member who last saved the check, so saving one requires reading\nevery table its lookups read. It reads committed rows: two writes at the same moment can each pass a\ncheck the other would fail, so values no two rows may share are the table\'s `unique`, not a check.\n\nA `when`, a lookup or a `fail_when` that cannot decide \u2014 it fails, or the write\'s time runs out \u2014\nrefuses the write, naming the part it was reading and, for an expression or a filter, what it met\n(`lookups.clash: Filter on "start_on": date \'before\' value \u2026 is not a date`). A draft is checked when it is submitted, as a create; its edits before that are\nnot. A restored record is checked as a create. Cloning a table copies its checks to the member who\nclones it, and is refused when that member cannot read a table a check\'s lookup reads.\n\nOne open order per reference, a row never counting against itself:\n\n```json\n{\n "tables": [\n {\n "ref": "orders",\n "label": "Orders",\n "fields": {\n "reference": { "type": "text", "label": "Reference" },\n "closed_at": { "type": "date", "label": "Closed at" }\n },\n "checks": {\n "one_open_order_per_reference": {\n "on": ["create", "update"],\n "when": "isNull(record.closed_at)",\n "lookups": {\n "same_ref": {\n "table": "orders",\n "filter": {\n "node_type": "group",\n "logic": "and",\n "children": [\n { "node_type": "condition", "field_key": "reference", "operator": "equals", "value": { "$expr": "record.reference" } },\n { "node_type": "condition", "field_key": "closed_at", "operator": "is_empty" }\n ]\n },\n "limit": 2\n }\n },\n "fail_when": "some(lookups.same_ref, (r) => r.id != record.id)",\n "message": "\\"An open order already has this reference.\\"",\n "field": "reference"\n }\n }\n }\n ]\n}\n```\n\n## Table workflows\n\nA table workflow is what follows every committed write of a table\'s rows, whoever makes it: the grid, an import, an agent or\nany app. An effect that must hold for all of them belongs to the table. A button on one screen is that app\'s act, a value fixed\nwhen a row is made is `default_from`, and a rule that refuses a write is a check; a table workflow runs after the write has\ncommitted and cannot refuse it.\n\nIt is stated in the table\'s document, under `workflows`, each under an alias of its own, and `set_tables` writes it with the\nrest of the table: a merge patch edits one, `null` removes it, and `get_table` states a table\'s workflows in its document, with\nthe failures of the last 7 days beside them, to whoever manages the table. A dry run of `set_tables` with a `record_id` says what\neach workflow the call adds or changes would write on that row; reads are real and nothing is written.\n\n| part | what it holds |\n|---|---|\n| `on` | the write it follows: `create`, `update` or `delete` |\n| `when` | optional expression over `record`, `prev_record` and `changes`; the workflow runs only after a write where it is truthy |\n| `changed` | optional, with `on: update`: field aliases; the workflow runs only after an update that changes any one of them |\n| `body` | the steps, written as an app workflow\'s body is |\n| `enabled` | `false` keeps the workflow without running it; absent, it runs |\n\n```json\n{\n "tables": [\n {\n "ref": "repairs",\n "label": "Repairs",\n "fields": {\n "status": { "type": "select", "label": "Status", "options": [{ "alias": "queued", "label": "Queued" }, { "alias": "fixed", "label": "Fixed" }] },\n "fixed_on": { "type": "date", "label": "Fixed on" }\n },\n "workflows": {\n "stamp_fixed": {\n "on": "update",\n "changed": ["status"],\n "when": "record.status == \\"fixed\\"",\n "body": "await update_records({ table_id: \\"repairs\\", record_ids: [trigger.record_id], set: { fixed_on: formatDate(now(), \\"yyyy-MM-dd\\") } });"\n }\n }\n }\n ]\n}\n```\n\nThe body names the table\'s fields, select options and other tables by alias, and reads the write as `record`,\n`prev_record` and `changes` and the row\'s id as `trigger.record_id`. It operates on records and integrations: a body\nthat calls a tool changing a table, an app or any other structure is refused when it is saved. It runs as the table\'s owner, so\nit reaches the tables the owner reaches, and its writes meet the table\'s checks like any other write; a write refused by a\ncheck fails that run and leaves the row as it was. A body names a field by alias where it can tell which table\'s field it is: a\nread of `record`, `prev_record` or `changes`, a row from `get_record` or `query_records` called with a literal `table_id`, and a\nloop over such rows. A name it cannot tie to a table, and an option compared to a value of unknown type (not a read of a field, and\nnot a text, number or other value the type proves holds no option), are refused with what to change. A link reads as the list of the row ids it holds, a one-row link too: compare `first(record.customer) == id`. A lookup reads as one flat list of what it looks up: `record.x != prev_record.x` on one is refused, so compare `first(record.x)` with `first(prev_record.x)`. A single member, a single select, a number or a text reads as the value itself: `first(record.technician)` is refused at save, naming the read.\n\nA write the workflow itself makes sets off the workflows of its table in turn, and a chain of them stops after five. A workflow\nthat writes the row it follows is gated by a `changed` or a `when` that its own write does not satisfy. One run is queued for a\nrow and a workflow at a time, so a burst of writes to one row runs it once more after the run in flight. A field a workflow\nreads cannot be deleted or retyped until the workflow is changed or removed in the same call or before. Cloning a table copies its\nworkflows for whoever manages it (`include_workflows: false` leaves them), each with a body of its own, and is refused when a\nbody names a table the cloner cannot reach. A workflow runs as the table\'s owner, so whoever saves or rehearses one must manage the\ntable and read every table it reads in full: the body\'s, the gate\'s links\', the written table. An option is written where\nit is compared to, listed against or set on its field; a constant holding one is refused.\n\nA lock is the table\'s: a table workflow calls `lock_records` on a row (an app\'s act may too), and every door then refuses\nthe row\'s writes until whoever manages the table calls `unlock_records` \u2014 in an app\'s run, judged as the member who pressed.\nA locked row reads locked in every app. An app\'s own Check or `edit: false` only makes a row read-only in that app.\n\n## Runs\n\nEach start of a workflow is a run: an app\'s workflow or act, a table workflow, a button, an automation. An agent finds,\nreads, retries and stops runs on chat, MCP and the CLI. A run is read by whoever manages what its workflow belongs to: its\nautomation or its table, and for an app\'s workflow the app\'s owner or an admin, never a manager of the app.\n\n- **Find.** `query_workflow_executions` lists one workflow\'s runs (`workflow_id`), every run started for one row\n (`record_id`) \u2014 a table workflow\'s written row, a pressed button\'s row, an app run\'s `record_id` input \u2014 or both, newest\n first, a page at a time (`next_cursor`). Each run names its workflow and kind, what started it, its row and how it ended:\n for a failure, the step that threw, its tool and the error; for a waiting run, when it wakes, or the approval and the\n approvers it waits on, or the payment. A run started before runs recorded their row carries no `record_id`.\n- **Read.** `get_workflow_execution` gives one run\'s steps in order with each one\'s tool, input, output and error, what it\n returned, how many rows it wrote (a row\'s own history is in `query_audit_logs`) and the runs its writes started. Ninety days\n after a run starts, its trigger and its steps\' input and output are pruned; the read says so and gives the rest.\n `run_app_workflow` answers each run it makes with its `execution_id`.\n- **Retry.** `retry_workflow_execution` runs a failed, refused or cancelled run again as a new run linked to the first\n (`retry_of`): the first run\'s trigger and row, the workflow\'s current steps and its owner\'s current access, with the\n member who retries it as the one who started it. A run is retried once; a retry that fails is retried in turn. It asks\n first when the run, or a run it retried, already wrote rows or called a service no delete reverses. An automation\'s or a\n table workflow\'s retry runs in the background, as its runs do, and answers `queued`; a button\'s and an app\'s run while\n the call waits. An app\'s call is checked again against the workflow\'s current inputs. A pruned run, a run of a workflow\n that is off, a reviewed act\'s run (call the act again) and a recording\'s filing (the recording retries its delivery)\n are not run again.\n- **Stop.** `cancel_workflow_execution` stops a waiting run and withdraws its pending approvals.\n\nOff means off: a waiting run whose workflow was turned off, deleted, or taken off its automation, table, button or app ends\n`cancelled` when it would resume, saying why, and runs no further step.\n\nA waiting run keeps its version: when it resumes, it runs the steps of the version it started with, so an edit saved while it\nwaited changes later runs only. When that version is gone, the run ends failed and says why.\n';
|
|
1791
|
+
var workflows_default = '# Workflows \u2014 the steps a workflow runs\n\nA workflow is written as a strict subset of JavaScript. The source is never run as JavaScript: a\nsave parses it into steps, type-checks it against what its trigger supplies, and stores the steps.\nOnly the forms below are accepted; anything else is refused at save with the line, the column and\nwhat to write instead. A save that succeeds may still return `warnings` \u2014 advisory hints such as a\nloop that may never end. Read them.\n\nA workflow changes records, calls integrations, notifies and makes papers. A tool for anything else \u2014 views, history,\ntables and apps, members, knowledge, skills, editing a file \u2014 is an agent\'s, on chat, MCP or the CLI, and a body that\ncalls one is refused at save with that reason. One saved before is refused at run, before its first step.\n\nThe same grammar serves an app\'s workflow, which `set_app_workflow` saves, an automation and the expressions of a table\ncheck. An app workflow takes its trigger from the app that calls it; only an automation\'s source opens with a trigger\ndeclaration.\n\n## Triggers\n\nAn app workflow reads its call as `trigger.app_workflow.inputs.<name>`, or `inputs.<name>`, which is the same read. A Workflow node receives the inputs it declares;\nan Act\'s `workflow` receives `record_id` (`record_ids` on an act over several rows) and what the Act asks.\n\n### Automations\n\nAn automation answers an event outside the records: a schedule, a webhook, an inbound email. `set_automation` saves one\nfrom its source \u2014 the `on({...})` declaration, then its steps \u2014 and, given its `automation_id`, replaces it: the\ntrigger config is the one the header states, a key it leaves out removed, and steps left as it runs them are checked again\nonly when the call turns it on, so one a later rule refuses can still be switched off. `get_automation` reads that source back with its latest\nruns, and `dry_run_workflow` rehearses a source before it is saved. It runs with its owner\'s current permissions, a connected service it names is its owner\'s account, and only its\nowner or an admin reads, changes or deletes it.\n\n```\non({ type: "recurring_schedule", cron_expression: "0 9 * * MON" });\n```\n\nEvery source below is typed, so a misspelled member is refused at save rather than read as nothing.\nAll three carry `trigger.trigger_id`.\n\n| trigger | required config | what the body reads |\n|---|---|---|\n| `recurring_schedule` | `cron_expression` | `runtime.timezone`; the current time is `now()` |\n| `receive_webhook` | `secret`? | `trigger.method`, `trigger.headers[...]`, `trigger.query[...]`, `trigger.body` (any JSON, or raw text when the request is not JSON), `trigger.received_at` |\n| `receive_gmail_email` / `receive_outlook_email` | `connected_account_id`, `filter` | `trigger.email_id`, `trigger.from`, `trigger.to`, `trigger.cc`, `trigger.bcc`, `trigger.subject`, `trigger.date`, `trigger.body` (plain text), `trigger.reply_to`, `trigger.in_reply_to`, `trigger.attachments` (file refs \u2014 assign them straight to a files field). One type serves both providers, so `labels`, `thread_id`, `importance` and `conversation_id` are not readable. |\n\n## Steps\n\n| step | syntax |\n|---|---|\n| tool call, kept | `const <id> = await <tool>({ ...inputs });` \u2014 also `let x = await \u2026` and `x = await \u2026` |\n| tool call | `await <tool>({ ...inputs });` \u2014 the platform\'s own steps (`send_notification`, `generate_document`, \u2026) and a connection\'s tools, each a workflow step (`bkav_submit_invoices`, `kiotviet_create_order`, \u2026); a step that fails ends the run with its reason unless a `try`/`catch` around it handles it |\n| agent | `const <id> = await agent({ instructions, input, model, output });` \u2014 see **An agent step** |\n| app agent | `const <id> = await app_agent({ alias: "<agent alias>", input: { ... } });` \u2014 runs one of the app\'s declared agents, in an app workflow only, and resolves to its declared outputs or its final text. `wait: false` only starts the run and resolves to `{ run_id }`; the run\'s failure is then its own, not the workflow\'s. Refused in a run an agent started. |\n| bind | `const <id> = <expression>;` \u2014 evaluated once; later steps read `<id>`. Bind any expression used twice. |\n| if / else | `if (<expr>) { ... } else { ... }` \u2014 `else` optional, `else if` chains allowed |\n| switch | `switch (<expr>) { case "X": { ... } default: { ... } }` \u2014 string cases only, no fallthrough |\n| for | `for (const <name> of <expr>) { ... }` |\n| wait | `await wait({ duration_in_minutes: 5 });` |\n| wait for an event | `await wait_for_event({ event_type: "webhook", event_ref: <expr>, timeout_in_minutes: 60 });` |\n| wait for an approval | `await wait_for_approval({ approvers: <expr>, prompt: <expr> });` \u2014 see **Waiting for an approval** |\n| return | `return({ status: "success" \\| "error", message: <expr>, field_errors: { ... }?, data: { ... }? });` \u2014 `data` holds the outputs a Workflow node declares |\n| validate | `validate({ checks: [{ fail_when: <expr>, field_key: "...", message: <expr> }, ...] });` |\n\nA delayed run is never cancelled from a workflow: after a `wait`, it reads the row\'s state and acts only if it still holds.\n\nA tool\'s inputs and answer are in its own description; the steps only a body runs \u2014 a connection\'s, a mail\'s \u2014 are each\nan entry of `workflow_steps`. A person connects a service in Lotics under Settings \u2192 Connections; a step names that\nconnection by its `connected_account_id`, which `get_workspace` with `kind: "connection"` lists for the connections the\ncaller can use. The app\'s owner and whoever saves the body must both be able to use that connection.\n\nIn a workflow, email reaches a member of the workspace through the step `send_notification`: Lotics shows it to them and\nmails it to their own address, with no connection. Its recipients are members by `member_id` (`get_workspace` with\n`kind: "member"` lists them), member groups or the whole organization. Mail to anyone else goes from a person\'s\nconnected Gmail or Outlook (`gmail_send_email`, `outlook_send_email`), which leaves an unsent draft unless `draft` is\n`false`.\n\n`return` is a call, not a JavaScript `return` statement. `validate` ends the workflow with an error\nwhen a check\'s `fail_when` is truthy; the check\'s `field_key` names the control its message lands on \u2014\nin an app workflow a declared input, or the alias of a field on a table the body names; in an\nautomation it is not checked.\n\nThe name in `const x = await tool({...})` is the step\'s id; later expressions read its output as\n`x.records`, `x.id` and so on. A `// id: my_step` comment directly above a statement sets an explicit\nid; any other `//` comment there becomes the step\'s description.\n\n**`await` inside a statement** runs the call as its own step first, then reads its output where it is\nwritten: `size((await query_records({...})).records)`, an `if` test, a `return` value, a `for-of`\niterable, a tool input. `const x = c ? await t({...}) : null;` is stored as the `if` it means. An\n`await` that would run on some paths only \u2014 inside `&&`, `||`, `??`, `?.`, a nested ternary, a loop\ntest, a `validate` check \u2014 is refused; write the `if`.\n\n**Names are block-scoped, as in JavaScript** \u2014 `const`, `let`, loop items and `catch (e)`: sibling\nblocks may reuse a name and an inner block may shadow an outer one. A local may take a helper\'s name\n(`const size = 3;`), but calling `size(...)` while it is in scope is refused. The names of the tools a\nworkflow calls, the reserved roots, `linked` and `_s` followed by digits cannot be bound.\n\n### Waiting for an approval\n\n`wait_for_approval` is the wait worth binding \u2014 `wait` and `wait_for_event` carry nothing to read.\nBind it to branch on the decision:\n\n```\nconst approval = await wait_for_approval({\n approvers: trigger.app_workflow.inputs.approvers,\n prompt: `Approve this quote?`,\n});\n\nif (approval.status == "approved") {\n // the approved path\n} else {\n // the rejected or timed-out path; approval.decision_comment says why\n}\n```\n\nIt resolves to `{ status: "approved" | "rejected" | "timed_out", decided_by: MemberId | null,\ndecided_at: ISO string, decision_comment: string | null }`.\n\n`approvers` takes a bare member id (`"mbr_\u2026"`), a bare group id (`"grp_\u2026"`) or a full principal\n(`{ type: "member_group", id: "grp_x" }`), alone or in a list \u2014 so a member field\'s value passes as it\nis.\n\n### An agent step\n\n`const x = await agent({ instructions, input, model, output });` runs a model as one step \u2014 read a paper, classify,\ndraft text. It calls no tool: the workflow\'s own steps write, send and notify with what it returns, and a step that\nstates `tools` is refused.\n\n- `instructions` \u2014 a fixed string.\n- `input` \u2014 an object literal; its fields are expressions, evaluated and handed to the model (`{}` for\n none). It is the ONLY workflow data the agent sees: read the trigger, prior steps, `runtime` or the loop\n item here, never inside `instructions`, which is a fixed literal and reads no binding. A field holding a file, or a\n list of files (a files field, a mail\'s attachments), hands the agent the files themselves: a picture or a PDF it\n sees, any other paper read into text.\n- `model` \u2014 optional; omit it to follow the platform\'s default model.\n- `output` \u2014 `{ mode: "text" }` resolves to a string; `{ mode: "object", schema }` to a typed object.\n\nOnly `x` comes back: in text mode `x` is the string itself, in object mode `x.field` reads a field the\n`schema` declares.\n\n## Naming tables and templates\n\nA body names a table or document template by its alias (`query_records({ table_id: "orders" })`); ids still work. `get_app_workflow` and `get_automation` show aliases. An app\'s workflow reaches only the tables the app uses.\n\n## Fields and options\n\n1. **A body names a table\'s fields and select options by alias**, the names the table\'s document states: a field reads\n as `record["status"]`, an option as `"done"`, a linked field as `linked(record["customer"])[0]["name"]`, in the\n body of a Workflow node, of an Act\'s own `workflow`, of an automation (`set_automation`) and of a table\'s workflow. It is stored by key and `get_app_workflow`, `get_automation` and `get_table` show it by alias\n again. A field\'s or option\'s key is accepted where its alias is and is kept as written; a display name is refused with the\n aliases there are, and a row that names one field by both its alias and its key is refused. A row written to a table is\n stored by field key wherever the save can follow it to the record tool that writes it: a row or list written in the call,\n a conditional of them, a name holding them, pushed to a `let`, and through `map`, `flatMap`, `concat`, `filter`, `sortBy`,\n `slice` and `reverse`. A field of such rows is read by alias as well (`picks.toSorted((a, b) => a.quantity - b.quantity)`,\n `sortBy(picks, "quantity")`, `rows[0].note`) and stored by key. A body that cannot be stored by key is refused at save,\n naming the statement.\n2. **An Act\'s own `workflow`** has the app\'s guards placed at the top of the body, between `<lotics:guards>` tags,\n replaced on every save: a save that leaves them out or edits them gets the app\'s again, at the top wherever the tags\n sat, so the act\'s row rule, `when`, `requires`, checks and locks always run first.\n3. **A value is read as the record tools store it.** A single select reads as its option and a member as its id; an\n option is written by its alias wherever it meets a select \u2014 compared, listed, switched on, written \u2014 also beside a\n name set from the field (`const s = record.status; s == "done"`), and a select\n value handed on whole (returned, written to a text field) is its key. An option\'s alias searched for in a list the\n save cannot place it against (`includes(filter(record.tags, \u2026), "hot")`) is refused: search the field itself. A link reads as the list of its row ids, a\n one-row link too: `first(record.customer)` is the one id, and `record.customer` used as one id is refused at save,\n naming `first(record.customer)`. A one-row link is written with its one id or a list of one; a many-row link with a list. A lookup reads as one flat list of the values it looks up: `includes(record.owner,\n me)`. A select\'s value is one of another select\'s options only where that table shares its keys, by having been\n duplicated from it, not where an option merely shares its alias. A row that takes its parent\'s option gets it from\n `default_from`, and a lookup shows it.\n4. **What an app sends takes an option by alias or key.** An input or output drawn from a field (`field`, written\n `<table id>.<alias>`) compares and returns by alias in the body, and the run hands it on as the option\'s key. A\n refusal\'s `field_errors` and a `validate` check\'s `field_key` name a declared input or a field\'s alias.\n5. **`==` against a multi-select means "includes".** `record["tags"] == "urgent"` on a multi-select is stored as\n `includes(record["tags"], "urgent")`; either spelling works.\n6. **Text renders labels.** Inside a template or a text tool input, a select, member or link value renders its display\n text: `` `${record["status"]}` `` writes "Done", not `done`.\n\n## What an expression reads\n\n- `record`, `prev_record`, `changes` \u2014 in a table check, the write it checks (see **Table checks**).\n `changes` is a PARTIAL map: `changes["x"]?.next_value` and `?.prev_value`, the `?.` required.\n- `trigger` \u2014 what the trigger carries (see **Triggers**).\n- a loop\'s item name, and `index`, the iteration\'s 0-based index.\n- `<step id>` \u2014 an earlier step\'s output (`dup.records`).\n- `runtime.timezone`, `runtime.workflow_id`, `runtime.execution_id`, `runtime.workspace_id`,\n `runtime.organization_id`, `runtime.change_origin`, `runtime.triggered_by_member_id`. The current\n time is `now()`.\n\n## Paths\n\n- `record.x` and `record["x"]` read a field; `[n]` reads a list\'s n-th item.\n- A path may start at any expression: an awaited call `(await get_record({...})).data["x"]`, a helper\'s\n result `first(found.records).id`, a filter `rows.filter((r) => r.ok)[0].n`, a `(a ?? b).c`. `?.` reads\n through a null as usual, except on a value drawn at random (`sample(xs)?.id` is refused: bind it first).\n- A record reads in its declared shape wherever it comes from \u2014 the trigger, a tool result, a `let`, a\n callback parameter: a single select as its option alias or null, a member as its id, a link as its ids. (The record\n tools called outside a workflow return the stored arrays instead.)\n- `linked(record["link"])[n]["x"]` fetches the n-th linked row and reads a field of it; bare\n `linked(record["link"])` is every linked row, one fetch per row. Its argument is a path ending on\n a link field and carries the guard: `linked(record?.["link"])[0]`.\n- `changes["link"]?.next_value` is the same list of ids; `linked()` over a change is refused \u2014\n read `linked(record["link"])` or `linked(prev_record["link"])`.\n\n## Operators and statements\n\nOperators in JavaScript precedence: `? :`, `||`, `??`, `&&`, `== != === !==`, `< <= > >=`, `+ -`, `* / %`,\nprefix `! -`.\n\n- Arithmetic is exact decimal (`0.1 + 0.2` is `0.3`). A result computed from a division that does not end\n is the short decimal it reads as (`1 / 3 * 3` is `1`), and stands as computed where it reads long\n (`1 / 3 * 100` is `33.33333333333333`). A comparison between two numbers reads both at 15 significant\n digits. `round` and `formatNumber` round the decimal, a half away from zero: `round(-2.5)` is `-3`. A\n table filter compares stored values exactly.\n- `??` is `coalesce(left, right)`; `?.` reads through a null (`a?.b`, `a?.[0]`, and `s?.trim()` for the\n method names listed under **Helpers**). `x?.()` is refused: helpers and tools are not values.\n- Templates use backticks and `${...}`.\n- Destructuring \u2014 `const { status, name } = record;` binds each name. Renames\n (`{ status: s }`), quoted keys (`{ "ref": ref }`), array patterns with holes\n (`const [first, , third] = xs;`) and defaults (`{ note = "" }`, which apply on null too) work on\n `const`, `let` and `for (const { id } of rows)`. A tool result destructures directly:\n `const { records } = await query_records({...});`. `const a = 1, b = 2;` declares both. Nested\n patterns and rest are refused.\n- Spread \u2014 `[...a, b]` and `{ ...a, b: 1 }` (later keys win). Refused inside a tool input: bind the\n merged value first and pass the binding.\n- Shorthand \u2014 `{ table_id }` is `{ table_id: table_id }`.\n- `xs.push(a, b);` appends, also on a key (`o.items.push(v)`); `o.a.b = v;` sets a nested key;\n `x ??= v`, `x ||= v` and `x &&= v` assign. A `const` may be pushed to or have a key set, as in\n JavaScript; a loop item is read-only.\n- `undefined` is the same value as `null`, so `x === undefined` is `isNull(x)` and `x !== undefined` is\n `isNotNull(x)`; a key set to null and a key never sent are not told apart (`"k" in o` tells a key that\n is there).\n- `typeof x` answers JavaScript\'s words, and a null (a missing value too) is `"object"`. `k in o` tests an\n object\'s own keys, or a list\'s indexes and `length`.\n- A filter node may leave out `node_type` when its keys say which it is: `field_key` / `operator` /\n `value` a condition, `logic` / `children` a group, `path` / `condition` a traversal.\n- `function name(p = <default>) { return <expr>; }` \u2014 top level only, expanded at every call (a call\n may come before it). Every parameter needs a default, which gives it its type; the body is one\n `return <expr>;` reading only its parameters, helpers and the reserved roots \u2014 never the caller\'s\n names, nor `index`. No recursion; a function never called is refused. A body read back shows the\n expression at each call, not the `function`.\n\n**try / catch.** `try { ... } catch (e) { ... }` catches a tool error or an expression error inside the\nbody; `e` is `{ message, type, step_id?, detail? }`. A failed `validate` and a `return` are not\nerrors \u2014 they end the workflow \u2014 and a step that runs after a wait inside the `try` is outside it.\n`finally` is refused: put always-run steps after the `try`.\n\n**Loops.** `for-of`, `while (cond) { ... }`, `do { ... } while (cond);` and\n`for (let i = 0; i < n; i++) { ... }`, each capped at 10,000 iterations. `break;` and `continue;` act\non the innermost loop; labels and `for-in` are refused.\n\n**`let`.** `let x = <expr>;` declares a block-scoped variable; the initializer is required\n(`let x = null;`). Reassign with `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `??=`, `++` and `--`. Re-declaring in\nthe same block is refused; shadowing in a nested block is allowed. A `let` keeps its value across a\n`wait`, `wait_for_event` or `wait_for_approval`.\n\n**Refused, each because a save must know what runs and a run must give the same answer twice:** regex\n(unbounded matching: use the string methods), `Math.random`, `Date.now()` and `new Date()` (use `now()` and\n`parseDate`), a tool or helper as a value or a computed callee (callees are named), `new` of anything,\n`instanceof`, `delete`, `void`, rest elements, computed keys, classes, `throw`, `finally`, `import`,\n`export`, `eval`, labels, and a function as a value (`const f = (x) => ...`: pass the arrow to the helper\nthat takes it). `xs.sort(...)` on a name or path is refused too, since a value is never changed in place:\nwrite `xs.toSorted(...)` or `[...xs].sort(...)`.\n\n## Typing\n\n- The body is type-checked as TypeScript before it is read: annotations, `as`, `satisfies` and a non-null `!`\n are accepted and erased, and the checker is strict about null. The stored body keeps none of them and is checked again as\n stored, by field key, as every later change to its app or tables checks it: a body that types only through one of them is\n refused at save.\n- Narrow what is not typed (a connector\'s JSON is `unknown`) with `typeof`, `in`, `Array.isArray` and `isNull`.\n- `first(xs)` may be undefined; `requireFirst(xs)` is the list\'s first item or an error.\n- `obj[key]` needs `key` to be a key of `obj`, or `obj` to be a record with a string index.\n\n## Helpers\n\nOrdinary JavaScript works \u2014 the standard library below, the array and string methods, `Math`, `JSON`,\n`Object`, `Array`, `Number`, `parseInt` \u2014 and means what it means in JavaScript, with the differences\nlisted here. Where JavaScript answers NaN, Infinity or undefined, a workflow has none of them and the\nanswer is `null`.\n\n- Standard library: `Math.abs/ceil/floor/trunc/sign/min/max/pow/sqrt`, `Number(x)`, `String(x)`,\n `Boolean(x)`, `parseInt(s, radix?)`, `parseFloat(s)`, `Number.isFinite/isInteger`,\n `Object.keys/values/entries/fromEntries`, `Array.isArray/from` (a list, text, or `{ length: n }`),\n `JSON.parse/stringify`.\n- Arrays: `length, map, filter, find, findIndex, findLast, some, every, reduce, includes, indexOf, join,\n slice, concat, flat, flatMap, at, reverse, toReversed, toSorted` and `[...xs].sort(compare?)`.\n- Strings: `length, toLowerCase, toUpperCase, trim, trimStart, trimEnd, startsWith, endsWith, includes,\n indexOf, slice, substring, split, replace, replaceAll, padStart, padEnd, repeat, charAt, at,\n localeCompare` (en-US order, wherever it runs). Numbers: `toFixed, toString`.\n- Differences from JavaScript: `Math.round` is the engine\'s `round` (a half goes away from zero:\n `Math.round(-2.5)` is `-3`); `Number(x)` and `parseFloat(x)` are `toNumber`, which refuses text that is no\n number; `String(null)` is `""`; `join` writes a null item as `null`; `concat` skips a null; `toFixed`\n rounds the decimal, not the binary double; `replaceAll` writes the replacement as text\n (`$&` stays as typed), while `replace` expands `$&`, `$$`, `` $` `` and `$\'` in it as JavaScript does; `split` takes no limit; `sort` without a comparator orders by text, as JavaScript does. `==`, `!=`, `===` and `!==` between two list values (files, a multi-select, a link of any cardinality, a lookup) are refused at save, since a list compares by identity, not by its items: `!isNull(changes.<field>)` asks whether one changed.\n\nThe same work can be written with the helpers below, which are additions to JavaScript. They are called as\n`size(arr)`; the method form works only where the name is also a JavaScript method \u2014\n`x.trim()`, `arr.includes(v)`, `arr.at(-1)`, `arr.map(fn)`, `s.split(",")`; `arr.size()` is refused.\n\n- **Null and type**: `isNull`, `isNotNull`, `isEmpty`, `isString`, `isNumber`, `isBoolean`, `isArray`,\n `isObject`, `coalesce`, `get`, `toNumber`, `toFigure`, `toString` (a text, number, yes/no or date; a record, file or list is refused at save, an object at run), `typeOf`, `parseJson`, `toJson`. `isNull` is strict (`null` and `undefined` only), but on a list field of the record (a link, files, a multi-select) it asks whether the list holds nothing, since a cleared list is `[]`: `!isNull(record.vehicle)` reads as `!isEmpty(record.vehicle)`. `isNull(changes.<field>)` stays strict: null there means unchanged\n- **Lists**: `size`, `first`, `requireFirst` (the first item, refusing an empty list \u2014 after a\n `validate` on the size it saves an `if`), `last`, `nth`, `at` (`at(arr, -1)` counts from the end),\n `includes`, `filter`, `find`, `some`, `every`, `pluck`, `sortBy`, `groupBy`, `countBy`, `unique`,\n `uniqueBy`, `compact` (drops falsy items), `flatten`, `reverse`, `slice`, `concat`, `difference`,\n `differenceBy`, `intersection`, `intersectionBy`, `list`, `reduce(arr, (acc, x) => ..., initial)`,\n `range(end)` / `range(start, end)`\n- **Math**: `sum`, `sumBy`, `mean`, `meanBy`, `minBy`, `maxBy`, `round`, `ceil`, `floor`, `min`, `max`,\n `abs`, `mod`, `pow`, `sqrt`, `clamp`, `percentage`\n- **Text**: `upper`, `lower`, `capitalize`, `trim`, `contains`, `startsWith`, `endsWith`, `replace`,\n `replaceAll`, `removeChars(str, chars)` (without every character that occurs in `chars`),\n `removeSpaces(str)` (without any whitespace; a Word template command calls both too), `substring`, `length`, `split`, `join`, `padStart(str, length, char)`,\n `padEnd(str, length, char)`, `formatNumber(x, decimals)` (ungrouped; rounds the decimal, a half away\n from zero: `formatNumber(1.005, 2)` is `1.01`),\n `formatDecimal(x, decimals, locale)` (grouped: `formatDecimal(151000, 0, "vi-VN")` is `151.000`),\n `numberToWords(x, lang?)` (an integer in words: Vietnamese, or English with `"en"`),\n `uuidFrom(text)` (the same UUID for the same text, refusing null and empty text: `uuidFrom(record.id)`)\n- **Objects**: `keys`, `values`, `entries`, `nonNullKeys`, `pick`, `omit`, `merge`\n- **Dates**, in the workspace\'s timezone: `now`, `formatDate`, `parseDate`, `addDays`, `subDays`,\n `addHours`, `subHours`, `addMinutes`, `subMinutes`, `startOfDay`, `endOfDay`,\n `differenceInCalendarDays`, `differenceInHours`, `differenceInMinutes`, `isBefore`, `isAfter`,\n `isSameDay`, `isToday`, `isWithinRange`, `meetsBounds`\n- **Other**: `formatCurrency(amount, locale, currency)`, `randomNumber(len)`,\n `randomAlphaNumeric(len)`, `sample(items)` (a random item)\n\nOver a null or empty list `mean`, `meanBy`, `min`, `max`, `minBy` and `maxBy` answer null, while `sum`\nand `sumBy` answer 0: `maxBy(pieces, "reach")?.Name` prints a blank for a month with no pieces.\n\nA date helper given a null or empty date returns null, so guard its result before comparing or\nwriting it: `differenceInCalendarDays(a, b) ?? 0`.\n\n`filter`, `find`, `some`, `every`, `sortBy`, `pluck`, `sumBy`, `meanBy`, `minBy`, `maxBy`, `groupBy`,\n`countBy`, `uniqueBy`, `differenceBy` and `intersectionBy` take a path string\n(`filter(record.items, "Status", "open")`) or a callback\n(`filter(record.items, x => x.Status == "open" && x.Amount > 100)`); `reduce` takes a callback and an\ninitial value. A callback gets `(item, idx)` (`reduce`: `(acc, item, idx)`) and reads `record`,\n`runtime`, the names in scope, loop items and an enclosing callback\'s parameters. Its body is one\nexpression \u2014 `(x) => <expr>`, `(x) => { return <expr>; }` or `function (x) { return <expr>; }`;\nseveral statements, `async` and named function expressions are refused.\n\nLook a row up by key rather than filtering for it: a `filter` inside a callback walks its whole list again\nfor every item, so a thousand items against a few hundred rows is hundreds of thousands of calls. Group the\nrows once, then read each group by its key. `groupBy` answers an object of lists, and `byCode[code] ?? []`\nis the group a code names, or an empty list:\n\n```\nconst parties = await query_records({ table_id: "parties" });\nconst lines = await query_records({ table_id: "sheet_lines" });\nconst byCode = groupBy(parties.records, (p) => p.data["code"] ?? "");\nconst holders = lines.records.map((line) => ({\n line: line.id,\n parties: (byCode[line.data["code"] ?? ""] ?? []).map((p) => p.id),\n}));\n```\n\nA key is read exactly: a code holding a dot, a slash or a bracket reads its own group, and a name every\nobject inherits (`constructor`) reads nothing unless a row made a group of that name. `get(o, "a.b")`\nfollows a dotted path instead, so a key that holds a dot is read with `o[key]`.\n\n## Writing a field: null and undefined\n\nIn `update_records`\' `set` and `create_records`\' `records`:\n\n- `null` clears the field.\n- `undefined`, or leaving the key out, leaves the field as it is.\n\nSo passing a read that may be null (`record["x"]`) keeps a value when there is one and clears the\nfield when there is none. Use `coalesce(x, fallback)` only for a real fallback value.\n\n## Examples\n\nCompare a link by id. Display text is not unique, and a link reads as a list of ids \u2014 `==` against a\nstring is a type error:\n\n```\nconst found = await query_records({\n table_id: "customers",\n filters: { node_type: "condition", field_key: "name", operator: "equals", value: "ACME Corp" },\n});\nconst customer = first(found.records);\nconst order = await get_record({ table_id: "orders", record_id: trigger.app_workflow.inputs.order_id });\n\nif (customer && includes(order.data["customer"], customer.id)) {\n // \u2026\n}\n```\n\nRead an uploaded sheet and add the rows a table lacks. `excel_get_rows`, a workflow step, answers every\nrow of an `.xlsx`, `.xls` or `.csv` sheet in one call, each an object keyed by the header row\'s text. A cell is a number,\n`true` or `false`, text, a date as ISO text, or null when blank, so `row["Header"]` is typed\n`string | number | boolean | null` and a helper narrows it: `toString` for text, `toNumber` for a number,\n`isString` before a date. Every cell of a `.csv` is text. `columns` keeps only the ones the body reads.\nA sheet past what one call reads is refused with its size, never cut short: the step\'s entry in\n`workflow_steps` states the bounds.\n\n`import_rows`, a workflow step, matches those rows to a table by a key and writes what differs. `match`\nnames the fields that identify a row, compared as trimmed text with case ignored: a row matching no live\nrow is added, one matching a row changes only the cells that differ, and one that changes nothing is\ncounted and not written, so the same file run twice writes nothing the second time. `values` holds a\nrow\'s cells by field alias, as `create_records` takes them; a key the row omits is left as the table holds\nit, and `null` is an empty cell, which differs from a filled one as any other value does. `dry_run: true` judges the import as a real one would be, rolls it back and answers what it would write or why it is refused.\n\nA `match` field may also be a link field holding one row, compared by the row linked: lines keyed by\ntheir parent and a line number state `match: ["invoice", "line"]`, the parent given as its\nrecord id or as `{ lookup: "<text>" }`.\n\nWhat the step is not certain of is held \u2014 answered with why, and not written: a row carrying `hold`\n(its text is the message); a row whose key several live rows carry, or an earlier row of the same call;\na row the table\'s checks refuse, or whose row is locked; with `create: false`, a row matching none. A\nheld row never fails the step, and the rows beside it are written. A row whose key names a row outside\nthe rows the run may write is held `refused` with nothing of that row: no `record_id`, no `changes`.\n\nThe step removes no row, and a row the source leaves out is never touched. A row carrying\n`remove: true`, with its key in `values`, proposes moving the row its key names to the trash: it is\nalways held, whatever `create` and `conflicts` say, and a key naming no row is counted unchanged.\n\n`conflicts` states what a matched row does where a live cell holds another value: `"hold"` writes the\nrow\'s empty cells and holds the differing ones, `"overwrite"` writes over them, `"fill_empty"` leaves\nthem. `conflicts.fields` gives one field its own. A number and the field its unit or currency is read\nfrom on each row are one cell, under the number\'s policy: both are written, or both held.\n\nA link cell takes `{ lookup: "<text>" }` in place of record ids: the linked row is the one row holding\nthat text in the linked table\'s unique or autonumber field, else the one titled it, among the rows the\nrun reads of that table. A text naming no row, or several, holds the row, and the rows it could be are\nanswered beside it. A link holding many rows takes a list, `[{ lookup: "<text>" }, \u2026]`: each text is\nresolved by itself.\n\nThe step keeps nothing of its own: its answer is everything it did. `written` lists each record it\nwrote \u2014 a changed one with each field from old to new as text, a created one by its id alone \u2014 and\n`held_rows` each row it held: its `kind`, `reason` and `message`, the `record_id` of the row it matched,\nits `key`, the `changes` it would make, the `links` it could not resolve with their `candidates`, and\n`values` \u2014 its key and the cells it would write, as the call gave them. A later call given a held row\'s\n`values` under `conflicts: { default: "overwrite" }` writes it. Both lists are read under the run\'s own\nreach of the table, and a workflow hands its caller only what it returns. Two calls matching one table\nby the same fields take turns, so the second matches the rows the first added. The step\'s entry in\n`workflow_steps` states its bounds.\n\nA workflow does not put an import back: that reads the history of what the run wrote, which a member\'s agent reads\nunder the member\'s own access to the table and no workflow does. On chat, MCP or the CLI, `undo_record_changes` takes the\ntable, up to 500 record ids and the run \u2014 what `runtime.execution_id` held in it, which a body keeps on a row of its own \u2014\nand puts back what that run wrote, as the member\'s own writes. A field goes back where the row still holds what the run\nwrote. A row the run created goes to the trash where nothing else has written it or linked a row to it since; in a table\nwhere a workflow writes each new row, every row created reads as written since, and is kept. A row the run moved to the\ntrash comes back where nothing has touched it since. A row the run brought back from the trash stays out of it. What the\ntool leaves as it stands is answered in `kept`, one entry a record and reason. `undo_record_change` puts back one edit\nnamed by its audit-log entry, the same way.\n\nWhat a business keeps of an import \u2014 the file, its counts, the lines waiting for a person \u2014 is rows of\nits own tables, written by the workflow that ran the step. A garden centre brings its grower\'s price\nsheet into its plants, and keeps each sheet and its lines. A line names its plant through a link\nholding many rows: one holding a single row would make the line that plant\'s own, and a plant its\nlines belong to is not moved to the trash.\n\nThese tables are shown beside the plants, in the same app: the sheets as a log of the plants\' register\n(a further List at the root, `model/app-node`), a sheet\'s lines on the page it opens. A line keeping what a cell held\nand what it holds now in two fields reads one against the other (`compare`, `model/list-node`).\n\n```\nconst sheet = await excel_get_rows({ // a workflow step\n file_id: trigger.app_workflow.inputs.file,\n columns: ["Code", "Name", "Price"],\n});\n\nconst imported = await import_rows({ // a workflow step\n table_id: "plants",\n match: ["code"],\n rows: sheet.rows.map((row, at) => ({\n line: at + 2,\n hold: isNull(row["Price"]) ? "The sheet states no price" : null,\n values: {\n code: toString(row["Code"]),\n name: toString(row["Name"]),\n price: toNumber(row["Price"]),\n },\n })),\n});\n\nconst kept = await create_records({\n table_id: "price_sheets",\n records: [{\n file: [trigger.app_workflow.inputs.file],\n run: runtime.execution_id,\n read: imported.read,\n created: imported.created,\n changed: imported.changed,\n held: imported.held,\n }],\n});\nconst sheet_id = kept.record_ids[0];\n\nfor (let start = 0; start < size(imported.written); start += 50) {\n await create_records({\n table_id: "sheet_lines",\n records: slice(imported.written, start, start + 50).map((one) => ({\n sheet: [sheet_id],\n plant: [one.record_id],\n line: one.line,\n outcome: one.kind,\n changes: join(one.changes.map((change) => `${change.label}: ${change.before ?? "\u2014"} \u2192 ${change.after ?? "\u2014"}`), "; "),\n })),\n });\n}\nfor (let start = 0; start < size(imported.held_rows); start += 50) {\n await create_records({\n table_id: "sheet_lines",\n records: slice(imported.held_rows, start, start + 50).map((one) => ({\n sheet: [sheet_id],\n plant: isNull(one.record_id) ? [] : [one.record_id],\n line: one.line,\n outcome: "held",\n why: one.message,\n code: toString(sheet.rows[coalesce(one.line, 2) - 2]["Code"]),\n name: toString(sheet.rows[coalesce(one.line, 2) - 2]["Name"]),\n price: toNumber(sheet.rows[coalesce(one.line, 2) - 2]["Price"]),\n changes: join(one.changes.map((change) => `${change.label}: ${change.before ?? "\u2014"} \u2192 ${change.after ?? "\u2014"}`), "; "),\n })),\n });\n}\n\nreturn({ status: "success", message: `${imported.created} added, ${imported.changed} changed, ${imported.held} held` });\n```\n\nA held line keeps the cells the sheet states for it, found by the `line` the step answered, in\nfields of its own. A held row\'s `key` and `values` name fields by their keys, as every record a step answers a body does, so\nthe body reads the sheet again rather than those names. It is applied by the act on it, whose workflow hands the step those values again and\nlets it write over what the plant holds. A cell the line holds none of is left out, so a held line\nnever empties a cell of the plant:\n\n```\nconst line = await get_record({ table_id: "sheet_lines", record_id: trigger.app_workflow.inputs.record_id });\nconst values = {\n code: line.data["code"],\n ...(isEmpty(line.data["name"]) ? {} : { name: line.data["name"] }),\n ...(isNull(line.data["price"]) ? {} : { price: line.data["price"] }),\n};\n\nconst applied = await import_rows({ // a workflow step\n table_id: "plants",\n match: ["code"],\n conflicts: { default: "overwrite" },\n rows: [{ values }],\n});\nif (applied.held > 0) {\n return({ status: "error", message: applied.held_rows[0].message });\n}\n\nawait update_records({ table_id: "sheet_lines", record_ids: [line.id], set: { outcome: "applied" } });\nreturn({ status: "success", message: "Applied." });\n```\n\nA sheet is put back by a member\'s agent on chat, MCP or the CLI: `undo_record_changes` over the plants its lines name,\n500 at a time, with the run the sheet kept.\n\nA shop matches a bank statement to the notes it is owed by an act that states `review`. That act\'s workflow only\nproposes: its body calls steps that read and no other, and runs no agent \u2014 either is refused when the body is saved \u2014\nand returns in `data.review` one changeset of the act\'s table, which the platform writes the approved rows of. Called\nwith what the act asks, it answers the proposal and nothing is written; called again with\n`approved: { updates: [{ record_id, sha }], creates: [{ key, sha }] }`, the body runs again and the platform writes\nthose rows as this second run proposes them, so the body never reads `approved`. The platform adds a `sha` to every row\nof the proposal it answers \u2014 the body states none, and one it states is dropped \u2014 and the caller echoes the `sha` of\neach row it approves. A row the second run proposes with other values than the `sha` was read from is left unwritten\n(`changed`), so the body must propose the same for the same inputs and the rows it reads.\nAn update is of a record the act is run over (`record_id`, or one of `record_ids`): one of any other record is left\nunwritten. An act over every row in view is handed no rows: its body reads the rows it may change, and any of them may\nbe proposed. A create carries a `key`, a text of the body\'s own that names the same line on every run\n(`key: toString(line["Reference"])`), unique among the creates; where a confirm repeated must not add a second row,\nthe body also writes the key into a field the table keeps unique, so the repeat\'s create is left as a `conflict`.\nEach row\'s `data` holds only stored fields that change \u2014 never one the platform computes or stamps, nor a file \u2014 with\na `note` and a `ticked` the reader starts from, and `unplaced` lists what was read and matched nothing. Every approved\nrow is checked against its fields before any is written, and they are written in one transaction, so a flawed one\nwrites none. The status history of every row written that moved, or was made holding a status, is recorded in the\nsame run. The answer\'s `applied.previous` holds what each updated record\'s written fields held before, so one `update_records` of it puts the values back, and `delete_records` of a `created` record takes that row away. A body that stops proposing a row once it is\nwritten \u2014 it matches against what the rows hold now \u2014 makes approving twice write once.\n\n```\nconst inputs = trigger.app_workflow.inputs;\n\nconst sheet = await excel_get_rows({ // a workflow step\n file_id: inputs.statement,\n columns: ["Reference", "Amount", "Paid on"],\n});\nconst unpaid = await query_records({\n table_id: "notes",\n filters: { field_key: "paid_on", operator: "is_empty" },\n pagination: { offset: 0, limit: 500 },\n});\nconst acted = groupBy(inputs.record_ids, (id) => toString(id));\nconst owed = filter(unpaid.records, (one) => size(acted[toString(one.id)] ?? []) > 0);\nconst byRef = groupBy(owed, (one) => toString(one.data["ref"]));\n\nconst lines = sheet.rows.map((line) => ({\n line,\n note: first(byRef[toString(line["Reference"])] ?? []),\n}));\nconst updates = filter(lines, (one) => !isNull(one.note)).map((one) => ({\n record_id: toString(one.note?.id),\n note: `Transfer of ${toString(one.line["Amount"])}`,\n ticked: toNumber(one.line["Amount"]) == sumBy(filter(owed, (owing) => owing.id == one.note?.id), (owing) => coalesce(owing.data["amount"], 0)),\n data: { paid_on: toString(one.line["Paid on"]) },\n}));\nconst unplaced = filter(lines, (one) => isNull(one.note)).map((one) => ({\n title: `${toString(one.line["Reference"])}: ${toString(one.line["Amount"])}`,\n note: "Matches no unpaid note",\n}));\n\nreturn({\n status: "success",\n message: `${size(updates)} of ${size(lines)} transfers match a note`,\n data: { review: { version: 1, table_id: "notes", creates: [], updates, unplaced } },\n});\n```\n\n### Saving a record with its lines\n\n`save_record`, a workflow step, saves one record and the rows of other tables that hang off it, all or\nnothing, and recognises what exists by the identity the input carries \u2014 so a call sent twice updates and\nadds nothing. `match` is the record\'s identity, `{ field_key: value }`, and must be exactly a uniqueness\nits table declares: one field marked `unique`, or the fields of one of the table\'s `unique` sets together\n(text, number, date, single select and one-row link fields). A `match` that is no declared uniqueness is\nrefused, naming what the table does declare. A live record carrying the match is found and only the cells\nof `set` that differ are written; none is found and it is added with `match` and `set`. `add_to` appends\nto a files, multi-select or many-link field and keeps what it holds.\n\n`lines` saves rows of other tables that link the record. Each entry names the child `table_id`, its\n`link` \u2014 a field of the child holding one row of this table, which the step fills and no row states \u2014\nthe child fields that, with `link`, are one uniqueness of the child table (`match`), and `rows`, each\nstating every `match` field. A line found by its match changes only the cells that differ, one not found\nis added, and a line the call does not list is left as it is: the step deletes nothing. Two rows of one\nentry cannot share a match.\n\nEverything is checked before the first write, and everything is written in one transaction: a table check\nthat refuses one line, a locked record or line the call would change, a value a field does not take, or a\ntable the run may not write refuses the call and saves nothing, naming the row. Two calls of one identity\ntake turns, so the second finds what the first saved. The answer is `record_id`, `created`,\n`updated_field_keys` (the cells changed on a record that existed) and, per entry of `lines`, the ids it\nadded, changed and left unchanged; these ids are typed for their tables, so a declared `record_link`\noutput or a follow-up write links straight to them.\n\n```\nconst inputs = trigger.app_workflow.inputs;\n\nconst sheet = await excel_get_rows({ // a workflow step\n file_id: inputs.release_order,\n columns: ["Size", "Quantity"],\n});\n\nconst saved = await save_record({ // a workflow step\n table_id: "release_orders",\n match: { number: inputs.number },\n set: { note: "Read from the paper" },\n lines: [{\n table_id: "release_lines",\n link: "order",\n match: ["size"],\n rows: sheet.rows.map((row) => ({\n size: toString(row["Size"]),\n quantity: toNumber(row["Quantity"]),\n })),\n }],\n});\n\nreturn({\n status: "success",\n message: `${size(saved.lines[0].created_ids)} lines added`,\n data: { record_id: saved.record_id },\n});\n```\n\n## Reaching a connected service\n\nA service is reached only by a workflow step, and a step is called inside an app\'s workflow or an automation. From\nnothing to an app\'s run:\n\n1. `get_workspace` with `kind: "connection"` lists each `connected_account_id`.\n2. `set_app` declares a Workflow node that states what a call takes and hands back:\n\n```json\n{\n "type": "Workflow",\n "key": "invoice_order",\n "description": "Issues the KiotViet invoice for a confirmed order and hands back its code.",\n "inputs": { "order": { "type": "record_link", "entity": "order" } },\n "outputs": { "invoice_code": { "type": "text" } }\n}\n```\n\n3. `set_app_workflow` with the app, the alias `invoice_order` and this `source` (the step\'s entry is in `workflow_steps`):\n\n```\nconst order = await get_record({ table_id: "orders", record_id: trigger.app_workflow.inputs.order });\nconst made = await kiotviet_create_invoice({ // a workflow step\n connected_account_id: "cac_\u2026",\n branch_id: 1,\n sold_by_id: 7,\n lines: [{ product_id: 42, quantity: toNumber(order.data["quantity"]), price: toNumber(order.data["price"]) }],\n});\nreturn({ status: "success", message: "Invoiced", data: { invoice_code: made.invoice.code } });\n```\n\n4. `run_app_workflow` with `dry_run: true`, then the same call without it.\n\nA step wants the provider\'s own ids (`branch_id`, `sold_by_id`, `product_id`) and no tool lists them: declare a Workflow\nwhose body calls the provider\'s `list_*` workflow step (`kiotviet_list_branches`), run it, and read the ids from its answer.\nNo step lists KiotViet\'s users: read `sold_by_id` off a row `kiotviet_list_invoices` returns (KiotViet\'s `soldById`), or ask the owner.\n\n## Table checks\n\nA table check is one condition its table\'s record writes must not meet, declared beside the table\'s\n`unique`. It is not a workflow: it reads the write and a few lookups and answers yes or no, so it\ncannot change data. It runs inside the write, on every create, update or delete it is `on`, whichever\nsurface makes it \u2014 a person, the API, the CLI, an agent, a workflow. A refusal writes nothing and\nanswers with the check\'s message, under its field when the check names one, and names the check.\n\nA check is stated in its table\'s document, under `checks`, each under an alias of its own, and\n`set_tables` writes it with the rest of the table: a merge patch edits one, `null` removes it, and a\ncall that makes a table can state its checks, a lookup naming another table of the call by its `ref`.\n`get_table` states a table\'s checks in its document.\n\n| part | what it holds |\n|---|---|\n| `on` | the writes it checks: any of `create`, `update`, `delete` |\n| `when` | optional expression; the check runs only on a write where it is truthy. It cannot read lookups. |\n| `lookups` | optional, by name, the rows to read: `{ table, filter, limit }`, `table` an id or the `ref` of a table of the call, the filter as `query_records` takes it with fields and select options named by alias (`id` matches a row by its own record id), `limit` from 1 to 50 (50 when left out). A filter value that reads the record is `{ "$expr": "record.code" }`. |\n| `fail_when` | expression; truthy refuses the write |\n| `message` | expression for the refusal\'s text \u2014 a fixed text is a quoted string |\n| `field` | optional; the alias of the field the refusal is shown under |\n| `enabled` | `false` keeps the check without deciding writes; absent, it decides |\n\nEvery expression is written in the grammar above, names a table\'s fields and select options by\nalias (`record.status == "closed"`), and reads:\n\n- `record` \u2014 the record as it will be stored; on a delete, the record being deleted.\n- `prev_record` \u2014 on an update or a delete, the record before.\n- `changes` \u2014 on an update, the fields it changes. `!isNull(changes.photos)` asks whether a field changed.\n- `lookups.<name>` \u2014 in `fail_when` and `message`, the rows that lookup read. A row reads as `record` does: its fields by alias beside `id` (`row.start_on`), and a lookup field as the flat list of what it looks up (`first(row.booking_start)` through a one-row link). A lookup over the written table never returns the row being checked, and finds its other rows as the write leaves them, this call\'s other rows included. A field given to a helper as text names it by alias too (`sumBy(lookups.paid, "amount")`); text that names no field of the rows\' table is refused when the check is saved.\n- `runtime.timezone`, `runtime.workspace_id`, `runtime.organization_id`, `runtime.change_origin` (the\n write\'s origin), `runtime.triggered_by_member_id` (the member writing; null for a write no member\n made), `now()`, and `member_of("grp_\u2026")` \u2014 true when the member writing belongs to the group.\n\nIn a check a select reads as its one value (`record.kind == "retail"`), and a link (a one-row link too) and a lookup read as lists (`first(record.customer)`).\n\nA check on several operations reads only what all of them carry, so one on creates and updates reads\nneither `prev_record` nor `changes`.\n\nA write that recomputes another table\'s rows (a rollup over lines, a lookup or a formula over a parent) judges each recomputed row as an update, by the checks `on` `update` that read a field the recompute changed; `changes` is what the recompute changed. A check that refuses on `changes` or on a status the row holds can therefore refuse the write that moved the parent, so it is gated on the field it means (`!isNull(changes.status)`).\n\nA lookup reads with the access of the member who last saved the check, so saving one requires reading\nevery table its lookups read. It reads committed rows: two writes at the same moment can each pass a\ncheck the other would fail, so values no two rows may share are the table\'s `unique`, not a check.\n\nA `when`, a lookup or a `fail_when` that cannot decide \u2014 it fails, or the write\'s time runs out \u2014\nrefuses the write, naming the part it was reading and, for an expression or a filter, what it met\n(`lookups.clash: Filter on "start_on": date \'before\' value \u2026 is not a date`). A draft is checked when it is submitted, as a create; its edits before that are\nnot. A restored record is checked as a create. Cloning a table copies its checks to the member who\nclones it, and is refused when that member cannot read a table a check\'s lookup reads.\n\nOne open order per reference, a row never counting against itself:\n\n```json\n{\n "tables": [\n {\n "ref": "orders",\n "label": "Orders",\n "fields": {\n "reference": { "type": "text", "label": "Reference" },\n "closed_at": { "type": "date", "label": "Closed at" }\n },\n "checks": {\n "one_open_order_per_reference": {\n "on": ["create", "update"],\n "when": "isNull(record.closed_at)",\n "lookups": {\n "same_ref": {\n "table": "orders",\n "filter": {\n "node_type": "group",\n "logic": "and",\n "children": [\n { "node_type": "condition", "field_key": "reference", "operator": "equals", "value": { "$expr": "record.reference" } },\n { "node_type": "condition", "field_key": "closed_at", "operator": "is_empty" }\n ]\n },\n "limit": 2\n }\n },\n "fail_when": "some(lookups.same_ref, (r) => r.id != record.id)",\n "message": "\\"An open order already has this reference.\\"",\n "field": "reference"\n }\n }\n }\n ]\n}\n```\n\n## Table workflows\n\nA table workflow is what follows every committed write of a table\'s rows, whoever makes it: the grid, an import, an agent or\nany app. An effect that must hold for all of them belongs to the table. A button on one screen is that app\'s act, a value fixed\nwhen a row is made is `default_from`, and a rule that refuses a write is a check; a table workflow runs after the write has\ncommitted and cannot refuse it.\n\nIt is stated in the table\'s document, under `workflows`, each under an alias of its own, and `set_tables` writes it with the\nrest of the table: a merge patch edits one, `null` removes it, and `get_table` states a table\'s workflows in its document, with\nthe failures of the last 7 days beside them, to whoever manages the table. A dry run of `set_tables` with a `record_id` says what\neach workflow the call adds or changes would write on that row; reads are real and nothing is written.\n\n| part | what it holds |\n|---|---|\n| `on` | the write it follows: `create`, `update` or `delete` |\n| `when` | optional expression over `record`, `prev_record` and `changes`; the workflow runs only after a write where it is truthy |\n| `changed` | optional, with `on: update`: field aliases; the workflow runs only after an update that changes any one of them |\n| `body` | the steps, written as an app workflow\'s body is |\n| `enabled` | `false` keeps the workflow without running it; absent, it runs |\n\n```json\n{\n "tables": [\n {\n "ref": "repairs",\n "label": "Repairs",\n "fields": {\n "status": { "type": "select", "label": "Status", "options": [{ "alias": "queued", "label": "Queued" }, { "alias": "fixed", "label": "Fixed" }] },\n "fixed_on": { "type": "date", "label": "Fixed on" }\n },\n "workflows": {\n "stamp_fixed": {\n "on": "update",\n "changed": ["status"],\n "when": "record.status == \\"fixed\\"",\n "body": "await update_records({ table_id: \\"repairs\\", record_ids: [trigger.record_id], set: { fixed_on: formatDate(now(), \\"yyyy-MM-dd\\") } });"\n }\n }\n }\n ]\n}\n```\n\nThe body names the table\'s fields, select options and other tables by alias, and reads the write as `record`,\n`prev_record` and `changes` and the row\'s id as `trigger.record_id`. It operates on records and integrations: a body\nthat calls a tool changing a table, an app or any other structure is refused when it is saved. It runs as the table\'s owner, so\nit reaches the tables the owner reaches, and its writes meet the table\'s checks like any other write; a write refused by a\ncheck fails that run and leaves the row as it was. A body names a field by alias where it can tell which table\'s field it is: a\nread of `record`, `prev_record` or `changes`, a row from `get_record` or `query_records` called with a literal `table_id`, and a\nloop over such rows. A name it cannot tie to a table, and an option compared to a value of unknown type (not a read of a field, and\nnot a text, number or other value the type proves holds no option), are refused with what to change. A link reads as the list of the row ids it holds, a one-row link too: compare `first(record.customer) == id`. A lookup reads as one flat list of what it looks up: `record.x != prev_record.x` on one is refused, so compare `first(record.x)` with `first(prev_record.x)`. A single member, a single select, a number or a text reads as the value itself: `first(record.technician)` is refused at save, naming the read.\n\nA write the workflow itself makes sets off the workflows of its table in turn, and a chain of them stops after five. A workflow\nthat writes the row it follows is gated by a `changed` or a `when` that its own write does not satisfy. One run is queued for a\nrow and a workflow at a time, so a burst of writes to one row runs it once more after the run in flight. A field a workflow\nreads cannot be deleted or retyped until the workflow is changed or removed in the same call or before. Cloning a table copies its\nworkflows for whoever manages it (`include_workflows: false` leaves them), each with a body of its own, and is refused when a\nbody names a table the cloner cannot reach. A workflow runs as the table\'s owner, so whoever saves or rehearses one must manage the\ntable and read every table it reads in full: the body\'s, the gate\'s links\', the written table. An option is written where\nit is compared to, listed against or set on its field; a constant holding one is refused.\n\nA lock is the table\'s: a table workflow calls `lock_records` on a row (an app\'s act may too), and every door then refuses\nthe row\'s writes until whoever manages the table calls `unlock_records` \u2014 in an app\'s run, judged as the member who pressed.\nA locked row reads locked in every app. An app\'s own Check or `edit: false` only makes a row read-only in that app.\n\n## Runs\n\nEach start of a workflow is a run: an app\'s workflow or act, a table workflow, a button, an automation. An agent finds,\nreads, retries and stops runs on chat, MCP and the CLI. A run is read by whoever manages what its workflow belongs to: its\nautomation or its table, and for an app\'s workflow the app\'s owner or an admin, never a manager of the app.\n\n- **Find.** `query_workflow_executions` lists one workflow\'s runs (`workflow_id`), every run started for one row\n (`record_id`) \u2014 a table workflow\'s written row, a pressed button\'s row, an app run\'s `record_id` input \u2014 or both, newest\n first, a page at a time (`next_cursor`). Each run names its workflow and kind, what started it, its row and how it ended:\n for a failure, the step that threw, its tool and the error; for a waiting run, when it wakes, or the approval and the\n approvers it waits on, or the payment. A run started before runs recorded their row carries no `record_id`.\n- **Read.** `get_workflow_execution` gives one run\'s steps in order with each one\'s tool, input, output and error, what it\n returned, how many rows it wrote (a row\'s own history is in `query_audit_logs`) and the runs its writes started. Ninety days\n after a run starts, its trigger and its steps\' input and output are pruned; the read says so and gives the rest.\n `run_app_workflow` answers each run it makes with its `execution_id`.\n- **Retry.** `retry_workflow_execution` runs a failed, refused or cancelled run again as a new run linked to the first\n (`retry_of`): the first run\'s trigger and row, the workflow\'s current steps and its owner\'s current access, with the\n member who retries it as the one who started it. A run is retried once; a retry that fails is retried in turn. It asks\n first when the run, or a run it retried, already wrote rows or called a service no delete reverses. An automation\'s or a\n table workflow\'s retry runs in the background, as its runs do, and answers `queued`; a button\'s and an app\'s run while\n the call waits. An app\'s call is checked again against the workflow\'s current inputs. A pruned run, a run of a workflow\n that is off, a reviewed act\'s run (call the act again) and a recording\'s filing (the recording retries its delivery)\n are not run again.\n- **Stop.** `cancel_workflow_execution` stops a waiting run and withdraws its pending approvals.\n\nOff means off: a waiting run whose workflow was turned off, deleted, or taken off its automation, table, button or app ends\n`cancelled` when it would resume, saying why, and runs no further step.\n\nA waiting run keeps its version: when it resumes, it runs the steps of the version it started with, so an edit saved while it\nwaited changes later runs only. When that version is gone, the run ends failed and says why.\n';
|
|
1792
1792
|
|
|
1793
1793
|
// docs/workflow_steps.md
|
|
1794
1794
|
var workflow_steps_default = '<!-- Generated by backend/scripts/generate_workflow_steps_doc.ts from each step\'s schemas \u2014 never edit by hand. -->\n\n# Workflow steps\n\nThe steps only a workflow body runs: a connection\'s (an invoice issuer, a mailbox, \u2026) and the platform\'s own. Each is\ncalled as `await <step>({ ...inputs })` (see `workflows`); a step\'s inputs and answer are its entry here. A connection\nstep takes the `connected_account_id` that `get_workspace` lists under kind `connection`, and the app\'s owner and\nwhoever saves the body must both be able to use that connection.\n\n**How a step fails.** A step that is refused \u2014 an input the schema rejects, a connection the workspace lacks, an\nanswer the provider refuses \u2014 ends the run, and the run\'s error reads `[<step>] <reason>`. Inside `try { \u2026 } catch\n(e) { \u2026 }` the body handles it instead: `e` is `{ message, type, step_id?, detail? }`, `message` the reason as\nabove. A provider\'s refusal carries the provider\'s own words.\n\n## bkav\n\n### bkav_attach_files (workflow step)\n\n- Attach workspace files (a bi\xEAn b\u1EA3n, a b\u1EA3ng k\xEA) to an invoice on Bkav.\n- Bkav accepts PDF and Excel, at most 1 MB each.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guid` | string | | |\n| `partner_invoice_id` | string | | Your PartnerInvoiceStringID, when invoice_guid is not set |\n| `file_ids` | string[] | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].PartnerInvoiceID` | number | yes | |\n| `results[].PartnerInvoiceStringID` | string \\\\| null | yes | Null on a delete\'s answer |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].InvoiceForm` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceSerial` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceNo` | number | yes | Assigned by a numbered kind; 0 on a draft |\n| `results[].MTC` | string \\\\| null | yes | M\xE3 tra c\u1EE9u the buyer looks the invoice up with; null on a draft\'s link answer |\n| `results[].MaCuaCQT` | string \\\\| null | | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise MessLog says why |\n| `results[].MessLog` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n### bkav_cancel_invoices (workflow step)\n\n- Cancel issued invoices.\n- A cancelled invoice still reports to the tax authority; correcting one instead is a replacement or adjustment through bkav_submit_invoices. (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guids` | string[] | | InvoiceGUIDs Bkav returned |\n| `partner_invoice_ids` | string[] | | Your PartnerInvoiceStringIDs |\n| `reason` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].PartnerInvoiceID` | number | yes | |\n| `results[].PartnerInvoiceStringID` | string \\\\| null | yes | Null on a delete\'s answer |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].InvoiceForm` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceSerial` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceNo` | number | yes | Assigned by a numbered kind; 0 on a draft |\n| `results[].MTC` | string \\\\| null | yes | M\xE3 tra c\u1EE9u the buyer looks the invoice up with; null on a draft\'s link answer |\n| `results[].MaCuaCQT` | string \\\\| null | | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise MessLog says why |\n| `results[].MessLog` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n### bkav_create_bill (workflow step)\n\n- Record a cash-register bill on Bkav.\n- Field names are Bkav\'s.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `bill` | object | yes | |\n| `bill.Bill` | object | yes | |\n| `bill.Bill.BillCode` | number | yes | |\n| `bill.Bill.OriginalBillCode` | number | | |\n| `bill.Bill.BillDate` | string | yes | |\n| `bill.Bill.BillStatusID` | number | yes | |\n| `bill.Bill.PayMethod` | string | | |\n| `bill.Bill.Note` | string | | |\n| `bill.ListBillDetails` | object[] | yes | |\n| `bill.ListBillDetails[].ItemTypeID` | number | | 0 goods, 4 description line, 15 promotional goods, \u2026 |\n| `bill.ListBillDetails[].IsDiscount` | boolean | | |\n| `bill.ListBillDetails[].ItemCode` | string | | |\n| `bill.ListBillDetails[].ItemName` | string | yes | |\n| `bill.ListBillDetails[].UnitName` | string | | |\n| `bill.ListBillDetails[].Qty` | number | | |\n| `bill.ListBillDetails[].Price` | number | | |\n| `bill.ListBillDetails[].Amount` | number | | |\n| `bill.ListBillDetails[].TaxRateID` | number | | 1 0%, 2 5%, 3 10%, 4 kh\xF4ng ch\u1ECBu thu\u1EBF, 9 8%, \u2026 |\n| `bill.ListBillDetails[].TaxRate` | number | | |\n| `bill.ListBillDetails[].TaxAmount` | number | | |\n| `bill.ListBillDetails[].OtherAmount` | number | | |\n| `bill.ListBillDetails[].DiscountRate` | number | | |\n| `bill.ListBillDetails[].DiscountAmount` | number | | |\n| `bill.ListBillDetails[].UserDefineDetails` | string | | |\n| `bill.ListBillDetails[].IsIncrease` | boolean \\\\| null | | On an adjustment: true raises the amount, false lowers it, absent adjusts information |\n| `bill.ListBillDetails[].SpecialtyItems` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `created` | true | yes | |\n\n### bkav_create_invoice_from_bills (workflow step)\n\n- Raise one invoice from cash-register bills, by their BillGUIDs.\n- Field names are Bkav\'s.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `input` | object | yes | |\n| `input.Invoice` | object | yes | |\n| `input.Invoice.InvoiceTypeID` | number | yes | 1 GTGT, 2 b\xE1n h\xE0ng, 5 PXK ki\xEAm VCNB, 6 PXK g\u1EEDi b\xE1n \u0111\u1EA1i l\xFD |\n| `input.Invoice.InvoiceDate` | string | yes | ISO date |\n| `input.Invoice.BuyerCode` | string | | |\n| `input.Invoice.BuyerName` | string | | |\n| `input.Invoice.BuyerTaxCode` | string | | |\n| `input.Invoice.BuyerUnitName` | string | | |\n| `input.Invoice.BuyerAddress` | string | | |\n| `input.Invoice.BuyerBankAccount` | string | | Sent empty when absent: Bkav refuses it missing |\n| `input.Invoice.PayMethodID` | number | | 1 TM, 2 CK, 3 TM/CK, \u2026 |\n| `input.Invoice.ReceiveTypeID` | number | | 1 email, 2 SMS, 3 both, 4 courier |\n| `input.Invoice.ReceiverEmail` | string | | |\n| `input.Invoice.ReceiverMobile` | string | | |\n| `input.Invoice.ReceiverAddress` | string | | Sent empty when absent: Bkav refuses it missing |\n| `input.Invoice.ReceiverName` | string | | Sent empty when absent: Bkav refuses it missing |\n| `input.Invoice.Note` | string | | |\n| `input.Invoice.BillCode` | string | | Sent empty when absent: Bkav refuses it missing |\n| `input.Invoice.CurrencyID` | string | | |\n| `input.Invoice.ExchangeRate` | number | | |\n| `input.Invoice.InvoiceForm` | string | | M\u1EABu s\u1ED1 |\n| `input.Invoice.InvoiceSerial` | string | | K\xFD hi\u1EC7u |\n| `input.Invoice.InvoiceNo` | number | | S\u1ED1; 0 when Bkav assigns it |\n| `input.Invoice.MaCuaCQT` | string | | Set only by a cash-register flow that already holds the authority\'s code |\n| `input.Invoice.UserDefine` | string | | JSON-encoded template fields agreed with Bkav |\n| `input.Invoice.UIDefine` | string | | JSON-encoded delivery-note fields (InvoiceTypeID 5 and 6) |\n| `input.Invoice.isBTH` | boolean | | |\n| `input.Invoice.CCCD` | string | | |\n| `input.Invoice.PassportNumber` | string | | |\n| `input.Invoice.FiscalCodes` | string | | |\n| `input.Invoice.Reason` | string | | Why \u2014 required on a replacement or adjustment |\n| `input.Invoice.OriginalInvoiceIdentify` | string | | `[m\u1EABu s\u1ED1]_[k\xFD hi\u1EC7u]_[s\u1ED1]` of the invoice a replacement or adjustment corrects |\n| `input.Invoice.InvoiceGUID` | string | | Addresses the invoice for an update by guid |\n| `input.BillGUIDs` | string[] | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].PartnerInvoiceID` | number | yes | |\n| `results[].PartnerInvoiceStringID` | string \\\\| null | yes | Null on a delete\'s answer |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].InvoiceForm` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceSerial` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceNo` | number | yes | Assigned by a numbered kind; 0 on a draft |\n| `results[].MTC` | string \\\\| null | yes | M\xE3 tra c\u1EE9u the buyer looks the invoice up with; null on a draft\'s link answer |\n| `results[].MaCuaCQT` | string \\\\| null | | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise MessLog says why |\n| `results[].MessLog` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n### bkav_delete_invoices (workflow step)\n\n- Delete invoices that were never issued.\n- A numbered one can be deleted only if its number is the highest in its range.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guids` | string[] | | InvoiceGUIDs Bkav returned |\n| `partner_invoice_ids` | string[] | | Your PartnerInvoiceStringIDs |\n| `reason` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].PartnerInvoiceID` | number | yes | |\n| `results[].PartnerInvoiceStringID` | string \\\\| null | yes | Null on a delete\'s answer |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].InvoiceForm` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceSerial` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceNo` | number | yes | Assigned by a numbered kind; 0 on a draft |\n| `results[].MTC` | string \\\\| null | yes | M\xE3 tra c\u1EE9u the buyer looks the invoice up with; null on a draft\'s link answer |\n| `results[].MaCuaCQT` | string \\\\| null | | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise MessLog says why |\n| `results[].MessLog` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n### bkav_explain_invoice_error (workflow step)\n\n- Explain an erroneous invoice to the tax authority without replacing or adjusting it, optionally answering one of its notices.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guid` | string | yes | |\n| `reason` | string | yes | |\n| `notice_number` | string | | S\u1ED1 of the authority\'s notice being answered |\n| `notice_date` | string | | yyyy-mm-dd; with notice_number |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise Msg says why |\n| `results[].Msg` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n### bkav_explain_replaced_invoice (workflow step)\n\n- Explain to the tax authority an invoice that was replaced or adjusted.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guid` | string | yes | |\n| `reason` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise Msg says why |\n| `results[].Msg` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n### bkav_get_bill (workflow step)\n\n- Read a cash-register bill.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `seller_tax_code` | string | yes | |\n| `organization_code` | string | yes | |\n| `bill_code` | number | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `bill` | object | yes | |\n\n### bkav_get_error_notice_pdf (workflow step)\n\n- Save the PDF of an invoice\'s 04/SS error notice as a workspace file.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `partner_invoice_id` | string | yes | Your PartnerInvoiceStringID |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | The ID of the generated file. |\n| `url` | string | yes | The download URL of the generated file. |\n| `filename` | string | yes | The filename of the generated file, including extension. |\n| `mime_type` | string | yes | The MIME type of the generated file. |\n\n### bkav_get_file_by_lookup_code (workflow step)\n\n- Save an invoice\'s display copy, conversion copy or XML as a workspace file, found by the m\xE3 tra c\u1EE9u the buyer was sent.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `lookup_code` | string | yes | M\xE3 tra c\u1EE9u |\n| `kind` | "display" \\\\| "converted" \\\\| "xml" | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | The ID of the generated file. |\n| `url` | string | yes | The download URL of the generated file. |\n| `filename` | string | yes | The filename of the generated file, including extension. |\n| `mime_type` | string | yes | The MIME type of the generated file. |\n\n### bkav_get_invoice (workflow step)\n\n- Read one invoice as Bkav holds it \u2014 header, lines, status and m\xE3 tra c\u1EE9u.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `id` | string | yes | Your PartnerInvoiceStringID or Bkav\'s InvoiceGUID |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invoice` | object | yes | |\n\n### bkav_get_invoice_file (workflow step)\n\n- Save an invoice\'s PDF or signed XML as a workspace file.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `partner_invoice_id` | string | yes | Your PartnerInvoiceStringID |\n| `format` | "pdf" \\\\| "xml" | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | The ID of the generated file. |\n| `url` | string | yes | The download URL of the generated file. |\n| `filename` | string | yes | The filename of the generated file, including extension. |\n| `mime_type` | string | yes | The MIME type of the generated file. |\n\n### bkav_get_invoice_history (workflow step)\n\n- Read everything that happened to an invoice on Bkav, oldest first.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guid` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `entries` | object[] | yes | |\n\n### bkav_get_invoice_link (workflow step)\n\n- Get a download link for an invoice\'s display copy, conversion copy, or its 04/SS error notice.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `partner_invoice_id` | string | yes | Your PartnerInvoiceStringID |\n| `kind` | "display" \\\\| "converted" \\\\| "notice_04ss" | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `url` | string | yes | |\n\n### bkav_get_invoice_status (workflow step)\n\n- Read an invoice\'s status on Bkav.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guid` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invoice_status_id` | number | yes | 1 m\u1EDBi t\u1EA1o, 2 \u0111\xE3 ph\xE1t h\xE0nh, 3 \u0111\xE3 hu\u1EF7, 5\u20138 ch\u1EDD/\u0111\xE3 thay th\u1EBF\u2013\u0111i\u1EC1u ch\u1EC9nh, 11 \u0111\xE3 c\u1EA5p s\u1ED1 ch\u1EDD k\xFD |\n\n### bkav_get_listing (workflow step)\n\n- Save the b\u1EA3ng k\xEA of one invoice type over a window as a workspace spreadsheet.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_type_id` | number | yes | 1 GTGT, 2 b\xE1n h\xE0ng, \u2026 |\n| `from_date` | string | yes | yyyy-mm-dd |\n| `to_date` | string | yes | yyyy-mm-dd |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | The ID of the generated file. |\n| `url` | string | yes | The download URL of the generated file. |\n| `filename` | string | yes | The filename of the generated file, including extension. |\n| `mime_type` | string | yes | The MIME type of the generated file. |\n\n### bkav_get_report_bc26 (workflow step)\n\n- Save the BC26/AC invoice usage report for a window as a workspace XML file.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `from_date` | string | yes | yyyy-mm-dd |\n| `to_date` | string | yes | yyyy-mm-dd |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | The ID of the generated file. |\n| `url` | string | yes | The download URL of the generated file. |\n| `filename` | string | yes | The filename of the generated file, including extension. |\n| `mime_type` | string | yes | The MIME type of the generated file. |\n\n### bkav_get_tax_status (workflow step)\n\n- Read the tax authority\'s status for an invoice and its m\xE3 c\u1EE7a CQT once granted.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guid` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `statuses` | object[] | yes | |\n| `statuses[].BkavStatus` | number | yes | InvoiceStatusID: 1 m\u1EDBi t\u1EA1o, 2 \u0111\xE3 ph\xE1t h\xE0nh, 3 \u0111\xE3 hu\u1EF7, \u2026 |\n| `statuses[].TaxStatus` | number | yes | 32 waiting, 33 accepted, 34\u201339 returned or failed |\n| `statuses[].TaxAuthorityCode` | string \\\\| null | yes | M\xE3 c\u1EE7a C\u01A1 quan thu\u1EBF, once granted |\n| `statuses[].ErrorContent` | string \\\\| null | yes | |\n\n### bkav_list_invoices_by_date (workflow step)\n\n- List invoices dated within a window, one page at a time.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `from_date` | string | yes | yyyy-mm-dd |\n| `to_date` | string | yes | yyyy-mm-dd |\n| `page` | number | | 1-based |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invoices` | object[] | yes | |\n\n### bkav_list_invoices_by_number (workflow step)\n\n- List invoices of one m\u1EABu s\u1ED1 and k\xFD hi\u1EC7u by number range.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `form` | string | yes | M\u1EABu s\u1ED1 |\n| `serial` | string | yes | K\xFD hi\u1EC7u |\n| `from_number` | number | yes | |\n| `to_number` | number | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invoices` | object[] | yes | |\n\n### bkav_lookup_company (workflow step)\n\n- Look up a company in the tax registry by M\xE3 s\u1ED1 thu\u1EBF, through the Bkav account.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `tax_code` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `company` | object | yes | |\n| `company.MaSoThue` | string | yes | |\n| `company.TenChinhThuc` | string | yes | |\n| `company.DiaChiGiaoDichChinh` | string | yes | |\n| `company.DiaChiGiaoDichPhu` | string | yes | |\n| `company.TrangThaiHoatDong` | string | yes | |\n| `company.ChuDoanhNghiep` | string | | |\n| `company.SoDienThoai` | string \\\\| null | | |\n| `company.LastUpdate` | string | | |\n\n### bkav_send_lookup_email (workflow step)\n\n- Send an invoice\'s lookup email to the buyer again, or to another email or phone.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guid` | string | yes | |\n| `email` | string | | |\n| `mobile` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `sent` | true | yes | |\n\n### bkav_sign_invoices (workflow step)\n\n- Sign \u2014 issue \u2014 invoices with the account\'s HSM signature, making them tax-registered documents.\n- An account that signs with a USB token cannot sign this way.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `invoice_guids` | string[] | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `signed` | string[] | yes | |\n\n### bkav_submit_invoices (workflow step)\n\n- Create, replace or adjust invoices on Bkav, in Bkav\'s field names.\n- Each carries PartnerInvoiceStringID, your own id for it, which Bkav uses to refuse a duplicate.\n- Attachments are not accepted here.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `kind` | "create_draft" \\\\| "create" \\\\| "create_draft_own_serial" \\\\| "create_own_number" \\\\| "create_own_serial" \\\\| "replace_draft" \\\\| "adjust_draft" \\\\| "adjust_discount_draft" \\\\| "replace" \\\\| "adjust" \\\\| "adjust_external_draft" \\\\| "adjust_discount" \\\\| "adjust_external" \\\\| "replace_external" \\\\| "replace_external_draft" | yes | create_draft: no number, deletable. create: Bkav numbers it. *_own_*: your form/serial. replace_* / adjust_*: each invoice names its original in Invoice.OriginalInvoiceIdentify and gives Invoice.Reason. *_external*: corrects an invoice issued outside Bkav; its first line is an ItemTypeID 4 description of it. *_draft kinds stay unnumbered. |\n| `invoices` | object[] | yes | |\n| `invoices[].Invoice` | object | yes | |\n| `invoices[].Invoice.InvoiceTypeID` | number | yes | 1 GTGT, 2 b\xE1n h\xE0ng, 5 PXK ki\xEAm VCNB, 6 PXK g\u1EEDi b\xE1n \u0111\u1EA1i l\xFD |\n| `invoices[].Invoice.InvoiceDate` | string | yes | ISO date |\n| `invoices[].Invoice.BuyerCode` | string | | |\n| `invoices[].Invoice.BuyerName` | string | | |\n| `invoices[].Invoice.BuyerTaxCode` | string | | |\n| `invoices[].Invoice.BuyerUnitName` | string | | |\n| `invoices[].Invoice.BuyerAddress` | string | | |\n| `invoices[].Invoice.BuyerBankAccount` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.PayMethodID` | number | | 1 TM, 2 CK, 3 TM/CK, \u2026 |\n| `invoices[].Invoice.ReceiveTypeID` | number | | 1 email, 2 SMS, 3 both, 4 courier |\n| `invoices[].Invoice.ReceiverEmail` | string | | |\n| `invoices[].Invoice.ReceiverMobile` | string | | |\n| `invoices[].Invoice.ReceiverAddress` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.ReceiverName` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.Note` | string | | |\n| `invoices[].Invoice.BillCode` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.CurrencyID` | string | | |\n| `invoices[].Invoice.ExchangeRate` | number | | |\n| `invoices[].Invoice.InvoiceForm` | string | | M\u1EABu s\u1ED1 |\n| `invoices[].Invoice.InvoiceSerial` | string | | K\xFD hi\u1EC7u |\n| `invoices[].Invoice.InvoiceNo` | number | | S\u1ED1; 0 when Bkav assigns it |\n| `invoices[].Invoice.MaCuaCQT` | string | | Set only by a cash-register flow that already holds the authority\'s code |\n| `invoices[].Invoice.UserDefine` | string | | JSON-encoded template fields agreed with Bkav |\n| `invoices[].Invoice.UIDefine` | string | | JSON-encoded delivery-note fields (InvoiceTypeID 5 and 6) |\n| `invoices[].Invoice.isBTH` | boolean | | |\n| `invoices[].Invoice.CCCD` | string | | |\n| `invoices[].Invoice.PassportNumber` | string | | |\n| `invoices[].Invoice.FiscalCodes` | string | | |\n| `invoices[].Invoice.Reason` | string | | Why \u2014 required on a replacement or adjustment |\n| `invoices[].Invoice.OriginalInvoiceIdentify` | string | | `[m\u1EABu s\u1ED1]_[k\xFD hi\u1EC7u]_[s\u1ED1]` of the invoice a replacement or adjustment corrects |\n| `invoices[].Invoice.InvoiceGUID` | string | | Addresses the invoice for an update by guid |\n| `invoices[].ListInvoiceDetailsWS` | object[] | yes | |\n| `invoices[].ListInvoiceDetailsWS[].ItemTypeID` | number | | 0 goods, 4 description line, 15 promotional goods, \u2026 |\n| `invoices[].ListInvoiceDetailsWS[].IsDiscount` | boolean | | |\n| `invoices[].ListInvoiceDetailsWS[].ItemCode` | string | | |\n| `invoices[].ListInvoiceDetailsWS[].ItemName` | string | yes | |\n| `invoices[].ListInvoiceDetailsWS[].UnitName` | string | | |\n| `invoices[].ListInvoiceDetailsWS[].Qty` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].Price` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].Amount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].TaxRateID` | number | | 1 0%, 2 5%, 3 10%, 4 kh\xF4ng ch\u1ECBu thu\u1EBF, 9 8%, \u2026 |\n| `invoices[].ListInvoiceDetailsWS[].TaxRate` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].TaxAmount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].OtherAmount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].DiscountRate` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].DiscountAmount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].UserDefineDetails` | string | | |\n| `invoices[].ListInvoiceDetailsWS[].IsIncrease` | boolean \\\\| null | | On an adjustment: true raises the amount, false lowers it, absent adjusts information |\n| `invoices[].ListInvoiceDetailsWS[].SpecialtyItems` | string | | |\n| `invoices[].PartnerInvoiceStringID` | string | yes | |\n| `invoices[].AttachListNumber` | string | | S\u1ED1 of a b\u1EA3ng k\xEA the invoice refers to |\n| `invoices[].AttachListDate` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].PartnerInvoiceID` | number | yes | |\n| `results[].PartnerInvoiceStringID` | string \\\\| null | yes | Null on a delete\'s answer |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].InvoiceForm` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceSerial` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceNo` | number | yes | Assigned by a numbered kind; 0 on a draft |\n| `results[].MTC` | string \\\\| null | yes | M\xE3 tra c\u1EE9u the buyer looks the invoice up with; null on a draft\'s link answer |\n| `results[].MaCuaCQT` | string \\\\| null | | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise MessLog says why |\n| `results[].MessLog` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n### bkav_update_invoices (workflow step)\n\n- Change invoices not yet issued. by names how each invoice is found: partner (its partner id), number (Invoice.InvoiceForm + InvoiceSerial + InvoiceNo) or guid (Invoice.InvoiceGUID).\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Bkav connected account |\n| `by` | "partner" \\\\| "number" \\\\| "guid" | yes | |\n| `invoices` | object[] | yes | |\n| `invoices[].Invoice` | object | yes | |\n| `invoices[].Invoice.InvoiceTypeID` | number | yes | 1 GTGT, 2 b\xE1n h\xE0ng, 5 PXK ki\xEAm VCNB, 6 PXK g\u1EEDi b\xE1n \u0111\u1EA1i l\xFD |\n| `invoices[].Invoice.InvoiceDate` | string | yes | ISO date |\n| `invoices[].Invoice.BuyerCode` | string | | |\n| `invoices[].Invoice.BuyerName` | string | | |\n| `invoices[].Invoice.BuyerTaxCode` | string | | |\n| `invoices[].Invoice.BuyerUnitName` | string | | |\n| `invoices[].Invoice.BuyerAddress` | string | | |\n| `invoices[].Invoice.BuyerBankAccount` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.PayMethodID` | number | | 1 TM, 2 CK, 3 TM/CK, \u2026 |\n| `invoices[].Invoice.ReceiveTypeID` | number | | 1 email, 2 SMS, 3 both, 4 courier |\n| `invoices[].Invoice.ReceiverEmail` | string | | |\n| `invoices[].Invoice.ReceiverMobile` | string | | |\n| `invoices[].Invoice.ReceiverAddress` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.ReceiverName` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.Note` | string | | |\n| `invoices[].Invoice.BillCode` | string | | Sent empty when absent: Bkav refuses it missing |\n| `invoices[].Invoice.CurrencyID` | string | | |\n| `invoices[].Invoice.ExchangeRate` | number | | |\n| `invoices[].Invoice.InvoiceForm` | string | | M\u1EABu s\u1ED1 |\n| `invoices[].Invoice.InvoiceSerial` | string | | K\xFD hi\u1EC7u |\n| `invoices[].Invoice.InvoiceNo` | number | | S\u1ED1; 0 when Bkav assigns it |\n| `invoices[].Invoice.MaCuaCQT` | string | | Set only by a cash-register flow that already holds the authority\'s code |\n| `invoices[].Invoice.UserDefine` | string | | JSON-encoded template fields agreed with Bkav |\n| `invoices[].Invoice.UIDefine` | string | | JSON-encoded delivery-note fields (InvoiceTypeID 5 and 6) |\n| `invoices[].Invoice.isBTH` | boolean | | |\n| `invoices[].Invoice.CCCD` | string | | |\n| `invoices[].Invoice.PassportNumber` | string | | |\n| `invoices[].Invoice.FiscalCodes` | string | | |\n| `invoices[].Invoice.Reason` | string | | Why \u2014 required on a replacement or adjustment |\n| `invoices[].Invoice.OriginalInvoiceIdentify` | string | | `[m\u1EABu s\u1ED1]_[k\xFD hi\u1EC7u]_[s\u1ED1]` of the invoice a replacement or adjustment corrects |\n| `invoices[].Invoice.InvoiceGUID` | string | | Addresses the invoice for an update by guid |\n| `invoices[].ListInvoiceDetailsWS` | object[] | yes | |\n| `invoices[].ListInvoiceDetailsWS[].ItemTypeID` | number | | 0 goods, 4 description line, 15 promotional goods, \u2026 |\n| `invoices[].ListInvoiceDetailsWS[].IsDiscount` | boolean | | |\n| `invoices[].ListInvoiceDetailsWS[].ItemCode` | string | | |\n| `invoices[].ListInvoiceDetailsWS[].ItemName` | string | yes | |\n| `invoices[].ListInvoiceDetailsWS[].UnitName` | string | | |\n| `invoices[].ListInvoiceDetailsWS[].Qty` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].Price` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].Amount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].TaxRateID` | number | | 1 0%, 2 5%, 3 10%, 4 kh\xF4ng ch\u1ECBu thu\u1EBF, 9 8%, \u2026 |\n| `invoices[].ListInvoiceDetailsWS[].TaxRate` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].TaxAmount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].OtherAmount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].DiscountRate` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].DiscountAmount` | number | | |\n| `invoices[].ListInvoiceDetailsWS[].UserDefineDetails` | string | | |\n| `invoices[].ListInvoiceDetailsWS[].IsIncrease` | boolean \\\\| null | | On an adjustment: true raises the amount, false lowers it, absent adjusts information |\n| `invoices[].ListInvoiceDetailsWS[].SpecialtyItems` | string | | |\n| `invoices[].PartnerInvoiceStringID` | string | yes | |\n| `invoices[].AttachListNumber` | string | | S\u1ED1 of a b\u1EA3ng k\xEA the invoice refers to |\n| `invoices[].AttachListDate` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `results` | object[] | yes | |\n| `results[].PartnerInvoiceID` | number | yes | |\n| `results[].PartnerInvoiceStringID` | string \\\\| null | yes | Null on a delete\'s answer |\n| `results[].InvoiceGUID` | string | yes | |\n| `results[].InvoiceForm` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceSerial` | string \\\\| null | yes | Null on a draft\'s link answer |\n| `results[].InvoiceNo` | number | yes | Assigned by a numbered kind; 0 on a draft |\n| `results[].MTC` | string \\\\| null | yes | M\xE3 tra c\u1EE9u the buyer looks the invoice up with; null on a draft\'s link answer |\n| `results[].MaCuaCQT` | string \\\\| null | | |\n| `results[].Status` | number | yes | 0 succeeded; otherwise MessLog says why |\n| `results[].MessLog` | string | yes | |\n| `failed` | number | yes | Invoices whose own Status is not 0 |\n\n## drive\n\n### drive_export_file (workflow step)\n\n- Upload a Lotics file to the member\'s Google Drive.\n- The file is uploaded as-is \u2014 a .docx stays a .docx, a .xlsx stays a .xlsx.\n- It is deliberately NOT converted into a Google Doc or Sheet: conversion re-flows the layout of a generated document, and a native Google file is live, so its figures would drift away from the record they came from.\n- Use this to archive a finished document, the way you would attach it to an email.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The ID of the connected Google Drive account to use |\n| `file_id` | string | yes | Lotics file ID to upload |\n| `folder_id` | string | | Drive folder ID to place the file in; omit for the member\'s Drive root |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `drive_file_id` | string | yes | Drive file ID of the uploaded copy |\n| `web_view_link` | string \\\\| null | yes | URL to open the uploaded file in Drive |\n\n### drive_import_file (workflow step)\n\n- Import a Google Drive file into the workspace as a Lotics file, and return its file id.\n- Google Docs arrive as .docx, Sheets as .xlsx, Slides as .pptx; everything else transfers unchanged. **This is also how you READ a Drive file.** There is no separate Drive read tool: import the file, then use `view_files` on the returned id. (chat)\n- That path handles scanned documents \u2014 a photographed B/L or a stamped invoice inside a .docx reaches the model as a picture.\n- Exporting Drive content as plain text would return an empty document for exactly those files.\n- Folders, Forms, shortcuts and other Drive items with no file form cannot be imported.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The ID of the connected Google Drive account to use |\n| `file_id` | string | yes | Drive file ID, from drive_query_files (workflow step, chat) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `id` | string | yes | Lotics file ID \u2014 pass this to view_files to read the contents (chat) |\n| `filename` | string | yes | |\n| `mime_type` | string | yes | |\n| `url` | string | yes | |\n\n### drive_query_files (workflow step)\n\n- Search the member\'s Google Drive using Drive query syntax. ## Query syntax Terms are `field operator value`; combine with `and` / `or` / `not`.\n- String values are single-quoted. - Name: `name = \'Rate card\'`, `name contains \'invoice\'` - Full text (body + title): `fullText contains \'CMA CGM\'` - Type: `mimeType = \'application/vnd.google-apps.spreadsheet\'`, `mimeType contains \'pdf\'` - Folder: `\'<folder_id>\' in parents` - Time (RFC 3339): `modifiedTime > \'2026-01-01T00:00:00\'` - Exclude deleted: `trashed = false` (always worth adding) - Starred / shared: `starred = true`, `sharedWithMe = true` Google-native types: `application/vnd.google-apps.document` (Docs), `.spreadsheet` (Sheets), `.presentation` (Slides), `.folder`.\n- Examples: - `name contains \'packing list\' and trashed = false` - `\'1a2b3c\' in parents and modifiedTime > \'2026-08-01T00:00:00\'` - `fullText contains \'demurrage\' and mimeType = \'application/vnd.google-apps.document\'` Results carry enough metadata (name, type, modified time, size, owner) to pick a file WITHOUT importing it.\n- Import only the one you actually need \u2014 `drive_import_file` persists a copy in the workspace. (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The ID of the connected Google Drive account to use |\n| `query` | string | yes | Drive query syntax expression |\n| `max_result` | number | yes | Maximum number of files to retrieve (1-50) |\n| `order_by` | string | | Sort order, e.g. \'modifiedTime desc\', \'name\' |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `files` | object[] | yes | List of matching Drive files |\n| `files[].id` | string | yes | Drive file ID \u2014 pass this to drive_import_file (workflow step, chat) |\n| `files[].name` | string | yes | File name as it appears in Drive |\n| `files[].mime_type` | string | yes | Drive MIME type; \'application/vnd.google-apps.*\' means Google-native |\n| `files[].modified_time` | string \\\\| null | yes | RFC 3339 timestamp of the last modification |\n| `files[].size_bytes` | number \\\\| null | yes | Size in bytes; null for Google-native files, which have no stored size until exported |\n| `files[].owner` | string \\\\| null | yes | Email address of the file\'s owner |\n| `files[].web_view_link` | string \\\\| null | yes | URL to open the file in Drive |\n\n## evaluate\n\n### evaluate_formula (workflow step)\n\n- Compute a formula over one record as its table\'s own formula fields compute: fields referenced as `{alias}`, lookups and rollups read as the row holds them.\n- Writes nothing.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `table_id` | string | yes | |\n| `record_id` | string | yes | |\n| `expression` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `value` | number \\\\| string \\\\| boolean \\\\| null | yes | |\n\n## excel\n\n### excel_get_rows (workflow step)\n\n- Read every row of a sheet in an .xlsx, .xls or .csv file as a list of objects keyed by the header row\'s text.\n- A cell is its typed value: a number, true or false, text, a date as ISO text ("2026-03-02", "2026-03-02T09:30:00", or "09:30:00" for a time alone), null when blank.\n- A formula cell is the value the file last computed; an error cell is its code as text ("#N/A").\n- Every CSV cell is text or null.\n- A header cell inside a merge takes the merge\'s text; a blank header is keyed by its column letter ("C"), and a header text that repeats gets its column letter added ("Amount (F)"). headers lists the keys in sheet order.\n- Rows above header_row and rows blank in every column are left out.\n- One call reads a sheet of at most 10000 rows, its header counted, and returns at most 250000 cells (rows \xD7 kept columns); a sheet past either is refused with its row count, never cut short.\n- Read-only.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | |\n| `worksheet` | string \\\\| number | | Sheet name, or 0-based index. |\n| `header_row` | number | | 1-based row holding the headers. |\n| `columns` | string[] | | Keys to keep, as headers lists them. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | |\n| `worksheet_name` | string | yes | |\n| `header_row` | number | yes | |\n| `headers` | string[] | yes | The key of each returned column, in sheet order |\n| `row_count` | number | yes | |\n| `rows` | object[] | yes | |\n\n## facebook\n\n### facebook_create_post (workflow step)\n\n- Publish a post to a Facebook Page feed.\n- Requires a Facebook connected account whose page allows content management.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `page_id` | string | yes | Facebook Page to post to \u2014 one of the connected account\'s managed pages. |\n| `message` | string | yes | |\n| `link` | string | | A post carries either a link or media. |\n| `media_file_ids` | string[] | | Photos, or one video on its own. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `post_id` | string | yes | |\n| `url` | string | yes | |\n\n### facebook_list_comments (workflow step)\n\n- Read the most recent comments left on a Facebook Page\'s posts.\n- Reply with facebook_reply_to_comment, passing the comment_id. (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `page_id` | string | yes | One of the connected account\'s managed pages. |\n| `limit` | integer | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `comments` | object[] | yes | |\n| `comments[].comment_id` | string | yes | |\n| `comments[].post_id` | string | yes | |\n| `comments[].author_id` | string | yes | |\n| `comments[].author_name` | string | yes | |\n| `comments[].message` | string | yes | |\n| `comments[].created_time` | string | yes | |\n\n### facebook_list_conversations (workflow step)\n\n- Read a Facebook Page\'s Messenger conversations with their recent messages.\n- Answer someone with facebook_send_message, passing the person_id. (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `page_id` | string | yes | One of the connected account\'s managed pages. |\n| `limit` | integer | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `conversations` | object[] | yes | |\n| `conversations[].conversation_id` | string | yes | |\n| `conversations[].person_id` | string \\\\| null | yes | |\n| `conversations[].person_name` | string \\\\| null | yes | |\n| `conversations[].messages` | object[] | yes | |\n| `conversations[].messages[].from_id` | string | yes | |\n| `conversations[].messages[].from_name` | string | yes | |\n| `conversations[].messages[].message` | string | yes | |\n| `conversations[].messages[].created_time` | string | yes | |\n\n### facebook_reply_to_comment (workflow step)\n\n- Reply publicly to a comment on a Facebook Page post, as the page.\n- The reply is visible to everyone who can see the comment.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `page_id` | string | yes | The page that owns the post the comment is on. |\n| `comment_id` | string | yes | From facebook_list_comments. (workflow step, chat) |\n| `message` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `comment_id` | string | yes | The reply\'s own id. |\n\n### facebook_send_message (workflow step)\n\n- Answer someone who messaged a Facebook Page, as the page.\n- Meta accepts a reply only within 24 hours of that person\'s last message.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `page_id` | string | yes | The page the person wrote to. |\n| `person_id` | string | yes | From facebook_list_conversations. Page-scoped to this page. (workflow step, chat) |\n| `message` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message_id` | string | yes | |\n\n## fedex\n\n### fedex_cancel_draft (workflow step)\n\n- Cancel (delete) an unconfirmed FedEx draft (Open Ship) booking by its reference.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | FedEx connected account |\n| `reference` | string | yes | The draft\'s reference (Open Ship index) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `cancelled` | boolean | yes | |\n\n### fedex_confirm_shipment (workflow step)\n\n- Confirm a FedEx draft (Open Ship) booking by its reference, generating the label / AWB.\n- Returns the master tracking number and any piece tracking numbers + label URLs.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | FedEx connected account |\n| `reference` | string | yes | The draft\'s reference (Open Ship index) from fedex_create_draft_shipment (workflow step, chat) |\n| `master_tracking_number` | string | | The master_tracking_number returned by fedex_create_draft_shipment \u2014 required to finalize the label (workflow step, chat) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `master_tracking_number` | string | | |\n| `tracking_numbers` | string[] | yes | |\n| `label_urls` | string[] | yes | |\n\n### fedex_create_draft_shipment (workflow step)\n\n- Create a FedEx draft (Open Ship) booking \u2014 no label yet.\n- Confirm later with fedex_confirm_shipment. reference is a unique key you choose (e.g. the shipment order number) used to confirm or cancel. (workflow step, chat)\n- Supports multiple packages, special services, and (international) customs commodities.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | FedEx connected account |\n| `reference` | string | yes | Unique key for this draft (the Open Ship index) |\n| `shipper` | object | yes | |\n| `shipper.contact` | object | | |\n| `shipper.contact.person_name` | string | | |\n| `shipper.contact.company_name` | string | | |\n| `shipper.contact.phone_number` | string | | |\n| `shipper.address` | object | yes | |\n| `shipper.address.postal_code` | string | yes | |\n| `shipper.address.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `shipper.address.city` | string | | |\n| `shipper.address.state_or_province_code` | string | | |\n| `shipper.address.street_lines` | string[] | | |\n| `shipper.address.residential` | boolean | | |\n| `recipient` | object | yes | |\n| `recipient.contact` | object | | |\n| `recipient.contact.person_name` | string | | |\n| `recipient.contact.company_name` | string | | |\n| `recipient.contact.phone_number` | string | | |\n| `recipient.address` | object | yes | |\n| `recipient.address.postal_code` | string | yes | |\n| `recipient.address.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `recipient.address.city` | string | | |\n| `recipient.address.state_or_province_code` | string | | |\n| `recipient.address.street_lines` | string[] | | |\n| `recipient.address.residential` | boolean | | |\n| `service_type` | string | yes | FedEx service type e.g. INTERNATIONAL_PRIORITY |\n| `packaging_type` | string | | FedEx packaging type e.g. YOUR_PACKAGING |\n| `packages` | object[] | yes | One entry per package; weight in kg |\n| `packages[].weight` | number | yes | |\n| `packages[].weight_units` | "KG" \\\\| "LB" | | Weight units; FedEx requires LB for US domestic express |\n| `packages[].length` | number | | |\n| `packages[].width` | number | | |\n| `packages[].height` | number | | |\n| `packages[].dimension_units` | "CM" \\\\| "IN" | | |\n| `ship_datestamp` | string | | Ship date YYYY-MM-DD |\n| `pickup_type` | string | | FedEx pickup type e.g. USE_SCHEDULED_PICKUP |\n| `special_services` | string[] | | FedEx special service types e.g. SATURDAY_DELIVERY, FEDEX_ONE_RATE |\n| `commodities` | object[] | | Customs commodities for international shipments |\n| `commodities[].description` | string | yes | |\n| `commodities[].quantity` | number | yes | |\n| `commodities[].customs_value` | number | yes | |\n| `commodities[].currency` | string | yes | |\n| `commodities[].weight` | number | yes | |\n| `commodities[].weight_units` | "KG" \\\\| "LB" | | |\n| `commodities[].country_of_manufacture` | string | | ISO 3166-1 alpha-2 |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `reference` | string | yes | |\n| `status` | "draft" | yes | |\n| `master_tracking_number` | string | | Pass this to fedex_confirm_shipment to finalize the draft (workflow step, chat) |\n\n### fedex_create_shipment (workflow step)\n\n- Create a FedEx shipment and generate the label / AWB in one call.\n- Returns the master tracking number, piece tracking numbers, and label URLs.\n- Use the Open Ship draft flow instead when you need to stage a booking before committing.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | FedEx connected account |\n| `shipper` | object | yes | |\n| `shipper.contact` | object | | |\n| `shipper.contact.person_name` | string | | |\n| `shipper.contact.company_name` | string | | |\n| `shipper.contact.phone_number` | string | | |\n| `shipper.address` | object | yes | |\n| `shipper.address.postal_code` | string | yes | |\n| `shipper.address.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `shipper.address.city` | string | | |\n| `shipper.address.state_or_province_code` | string | | |\n| `shipper.address.street_lines` | string[] | | |\n| `shipper.address.residential` | boolean | | |\n| `recipient` | object | yes | |\n| `recipient.contact` | object | | |\n| `recipient.contact.person_name` | string | | |\n| `recipient.contact.company_name` | string | | |\n| `recipient.contact.phone_number` | string | | |\n| `recipient.address` | object | yes | |\n| `recipient.address.postal_code` | string | yes | |\n| `recipient.address.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `recipient.address.city` | string | | |\n| `recipient.address.state_or_province_code` | string | | |\n| `recipient.address.street_lines` | string[] | | |\n| `recipient.address.residential` | boolean | | |\n| `service_type` | string | yes | FedEx service type e.g. INTERNATIONAL_PRIORITY |\n| `packaging_type` | string | | FedEx packaging type e.g. YOUR_PACKAGING |\n| `packages` | object[] | yes | One entry per package; weight in kg |\n| `packages[].weight` | number | yes | |\n| `packages[].weight_units` | "KG" \\\\| "LB" | | Weight units; FedEx requires LB for US domestic express |\n| `packages[].length` | number | | |\n| `packages[].width` | number | | |\n| `packages[].height` | number | | |\n| `packages[].dimension_units` | "CM" \\\\| "IN" | | |\n| `ship_datestamp` | string | | Ship date YYYY-MM-DD |\n| `pickup_type` | string | | FedEx pickup type e.g. USE_SCHEDULED_PICKUP |\n| `special_services` | string[] | | FedEx special service types |\n| `commodities` | object[] | | Customs commodities for international shipments |\n| `commodities[].description` | string | yes | |\n| `commodities[].quantity` | number | yes | |\n| `commodities[].customs_value` | number | yes | |\n| `commodities[].currency` | string | yes | |\n| `commodities[].weight` | number | yes | |\n| `commodities[].weight_units` | "KG" \\\\| "LB" | | |\n| `commodities[].country_of_manufacture` | string | | ISO 3166-1 alpha-2 |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `master_tracking_number` | string | | |\n| `tracking_numbers` | string[] | yes | |\n| `label_urls` | string[] | yes | |\n\n### fedex_rate (workflow step)\n\n- Get FedEx rate quotes for a shipment.\n- Returns one priced row per available service (or just the requested service_type).\n- Use before booking to compare costs.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | FedEx connected account |\n| `shipper` | object | yes | |\n| `shipper.postal_code` | string | yes | |\n| `shipper.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `shipper.city` | string | | |\n| `shipper.state_or_province_code` | string | | |\n| `shipper.street_lines` | string[] | | |\n| `shipper.residential` | boolean | | |\n| `recipient` | object | yes | |\n| `recipient.postal_code` | string | yes | |\n| `recipient.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `recipient.city` | string | | |\n| `recipient.state_or_province_code` | string | | |\n| `recipient.street_lines` | string[] | | |\n| `recipient.residential` | boolean | | |\n| `packages` | object[] | yes | One entry per package; weight in kg |\n| `packages[].weight` | number | yes | |\n| `packages[].weight_units` | "KG" \\\\| "LB" | | Weight units; FedEx requires LB for US domestic express |\n| `packages[].length` | number | | |\n| `packages[].width` | number | | |\n| `packages[].height` | number | | |\n| `packages[].dimension_units` | "CM" \\\\| "IN" | | |\n| `service_type` | string | | FedEx service type e.g. INTERNATIONAL_PRIORITY. Omit to rate all available services. |\n| `packaging_type` | string | | FedEx packaging type e.g. YOUR_PACKAGING |\n| `pickup_type` | string | | FedEx pickup type e.g. USE_SCHEDULED_PICKUP |\n| `ship_datestamp` | string | | Ship date YYYY-MM-DD |\n| `special_services` | string[] | | FedEx special service types e.g. SATURDAY_DELIVERY |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `services` | object[] | yes | |\n| `services[].service_type` | string | yes | |\n| `services[].service_name` | string | | |\n| `services[].total_charge` | number | | |\n| `services[].currency` | string | | |\n\n### fedex_service_availability (workflow step)\n\n- Check what FedEx services serve a lane. query "transit_times" returns delivery commitments per service; "package_and_service_options" returns valid service/packaging combinations; "special_service_options" returns available special services.\n- Use before rating/booking to learn the valid service types for an origin \u2192 destination.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | FedEx connected account |\n| `query` | "transit_times" \\\\| "special_service_options" \\\\| "package_and_service_options" | yes | |\n| `shipper` | object | yes | |\n| `shipper.postal_code` | string | yes | |\n| `shipper.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `shipper.city` | string | | |\n| `shipper.state_or_province_code` | string | | |\n| `shipper.street_lines` | string[] | | |\n| `shipper.residential` | boolean | | |\n| `recipient` | object | yes | |\n| `recipient.postal_code` | string | yes | |\n| `recipient.country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `recipient.city` | string | | |\n| `recipient.state_or_province_code` | string | | |\n| `recipient.street_lines` | string[] | | |\n| `recipient.residential` | boolean | | |\n| `packages` | object[] | yes | One entry per package; weight in kg unless weight_units set |\n| `packages[].weight` | number | yes | |\n| `packages[].weight_units` | "KG" \\\\| "LB" | | Weight units; FedEx requires LB for US domestic express |\n| `packages[].length` | number | | |\n| `packages[].width` | number | | |\n| `packages[].height` | number | | |\n| `packages[].dimension_units` | "CM" \\\\| "IN" | | |\n| `ship_datestamp` | string | | Ship date YYYY-MM-DD |\n| `carrier_codes` | string[] | yes | FedEx carrier codes \u2014 at least one, e.g. FDXE (Express), FDXG (Ground) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `result` | object | yes | |\n\n### fedex_validate_address (workflow step)\n\n- Validate one or more addresses with FedEx.\n- Returns each resolved address plus its classification (e.g.\n- BUSINESS / RESIDENTIAL) and deliverability attributes.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | FedEx connected account |\n| `addresses` | object[] | yes | |\n| `addresses[].postal_code` | string | yes | |\n| `addresses[].country_code` | string | yes | ISO 3166-1 alpha-2 country code, e.g. VN, US, SG |\n| `addresses[].city` | string | | |\n| `addresses[].state_or_province_code` | string | | |\n| `addresses[].street_lines` | string[] | | |\n| `addresses[].residential` | boolean | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `resolved_addresses` | object[] | yes | |\n| `resolved_addresses[].city` | string | | |\n| `resolved_addresses[].state_or_province_code` | string | | |\n| `resolved_addresses[].postal_code` | string | | |\n| `resolved_addresses[].country_code` | string | | |\n| `resolved_addresses[].classification` | string | | |\n| `resolved_addresses[].attributes` | object | | |\n\n## generate\n\n### generate_bank_qr_code (workflow step)\n\n- Generate a bank-payment QR code (VietQR for Vietnam) and save it as a PNG to the workspace.\n- The QR encodes the recipient bank, account number, and optional amount/description so any banking app can scan and pre-fill the transfer.\n- Call list_banks first to discover valid bank codes. (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `country` | "VN" | yes | Country whose bank-payment QR standard to use. Currently only \'VN\' (Vietnam / VietQR) is supported. |\n| `bank_code` | "ICB" \\\\| "VCB" \\\\| "BIDV" \\\\| "VBA" \\\\| "OCB" \\\\| "MB" \\\\| "TCB" \\\\| "ACB" \\\\| "VPB" \\\\| "TPB" \\\\| "STB" \\\\| "HDB" \\\\| "VCCB" \\\\| "SCB" \\\\| "VIB" \\\\| "SHB" \\\\| "EIB" \\\\| "MSB" \\\\| "CAKE" \\\\| "Ubank" \\\\| "TIMO" \\\\| "SGICB" \\\\| "BAB" \\\\| "momo" \\\\| "PVDB" \\\\| "PVCB" \\\\| "MBV" \\\\| "NCB" \\\\| "SHBVN" \\\\| "ABB" \\\\| "VAB" \\\\| "NAB" \\\\| "PGB" \\\\| "VIETBANK" \\\\| "BVB" \\\\| "SEAB" \\\\| "COOPBANK" \\\\| "LPB" \\\\| "KLB" \\\\| "KBank" \\\\| "CIMB" \\\\| "WVN" | yes | Bank code from list_banks. Must belong to the specified country. (workflow step, chat) |\n| `account_number` | string | yes | Beneficiary account number at the bank. |\n| `amount` | number | | Payment amount in the local currency (VND for Vietnam). Omit for a static QR with no fixed amount. |\n| `description` | string | | Transfer description / memo. Max 50 bytes; Vietnamese diacritics use 2\u20133 bytes per character. |\n| `account_name` | string | | Beneficiary name shown on the payer\'s banking app confirmation screen. Max 25 bytes \u2014 banks normalize Vietnamese names to ASCII uppercase, pass the normalized form (e.g. \'NGUYEN VAN A\'). |\n| `filename` | string | | Output filename. Defaults to \'bank_qr_<bank_code>.png\'. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `id` | string | yes | |\n| `filename` | string | yes | |\n| `mime_type` | string | yes | |\n| `url` | string | yes | |\n| `bank` | object | yes | |\n| `bank.country` | string | yes | |\n| `bank.code` | string | yes | |\n| `bank.short_name` | string | yes | |\n| `bank.full_name` | string | yes | |\n\n### generate_qr_code (workflow step)\n\n- Generate a QR code PNG from text or a URL and save it to the workspace.\n- For bank payment QR codes, use generate_bank_qr_code instead \u2014 it builds the correct EMVCo payload from a bank code and account number. (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `data` | string | yes | Text or URL to encode into the QR code. |\n| `filename` | string | | Output filename. Defaults to \'qr_code.png\'. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `id` | string | yes | |\n| `filename` | string | yes | |\n| `mime_type` | string | yes | |\n| `url` | string | yes | |\n\n## gmail\n\n### gmail_get_thread (workflow step)\n\n- Retrieve all messages in a Gmail thread.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The ID of the connected Gmail account to use |\n| `thread_id` | string | yes | Gmail thread ID |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `messages` | object[] | yes | |\n| `messages[].id` | string | yes | Gmail message ID |\n| `messages[].threadId` | string | yes | Gmail thread ID |\n| `messages[].labelIds` | string[] | yes | Gmail label IDs (e.g. INBOX, UNREAD) |\n| `messages[].subject` | string | yes | Email subject |\n| `messages[].from` | string | yes | Sender email address |\n| `messages[].to` | string | yes | Recipient email address |\n| `messages[].date` | string | yes | Email date in the workspace timezone, with its offset |\n| `messages[].snippet` | string | yes | Short plain-text preview of the email body |\n| `messages[].cc` | string \\\\| null | | CC recipients |\n| `messages[].bcc` | string \\\\| null | | BCC recipients |\n\n### gmail_query_emails (workflow step)\n\n- Search Gmail messages using Gmail search syntax. ## Query syntax Operators (no space after colon): - People: from:, to:, cc:, bcc:, deliveredto: - Content: subject:, "exact phrase", +exact_word, AROUND N (proximity) - Status: is:unread, is:read, is:starred, is:important, is:muted - Labels: label:name, in:inbox, in:sent, in:drafts, in:spam, in:trash, in:anywhere - Category: category:primary, category:social, category:promotions, category:updates, category:forums - Attachments: has:attachment, has:drive, filename:pdf, filename:report.xlsx (has:attachment matches non-inline attachments only \u2014 it misses inline-embedded PDFs, common in receipts/invoices; open the message with gmail_read_email to detect those) - Size: larger:5M, smaller:2M, size:1000000 - Dates (YYYY/MM/DD): after:2024/01/01, before:2024/12/31 - Relative dates: newer_than:7d, older_than:3m, newer_than:1y (d=days, m=months, y=years) Combining: space = AND, OR (uppercase), - (exclude), () for grouping, {} alternative OR syntax. (workflow step, chat)\n- Examples: - from:alice is:unread has:attachment - subject:"quarterly review" newer_than:2d - {from:alice from:bob} subject:project -in:drafts - larger:5M filename:pdf older_than:6m - in:anywhere deliveredto:billing@company.com\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The ID of the connected Gmail account to use |\n| `query` | string | yes | Gmail search syntax expression |\n| `max_result` | number | yes | Maximum number of emails to retrieve (1-50) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `messages` | object[] | yes | List of matching Gmail messages |\n| `messages[].id` | string | yes | Gmail message ID |\n| `messages[].threadId` | string | yes | Gmail thread ID |\n| `messages[].labelIds` | string[] | yes | Gmail label IDs (e.g. INBOX, UNREAD) |\n| `messages[].subject` | string | yes | Email subject |\n| `messages[].from` | string | yes | Sender email address |\n| `messages[].to` | string | yes | Recipient email address |\n| `messages[].date` | string | yes | Email date in the workspace timezone, with its offset |\n| `messages[].snippet` | string | yes | Short plain-text preview of the email body |\n| `messages[].cc` | string \\\\| null | | CC recipients |\n| `messages[].bcc` | string \\\\| null | | BCC recipients |\n\n### gmail_query_labels (workflow step)\n\n- List Gmail labels with message and thread counts.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The ID of the connected Gmail account to use |\n| `name` | string | | Match labels by name prefix. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `labels` | object[] | yes | |\n| `labels[].id` | string | yes | |\n| `labels[].name` | string | yes | |\n| `labels[].type` | string | yes | system or user |\n| `labels[].messagesTotal` | number | yes | |\n| `labels[].messagesUnread` | number | yes | |\n| `labels[].threadsTotal` | number | yes | |\n| `labels[].threadsUnread` | number | yes | |\n\n### gmail_read_email (workflow step)\n\n- Fetch a Gmail message\'s body and the list of its attachments.\n- Attachments are stored as workspace files \u2014 the result names each one\'s file_id; read one with view_files, attach one to a record with update_records. (chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The ID of the connected account |\n| `message_id` | string | yes | The ID of the Gmail message to retrieve |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message` | object | yes | The full Gmail message |\n| `message.id` | string | | Gmail message ID |\n| `message.threadId` | string | | Gmail thread ID |\n| `message.historyId` | string | | Gmail history ID |\n| `message.labelIds` | string[] | | Gmail label IDs |\n| `message.subject` | string | yes | Email subject |\n| `message.from` | string | yes | Sender email address |\n| `message.to` | string | yes | Recipient email address |\n| `message.date` | string | yes | Email date in the workspace timezone, with its offset |\n| `message.body` | object | yes | Email body |\n| `message.body.type` | "text" \\\\| "html" | yes | Body content type |\n| `message.body.content` | string | yes | Body content |\n| `message.cc` | string \\\\| null | | CC recipients |\n| `message.bcc` | string \\\\| null | | BCC recipients |\n| `message.inReplyTo` | string \\\\| null | | In-Reply-To header |\n| `message.replyTo` | string \\\\| null | | Reply-To header |\n| `message.references` | string[] \\\\| null | | References header |\n| `message.attachments` | object[] | yes | Email attachments |\n| `message.attachments[].partId` | string | yes | MIME part ID |\n| `message.attachments[].filename` | string | yes | Attachment filename as stored, which may differ from the sender\'s |\n| `message.attachments[].mimeType` | string | yes | MIME type as stored, recovered from the filename when the sender declared a generic one |\n| `message.attachments[].attachmentId` | string | yes | Gmail attachment ID |\n| `message.attachments[].file_id` | string | | Stored file ID \u2014 pass to view_files to read it, or to update_records to attach it (chat) |\n| `message.attachments[].size` | number | | Stored size in bytes |\n\n### gmail_reply_email (workflow step)\n\n- Draft or send a reply to a Gmail message.\n- Use reply_body (markdown) or template_id + template_data \u2014 not both.\n- Attach stored files by passing their file_ids.\n- Leaves an unsent draft unless draft is false.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `original_message_id` | string | yes | |\n| `reply_body` | string | | Reply body (markdown). Mutually exclusive with template_id. |\n| `template_id` | string | | |\n| `template_data` | object | | |\n| `cc` | string | | CC recipients, comma-separated |\n| `bcc` | string | | BCC recipients, comma-separated |\n| `draft` | boolean | | Leave true to save an unsent draft the member reviews. Pass false only to deliver the message now \u2014 delivery is irreversible. |\n| `file_ids` | string[] | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message_id` | string | yes | The ID of the sent message or saved draft |\n| `subject` | string | yes | The subject as sent |\n| `html` | string | yes | The body as sent, rendered to HTML |\n| `markdown` | string | | The body as written in markdown; absent when a template rendered it |\n| `to` | string | yes | The original sender the reply went to |\n\n### gmail_send_email (workflow step)\n\n- Draft or send a Gmail email.\n- Use content (markdown) or template_id + template_data \u2014 not both.\n- Attach stored files by passing their file_ids.\n- Leaves an unsent draft unless draft is false.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `to` | string | | Recipient email(s), comma-separated. Required unless using a template. |\n| `subject` | string | | |\n| `cc` | string | | CC recipients, comma-separated |\n| `bcc` | string | | BCC recipients, comma-separated |\n| `content` | string | | Email body (markdown). Mutually exclusive with template_id. |\n| `template_id` | string | | |\n| `template_data` | object | | |\n| `draft` | boolean | | Leave true to save an unsent draft the member reviews. Pass false only to deliver the message now \u2014 delivery is irreversible. |\n| `file_ids` | string[] | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message_id` | string | yes | The ID of the sent message or saved draft |\n| `subject` | string | yes | The subject as sent |\n| `html` | string | yes | The body as sent, rendered to HTML |\n| `markdown` | string | | The body as written in markdown; absent when a template rendered it |\n| `to` | string | yes | The recipients as sent, comma-separated |\n\n## import\n\n### import_rows (workflow step)\n\n- Match a source\'s rows to a table by a key and write what differs: a row whose match fields equal no live row\'s is added, one equalling a row\'s changes the cells that differ, one that changes nothing is counted and not written.\n- It keeps nothing of its own: what it wrote and what it held are its answer, read under the run\'s own reach of the table \u2014 a workflow hands its caller only what it returns. match names the fields that identify a row: text, number or date fields a row is written with, compared as trimmed text, case ignored, and link fields holding one row, compared by the row linked. values holds a row\'s cells by field key, as create_records takes them; a link cell also takes { "lookup": "<text>" }, the linked row found by that text in its table\'s unique or autonumber field, else its title, among the rows the run reads of that table, and a link holding many rows a list of them. (workflow step, chat, MCP, CLI)\n- A row is held, never written, when: it carries hold (its text is the message); several live rows or an earlier row of the call carry its key; a link\'s text names no row, or several; the table refuses it or its row is locked; create is false and no row carries its key; or, under conflicts "hold", a live cell holds another value \u2014 the row\'s empty cells are still written. conflicts "overwrite" writes over a differing cell, "fill_empty" leaves it. conflicts.fields sets one field\'s own.\n- A number and the field its unit or currency is read from are one cell, under the number\'s policy: both written, or both held.\n- A row whose key names a row outside the rows the run may write is held "refused" with nothing of that row: no record_id, no changes.\n- A row carrying remove: true proposes moving the row its key names to the trash: always held, whatever create and conflicts say, and counted unchanged where no row carries its key.\n- A row the source leaves out is never touched.\n- One call takes at most 10000 rows; more is refused with the count, never cut short.\n- Returns { read, created, changed, unchanged, held, written, written_omitted, held_rows, held_omitted }: the rows the call took, the rows it added, the rows it changed a cell of, the rows that changed nothing, and the rows held \u2014 a row can be both changed and held. written lists each record written: record_id, kind ("created" or "changed"), line, and changes \u2014 each field changed, with its field_key, its label and its before and after as text, as a read of the table shows them (an option\'s label, a linked row\'s display, a member\'s name; null where empty).\n- A created row lists no changes: its values are the row\'s own. held_rows lists each row held: kind ("create", "update", "remove", or "unmatched" where no row carries its key and create is false), reason, message, record_id where one row carries its key, key (each match field as text), the changes it would make, links (each link text naming no one row, with the rows it could be as candidates { id, display }), and values \u2014 its key and the cells it would write, as the call gave them.\n- A later call given those values under conflicts { "default": "overwrite" } writes it.\n- The two lists hold at most 10000 entries and 8388608 characters of JSON between them, each in the rows\' order, what was written before what was held; written_omitted and held_omitted count what was left out.\n- A row gives each list at most one entry. dry_run: true runs the whole import, judged as a real one, and rolls it back: the answer says what it would write and hold, with dry_run true, and no row, number, event or workflow run is left behind.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `table_id` | string | yes | |\n| `match` | string[] | yes | |\n| `rows` | object[] | yes | |\n| `rows[].values` | object | yes | |\n| `rows[].hold` | string \\\\| null | | |\n| `rows[].remove` | boolean \\\\| null | | |\n| `rows[].line` | number \\\\| null | | Its line in the source, 1-based. |\n| `create` | boolean \\\\| null | | |\n| `conflicts` | object \\\\| null | | |\n| `dry_run` | boolean \\\\| null | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `dry_run` | true | | The import was judged and rolled back: the lists name what it would write, and no record_id in them exists. |\n| `read` | number | yes | |\n| `created` | number | yes | |\n| `changed` | number | yes | |\n| `unchanged` | number | yes | |\n| `held` | number | yes | |\n| `written` | object[] | yes | |\n| `written[].record_id` | string | yes | |\n| `written[].kind` | "created" \\\\| "changed" | yes | |\n| `written[].line` | number | | |\n| `written[].changes` | object[] | yes | |\n| `written[].changes[].field_key` | string | yes | |\n| `written[].changes[].label` | string | yes | |\n| `written[].changes[].before` | string \\\\| null | yes | |\n| `written[].changes[].after` | string \\\\| null | yes | |\n| `written_omitted` | number | yes | |\n| `held_rows` | object[] | yes | |\n| `held_rows[].line` | number | | |\n| `held_rows[].kind` | "create" \\\\| "update" \\\\| "remove" \\\\| "unmatched" | yes | |\n| `held_rows[].reason` | "stated" \\\\| "removal" \\\\| "conflict" \\\\| "no_match" \\\\| "ambiguous_key" \\\\| "unresolved_link" \\\\| "ambiguous_link" \\\\| "locked" \\\\| "refused" | yes | |\n| `held_rows[].message` | string | yes | |\n| `held_rows[].record_id` | string | | |\n| `held_rows[].key` | object | yes | |\n| `held_rows[].values` | object | yes | |\n| `held_rows[].changes` | object[] | yes | |\n| `held_rows[].changes[].field_key` | string | yes | |\n| `held_rows[].changes[].label` | string | yes | |\n| `held_rows[].changes[].before` | string \\\\| null | yes | |\n| `held_rows[].changes[].after` | string \\\\| null | yes | |\n| `held_rows[].links` | object[] | yes | |\n| `held_rows[].links[].field_key` | string | yes | |\n| `held_rows[].links[].text` | string | yes | |\n| `held_rows[].links[].candidates` | object[] | yes | |\n| `held_rows[].links[].candidates[].id` | string | yes | |\n| `held_rows[].links[].candidates[].display` | string | yes | |\n| `held_omitted` | number | yes | |\n\n## instagram\n\n### instagram_create_post (workflow step)\n\n- Publish to the connected Instagram account.\n- Instagram has no text-only posts.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `caption` | string | yes | |\n| `media_file_ids` | string[] | yes | One image, one video (published as a reel), or 2\u201310 images and videos as a carousel. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `post_id` | string | yes | |\n| `url` | string | yes | |\n\n## kiotviet\n\n### kiotviet_cancel_invoice (workflow step)\n\n- Cancel a KiotViet sales invoice by id, returning its stock.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n| `void_payment` | boolean | | Also void payments recorded on the invoice |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `cancelled` | true | yes | |\n\n### kiotviet_cancel_order (workflow step)\n\n- Cancel a KiotViet sales order by id, releasing the stock it reserved.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n| `void_payment` | boolean | | Also void payments recorded on the order |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `cancelled` | true | yes | |\n\n### kiotviet_create_category (workflow step)\n\n- Create a KiotViet product group (nh\xF3m h\xE0ng), optionally under a parent group.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `name` | string | yes | |\n| `parent_id` | number | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `category` | object | yes | |\n\n### kiotviet_create_customer (workflow step)\n\n- Create a KiotViet customer (kh\xE1ch h\xE0ng).\n- Returns the customer as KiotViet stores it, with its id and code.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `name` | string | yes | |\n| `code` | string | | Customer code (m\xE3 kh\xE1ch h\xE0ng) |\n| `contact_number` | string | | |\n| `email` | string | | |\n| `address` | string | | |\n| `location_name` | string | | Province/district as KiotViet names it |\n| `ward_name` | string | | |\n| `gender` | boolean | | true for male, false for female |\n| `birth_date` | string | | yyyy-mm-dd |\n| `group_ids` | number[] | | Customer group ids |\n| `comments` | string | | |\n| `branch_id` | number | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `customer` | object | yes | |\n\n### kiotviet_create_invoice (workflow step)\n\n- Create a KiotViet sales invoice (h\xF3a \u0111\u01A1n), which deducts stock.\n- Pass order_id to invoice an existing order.\n- Returns the invoice as KiotViet stores it, with its id and code.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `branch_id` | number | yes | |\n| `customer_id` | number | | |\n| `sold_by_id` | number | yes | KiotViet user id of the seller |\n| `purchase_date` | string | | ISO datetime |\n| `description` | string | | |\n| `discount` | number | | Discount amount on the whole document |\n| `lines` | object[] | yes | |\n| `lines[].product_id` | number | yes | |\n| `lines[].quantity` | number | yes | |\n| `lines[].price` | number | | Unit price |\n| `lines[].discount` | number | | Discount amount on the line |\n| `lines[].note` | string | | |\n| `order_id` | number | | The sales order this invoice settles |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invoice` | object | yes | |\n\n### kiotviet_create_order (workflow step)\n\n- Create a KiotViet sales order (\u0111\u1EB7t h\xE0ng) \u2014 the pre-invoice stage, which reserves stock.\n- Returns the order as KiotViet stores it, with its id and code.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `branch_id` | number | yes | |\n| `customer_id` | number | | |\n| `sold_by_id` | number | | KiotViet user id of the seller |\n| `purchase_date` | string | | ISO datetime |\n| `description` | string | | |\n| `discount` | number | | Discount amount on the whole document |\n| `lines` | object[] | yes | |\n| `lines[].product_id` | number | yes | |\n| `lines[].quantity` | number | yes | |\n| `lines[].price` | number | | Unit price |\n| `lines[].discount` | number | | Discount amount on the line |\n| `lines[].note` | string | | |\n| `make_invoice` | boolean | | Also issue the invoice at once |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `order` | object | yes | |\n\n### kiotviet_create_product (workflow step)\n\n- Create a KiotViet product (h\xE0ng h\xF3a), optionally with opening stock per branch.\n- Returns the product as KiotViet stores it, with its id and code.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `name` | string | yes | |\n| `code` | string | | Product code (m\xE3 h\xE0ng) |\n| `category_id` | number | yes | Product group id |\n| `base_price` | number | | Selling price |\n| `unit` | string | | |\n| `allows_sale` | boolean | | |\n| `is_active` | boolean | | |\n| `description` | string | | |\n| `weight` | number | | Grams |\n| `inventories` | object[] | | Stock per branch |\n| `inventories[].branch_id` | number | yes | |\n| `inventories[].on_hand` | number | yes | |\n| `inventories[].cost` | number | | Unit cost at this branch |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `product` | object | yes | |\n\n### kiotviet_delete_product (workflow step)\n\n- Delete a KiotViet product by id.\n- KiotViet refuses one that already appears on invoices.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `deleted` | true | yes | |\n\n### kiotviet_get_customer (workflow step)\n\n- Get one KiotViet customer by id or by customer code (m\xE3 kh\xE1ch h\xE0ng).\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | | |\n| `code` | string | | Customer code (m\xE3 kh\xE1ch h\xE0ng), e.g. KH000123 |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `customer` | object | yes | |\n\n### kiotviet_get_invoice (workflow step)\n\n- Get one KiotViet sales invoice by id or by invoice code (m\xE3 h\xF3a \u0111\u01A1n), with its line items and payments.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | | |\n| `code` | string | | Invoice code (m\xE3 h\xF3a \u0111\u01A1n), e.g. HD000123 |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invoice` | object | yes | |\n\n### kiotviet_get_product (workflow step)\n\n- Get one KiotViet product by id or by product code (m\xE3 h\xE0ng), with stock per branch in inventories[].\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | | |\n| `code` | string | | Product code (m\xE3 h\xE0ng), e.g. SP000123 |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `product` | object | yes | |\n\n### kiotviet_list_branches (workflow step)\n\n- List the store\'s KiotViet branches (chi nh\xE1nh).\n- Branch ids scope inventory, invoices and orders.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `page_size` | number | | Rows per page, up to 100 |\n| `current_item` | number | | Zero-based offset of the first row |\n| `last_modified_from` | string | | ISO datetime; only rows modified at or after it |\n| `include_removed_ids` | boolean | | Also return removed_ids \u2014 rows deleted since last_modified_from |\n| `order_by` | string | | KiotViet field name to sort by |\n| `order_direction` | "Asc" \\\\| "Desc" | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `total` | number | | Rows matching the query across all pages |\n| `page_size` | number | | |\n| `items` | object[] | yes | Rows as KiotViet returns them |\n| `removed_ids` | number[] | | |\n\n### kiotviet_list_categories (workflow step)\n\n- List KiotViet product groups (nh\xF3m h\xE0ng).\n- Each row carries its parentId, so the tree can be rebuilt from the flat list.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `page_size` | number | | Rows per page, up to 100 |\n| `current_item` | number | | Zero-based offset of the first row |\n| `last_modified_from` | string | | ISO datetime; only rows modified at or after it |\n| `include_removed_ids` | boolean | | Also return removed_ids \u2014 rows deleted since last_modified_from |\n| `order_by` | string | | KiotViet field name to sort by |\n| `order_direction` | "Asc" \\\\| "Desc" | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `total` | number | | Rows matching the query across all pages |\n| `page_size` | number | | |\n| `items` | object[] | yes | Rows as KiotViet returns them |\n| `removed_ids` | number[] | | |\n\n### kiotviet_list_customers (workflow step)\n\n- List KiotViet customers (kh\xE1ch h\xE0ng) with their lifetime totals \u2014 totalInvoiced, totalRevenue, debt, totalPoint.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `page_size` | number | | Rows per page, up to 100 |\n| `current_item` | number | | Zero-based offset of the first row |\n| `last_modified_from` | string | | ISO datetime; only rows modified at or after it |\n| `include_removed_ids` | boolean | | Also return removed_ids \u2014 rows deleted since last_modified_from |\n| `order_by` | string | | KiotViet field name to sort by |\n| `order_direction` | "Asc" \\\\| "Desc" | | |\n| `code` | string | | Customer code (m\xE3 kh\xE1ch h\xE0ng) |\n| `name` | string | | Name search |\n| `contact_number` | string | | Phone number |\n| `group_id` | number | | Only customers in this customer group |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `total` | number | | Rows matching the query across all pages |\n| `page_size` | number | | |\n| `items` | object[] | yes | Rows as KiotViet returns them |\n| `removed_ids` | number[] | | |\n\n### kiotviet_list_invoices (workflow step)\n\n- List KiotViet sales invoices (h\xF3a \u0111\u01A1n) with their line items in invoiceDetails[] \u2014 productCode, productName, quantity, price, discount, subTotal.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `page_size` | number | | Rows per page, up to 100 |\n| `current_item` | number | | Zero-based offset of the first row |\n| `last_modified_from` | string | | ISO datetime; only rows modified at or after it |\n| `include_removed_ids` | boolean | | Also return removed_ids \u2014 rows deleted since last_modified_from |\n| `order_by` | string | | KiotViet field name to sort by |\n| `order_direction` | "Asc" \\\\| "Desc" | | |\n| `branch_ids` | number[] | | |\n| `customer_ids` | number[] | | |\n| `customer_code` | string | | Customer code (m\xE3 kh\xE1ch h\xE0ng) |\n| `status` | number[] | | KiotViet status codes; each row carries statusValue with the label |\n| `from_purchase_date` | string | | Date, yyyy-mm-dd |\n| `to_purchase_date` | string | | Date, yyyy-mm-dd |\n| `include_payment` | boolean | | Include payments[] on each row |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `total` | number | | Rows matching the query across all pages |\n| `page_size` | number | | |\n| `items` | object[] | yes | Rows as KiotViet returns them |\n| `removed_ids` | number[] | | |\n\n### kiotviet_list_orders (workflow step)\n\n- List KiotViet sales orders (\u0111\u1EB7t h\xE0ng) \u2014 the pre-invoice stage \u2014 with line items in orderDetails[].\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `page_size` | number | | Rows per page, up to 100 |\n| `current_item` | number | | Zero-based offset of the first row |\n| `last_modified_from` | string | | ISO datetime; only rows modified at or after it |\n| `include_removed_ids` | boolean | | Also return removed_ids \u2014 rows deleted since last_modified_from |\n| `order_by` | string | | KiotViet field name to sort by |\n| `order_direction` | "Asc" \\\\| "Desc" | | |\n| `branch_ids` | number[] | | |\n| `customer_ids` | number[] | | |\n| `customer_code` | string | | Customer code (m\xE3 kh\xE1ch h\xE0ng) |\n| `status` | number[] | | KiotViet status codes; each row carries statusValue with the label |\n| `from_purchase_date` | string | | Date, yyyy-mm-dd |\n| `to_purchase_date` | string | | Date, yyyy-mm-dd |\n| `include_payment` | boolean | | Include payments[] on each row |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `total` | number | | Rows matching the query across all pages |\n| `page_size` | number | | |\n| `items` | object[] | yes | Rows as KiotViet returns them |\n| `removed_ids` | number[] | | |\n\n### kiotviet_list_products (workflow step)\n\n- List KiotViet products (h\xE0ng h\xF3a) with stock per branch.\n- Each row\'s inventories[] carries branchId, onHand (t\u1ED3n kho), reserved (\u0111\u1EB7t h\xE0ng) and cost.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `page_size` | number | | Rows per page, up to 100 |\n| `current_item` | number | | Zero-based offset of the first row |\n| `last_modified_from` | string | | ISO datetime; only rows modified at or after it |\n| `include_removed_ids` | boolean | | Also return removed_ids \u2014 rows deleted since last_modified_from |\n| `order_by` | string | | KiotViet field name to sort by |\n| `order_direction` | "Asc" \\\\| "Desc" | | |\n| `category_id` | number | | Only products in this product group |\n| `is_active` | boolean | | Only products still on sale (true) or discontinued (false) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `total` | number | | Rows matching the query across all pages |\n| `page_size` | number | | |\n| `items` | object[] | yes | Rows as KiotViet returns them |\n| `removed_ids` | number[] | | |\n\n### kiotviet_list_purchase_orders (workflow step)\n\n- List KiotViet purchase orders (phi\u1EBFu nh\u1EADp h\xE0ng) \u2014 goods received from suppliers \u2014 with line items in purchaseOrderDetails[].\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `page_size` | number | | Rows per page, up to 100 |\n| `current_item` | number | | Zero-based offset of the first row |\n| `last_modified_from` | string | | ISO datetime; only rows modified at or after it |\n| `include_removed_ids` | boolean | | Also return removed_ids \u2014 rows deleted since last_modified_from |\n| `order_by` | string | | KiotViet field name to sort by |\n| `order_direction` | "Asc" \\\\| "Desc" | | |\n| `branch_ids` | number[] | | |\n| `status` | number[] | | KiotViet status codes; each row carries statusValue with the label |\n| `from_purchase_date` | string | | Date, yyyy-mm-dd |\n| `to_purchase_date` | string | | Date, yyyy-mm-dd |\n| `include_payment` | boolean | | Include payments[] on each row |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `total` | number | | Rows matching the query across all pages |\n| `page_size` | number | | |\n| `items` | object[] | yes | Rows as KiotViet returns them |\n| `removed_ids` | number[] | | |\n\n### kiotviet_update_category (workflow step)\n\n- Rename a KiotViet product group or move it under another parent.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n| `name` | string | | |\n| `parent_id` | number | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `category` | object | yes | |\n\n### kiotviet_update_customer (workflow step)\n\n- Update a KiotViet customer by id.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n| `name` | string | | |\n| `code` | string | | Customer code (m\xE3 kh\xE1ch h\xE0ng) |\n| `contact_number` | string | | |\n| `email` | string | | |\n| `address` | string | | |\n| `location_name` | string | | Province/district as KiotViet names it |\n| `ward_name` | string | | |\n| `gender` | boolean | | true for male, false for female |\n| `birth_date` | string | | yyyy-mm-dd |\n| `group_ids` | number[] | | Customer group ids |\n| `comments` | string | | |\n| `branch_id` | number | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `customer` | object | yes | |\n\n### kiotviet_update_invoice (workflow step)\n\n- Update a KiotViet sales invoice by id.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n| `branch_id` | number | | |\n| `customer_id` | number | | |\n| `sold_by_id` | number | | KiotViet user id of the seller |\n| `purchase_date` | string | | ISO datetime |\n| `description` | string | | |\n| `discount` | number | | Discount amount on the whole document |\n| `lines` | object[] | | |\n| `lines[].product_id` | number | yes | |\n| `lines[].quantity` | number | yes | |\n| `lines[].price` | number | | Unit price |\n| `lines[].discount` | number | | Discount amount on the line |\n| `lines[].note` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invoice` | object | yes | |\n\n### kiotviet_update_order (workflow step)\n\n- Update a KiotViet sales order by id.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n| `branch_id` | number | | |\n| `customer_id` | number | | |\n| `sold_by_id` | number | | KiotViet user id of the seller |\n| `purchase_date` | string | | ISO datetime |\n| `description` | string | | |\n| `discount` | number | | Discount amount on the whole document |\n| `lines` | object[] | | |\n| `lines[].product_id` | number | yes | |\n| `lines[].quantity` | number | yes | |\n| `lines[].price` | number | | Unit price |\n| `lines[].discount` | number | | Discount amount on the line |\n| `lines[].note` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `order` | object | yes | |\n\n### kiotviet_update_product (workflow step)\n\n- Update a KiotViet product by id; inventories set the stock level per branch.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | KiotViet connected account |\n| `id` | number | yes | |\n| `name` | string | | |\n| `code` | string | | Product code (m\xE3 h\xE0ng) |\n| `category_id` | number | | Product group id |\n| `base_price` | number | | Selling price |\n| `unit` | string | | |\n| `allows_sale` | boolean | | |\n| `is_active` | boolean | | |\n| `description` | string | | |\n| `weight` | number | | Grams |\n| `inventories` | object[] | | Stock per branch |\n| `inventories[].branch_id` | number | yes | |\n| `inventories[].on_hand` | number | yes | |\n| `inventories[].cost` | number | | Unit cost at this branch |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `product` | object | yes | |\n\n## lark\n\n### lark_create_records (workflow step)\n\n- Create one or many records in a Lark Base (Bitable) table (max 500 per call). fields are keyed by Lark field name.\n- Returns the new record ids.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Lark connected account |\n| `app_token` | string | yes | Lark Base app_token (the Base id) |\n| `table_id` | string | yes | Lark Bitable table id |\n| `records` | object[] | yes | |\n| `records[].fields` | object | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `created` | number | yes | |\n| `records` | object[] | yes | |\n| `records[].record_id` | string | yes | |\n| `records[].fields` | object | yes | |\n\n### lark_delete_records (workflow step)\n\n- Delete one or many records from a Lark Base (Bitable) table by record_id (max 500 per call).\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Lark connected account |\n| `app_token` | string | yes | Lark Base app_token (the Base id) |\n| `table_id` | string | yes | Lark Bitable table id |\n| `record_ids` | string[] | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `deleted` | number | yes | |\n| `record_ids` | string[] | yes | |\n\n### lark_get_record (workflow step)\n\n- Get one record from a Lark Base (Bitable) table by its record_id.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Lark connected account |\n| `app_token` | string | yes | Lark Base app_token (the Base id) |\n| `table_id` | string | yes | Lark Bitable table id |\n| `record_id` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `record_id` | string | yes | |\n| `fields` | object | yes | |\n\n### lark_list_fields (workflow step)\n\n- List the fields (columns) of a Lark Base (Bitable) table \u2014 name, id, and type.\n- Use to discover the schema before reading or writing records.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Lark connected account |\n| `app_token` | string | yes | Lark Base app_token (the Base id) |\n| `table_id` | string | yes | Lark Bitable table id |\n| `page_size` | number | | |\n| `page_token` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `fields` | object[] | yes | |\n| `fields[].field_id` | string | | |\n| `fields[].field_name` | string | | |\n| `fields[].type` | number | | |\n| `fields[].ui_type` | string | | |\n| `fields[].is_primary` | boolean | | |\n| `has_more` | boolean | yes | |\n| `page_token` | string | | |\n\n### lark_list_records (workflow step)\n\n- List records from a Lark Base (Bitable) table.\n- Returns each record\'s id and fields (keyed by Lark field name).\n- Optionally narrow with filter / sort / field_names / view_id; paginate with page_token.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Lark connected account |\n| `app_token` | string | yes | Lark Base app_token (the Base id) |\n| `table_id` | string | yes | Lark Bitable table id |\n| `filter` | object | | |\n| `filter.conjunction` | "and" \\\\| "or" | yes | |\n| `filter.conditions` | object[] | yes | |\n| `filter.conditions[].field_name` | string | yes | |\n| `filter.conditions[].operator` | string | yes | e.g. is, isNot, contains, isGreater, isEmpty |\n| `filter.conditions[].value` | string[] | | |\n| `sort` | object[] | | |\n| `sort[].field_name` | string | yes | |\n| `sort[].desc` | boolean | | |\n| `field_names` | string[] | | Restrict returned fields by name |\n| `view_id` | string | | |\n| `page_size` | number | | |\n| `page_token` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `records` | object[] | yes | |\n| `records[].record_id` | string | yes | |\n| `records[].fields` | object | yes | |\n| `has_more` | boolean | yes | |\n| `page_token` | string | | |\n| `total` | number | | |\n\n### lark_list_tables (workflow step)\n\n- List the tables in a Lark Base (Bitable) \u2014 id and name.\n- Use to discover which table_id to read or write.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Lark connected account |\n| `app_token` | string | yes | Lark Base app_token (the Base id) |\n| `page_size` | number | | |\n| `page_token` | string | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `tables` | object[] | yes | |\n| `tables[].table_id` | string | | |\n| `tables[].name` | string | | |\n| `has_more` | boolean | yes | |\n| `page_token` | string | | |\n\n### lark_update_records (workflow step)\n\n- Update one or many existing records in a Lark Base (Bitable) table by record_id (max 500 per call). fields are keyed by Lark field name; only provided fields change.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Lark connected account |\n| `app_token` | string | yes | Lark Base app_token (the Base id) |\n| `table_id` | string | yes | Lark Bitable table id |\n| `records` | object[] | yes | |\n| `records[].record_id` | string | yes | |\n| `records[].fields` | object | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `updated` | number | yes | |\n| `records` | object[] | yes | |\n| `records[].record_id` | string | yes | |\n| `records[].fields` | object | yes | |\n\n## linkedin\n\n### linkedin_create_post (workflow step)\n\n- Publish a post to the connected LinkedIn member\'s profile.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `text` | string | yes | |\n| `link` | string | | Shown as an article card; needs link_title. A post carries either a link or media. |\n| `link_title` | string | | |\n| `media_file_ids` | string[] | | Up to 20 images, or one video. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `post_id` | string | yes | |\n| `url` | string | yes | |\n\n## list\n\n### list_banks (workflow step)\n\n- List banks supported by generate_bank_qr_code. (workflow step, chat)\n- Returns the bank code (used as input to the generator), display name, and country.\n- Call this before generate_bank_qr_code to discover valid codes. (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `country` | "VN" | | Filter to banks from a specific country. Omit to list all supported banks. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `banks` | object[] | yes | |\n| `banks[].country` | string | yes | |\n| `banks[].code` | string | yes | |\n| `banks[].bin` | string | yes | |\n| `banks[].short_name` | string | yes | |\n| `banks[].full_name` | string | yes | |\n\n## lookup\n\n### lookup_business (workflow step)\n\n- Look up business registration info by tax / registration code.\n- Defaults to Vietnam (country=\'VN\').\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `tax_code` | string | yes | Business tax / registration code. Format varies by country; for VN this is a 10\u201313 digit m\xE3 s\u1ED1 thu\u1EBF. |\n| `country` | string | | ISO country code. Defaults to \'VN\' (Vietnam). Only \'VN\' is supported today. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `country` | string | yes | |\n| `tax_code` | string | yes | |\n| `name` | string | yes | |\n| `address` | string | yes | |\n| `representative` | string \\\\| null | yes | |\n| `status` | string \\\\| null | yes | |\n| `international_name` | string \\\\| null | yes | |\n| `short_name` | string \\\\| null | yes | |\n| `operating_since` | string \\\\| null | yes | |\n| `managed_by` | string \\\\| null | yes | |\n| `company_type` | string \\\\| null | yes | |\n| `primary_industry` | string \\\\| null | yes | |\n\n## misa\n\n### misa_get_dictionary (workflow step)\n\n- Read master-data records from MISA AMIS to look up codes (customer code, item code, warehouse code, etc.) before pushing a voucher.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | MISA connected account to read from |\n| `data_type` | 1 \\\\| 2 \\\\| 3 \\\\| 4 \\\\| 5 \\\\| 6 \\\\| 8 \\\\| 9 \\\\| 10 \\\\| 11 \\\\| 12 | yes | 1 objects (customers/suppliers/employees), 2 inventory items, 3 stocks, 4 units, 5 chart of accounts, 6 organization units, 8 bank accounts, 9 projects, 10 cost objects, 11 payment terms, 12 banks |\n| `skip` | integer | | |\n| `take` | integer | | Max 100 per page |\n| `last_sync_time` | string | | ISO timestamp \u2014 only return records changed since this time |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `success` | boolean | yes | |\n| `data` | object[] | yes | The dictionary records, each as MISA names its fields |\n| `last_sync_time` | string | | Cursor \u2014 pass as last_sync_time on the next call to fetch only newer changes |\n| `error_code` | string | | |\n| `error_message` | string | | |\n\n### misa_save_bank_voucher (workflow step)\n\n- Push a bank voucher (deposit / withdrawal / transfer) into MISA AMIS. voucher_type: 1 deposit (b\xE1o c\xF3), 3 withdrawal/debit-advice (b\xE1o n\u1EE3), 2 internal transfer. bank_account_id is the company-side bank account GUID \u2014 look it up with misa_get_dictionary (data_type 8). (workflow step, chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `voucher_type` | 1 \\\\| 2 \\\\| 3 | yes | |\n| `reftype` | integer | | |\n| `org_refid` | string | yes | a UUID that stays the same for the document \u2014 `uuidFrom(record.id)` |\n| `org_refno` | string | | Source-system document number |\n| `refdate` | string | yes | Document date, ISO 8601 |\n| `posted_date` | string | yes | Accounting posting date, ISO 8601 |\n| `branch_id` | string | | MISA branch GUID, optional for single-branch tenants |\n| `currency_id` | string | | Currency code, default VND |\n| `exchange_rate` | number | | FX rate, default 1.0 |\n| `journal_memo` | string | | Voucher memo/notes |\n| `custom_field1` | string | | |\n| `custom_field2` | string | | |\n| `custom_field3` | string | | |\n| `custom_field4` | string | | |\n| `custom_field5` | string | | |\n| `bank_account_id` | string | yes | Bank account GUID in MISA \u2014 look up via misa_get_dictionary (workflow step, chat) |\n| `account_object_code` | string | | |\n| `account_object_name` | string | | |\n| `total_amount_oc` | number | yes | |\n| `total_amount` | number | yes | |\n| `detail` | object[] | yes | |\n| `detail[].description` | string | yes | |\n| `detail[].amount_oc` | number | yes | |\n| `detail[].amount` | number | yes | |\n| `detail[].debit_account` | string | yes | |\n| `detail[].credit_account` | string | yes | |\n| `detail[].sort_order` | integer | | |\n| `connected_account_id` | string | yes | MISA connected account to push to |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `queued` | boolean | yes | |\n| `org_refid` | string | yes | |\n| `error_code` | string | | |\n| `error_message` | string | | |\n\n### misa_save_cash_voucher (workflow step)\n\n- Push a cash voucher (cash receipt or cash payment) into MISA AMIS. voucher_type: 5 receipt (thu ti\u1EC1n), 4 payment (chi ti\u1EC1n). detail array carries the journal lines with debit_account/credit_account per Vietnamese chart-of-accounts.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `voucher_type` | 4 \\\\| 5 | yes | |\n| `reftype` | integer | | |\n| `org_refid` | string | yes | a UUID that stays the same for the document \u2014 `uuidFrom(record.id)` |\n| `org_refno` | string | | Source-system document number |\n| `refdate` | string | yes | Document date, ISO 8601 |\n| `posted_date` | string | yes | Accounting posting date, ISO 8601 |\n| `branch_id` | string | | MISA branch GUID, optional for single-branch tenants |\n| `currency_id` | string | | Currency code, default VND |\n| `exchange_rate` | number | | FX rate, default 1.0 |\n| `journal_memo` | string | | Voucher memo/notes |\n| `custom_field1` | string | | |\n| `custom_field2` | string | | |\n| `custom_field3` | string | | |\n| `custom_field4` | string | | |\n| `custom_field5` | string | | |\n| `account_object_code` | string | | Counterparty code (customer/supplier/employee) |\n| `account_object_name` | string | | |\n| `total_amount_oc` | number | yes | |\n| `total_amount` | number | yes | |\n| `detail` | object[] | yes | |\n| `detail[].description` | string | yes | |\n| `detail[].amount_oc` | number | yes | |\n| `detail[].amount` | number | yes | |\n| `detail[].debit_account` | string | yes | |\n| `detail[].credit_account` | string | yes | |\n| `detail[].sort_order` | integer | | |\n| `connected_account_id` | string | yes | MISA connected account to push to |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `queued` | boolean | yes | |\n| `org_refid` | string | yes | |\n| `error_code` | string | | |\n| `error_message` | string | | |\n\n### misa_save_dictionary (workflow step)\n\n- Create or update master-data records in MISA AMIS (customers, suppliers, items, warehouses, etc.).\n- Use this before pushing a voucher that references a code MISA doesn\'t yet know about.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | MISA connected account to push to |\n| `dictionary_type` | 1 \\\\| 2 \\\\| 3 \\\\| 4 \\\\| 5 \\\\| 6 \\\\| 7 \\\\| 8 \\\\| 9 \\\\| 10 \\\\| 12 | yes | 1 customers/suppliers/employees, 2 object groups, 3 inventory items, 4 item categories, 5 warehouses, 6 units, 7 bank accounts, 8 banks, 9 expense items, 10 revenue/expense categories, 12 cost allocation |\n| `items` | object[] | yes | Master-data records to create or update. 1\u2013200 per call. Each item\'s required fields depend on dictionary_type \u2014 see MISA Open API docs (chat, MCP, CLI) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `queued` | boolean | yes | |\n| `count` | integer | yes | |\n| `error_code` | string | | |\n| `error_message` | string | | |\n\n### misa_save_purchase_voucher (workflow step)\n\n- Push a purchase voucher (purchase invoice / return / discount-debit-note-to-supplier / service / general purchase / purchase order) into MISA AMIS. voucher_type: 15 invoice, 18 voucher, 17 service, 16 return, 14 discount (debit note to supplier), 21 purchase order. account_object_code identifies the supplier in MISA \u2014 must already exist.\n- MISA sync ack only confirms queueing; final accept/reject arrives async.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `voucher_type` | 14 \\\\| 15 \\\\| 16 \\\\| 17 \\\\| 18 \\\\| 21 | yes | |\n| `reftype` | integer | | |\n| `org_refid` | string | yes | a UUID that stays the same for the document \u2014 `uuidFrom(record.id)` |\n| `org_refno` | string | | Source-system document number |\n| `refdate` | string | yes | Document date, ISO 8601 |\n| `posted_date` | string | yes | Accounting posting date, ISO 8601 |\n| `branch_id` | string | | MISA branch GUID, optional for single-branch tenants |\n| `currency_id` | string | | Currency code, default VND |\n| `exchange_rate` | number | | FX rate, default 1.0 |\n| `journal_memo` | string | | Voucher memo/notes |\n| `custom_field1` | string | | |\n| `custom_field2` | string | | |\n| `custom_field3` | string | | |\n| `custom_field4` | string | | |\n| `custom_field5` | string | | |\n| `account_object_code` | string | yes | Supplier code in MISA |\n| `account_object_name` | string | | |\n| `account_object_tax_code` | string | | |\n| `account_object_address` | string | | |\n| `total_amount_oc` | number | yes | |\n| `total_amount` | number | yes | |\n| `total_vat_amount_oc` | number | yes | |\n| `total_vat_amount` | number | yes | |\n| `detail` | object[] | yes | |\n| `detail[].inventory_item_code` | string | | Item/product code in MISA. Skip when is_description=true |\n| `detail[].inventory_item_name` | string | yes | Item/product display name |\n| `detail[].inventory_item_type` | 0 \\\\| 1 \\\\| 2 \\\\| 3 | | Item type \u2014 0 goods, 1 finished, 2 service, 3 raw material |\n| `detail[].description` | string | | Line description |\n| `detail[].is_description` | boolean | | Description-only line \u2014 when true, only `description` is required |\n| `detail[].stock_code` | string | | Warehouse code in MISA |\n| `detail[].unit_name` | string | | Unit of measure name |\n| `detail[].quantity` | number | yes | Line quantity |\n| `detail[].unit_price` | number | yes | Unit price (in currency_id of voucher) |\n| `detail[].amount_oc` | number | yes | Line total in original currency |\n| `detail[].amount` | number | yes | Line total in accounting currency (= amount_oc when VND) |\n| `detail[].discount_rate` | number | | Line discount percent |\n| `detail[].discount_amount_oc` | number | | Discount amount, original currency |\n| `detail[].discount_amount` | number | | Discount amount, accounting currency |\n| `detail[].vat_rate` | number | yes | VAT percent. 0/5/8/10 for standard, -1 exempt, -2 not creditable, -3 other (set other_vat_rate) |\n| `detail[].other_vat_rate` | number | | Custom VAT percent \u2014 required when vat_rate=-3 |\n| `detail[].vat_amount_oc` | number | yes | VAT amount, original currency |\n| `detail[].vat_amount` | number | yes | VAT amount, accounting currency |\n| `detail[].debit_account` | string | yes | Chart-of-accounts code on debit side, e.g. 131 |\n| `detail[].credit_account` | string | yes | Chart-of-accounts code on credit side, e.g. 511 |\n| `detail[].sale_account` | string | | |\n| `detail[].vat_account` | string | | |\n| `detail[].inventory_account` | string | | |\n| `detail[].sort_order` | integer | | |\n| `detail[].lot_no` | string | | |\n| `detail[].expiry_date` | string | | ISO date |\n| `detail[].organization_unit_code` | string | | Department/branch code |\n| `connected_account_id` | string | yes | MISA connected account to push to |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `queued` | boolean | yes | |\n| `org_refid` | string | yes | |\n| `error_code` | string | | |\n| `error_message` | string | | |\n\n### misa_save_sales_voucher (workflow step)\n\n- Push a sales voucher (invoice / sales return / discount-credit-note / sales order) into MISA AMIS. voucher_type: 11 invoice, 13 sales voucher, 12 return, 10 discount/credit note, 20 sales order. reftype refines the sub-type (e.g. 3530 unpaid sale, 3560 sales invoice, 3571 service cash). account_object_code identifies the customer in MISA \u2014 must already exist (use misa_save_dictionary first if not). (workflow step, chat)\n- MISA sync ack only confirms queueing; final accept/reject arrives async on the registered callback URL.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `voucher_type` | 10 \\\\| 11 \\\\| 12 \\\\| 13 \\\\| 20 | yes | |\n| `reftype` | integer | | Voucher sub-type code \u2014 MISA assigns a default when omitted |\n| `org_refid` | string | yes | a UUID that stays the same for the document \u2014 `uuidFrom(record.id)` |\n| `org_refno` | string | | Source-system document number |\n| `refdate` | string | yes | Document date, ISO 8601 |\n| `posted_date` | string | yes | Accounting posting date, ISO 8601 |\n| `branch_id` | string | | MISA branch GUID, optional for single-branch tenants |\n| `currency_id` | string | | Currency code, default VND |\n| `exchange_rate` | number | | FX rate, default 1.0 |\n| `journal_memo` | string | | Voucher memo/notes |\n| `custom_field1` | string | | |\n| `custom_field2` | string | | |\n| `custom_field3` | string | | |\n| `custom_field4` | string | | |\n| `custom_field5` | string | | |\n| `account_object_code` | string | yes | Customer code in MISA |\n| `account_object_name` | string | | |\n| `account_object_tax_code` | string | | |\n| `account_object_address` | string | | |\n| `employee_code` | string | | Salesperson code |\n| `employee_name` | string | | |\n| `total_sale_amount_oc` | number | yes | |\n| `total_sale_amount` | number | yes | |\n| `total_discount_amount_oc` | number | | |\n| `total_discount_amount` | number | | |\n| `total_vat_amount_oc` | number | yes | |\n| `total_vat_amount` | number | yes | |\n| `total_amount_oc` | number | yes | |\n| `total_amount` | number | yes | |\n| `is_sale_with_outward` | boolean | | Auto-generate stock-out slip |\n| `detail` | object[] | yes | |\n| `detail[].inventory_item_code` | string | | Item/product code in MISA. Skip when is_description=true |\n| `detail[].inventory_item_name` | string | yes | Item/product display name |\n| `detail[].inventory_item_type` | 0 \\\\| 1 \\\\| 2 \\\\| 3 | | Item type \u2014 0 goods, 1 finished, 2 service, 3 raw material |\n| `detail[].description` | string | | Line description |\n| `detail[].is_description` | boolean | | Description-only line \u2014 when true, only `description` is required |\n| `detail[].stock_code` | string | | Warehouse code in MISA |\n| `detail[].unit_name` | string | | Unit of measure name |\n| `detail[].quantity` | number | yes | Line quantity |\n| `detail[].unit_price` | number | yes | Unit price (in currency_id of voucher) |\n| `detail[].amount_oc` | number | yes | Line total in original currency |\n| `detail[].amount` | number | yes | Line total in accounting currency (= amount_oc when VND) |\n| `detail[].discount_rate` | number | | Line discount percent |\n| `detail[].discount_amount_oc` | number | | Discount amount, original currency |\n| `detail[].discount_amount` | number | | Discount amount, accounting currency |\n| `detail[].vat_rate` | number | yes | VAT percent. 0/5/8/10 for standard, -1 exempt, -2 not creditable, -3 other (set other_vat_rate) |\n| `detail[].other_vat_rate` | number | | Custom VAT percent \u2014 required when vat_rate=-3 |\n| `detail[].vat_amount_oc` | number | yes | VAT amount, original currency |\n| `detail[].vat_amount` | number | yes | VAT amount, accounting currency |\n| `detail[].debit_account` | string | yes | Chart-of-accounts code on debit side, e.g. 131 |\n| `detail[].credit_account` | string | yes | Chart-of-accounts code on credit side, e.g. 511 |\n| `detail[].sale_account` | string | | |\n| `detail[].vat_account` | string | | |\n| `detail[].inventory_account` | string | | |\n| `detail[].sort_order` | integer | | |\n| `detail[].lot_no` | string | | |\n| `detail[].expiry_date` | string | | ISO date |\n| `detail[].organization_unit_code` | string | | Department/branch code |\n| `connected_account_id` | string | yes | MISA connected account to push to |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `queued` | boolean | yes | |\n| `org_refid` | string | yes | |\n| `error_code` | string | | |\n| `error_message` | string | | |\n\n## outlook\n\n### outlook_count_emails (workflow step)\n\n- Count Outlook emails matching a filter or search query.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The Lotics connected account ID (cac_xxx), not the Microsoft user ID |\n| `search` | string | | KQL expression |\n| `filter` | string | | OData expression |\n| `folder` | string | | Mail folder: well-known name or folder ID |\n| `user_id` | string | | Mailbox owner user ID. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `count` | number | yes | Number of matching emails |\n\n### outlook_query_emails (workflow step)\n\n- Search and filter Outlook emails.\n- Two modes: search (KQL keywords) or structured filters (from, to, subject, dates, etc.).\n- When search is provided, structured filters are ignored. ## KQL search syntax (for the search field) Bare keywords search across from, subject, and body.\n- Property restrictions (no space after colon): - People: from:, to:, cc:, bcc:, participants: (all people fields), recipients: (to+cc+bcc) - Content: subject:, body:, "exact phrase" - Attachments: hasAttachments:true, attachment:filename* - Dates: received:2024-01-01, received:2024-01-01..2024-01-31, received>=2024-01-01, received:today, received:"this week", sent:2024-06-01 - Size (bytes): size>1048576, size:1..10485760 - Status: importance:high, isRead:false, kind:email Combining (UPPERCASE required): AND, OR, NOT, - (exclude), () for grouping.\n- Prefix wildcard: subject:set* (end of word only, no *prefix or *mid*dle).\n- Examples: - from:john@contoso.com AND subject:"quarterly report" - participants:alice importance:high hasAttachments:true - body:contract AND received>=2024-01-01 AND received<=2024-06-30 - (from:legal OR from:compliance) AND subject:contract - attachment:annual* received:"this month"\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The Lotics connected account ID (cac_xxx), not the Microsoft user ID |\n| `search` | string | | KQL search query \u2014 overrides all structured filter fields when provided |\n| `from` | string | | Sender email address exact match |\n| `to` | string | | Recipient email address exact match |\n| `subject` | string | | Subject starts with this text |\n| `after` | string | | ISO date \u2014 received on or after |\n| `before` | string | | ISO date \u2014 received before |\n| `is_read` | boolean | | |\n| `is_draft` | boolean | | |\n| `has_attachments` | boolean | | |\n| `importance` | "low" \\\\| "normal" \\\\| "high" | | |\n| `is_flagged` | boolean | | Whether the email is flagged for follow-up |\n| `classification` | "focused" \\\\| "other" | | Focused Inbox classification |\n| `category` | string | | Color category name |\n| `conversation_id` | string | | Outlook conversation ID \u2014 retrieves all messages in a thread |\n| `folder` | string | | Mail folder: well-known name (inbox, sentitems, drafts, deleteditems, junkemail, archive) or folder ID |\n| `skip` | number | | Number of results to skip for pagination. |\n| `max_result` | number | yes | Maximum number of emails to retrieve (1-50) |\n| `user_id` | string | | The ID of the user whose mailbox the email belongs to. If not provided, the connected account\'s user ID (me) will be used. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `messages` | object[] | yes | List of matching Outlook messages |\n| `messages[].id` | string | yes | Outlook message ID |\n| `messages[].date` | string | yes | Email date in the workspace timezone, with its offset |\n| `messages[].from` | string | yes | Sender email address |\n| `messages[].subject` | string | yes | Email subject |\n| `messages[].to` | string | yes | Recipient email address |\n| `messages[].cc` | string \\\\| null | yes | CC recipients |\n| `messages[].bcc` | string \\\\| null | yes | BCC recipients |\n| `messages[].snippet` | string | yes | Short plain-text preview of the email body (first ~255 chars) |\n| `messages[].conversationId` | string | | Outlook conversation ID |\n| `messages[].receivedDateTime` | string | | Received date-time in ISO format |\n| `messages[].hasAttachments` | boolean | | Whether the email has attachments |\n| `messages[].importance` | string | | Email importance level |\n| `messages[].isRead` | boolean | | Whether the email has been read |\n| `messages[].isDraft` | boolean | | Whether the email is a draft |\n| `messages[].flagStatus` | string | | Flag status: notFlagged, complete, or flagged |\n| `messages[].classification` | string | | Focused Inbox classification: focused or other |\n| `messages[].categories` | string[] | | Color categories assigned to the email |\n\n### outlook_query_folders (workflow step)\n\n- List mail folders in an Outlook mailbox.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The Lotics connected account ID (cac_xxx), not the Microsoft user ID |\n| `parent_folder_id` | string | | Parent folder ID to list child folders. Omit for top-level folders. |\n| `name` | string | | Match folders by display name prefix. |\n| `include_hidden` | boolean | | Include hidden system folders |\n| `max_result` | number | | Maximum number of folders to return (1-100) |\n| `skip` | number | | Number of results to skip for pagination |\n| `user_id` | string | | The ID of the user whose mailbox to query. Defaults to the connected account owner. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `folders` | object[] | yes | List of mail folders |\n| `folders[].id` | string | yes | Folder ID |\n| `folders[].displayName` | string | yes | Folder display name |\n| `folders[].parentFolderId` | string | | Parent folder ID |\n| `folders[].childFolderCount` | number | yes | Number of child folders |\n| `folders[].totalItemCount` | number | yes | Total number of items in the folder |\n| `folders[].unreadItemCount` | number | yes | Number of unread items |\n| `folders[].isHidden` | boolean | yes | Whether the folder is hidden |\n\n### outlook_read_email (workflow step)\n\n- Fetch an Outlook email\'s body and the list of its attachments.\n- Attachments are stored as workspace files \u2014 the result names each one\'s file_id; read one with view_files, attach one to a record with update_records. (chat)\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The Lotics connected account ID (cac_xxx), not the Microsoft user ID |\n| `message_id` | string | yes | The ID of the Outlook message to retrieve |\n| `user_id` | string | | The ID of the user whose mailbox the email belongs to. If not provided, the connected account\'s user ID (me) will be used. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message` | object | yes | The full Outlook message |\n| `message.id` | string | | Outlook message ID |\n| `message.date` | string | yes | Email date in the workspace timezone, with its offset |\n| `message.from` | string | yes | Sender email address |\n| `message.subject` | string | yes | Email subject |\n| `message.to` | string | yes | Recipient email address |\n| `message.cc` | string \\\\| null | yes | CC recipients |\n| `message.bcc` | string \\\\| null | yes | BCC recipients |\n| `message.inReplyTo` | string \\\\| null | yes | In-Reply-To header |\n| `message.replyTo` | string \\\\| null | yes | Reply-To header |\n| `message.references` | string[] \\\\| null | yes | References header |\n| `message.conversationId` | string | | Outlook conversation ID |\n| `message.receivedDateTime` | string | | Received date-time in ISO format |\n| `message.hasAttachments` | boolean | | Whether the email has attachments |\n| `message.importance` | string | | Email importance level |\n| `message.isRead` | boolean | | Whether the email has been read |\n| `message.body` | object | yes | Email body |\n| `message.body.content` | string | yes | Body content |\n| `message.body.contentType` | "text" \\\\| "html" | yes | Body content type |\n| `message.attachments` | object[] | | Email attachments |\n| `message.attachments[].id` | string | yes | Outlook attachment ID |\n| `message.attachments[].fileName` | string | | Attachment filename as stored, which may differ from the sender\'s |\n| `message.attachments[].contentType` | string | | MIME type as stored, recovered from the filename when Graph declared a generic one |\n| `message.attachments[].size` | number | | Stored size in bytes |\n| `message.attachments[].file_id` | string | | Stored file ID \u2014 pass to view_files to read it, or to update_records to attach it (chat) |\n\n### outlook_reply_email (workflow step)\n\n- Draft or send a reply to an Outlook email.\n- Use reply_body (markdown) or template_id + template_data \u2014 not both.\n- Attach stored files by passing their file_ids.\n- Leaves an unsent draft unless draft is false.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The Lotics connected account ID (cac_xxx), not the Microsoft user ID |\n| `original_message_id` | string | yes | |\n| `reply_body` | string | | Reply body (markdown). Mutually exclusive with template_id. |\n| `template_id` | string | | |\n| `template_data` | object | | |\n| `cc` | string | | CC recipients, comma-separated |\n| `bcc` | string | | BCC recipients, comma-separated |\n| `draft` | boolean | | Leave true to save an unsent draft the member reviews. Pass false only to deliver the message now \u2014 delivery is irreversible. |\n| `file_ids` | string[] | yes | |\n| `user_id` | string | | Mailbox user ID |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message_id` | string | yes | The ID of the sent message or saved draft |\n| `subject` | string | yes | The subject as sent |\n| `html` | string | yes | The body as sent, rendered to HTML |\n| `markdown` | string | | The body as written in markdown; absent when a template rendered it |\n| `to` | string | yes | The original sender the reply went to |\n\n### outlook_send_email (workflow step)\n\n- Draft or send an Outlook email.\n- Use content (markdown) or template_id + template_data \u2014 not both.\n- Attach stored files by passing their file_ids.\n- Leaves an unsent draft unless draft is false, and while drafting \'to\' may be omitted to leave the recipient line for the member to fill in.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | The Lotics connected account ID (cac_xxx), not the Microsoft user ID |\n| `to` | string | | Recipient email(s), comma-separated. Required unless using a template. |\n| `subject` | string | | |\n| `cc` | string | | CC recipients, comma-separated |\n| `bcc` | string | | BCC recipients, comma-separated |\n| `content` | string | | Email body (markdown). Mutually exclusive with template_id. |\n| `template_id` | string | | |\n| `template_data` | object | | |\n| `draft` | boolean | | Leave true to save an unsent draft the member reviews. Pass false only to deliver the message now \u2014 delivery is irreversible. |\n| `file_ids` | string[] | yes | |\n| `user_id` | string | | Mailbox user ID |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message_id` | string | yes | The ID of the sent message or saved draft |\n| `subject` | string | yes | The subject as sent |\n| `html` | string | yes | The body as sent, rendered to HTML |\n| `markdown` | string | | The body as written in markdown; absent when a template rendered it |\n| `to` | string \\\\| null | yes | The recipients as sent, comma-separated; null on a draft saved without recipients |\n\n## press\n\n### press_button (workflow step)\n\n- Run a button field\'s action against a record \u2014 equivalent to a user clicking the button in the UI.\n- Buttons are a legacy field type; the action must already be wired.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `table_id` | string | yes | |\n| `record_id` | string | yes | |\n| `field_key` | string | yes | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `execution_id` | string | | |\n| `status` | "success" \\\\| "error" | | |\n| `message` | string | | |\n\n## rename\n\n### rename_file (workflow step)\n\n- Get a file under another name: a new file over the same bytes.\n- Write its file_id into the files field in place of the old one; every other place holding the old file keeps its name.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | |\n| `filename` | string | yes | The whole new name, keeping the file\'s extension exactly. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | The ID of the generated file. |\n| `url` | string | yes | The download URL of the generated file. |\n| `filename` | string | yes | The filename of the generated file, including extension. |\n| `mime_type` | string | yes | The MIME type of the generated file. |\n\n## save\n\n### save_file (workflow step)\n\n- Save a file from a URL to the workspace.\n- Max 20MB.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `url` | string | yes | Public URL to download the file from (https:// or http://). |\n| `filename` | string | | Filename with extension, e.g. \'report.pdf\'. Derived from URL if omitted. |\n| `mime_type` | string | | MIME type, e.g. \'application/pdf\'. Derived from URL response if omitted. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `id` | string | yes | File ID. |\n| `filename` | string | yes | |\n| `mime_type` | string | yes | |\n| `url` | string | yes | |\n\n### save_record (workflow step)\n\n- Save a record found by its identity, with its lines, all or nothing.\n- A call whose match is carried by no live record adds it; one carried by a record changes the cells of set that differ and appends add_to, so sending the same call twice writes nothing the second time. match holds the record\'s identity: field keys with their values, exactly the fields of one uniqueness its table declares (a unique field, or fields unique together). set holds its other cells. add_to lists items to append to a files, multi-select or many-link field, keeping what it holds. lines saves rows of other tables that link the record.\n- Each entry names the child table_id, its one-row link field to this table, match (the child fields that with link are one uniqueness of the child table) and rows, each stating every match field. link is set by the step and no row states it.\n- A line found by its match changes the cells that differ; one not found is added; a line not listed is left as it is.\n- Any refusal \u2014 a table check, a locked record, a value a field does not take, no write access to a table \u2014 saves nothing.\n- Returns { record_id, created, updated_field_keys, lines }: the cells changed on a record that existed, and per entry of lines the ids it added, changed and left unchanged.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `table_id` | string | yes | |\n| `match` | object | yes | |\n| `set` | object \\\\| null | | |\n| `add_to` | object \\\\| null | | |\n| `lines` | object[] \\\\| null | | |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `record_id` | string | yes | |\n| `created` | boolean | yes | |\n| `updated_field_keys` | string[] | yes | |\n| `lines` | object[] | yes | |\n| `lines[].table_id` | string | yes | |\n| `lines[].created_ids` | string[] | yes | |\n| `lines[].updated_ids` | string[] | yes | |\n| `lines[].unchanged_ids` | string[] | yes | |\n\n## send\n\n### send_notification (workflow step)\n\n- Notify workspace members.\n- Lotics shows it to each recipient and emails it to their own address, through no connection; a member who signs in with an identifier an admin issued has no mailbox and sees it in Lotics alone.\n- A member is named by the member_id get_workspace kind member lists. (chat, MCP, CLI)\n- In a workflow body, every member is { type: "organization", id: runtime.organization_id } and the member who started the run { type: "member", id: runtime.triggered_by_member_id }; a run a schedule, a webhook or an email starts was started by no member.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `recipients` | any[] | yes | List of recipients. Each entry is a principal: { type: \'member\', id } \\| { type: \'member_group\', id } \\| { type: \'organization\', id } |\n| `title` | string | yes | |\n| `body` | string | yes | |\n| `level` | "urgent" \\\\| "warning" \\\\| "info" \\\\| "success" | | Notification level: urgent, warning, info, success |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message` | string | yes | |\n| `recipients_count` | number | yes | |\n\n## telegram\n\n### telegram_list_chats (workflow step)\n\n- List the chats that messaged a Telegram bot, or added it, in the last 24 hours \u2014 where a chat id comes from.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Telegram connected account |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `chats` | object[] | yes | |\n| `chats[].chat_id` | string | yes | |\n| `chats[].type` | string | yes | private, group, supergroup or channel |\n| `chats[].name` | string | yes | |\n| `chats[].username` | string | | |\n| `chats[].last_seen_at` | string | yes | ISO timestamp of the chat\'s latest update |\n\n### telegram_send_document (workflow step)\n\n- Send a stored file from a Telegram bot to a chat, as a document with an optional caption.\n- The chat must have messaged the bot first.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Telegram connected account |\n| `chat_id` | string | yes | Numeric chat id, or the @username of a public channel or group |\n| `file_id` | string | yes | |\n| `caption` | string | | |\n| `parse_mode` | "HTML" \\\\| "MarkdownV2" | | How the text is formatted: Telegram\'s HTML tags or its MarkdownV2 syntax |\n| `disable_notification` | boolean | | Deliver without a notification sound |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message_id` | number | yes | |\n| `chat_id` | string | yes | |\n| `sent_at` | string | yes | ISO timestamp |\n\n### telegram_send_message (workflow step)\n\n- Send a text message from a Telegram bot to a chat.\n- The chat must have messaged the bot first.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | Telegram connected account |\n| `chat_id` | string | yes | Numeric chat id, or the @username of a public channel or group |\n| `text` | string | yes | |\n| `parse_mode` | "HTML" \\\\| "MarkdownV2" | | How the text is formatted: Telegram\'s HTML tags or its MarkdownV2 syntax |\n| `disable_notification` | boolean | | Deliver without a notification sound |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `message_id` | number | yes | |\n| `chat_id` | string | yes | |\n| `sent_at` | string | yes | ISO timestamp |\n\n## threads\n\n### threads_create_post (workflow step)\n\n- Publish a post to the connected Threads account.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `text` | string | yes | |\n| `link` | string | | Shown as a link card. A post carries either a link or media. |\n| `media_file_ids` | string[] | | One image or video, or 2\u201320 images and videos as a carousel. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `post_id` | string | yes | |\n| `url` | string | yes | |\n\n## vietcombank\n\n### vietcombank_convert_currency (workflow step)\n\n- Convert between currencies using live Vietcombank exchange rates.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `from_currency` | string | yes | Source currency code (e.g., \'USD\', \'EUR\', \'JPY\') |\n| `to_currency` | string | yes | Target currency code (e.g., \'USD\', \'EUR\', \'JPY\') |\n| `amount` | number | yes | The amount to convert (must be positive) |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `converted_amount` | number | yes | The converted amount |\n| `exchange_rate` | number | yes | The exchange rate |\n\n## wininvoice\n\n### wininvoice_create_or_update_invoice (workflow step)\n\n- Create or update a Vietnamese e-invoice in WinInvoice. invRef identifies the invoice: a value that already has one resolves to that invoice rather than issuing a new one, and isExisting: 1 means it came back unchanged with nothing issued.\n- Derive invRef from what is being billed rather than from a record that may later describe something else.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `invName` | string | yes | Invoice sample name, ex: 1,2,6 |\n| `invSerial` | string | yes | Invoice sign, ex: C24TAA, K24TXX |\n| `invNumber` | string | yes | Invoice number |\n| `invDate` | string | yes | Invoice date (format: yyyy/mm/dd) |\n| `invCustomer` | 0 \\\\| 1 | | Customer is not company? Value 1\\|0 |\n| `invRef` | string | yes | 3rd party Bill-ID. Identifies the invoice: a value that already has an invoice resolves to that invoice instead of issuing a new one |\n| `invRefDate` | string | | Bill date (format: yyyy/mm/dd) |\n| `billNumber` | string | | Bill number - extend |\n| `buyerTax` | string | | Buyer tax-number |\n| `buyerCode` | string | | Buyer-id |\n| `buyerName` | string | yes | Buyer full name |\n| `buyerCompany` | string | | Buyer company/ organization name |\n| `buyerAddress` | string | | Buyer full address |\n| `buyerAcc` | string | | Buyer bank account number |\n| `buyerBank` | string | | Buyer bank name |\n| `buyerEmail` | string | | Buyer email |\n| `buyerPhone` | string | | Buyer phone number |\n| `buyerFax` | string | | Buyer fax number |\n| `buyerCitizenIDNumber` | string | | Citizen Identification Number |\n| `buyerPassportNumber` | string | | Passport Number |\n| `govUnitCode` | string | | Budget-related Unit Code |\n| `invSubTotal` | number | yes | Invoice Subtotal (not include VAT, not include discount) |\n| `invVatRate` | number | yes | VAT Rate: 0,5,8,10 (0%,5%,8%,10%), -1 (Not taxable), -2 (Not declare and pay taxes) |\n| `invVatAmount` | number | yes | VAT Amount |\n| `invTotalAmount` | number | yes | Invoice Total |\n| `invPayment` | string | | Payment method |\n| `invExchangeRate` | number | | Exchange rate (to VietNam \u0111\u1ED3ng) |\n| `invCurrency` | string | | Payment currency |\n| `note` | string | | Note for discount |\n| `invAutoSign` | 0 \\\\| 1 | | Request sign invoice immediately after create successful? Value 1\\|0 |\n| `privateCode` | string | | The UID string (10-12 chars), the buyer will use this code to lookup invoice (after invoice signed). If blank, WinInvoice will generate automatically |\n| `option` | 0 \\\\| 1 \\\\| 2 \\\\| 3 \\\\| 4 | | Invoice type: 0 (Normal invoice), 1 (Information-adjustment invoice), 2 (Increases-adjustment invoice), 3 (Decreases-adjustment invoice), 4 (Replacement invoice - original will be marked as deleted when signed) |\n| `invCodeOld` | string | | Original invoice number (required for adjustment/replacement invoices) |\n| `invNameOld` | string | | Original invoice sample name (required for adjustment/replacement invoices) |\n| `invSignOld` | string | | Original invoice sign (required for adjustment/replacement invoices) |\n| `create04SSHDDT` | 0 \\\\| 1 | | Auto create 04/SS-H\u0110\u0110T? Value 1\\|0 (default 1) |\n| `reason04SSHDDT` | string | | 04/SS-H\u0110\u0110T Reason |\n| `isDscnForSaleInv` | 0 \\\\| 1 | | Apply an 8% tax reduction to the sales invoice? Value 1\\|0 (default 0) |\n| `saleInvVATRate` | number | | Tax rate based on revenue (Only applicable when isDscnForSaleInv = 1) |\n| `saleInvDscnAmnt` | number | | The reduction amount determined based on revenue and corresponding tax rate (Only applicable when isDscnForSaleInv = 1) |\n| `privateNote` | string | | Used for internal notes |\n| `items` | object[] | yes | Array of item/product in this bill |\n| `items[].itemNo` | string | | Item number |\n| `items[].itemCode` | string | | Product/Item code |\n| `items[].itemName` | string | yes | Name of product |\n| `items[].itemPromo` | 0 \\\\| 1 | | Is promotional product (gift)? Value 1\\|0 (default 0) |\n| `items[].isDscnItem` | 0 \\\\| 1 | | Is the discount line? Value 1\\|0 (default 0) |\n| `items[].itemUnit` | string | | Product Unit |\n| `items[].itemQuantity` | number | yes | Product quantity |\n| `items[].itemPrice` | number | yes | Product unit price (not include VAT) |\n| `items[].itemVatRate` | number | | Product VAT rate |\n| `items[].itemVatAmnt` | number | | Product VAT Amount |\n| `items[].itemDscnAmnt` | number | | Product Discount Amount |\n| `items[].itemAmountNoVat` | number | | Product Amount (not include VAT)(not include item discount) |\n| `items[].adjustType` | "PRICE" \\\\| "QTTY" | | Type of adjustment: PRICE (Adjust unit price of item in original invoice), QTTY (Adjust quantity of item in original invoice). This field has effect when value of option field is 2 or 3 |\n| `items[].itemPack` | string | | Product Lot |\n| `items[].itemDate` | string | | Product expiration date |\n| `items[].itemNote` | string | | Product note |\n| `items[].specialGoodsType` | 0 \\\\| 1 \\\\| 2 \\\\| 3 | | Special category of goods: 0 (Normal item/good), 1 (Cars or motorcycles), 2 (Transportation service), 3 (Transportation services on digital and e-commerce platforms) |\n| `items[].specialGoodsInfo` | object | | Data requirements for specialized goods based on specialGoodsType |\n| `items[].specialGoodsInfo.chassisNumb` | string | | Chassis Number (for cars/motorcycles - specialGoodsType = 1) |\n| `items[].specialGoodsInfo.engineNumb` | string | | Engine Number (for cars/motorcycles - specialGoodsType = 1) |\n| `items[].specialGoodsInfo.licensePlate` | string | | License plate of the transport vehicle (for transportation service - specialGoodsType = 2) |\n| `items[].specialGoodsInfo.senderName` | string | | Sender\'s Name (for shipping service - specialGoodsType = 3) |\n| `items[].specialGoodsInfo.senderAddress` | string | | Sender\'s Address (for shipping service - specialGoodsType = 3) |\n| `items[].specialGoodsInfo.senderTaxCode` | string | | Sender\'s Tax Code (for shipping service - specialGoodsType = 3) |\n| `items[].specialGoodsInfo.senderCitizenIDNumber` | string | | Sender\'s Citizen Identification Number (for shipping service - specialGoodsType = 3) |\n| `items[].isNoteItem` | 0 \\\\| 1 | | Is the note line? Value 1\\|0 (default 0) |\n| `connected_account_id` | string | yes | The ID of the connected account |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `oid` | string | yes | WinInvoice\'s UID |\n| `invCode` | string | yes | Invoice number (is \'0000000\' if has not signed) |\n| `invRef` | string | yes | 3rd party Bill-ID |\n| `invSign` | string | yes | Invoice sign, (as invSerial) |\n| `invDate` | string | yes | Invoice date, format yyyy-mm-dd |\n| `invName` | string | yes | Full invoice sample (invName + invSign) |\n| `privateCode` | string | yes | Private lookup code for buyer |\n| `itemTotal` | number | | Count of products in invoice |\n| `itemError` | number | | Count of error-products in invoice |\n| `govTranfer` | number | yes | Invoice has transferred to GOV? 1\\|0 |\n| `govTranID` | string | yes | Transaction ID used to transfer invoice to GOV |\n| `govTranferErr` | number | yes | ansferred to GOV has FAIL? 1\\|0 |\n| `govTranText` | string | yes | Error message if transfer to GOV has FAIL |\n| `govCode` | string | yes | The UID string issue by GOV for this invoice |\n| `autoSign` | number | | Has request sign immediately ? 1\\|0 |\n| `isExisting` | 0 \\\\| 1 | | 1 when invRef already had an invoice and this call returned it unchanged \u2014 no invoice was issued |\n| `link` | string | yes | Link to view the invoice |\n\n## x\n\n### x_create_post (workflow step)\n\n- Publish a post to the connected X account.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `connected_account_id` | string | yes | |\n| `text` | string | yes | |\n| `media_file_ids` | string[] | | Up to 4 images, or one GIF, or one video. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `post_id` | string | yes | |\n| `url` | string | yes | |\n\n## zip\n\n### zip_files (workflow step)\n\n- Bundle multiple files into a zip archive.\n- Max 100 files.\n- An item in file_ids is a file id \u2014 or `{ id, name }` to name that entry in the archive: a file name, never a path, carrying the stored extension.\n- A bare id keeps the stored filename.\n\n**Inputs**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_ids` | string \\\\| object[] | yes | |\n| `filename` | string | | Name for the zip file, without the .zip extension. |\n\n**Answer**\n\n| Name | Type | Required | What it is |\n| --- | --- | --- | --- |\n| `file_id` | string | yes | The ID of the generated file. |\n| `url` | string | yes | The download URL of the generated file. |\n| `filename` | string | yes | The filename of the generated file, including extension. |\n| `mime_type` | string | yes | The MIME type of the generated file. |\n';
|
|
@@ -1797,7 +1797,7 @@ var workflow_steps_default = '<!-- Generated by backend/scripts/generate_workflo
|
|
|
1797
1797
|
var app_bindings_default = "# App bindings \u2014 the queries and workflows an app calls\n\nA custom-code app reaches workspace data through two kinds of binding, each declared on the app\nunder an alias \u2014 a JS identifier (`/^[a-zA-Z_$][a-zA-Z0-9_$]*$/`):\n\n| Binding | Declared with | The app calls it with | An agent calls it with |\n|---|---|---|---|\n| Query | `set_app_queries` (CLI, chat) | `useQuery(\"<alias>\", params?)` | `run_app_query` |\n| Workflow | `set_app_workflow` | `useWorkflow(\"<alias>\")({ ...inputs })` | `run_app_workflow` |\n\nEach runs under the app's authority (`{ type: \"app\", app_id }`), the app owner's reach \u2014 never the\ncalling member's. Declaring one needs the app's owner or an admin. A write is live: the app's next\ncall uses it. `get_app` lists an app's aliases with their params and inputs.\n\nEach reader returns a fingerprint of what it read \u2014 `get_app_workflow`'s `body_sha`, and on the CLI and in chat\n`get_app_query`'s `sha` \u2014 and a write built on that read passes it back:\n`set_app_queries`' `expected_shas` (alias \u2192 `sha`), `set_app_workflow`'s `expected_body_sha`,\n`remove_app_binding`'s `expected_sha`. The write is\nrefused, and writes nothing, when the binding changed since; omit it for an unconditional write.\n\n## Queries\n\nA query declaration is `{ ast, params?, description?, templates? }`:\n\n- `ast` \u2014 a query template (a QueryNode, below). It fixes the tables, filters and columns the alias\n reads, so a caller cannot widen it.\n- `params` \u2014 the typed value holes the caller fills, each an input declaration (see **Inputs**),\n named in the ast as `{{params.<name>}}`. A param fills a VALUE position only \u2014 a filter value, a\n `search` \u2014 never a `table_id` or a field key. Every token the ast names is declared. An optional\n param left unset drops the conditions that read it; a required param that arrives empty is\n refused where a filter reads it.\n- `description` \u2014 one line saying what the query returns, for an agent choosing between aliases.\n- `templates` \u2014 the document templates an export of its rows may fill, by name:\n `{ <name>: { template_id: \"dtl_\u2026\", rows: \"<key the template lists the rows under>\" } }`.\n\nA save checks what a deploy checks: every table is in this workspace and within the owner's reach,\nevery projected, filtered and sorted field resolves, every param token is declared, and a key a\nnode's kind does not take (a stray `sort`, `limit`, `filter`, `search`) is refused by name.\n\nOn the CLI and in chat, `set_app_queries` takes `{ <alias>: <declaration> }`, one alias or several, and merges each: send\nonly the fields you change, `null` clears `params`, `description` or `templates` (never `ast`), and a\nnew alias needs an `ast`. Any other field is refused by name. An alias it omits is kept.\n`remove_app_binding` with `kind: \"query\"` deletes one; `get_app_query` reads one back.\n\n### The query tree\n\nEvery node has a `kind`; all but `from_table` read the node under `from` (`join`: `left`/`right`,\n`union`: `sources`).\n\n| `kind` | Keys |\n|---|---|\n| `from_table` | `table_id`, `filter?`, `search?`, `sort?`, `limit?` |\n| `project` | `columns` \u2014 the output columns |\n| `filter` | `predicate` \u2014 a filter tree over the input's columns |\n| `join` | `left`, `right`, `on: { left_column, right_column }`, `type: \"inner\" \\| \"left\"` |\n| `union` | `sources` (at least two, columns aligned by name and type) |\n| `group` | `by` (columns, `{ bucket: { source, granularity, output } }`, or `{ band: { source, tiers, output } }`), `aggregates` |\n| `window` | `partition_by`, `order_by`, `frame?`, `aggregates?`, `functions?` |\n| `sort` | `by: [{ field_key, order }]` |\n| `limit` | `n`, `offset?` or `keyset?` |\n| `unpivot` | `passthrough`, `row_columns`, `rows` \u2014 one source row into several |\n| `unnest` | `source`, `output`, `display_output?`, `keep_empty?` \u2014 one row per element of a multi-value cell |\n\nFilters (`from_table.filter`, `filter.predicate`, an aggregate's `filter`) are the filter grammar \u2014 the\n`filters` reference \u2014 over the input's columns, taking `{{params.x}}` as values. `search` matches\nevery text-bearing field of the row, accent- and case-insensitive.\n\nA query names a table's fields by the alias its document states, wherever a node reads the table directly (a\n`from_table`'s filter and sort, the `project`, `filter`, `group`, `window`, `unnest` or `unpivot` over it) and a select\ncondition's values by the option's alias. A column a `project` writes answers under the alias it was written with; every\nname past a `project` or an aggregate is that column's own. A `union` whose arms read one alias from different tables\nprojects each arm's columns under one name first. The query is stored by key and read back by alias.\n\nA `project` column is a field's alias as a bare string (output name and type come from the field), or\n`{ output?, type?, source, writable_target?, limit? }` to rename, compute or bound one. A computed\n`source` is a field's alias, `{ literal }`, `{ eq: [a, b] }`, `{ neq: [a, b] }`, `{ isEmpty }`,\n`{ isNotEmpty }`, `{ coalesce: [...] }`, `{ concat: [...] }`, `{ record_id: true }`,\n`{ link: { source, field } }` (a field of the first linked record),\n`{ link_agg: { source, field, operation } }` (`sum`, `avg`, `min`, `max`, `count`, `string_agg`\nover every linked record), or `{ expression }`. A computed column states its `type`: `text`,\n`number`, `boolean`, `date`, `datetime`, `select`, `select_record_link`, `select_member`, `files`\nor `json`. `limit` (1\u201310) bounds a `files` column's entries per cell.\n\nAn aggregate column is `{ output, type, operation, input_column?, filter? }` \u2014 `string_agg` also\ntakes `separator`, `distinct` and `max_values`. A `window` function is\n`{ output, fn }` for `row_number`, `rank`, `dense_rank`, `percent_rank`, `cume_dist`, plus\n`buckets` for `ntile` and `input_column`, `offset?`, `default?` for `lag` / `lead`; functions need\na non-empty `order_by`.\n\n## Inputs\n\nA query's `params`, a workflow's `inputs` and an agent's `inputs` are one vocabulary: a map of\nname \u2192 `{ type, required?, description?, \u2026per type }`. An entry is required unless it states\n`required: false`.\n\n| `type` | Takes |\n|---|---|\n| `text`, `number`, `boolean`, `date`, `datetime`, `email` | \u2014 |\n| `record_link` | `table_id`; `multi?`; `max?` (with `multi`, the most ids one call carries) |\n| `select` | exactly one of `options: [{ label, value }]` or `field: \"<table id>.<alias>\"` (a select field whose CURRENT options back it); `multi?` |\n| `date_range` | `include_time?` |\n| `member` | `multi?`; `group?` (a member group the value must belong to) |\n| `file` | `multi?` \u2014 an uploaded `fil_\u2026` id, for a files field |\n| `json` | \u2014 any value |\n| `object` | `fields` \u2014 a nested map of entries |\n| `array` | `items` \u2014 one entry |\n\nNesting stops at depth 8. A `field`-form select tracks the field's options as they change; an\ninline `options` set is a fixed list.\n\n## Outputs\n\nA workflow's and an agent's `outputs` declare the structured data handed back: a map of\nname \u2192 `{ type, required?, description?, \u2026per type }`, every entry present unless it states\n`required: false`.\n\n| `type` | Takes |\n|---|---|\n| `text`, `number`, `boolean`, `date`, `datetime`, `email`, `json` | \u2014 |\n| `record_link` | `table_id`, `multi?` |\n| `select` | exactly one of `options: [{ label, value }]` or `field: \"<table id>.<alias>\"`; `multi?` |\n| `object` | `fields` |\n| `array` | `items` |\n\nThe returned value is checked against it, and the app reads it typed. An inline `options` set is\nalso enforced on the value returned, so the producer is told the legal keys; a `field`-form select\nkeeps the type tracking the live field.\n\n## Workflows\n\n`set_app_workflow` binds a workflow body to an alias: `{ app_id, alias, source, inputs?, outputs?,\nname?, description? }`. On an app written as a tree the alias is one the tree names \u2014 a Workflow node's\n`key` or an Act's `workflow` \u2014 and the node states `inputs`, `outputs` and `description`\n(`model/workflow-node`, whose value shapes differ from the **Inputs** below): a call stating any of\nthem for it is refused, and outputs the node leaves out are derived from the body. An alias the tree\nnames nowhere is archived by the next `set_app`. A Workflow node runs only for someone who holds the\napp: on an app shared by a public link, a visitor's call of it is refused as an alias the app does\nnot have. This page's `inputs` and `outputs` are for a custom-code app.\n\n- `source` is the body with no `on({...})` trigger \u2014 the app is the trigger. It reads each declared\n input as `trigger.app_workflow.inputs.<name>`; no record is in scope, so it reads each one by id\n with `get_record`.\n The body's steps are the `workflows` reference.\n- `inputs` is the payload the call site passes (see **Inputs**). Omitted, an existing workflow keeps\n its inputs; on a first bind it takes an untyped payload. `{}` clears them.\n- `outputs` is what `return({ data })` hands back (see **Outputs**). Usually omit it: the type is\n derived from the body's `return({ data })`, and the result echoes the bound `outputs`. Declared,\n the body does not save unless its return matches.\n- A save checks the body \u2014 parse, type-check against the declared inputs, names, lint, structure \u2014\n and answers with `[source/code] location: message` lines. `verify_only: true` makes every check\n and writes nothing. A call naming a live alias replaces its body in place, keeping its run history.\n- A call whose payload does not match `inputs` is refused before the body runs.\n\n`get_app_workflow` reads the body back; `set_app_workflow` with `verify_only` and `record_id` or `preview_inputs`\nrehearses a draft before binding; on the CLI and in chat, `remove_app_binding` with\n`kind: \"workflow\"` unbinds it.\n";
|
|
1798
1798
|
|
|
1799
1799
|
// docs/document_templates.md
|
|
1800
|
-
var document_templates_default = '# Document Templates\n\nTurn workspace data into finished documents \u2014 invoices, contracts, reports, letters,\nlabels, emails \u2014 by filling a reusable template instead of writing a file from scratch.\nYou register a template once, then generate a filled file (or a rendered email) from it as\nmany times as you like, feeding it data from records, a workflow, or an agent run.\n\nThis is a **capability + usage** guide. A tool\'s exact input schema is in its own description.\n\n## The five template types\n\n`set_template` writes all five. Its `type` names which one a new template is.\n\n| Type | What it fills | Output | `type` |\n|---|---|---|---|\n| `pdf-form` | fields overlaid on an **uploaded PDF** at fixed positions | PDF | `"pdf-form"` |\n| `html` (a PDF) | an **HTML + Handlebars** layout you write from scratch | PDF | `"html"` |\n| `excel` | an **uploaded `.xlsx`** with `{{marker}}` cells | `.xlsx` | `"excel"` |\n| `word` | an **uploaded `.docx`** with `{{marker}}`s | `.docx` | `"word"` |\n| `email` | an **inline HTML + Handlebars** body, rendered when the email is sent | email | `"email"` |\n\nTwo shapes underneath: **file-backed** (`pdf-form`, `excel`, `word`) clone an uploaded\noffice file and mark it up; **inline** (`html`, `email`) store the markup you author directly.\n\n## The lifecycle: create \u2192 generate \u2192 chain\n\n1. **Create** a template \u2014 register the file (or inline markup) and declare a `variables`\n map (the named slots the template fills). This returns a template id (`dtl_\u2026`).\n2. **Generate** a filled file \u2014 call `generate_document` with the\n template id, a `filename` (no extension), and a `data` map keyed by your variable names.\n It substitutes the markers and returns a **generated file** (with a `file_id`).\n3. **Chain** the result \u2014 generation only *produces* the file. Attaching it to a record,\n sending it, or saving it is a separate step: `update_records` with `add_to` on a files field\n files the `file_id` on a record (`remove_made`, a template id, takes out the paper that template\n made earlier); over MCP and on the CLI, `download_file` fetches it; inside a workflow, pass the `file_id` to the next\n step. A paper an app makes is its Act\'s `template`, which files it on the record itself\n (below).\n\nEmail is the exception: `generate_document` refuses an email template. An email template is\nrendered to a subject + body **at send time** from the template and the data the send step\nsupplies \u2014 so you author it with `set_template` and a workflow/app send step (or the\nweb composer) does the rendering and delivery. External agents send their own email directly.\n\n## Creating and changing a template\n\nA paper prints the owner\'s own terms only: the clauses, wording and fields of the sample they sent, never a term the owner did not state.\n\n`set_template` creates a template when it is given no `template_id`, and changes the named one\nwhen it is given one. Creating takes `type` and `name`, and the fields of its type; changing takes\n`expected_version` \u2014 the `current_version` that `get_template` returned, so a concurrent edit is\nrefused rather than overwritten \u2014 and only the fields to change. A field the type does not take is\nrefused rather than dropped. `description` (one line on what the template produces) is on every type.\n\n### Getting a file in\n\nA file-backed template starts from a file in the workspace: over MCP and on the CLI, `upload_file` takes a public URL,\nor bytes you hold, and returns a `file_id`. When the person has the file and your host shows you its content but not its\nbytes, `upload_file` with `person: true` returns a `drop_url` for them to open and a `ticket_id` per file; once they say\nthey dropped it, `upload_file` with that `ticket_id` returns the `file_id`.\n\nA new Word or Excel paper is a file you build yourself, in your own filesystem or sandbox, with its markers typed where\nthe values go, then, over MCP or on the CLI, send it with `upload_file` `files` and register with `set_template`. Where your sandbox cannot reach\nthis host, give the person the file and a `person` drop link.\n\n### By type\n\n- **`html`** \u2014 a full HTML document with inline CSS and `{{variable}}` placeholders, rendered to PDF; anything whose layout\n you control. `variables` are plain text, rich `html`, an auto-rendered `table` or a `list`; `format` (page size) and\n `orientation` are optional. A change may send `patch_ops`, exact search-and-replace edits of the stored markup, in place\n of the whole `template`.\n- **`pdf-form`** \u2014 an existing PDF filled in place. `set_template` with the uploaded `template_file_id`;\n `analyze_pdf_template` reads the labels and positions off the page; `set_template` again with `expected_version` and the\n positioned `variables` (`text` / `checkbox` / `image`, each with a page and x/y/size). Data is one scalar per field.\n- **`excel`** \u2014 a `.xlsx` with `{{marker}}`s in the cells to fill, and a `variables` map. Markers are validated at create\n time: a structural error blocks the save, and a declared variable with no marker comes back in `unmarked_variables`\n (skipped). Over MCP and on the CLI, `validate_excel_template` checks a file\'s markers without creating a template. Pass\n raw numbers, ISO dates and booleans at generate time; the cell\'s number format shows them.\n- **`word`** \u2014 a `.docx` with `{{marker}}`s. `variables` is optional: every marker is derived (`{{name}}` string,\n `{{FOR \u2026 IN name}}` array, `{{IF name}}` boolean), and a marker you did not declare is still added\n (`derived_variables`). At generate time `data` needs a key for **every** marker, `""` for a blank one. A marker may run\n across formatting runs and is still read whole. In a table, the row holding `{{FOR line IN lines}}` and the row holding\n `{{END-FOR line}}` enclose the row that repeats.\n- **`email`** \u2014 inline HTML + Handlebars, with `template` and `variables`; optionally a default `subject` (it takes\n `{{variable}}` too) and `default_to`, `default_cc`, `default_bcc`. Body and subject render from the data at send time.\n\n## Markers, at a usage level\n\nEvery type supports three shapes of substitution. The exact syntax differs per engine \u2014 read\nthe `set_template` description for the reference; here is the capability:\n\n- **Scalar** \u2014 a single value in one spot: `{{name}}`, `{{total}}`. Whole-cell scalars in\n Excel preserve their type via the cell\'s number format.\n- **Repeating rows / line items** \u2014 one template row rendered once per item in a list, for\n invoice lines, order rows, tables. Excel and the HTML/email engines use a Handlebars-style\n `{{#each items}}\u2026{{/each}}`; Word uses `{{FOR item IN items}}\u2026{{$item.field}}\u2026{{END-FOR item}}`. The HTML and email types\n also offer an auto-rendered `table` variable \u2014 declare its columns and pass an array, no\n hand-written loop. (Excel always uses the `{{#each}}` marker rows \u2014 it has no auto-rendered\n `table` type; its variable types are string/number/date/boolean/array.) Inside a loop the row\'s number counted from 1\n is `{{@ordinal}}` (Excel, HTML, email) or `{{$ordinal}}` (Word), the innermost loop\'s: an STT column is a cell holding\n `{{@ordinal}}` beside the row\'s other markers. `{{@index}}` counts from 0.\n- **Conditional sections** \u2014 a block shown only when a condition holds (a "paid" stamp, an\n optional notes block). Excel/HTML/email use `{{#if}}\u2026{{else}}\u2026{{/if}}`; Word uses `{{IF name}}\u2026{{END-IF name}}`.\n\nThe `html` and `email` types print a raw value through a helper in the template\'s `locale` (`vi` or `en`; none prints in the organization\'s language): `{{money total "VND"}}`, `{{number qty}}`, `{{date issued_on}}`, `{{words total "USD"}}` (`model/groups-and-templates` lists what each prints). `{{date (add_days issued_on -1)}}` prints the day before, `add_days` shifting a date by whole days. A value a helper cannot read fails the render.\n\nA field can also print as a **QR code** or a **Code 128 barcode** (a picture a phone\'s camera or a scanner reads back): `{{qr link}}` or `{{code128 position 6}}` in html (never email: mail clients do not show the picture, and a save refuses it), `{{position | qr}}` or `{{position | code128:6}}` as a whole Excel cell (its row grows to the picture), `{{IMAGE qr(position)}}` or `{{IMAGE code128(position, 6)}}` in Word. The optional number is the picture\'s width in centimetres. Without it the width follows the value\'s length \u2014 each module prints 0.5 mm in a QR code and 0.33 mm in a barcode, so a longer value prints wider; a width that would shrink a module below what a scanner reads (0.3 mm, 0.19 mm) is refused, naming the least width that scans. An empty field draws nothing.\n\nA record\'s **photos** (a files field, an act\'s paper hands each as a file cell `{id, name, mime, url}`) print too, and only for a file whoever makes the paper may read \u2014 the same rule the file proxy applies; any other file is never drawn. html: `{{#each photos}}<img src="{{url}}">{{/each}}` (only the data\'s file cells are inlined; a url typed into a text field stays a link). Word: `{{IMAGE picture(cover, 8)}}`, and `{{FOR p IN photos}}{{IMAGE picture($p, 8)}}{{END-FOR p}}` for several. Excel: `{{cover | picture:6}}` as the whole cell, or `{{this | picture}}` inside `{{#each photos}}` (each row grows to its picture). The number is the width in centimetres (8, or 6 in Excel, by default). `picture` takes one image file: a list (loop over it), a file that is no image or one that cannot be read is refused by name. Photos are embedded at most 1600 pixels across, about 200 dpi at 20 cm.\n\n`set_template`\'s description holds each type\'s exact marker grammar and helper list.\n\n## Generating a filled file\n\n```\nget_template {"template_id": "dtl_\u2026"} # its declared variables\ngenerate_document {"document_template_id": "dtl_\u2026", "filename": "invoice-1042",\n "data": {"company_name": "Acme", "total": 1042, "items": [...]}}\n```\n\n- `data` is a map keyed by your variable names. Scalars for scalar/form fields; arrays for\n `table`/`list`/loop variables. `filename` excludes the extension (the type sets it). Pass a select, a\n member or a link as `get_record`\'s `display` words, a number and a date raw.\n- `generate_document` renders the template\'s own type and returns a generated file object; take\n its `file_id` onward.\n- A PDF no template is wanted for takes `html`, a `filename` and its `data` in place of `document_template_id`: the markup is\n rendered as an `html` template is (the same markup rules, markers and helpers) and the PDF is stored as a workspace file. Name one of the two.\n- In a workflow, a **generate step** calls the same tool and hands the `file_id` to the next\n step (attach to a record, send as an attachment, etc.).\n\n## A template an app\'s Act makes\n\nAn Act with `template` (or `templates`) fills an `html`, `excel` or `word` template from its record and files the result\n(a PDF, an `.xlsx` or a `.docx`) in the record\'s files field `into`; `run_app_workflow` on `act_<key>` (or the `workflow` the Act names) presses it. The Act hands the template the record\'s fields by their\nalias, so name each variable after one: `{{customer}}`, `{{total}}`. A select, a member and a link arrive as their words\n(a link as the linked row\'s title); a number and a date arrive raw, for `{{money total "VND"}}` and `{{date issued_on}}`.\nThe rows of each child the record draws arrive under the name of the record\'s own link to them, for a loop:\n\n```html\n<h1>Quote {{code}}</h1>\n<p>{{customer}} \u2014 {{money total "VND"}}</p>\n{{#each lines}}<p>{{description}} \xD7 {{quantity}}</p>{{/each}}\n```\n\nHere `code`, `customer` and `total` are fields of the record and `lines` is its link to the child table. The rows\nalso arrive under the child table\'s `ref` (`quote_line`), so `{{#each quote_line}}` fills as well; a Word template loops the same rows with `{{FOR line IN lines}}` \u2026\n`{{$line.description}}` \u2026 `{{END-FOR line}}`.\n\nEvery List the record draws and the template names (an html template\'s variables, a Word template\'s markers) hands its\nrows, each List\'s `where` applied; a List the template does not name is not read. A List that only reads hands them under its\n`key` (the table\'s alias where it states none), and a List that sums a number hands the sums under `<name>_totals`, one\nentry per number the List draws as a column or as the table\'s figure. A `where` bound may be `{ "record": "<field>" }`,\na date or number the record itself holds, so a record can print a window of its own days. A service plan row (customer,\n`period_start`, `period_end`) draws the customer\'s visits within its period:\n\n```\n{ "type": "List", "key": "visits", "entity": "visit", "same": "customer", "label": "Visits", "columns": ["visited_on", "hours"],\n "where": { "visited_on": { "gte": { "record": "period_start" }, "lte": { "record": "period_end" } } } }\n```\n\n```html\n{{#each visits}}<p>{{visited_on}} \u2014 {{hours}} h</p>{{/each}}\n<p>Total {{visits_totals.hours}} h</p>\n```\n\nA bound the record leaves blank, or holds as anything but one whole day (`2026-05`) or one figure, keeps no row:\n`visits` is empty and `visits_totals.hours` is `0`. The sums add a number as the List\'s foot does: a cell holding a\nfigure adds it, any other cell (blank, an errored formula, a lookup\'s list) adds nothing. A Word template loops\n`{{FOR v IN visits}}` and prints `{{visits_totals.hours}}` the same way. A template that names a List narrowed to the\nreader (`"me"`) is refused, and so is a name a rows List and a `list` List both reach: give the `list` a `key`. A record\nbound narrows what is listed and is never an access bound; the table\'s row rules are.\n\nA paper over many rows (a client\'s month, a statement) is that: a record of the period whose List is windowed by its dates, made by the Act\'s `template`; or a List `Export` with `template` and `per` (`model/export-node`).\n`model/groups-and-templates` has the rest.\n\n## Discovering and managing templates\n\nThese tools work across all five types:\n\n| Tool | Does |\n|---|---|\n| `get_workspace` | list the workspace\'s templates (`kind: "template"`), each with its id, type and one line |\n| `get_template` | one template\'s full definition, incl. the `variables` it expects and the `current_version` a change needs |\n| `set_template` | create a template, or change one |\n| `clone_template` | copy a template to iterate on, in chat |\n| `delete_template` | remove a template |\n\nCall `get_template` before generating when you don\'t already know a template\'s variable names.\n';
|
|
1800
|
+
var document_templates_default = '# Document Templates\n\nTurn workspace data into finished documents \u2014 invoices, contracts, reports, letters,\nlabels, emails \u2014 by filling a reusable template instead of writing a file from scratch.\nYou register a template once, then generate a filled file (or a rendered email) from it as\nmany times as you like, feeding it data from records, a workflow, or an agent run.\n\nThis is a **capability + usage** guide. A tool\'s exact input schema is in its own description.\n\n## The five template types\n\n`set_template` writes all five. Its `type` names which one a new template is.\n\n| Type | What it fills | Output | `type` |\n|---|---|---|---|\n| `pdf-form` | fields overlaid on an **uploaded PDF** at fixed positions | PDF | `"pdf-form"` |\n| `html` (a PDF) | an **HTML + Handlebars** layout you write from scratch | PDF | `"html"` |\n| `excel` | an **uploaded `.xlsx`** with `{{marker}}` cells | `.xlsx` | `"excel"` |\n| `word` | an **uploaded `.docx`** with `{{marker}}`s | `.docx` | `"word"` |\n| `email` | an **inline HTML + Handlebars** body, rendered when the email is sent | email | `"email"` |\n\nTwo shapes underneath: **file-backed** (`pdf-form`, `excel`, `word`) clone an uploaded\noffice file and mark it up; **inline** (`html`, `email`) store the markup you author directly.\n\n## The lifecycle: create \u2192 generate \u2192 chain\n\n1. **Create** a template \u2014 register the file (or inline markup) and declare a `variables`\n map (the named slots the template fills). This returns a template id (`dtl_\u2026`).\n2. **Generate** a filled file \u2014 call `generate_document` with the\n template id, a `filename` (no extension), and a `data` map keyed by your variable names.\n It substitutes the markers and returns a **generated file** (with a `file_id`).\n3. **Chain** the result \u2014 generation only *produces* the file. Attaching it to a record,\n sending it, or saving it is a separate step: `update_records` with `add_to` on a files field\n files the `file_id` on a record (`remove_made`, a template id, takes out the paper that template\n made earlier); over MCP and on the CLI, `download_file` fetches it; inside a workflow, pass the `file_id` to the next\n step. A paper an app makes is its Act\'s `template`, which files it on the record itself\n (below).\n\nEmail is the exception: `generate_document` refuses an email template. An email template is\nrendered to a subject + body **at send time** from the template and the data the send step\nsupplies \u2014 so you author it with `set_template` and a workflow/app send step (or the\nweb composer) does the rendering and delivery. External agents send their own email directly.\n\n## Creating and changing a template\n\nA paper prints the owner\'s own terms only: the clauses, wording and fields of the sample they sent, never a term the owner did not state.\n\n`set_template` creates a template when it is given no `template_id`, and changes the named one\nwhen it is given one. Creating takes `type` and `name`, and the fields of its type; changing takes\n`expected_version` \u2014 the `current_version` that `get_template` returned, so a concurrent edit is\nrefused rather than overwritten \u2014 and only the fields to change. A field the type does not take is\nrefused rather than dropped. `description` (one line on what the template produces) is on every type.\n\n### Getting a file in\n\nA file-backed template starts from a file in the workspace: over MCP and on the CLI, `upload_file` takes a public URL,\nor bytes you hold, and returns a `file_id`. When the person has the file and your host shows you its content but not its\nbytes, `upload_file` with `person: true` returns a `drop_url` for them to open and a `ticket_id` per file; once they say\nthey dropped it, `upload_file` with that `ticket_id` returns the `file_id`.\n\nA new Word or Excel paper is a file you build yourself, in your own filesystem or sandbox, with its markers typed where\nthe values go, then, over MCP or on the CLI, send it with `upload_file` `files` and register with `set_template`. Where your sandbox cannot reach\nthis host, give the person the file and a `person` drop link.\n\n### By type\n\n- **`html`** \u2014 a full HTML document with inline CSS and `{{variable}}` placeholders, rendered to PDF; anything whose layout\n you control. `variables` are plain text, rich `html`, an auto-rendered `table` or a `list`; `format` (page size) and\n `orientation` are optional. A change may send `patch_ops`, exact search-and-replace edits of the stored markup, in place\n of the whole `template`.\n- **`pdf-form`** \u2014 an existing PDF filled in place. `set_template` with the uploaded `template_file_id`;\n `analyze_pdf_template` reads the labels and positions off the page; `set_template` again with `expected_version` and the\n positioned `variables` (`text` / `checkbox` / `image`, each with a page and x/y/size). Data is one scalar per field.\n- **`excel`** \u2014 a `.xlsx` with `{{marker}}`s in the cells to fill, and a `variables` map. Markers are validated at create\n time: a structural error blocks the save, and a declared variable with no marker comes back in `unmarked_variables`\n (skipped). Over MCP and on the CLI, `validate_excel_template` checks a file\'s markers without creating a template. Pass\n raw numbers, ISO dates and booleans at generate time; the cell\'s number format shows them.\n- **`word`** \u2014 a `.docx` with `{{marker}}`s. `variables` is optional: every marker is derived (`{{name}}` string,\n `{{FOR \u2026 IN name}}` array, `{{IF name}}` boolean), and a marker you did not declare is still added\n (`derived_variables`). At generate time `data` needs a key for **every** marker, `""` for a blank one. A marker may run\n across formatting runs and is still read whole. In a table, the row holding `{{FOR line IN lines}}` and the row holding\n `{{END-FOR line}}` enclose the row that repeats.\n- **`email`** \u2014 inline HTML + Handlebars, with `template` and `variables`; optionally a default `subject` (it takes\n `{{variable}}` too) and `default_to`, `default_cc`, `default_bcc`. Body and subject render from the data at send time.\n\n## Markers, at a usage level\n\nEvery type supports three shapes of substitution. The exact syntax differs per engine \u2014 read\nthe `set_template` description for the reference; here is the capability:\n\n- **Scalar** \u2014 a single value in one spot: `{{name}}`, `{{total}}`. Whole-cell scalars in\n Excel preserve their type via the cell\'s number format.\n- **Repeating rows / line items** \u2014 one template row rendered once per item in a list, for\n invoice lines, order rows, tables. Excel and the HTML/email engines use a Handlebars-style\n `{{#each items}}\u2026{{/each}}`; Word uses `{{FOR item IN items}}\u2026{{$item.field}}\u2026{{END-FOR item}}`. The HTML and email types\n also offer an auto-rendered `table` variable \u2014 declare its columns and pass an array, no\n hand-written loop. (Excel always uses the `{{#each}}` marker rows \u2014 it has no auto-rendered\n `table` type; its variable types are string/number/date/boolean/array.) Inside a loop the row\'s number counted from 1\n is `{{@ordinal}}` (Excel, HTML, email) or `{{$ordinal}}` (Word), the innermost loop\'s: an STT column is a cell holding\n `{{@ordinal}}` beside the row\'s other markers. `{{@index}}` counts from 0.\n- **Conditional sections** \u2014 a block shown only when a condition holds (a "paid" stamp, an\n optional notes block). Excel/HTML/email use `{{#if}}\u2026{{else}}\u2026{{/if}}`; Word uses `{{IF name}}\u2026{{END-IF name}}`.\n\nThe `html` and `email` types print a raw value through a helper in the template\'s `locale` (`vi` or `en`; none prints in the organization\'s language): `{{money total "VND"}}`, `{{number qty}}`, `{{date issued_on}}`, `{{words total "USD"}}` (`model/groups-and-templates` lists what each prints). `{{date (add_days issued_on -1)}}` prints the day before, `add_days` shifting a date by whole days. A value a helper cannot read fails the render.\n\nA field can also print as a **QR code** or a **Code 128 barcode** (a picture a phone\'s camera or a scanner reads back): `{{qr link}}` or `{{code128 position 6}}` in html (never email: mail clients do not show the picture, and a save refuses it), `{{position | qr}}` or `{{position | code128:6}}` as a whole Excel cell (its row grows to the picture), `{{IMAGE qr(position)}}` or `{{IMAGE code128(position, 6)}}` in Word. The optional number is the picture\'s width in centimetres. Without it the width follows the value\'s length \u2014 each module prints 0.5 mm in a QR code and 0.33 mm in a barcode, so a longer value prints wider; a width that would shrink a module below what a scanner reads (0.3 mm, 0.19 mm) is refused, naming the least width that scans. An empty field draws nothing.\n\nA record\'s **photos** (a files field, an act\'s paper hands each as a file cell `{id, name, mime, url}`) print too, and only for a file whoever makes the paper may read \u2014 the same rule the file proxy applies; any other file is never drawn. html: `{{#each photos}}<img src="{{url}}">{{/each}}` (only the data\'s file cells are inlined; a url typed into a text field stays a link). Word: `{{IMAGE picture(cover, 8)}}`, and `{{FOR p IN photos}}{{IMAGE picture($p, 8)}}{{END-FOR p}}` for several. Excel: `{{cover | picture:6}}` as the whole cell, or `{{this | picture}}` inside `{{#each photos}}` (each row grows to its picture). The number is the width in centimetres (8, or 6 in Excel, by default). `picture` takes one image file: a list (loop over it), a file that is no image or one that cannot be read is refused by name. Photos are embedded at most 1600 pixels across, about 200 dpi at 20 cm.\n\nA Word command (`{{ \u2026 }}`) reads the data and calls, by name, a Word helper (`picture`, `qr`, `code128`) or one of these text\nand number functions: `trim`, `trimStart`, `trimEnd`, `lower`, `upper`, `capitalize`, `replace`, `replaceAll`, `removeChars`, `removeSpaces`,\n`startsWith`, `endsWith`, `contains`, `substring`, `padStart`, `padEnd`, `length`, `join`, `split`, `repeat`, `formatNumber`,\n`formatDecimal`, `numberToWords`. It holds no JavaScript method or regular-expression literal: `{{upper(name)}}`,\n`{{formatDecimal(total, 0, "vi-VN")}}`, and `{{removeSpaces(removeChars(cccd || "", ".\u2026"))}}` for the text without its dots,\nellipses and spaces. `removeChars(text, chars)` removes every character of `text` that occurs in `chars` (a set of characters,\nnever a pattern); `removeSpaces(text)` removes every whitespace character of `text`, anywhere in it, the set `trim` trims.\n\nA field named like a function is read as the field, and calling the function then fails.\n\n`set_template`\'s description holds each type\'s exact marker grammar and helper list.\n\n## Generating a filled file\n\n```\nget_template {"template_id": "dtl_\u2026"} # its declared variables\ngenerate_document {"document_template_id": "dtl_\u2026", "filename": "invoice-1042",\n "data": {"company_name": "Acme", "total": 1042, "items": [...]}}\n```\n\n- `data` is a map keyed by your variable names. Scalars for scalar/form fields; arrays for\n `table`/`list`/loop variables. `filename` excludes the extension (the type sets it). Pass a select, a\n member or a link as `get_record`\'s `display` words, a number and a date raw.\n- `generate_document` renders the template\'s own type and returns a generated file object; take\n its `file_id` onward.\n- A PDF no template is wanted for takes `html`, a `filename` and its `data` in place of `document_template_id`: the markup is\n rendered as an `html` template is (the same markup rules, markers and helpers) and the PDF is stored as a workspace file. Name one of the two.\n- In a workflow, a **generate step** calls the same tool and hands the `file_id` to the next\n step (attach to a record, send as an attachment, etc.).\n\n## A template an app\'s Act makes\n\nAn Act with `template` (or `templates`) fills an `html`, `excel` or `word` template from its record and files the result\n(a PDF, an `.xlsx` or a `.docx`) in the record\'s files field `into`; `run_app_workflow` on `act_<key>` (or the `workflow` the Act names) presses it. The Act hands the template the record\'s fields by their\nalias, so name each variable after one: `{{customer}}`, `{{total}}`. A select, a member and a link arrive as their words\n(a link as the linked row\'s title); a number and a date arrive raw, for `{{money total "VND"}}` and `{{date issued_on}}`.\nThe rows of each child the record draws arrive under the name of the record\'s own link to them, for a loop:\n\n```html\n<h1>Quote {{code}}</h1>\n<p>{{customer}} \u2014 {{money total "VND"}}</p>\n{{#each lines}}<p>{{description}} \xD7 {{quantity}}</p>{{/each}}\n```\n\nHere `code`, `customer` and `total` are fields of the record and `lines` is its link to the child table. The rows\nalso arrive under the child table\'s `ref` (`quote_line`), so `{{#each quote_line}}` fills as well; a Word template loops the same rows with `{{FOR line IN lines}}` \u2026\n`{{$line.description}}` \u2026 `{{END-FOR line}}`.\n\nEvery List the record draws and the template names (an html template\'s variables, a Word template\'s markers) hands its\nrows, each List\'s `where` applied; a List the template does not name is not read. A List that only reads hands them under its\n`key` (the table\'s alias where it states none), and a List that sums a number hands the sums under `<name>_totals`, one\nentry per number the List draws as a column or as the table\'s figure. A `where` bound may be `{ "record": "<field>" }`,\na date or number the record itself holds, so a record can print a window of its own days. A service plan row (customer,\n`period_start`, `period_end`) draws the customer\'s visits within its period:\n\n```\n{ "type": "List", "key": "visits", "entity": "visit", "same": "customer", "label": "Visits", "columns": ["visited_on", "hours"],\n "where": { "visited_on": { "gte": { "record": "period_start" }, "lte": { "record": "period_end" } } } }\n```\n\n```html\n{{#each visits}}<p>{{visited_on}} \u2014 {{hours}} h</p>{{/each}}\n<p>Total {{visits_totals.hours}} h</p>\n```\n\nA bound the record leaves blank, or holds as anything but one whole day (`2026-05`) or one figure, keeps no row:\n`visits` is empty and `visits_totals.hours` is `0`. The sums add a number as the List\'s foot does: a cell holding a\nfigure adds it, any other cell (blank, an errored formula, a lookup\'s list) adds nothing. A Word template loops\n`{{FOR v IN visits}}` and prints `{{visits_totals.hours}}` the same way. A template that names a List narrowed to the\nreader (`"me"`) is refused, and so is a name a rows List and a `list` List both reach: give the `list` a `key`. A record\nbound narrows what is listed and is never an access bound; the table\'s row rules are.\n\nA paper over many rows (a client\'s month, a statement) is that: a record of the period whose List is windowed by its dates, made by the Act\'s `template`; or a List `Export` with `template` and `per` (`model/export-node`).\n`model/groups-and-templates` has the rest.\n\n## Discovering and managing templates\n\nThese tools work across all five types:\n\n| Tool | Does |\n|---|---|\n| `get_workspace` | list the workspace\'s templates (`kind: "template"`), each with its id, type and one line |\n| `get_template` | one template\'s full definition, incl. the `variables` it expects and the `current_version` a change needs |\n| `set_template` | create a template, or change one |\n| `clone_template` | copy a template to iterate on, in chat |\n| `delete_template` | remove a template |\n\nCall `get_template` before generating when you don\'t already know a template\'s variable names.\n';
|
|
1801
1801
|
|
|
1802
1802
|
// docs/knowledge_docs.md
|
|
1803
1803
|
var knowledge_docs_default = '# Knowledge Docs\n\nKnowledge docs are the workspace\'s **rulebook layer** \u2014 the facts and rules an agent can\'t know on its own: a\nprocedure, a policy, a glossary of in-house terms, a tariff book. You write them once; the agent **searches and reads\nthem on demand** while it works, pulling in only the lines it needs. They are not injected wholesale, so a 10,000-line\ntariff costs nothing until a question touches it.\n\nA menu with its prices, the customer list and the open orders are not knowledge: they are rows of tables, so apps, queries and documents\ncan use them.\n\n## Rulebook, not methodology\n\nPut in a knowledge doc the things the model **cannot guess**: your specific numbers, codes, names, exceptions and\npolicies. Do **not** put in it general skills the model already has ("how to write a polite email"). If the agent would\nget it right without the doc, the doc is noise.\n\n## Writing a doc \u2014 `set_knowledge`\n\n`set_knowledge` creates a doc when it is given no `knowledge_doc_id` and changes the named one when it is given one:\n\n- `name` \u2014 what it is. Just the name: **grouping belongs in `tags`, not in a prefix baked into every title.**\n- `description` \u2014 what the doc is **and what to grep it for**: its vocabulary and the synonyms an ambiguous query would\n use. Surfaced in the listing before any body is read, and truncated at 200 characters.\n- `content` \u2014 Markdown.\n- `tags` *(optional)* \u2014 the labels someone would filter by, e.g. `["H\u1ED3 s\u01A1 NOXH", "Kho\u1EA3n 13"]`. Give **every** one that is\n true of the doc: a corpus is narrowed by intersecting tags, so a doc carrying one label is found down one path only.\n On a change the array **replaces** what is stored, so resend the labels it should keep.\n- `files` *(optional)* \u2014 file ids of the source `.docx`/`.pdf`/`.xlsx`, stored alongside so a reader can open the original.\n\nA change sends only the fields it changes. `edits` (anchored replace, insert, append) makes a surgical change to the\nbody. Refine structure as you learn what users ask: add the synonym that failed to match, split a line that was too\ncoarse, restate a term the heading was carrying.\n\nA new doc is **owned by you and private**. `set_access` shares it: `resources[]` with `resource_type` `knowledge_doc`, and `principals[]` (a\nmember, a member group, or `organization` for everyone), each with a `role` of `user` or `manager`; a principal without a `role` has\nits access taken away. A doc reaches an agent only when the member can `use` it, an organization admin included.\n\n## How an agent uses a doc\n\nRetrieval works like a filesystem, and respects access: only docs the caller may use ever surface.\n\n1. **List and search** \u2014 `get_workspace` kind `knowledge` lists the corpus (id, name, tags, a truncated\n description); a search term matches a doc\'s name, description and tags. `get_workspace` with `search` finds where something is said: it\n matches across every readable doc and returns the **doc, line number and matching line**;\n `knowledge_doc_id` or `tag` confines the lines to one doc or to the docs carrying that tag.\n2. **Read** \u2014 `read_knowledge`, a doc whole or by line range, to see the context around a hit.\n\nMatching is **not ranked**: the *agent* does the narrowing. A distinctive phrase is sharply selective, a whole question\nmatches everything. A search always reports `total_matches`, so "too broad" is visible and cheap to fix.\n\nThe `search` is **literal text** \u2014 every character matches itself. Set `regex: true` to read it as a POSIX regular\nexpression (a word boundary is `\\y`, not `\\b`):\n\n```\n{ "search": "C/O (Form E)", "kind": "knowledge" }\n{ "search": "\\\\yNK\\\\y", "kind": "knowledge", "regex": true, "case_sensitive": true }\n```\n\nAs an expression `C/O (Form E)` would search for `C/O Form E`, which is not in the document, and return a confident\nzero; the literal default has no such failure. With `regex` omitted, a literal search that finds nothing while the\npattern, read as an expression, matches (`a|b`, the commonest) returns those lines under a note saying how they were\nread. `regex: false` keeps the literal zero; a search that finds nothing either\nway says so.\n\n- **Tone marks match exactly by default.** `diacritic_insensitive: true` folds them (`ca phe` matches `c\xE0 ph\xEA`); use it\n when the pattern was typed without them, since folding lets a short word match inside unrelated ones.\n- **Case folds by default.** `case_sensitive: true` matches exactly, for an acronym (`NK` vs `nk`).\n- **Whitespace is normalized on both sides.** Non-breaking spaces, soft hyphens and padded runs from PDF, Word or Excel\n bodies fold to single spaces before matching.\n\nA search shows at most 16,000 characters, shared by what it finds in knowledge, in structure and in records. Matched\nlines are admitted first and context fills what remains; anything cut is reported, with the true number of matching\nlines. Lines longer than 600 characters are clipped and marked; `read_knowledge` gives the rest.\n\n## Write for grep\n\nRetrieval addresses **lines**. Every rule follows from that one fact.\n\n- **One self-contained fact per line.** A hit returns *that line*. For dense or tabular data \u2014 a price row, a tariff\n code \u2014 put the whole record on one line.\n- **Repeat the searchable terms on every line; headings do not carry down.** A line reading `Rate: 15%` under a heading\n `Roasted coffee` will never match a search for `coffee`. Restate the identifying terms inline, even when it reads\n redundantly to a human. This is the rule most often got wrong.\n- **Keep a line under ~600 characters,** the identifying terms early and the long tail late.\n- **Put synonyms and translations on the line itself.** A bilingual row (`C\xE0 ph\xEA, \u0111\xE3 rang / Coffee, roasted`) matches\n queries in either language.\n- **For prose, keep a rule and its exception close.** A rule on line 40 and its exception on line 90 can be retrieved apart.\n\nMarkdown headings carry no retrieval weight of their own; a doc\'s `##` headings outside its blocks, two or more, are\nlisted on its page as its sections.\n\nThe **description is the discovery hint**: it is the only clue for *what term to grep for* before a body is read. Name\nthe doc\'s vocabulary in a sentence.\n\n## Classifying a corpus \u2014 tags\n\nA doc carries a SET of labels, not a folder. Order does not matter, a doc can wear several, and the vocabulary is\nwhatever the docs themselves use. `tags` on `set_knowledge` states one doc\'s complete set.\n\n## Deleting and restoring\n\n`delete_knowledge` deletes docs: they leave every listing, search and read. **Their sharing and version history are\nkept**, so a deleted doc is recoverable. `get_workspace` kind `knowledge` with `include_archived: true` lists the deleted\ndocs you manage, and `restore_knowledge` brings them back with the access they had. Both take many ids and are all or\nnone: every id must be a doc you manage, or nothing changes. A doc with no owner is restored only by an admin, who\nbecomes its owner. A doc that an app or workflow still names cannot be deleted.\n\n## Formatting \u2014 what a doc draws\n\nA doc is markdown with GFM tables. Beyond that it draws a few blocks, each in syntax you already write; the platform draws them in its own style, so every doc looks alike.\n\n| Write | Draws |\n|---|---|\n| `> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]` or `[!CAUTION]` opening a blockquote | a callout in that tone |\n| `<Note>`, `<Info>`, `<Tip>`, `<Check>`, `<Warning>`, `<Danger>` | the same callouts |\n| a ```mermaid fence | a diagram: flowchart, sequence, timeline, gantt, `pie`, `xychart-beta`, `quadrantChart` (a 2x2; quadrant-1, top right, is drawn as the target), in the platform\'s theme; a flowchart\'s or a 2x2\'s own colour statements are dropped |\n| `<Steps>` holding `<Step title="\u2026">` | a numbered walk |\n| `<Tabs>` holding `<Tab title="\u2026">` | switchable panels |\n| `<Columns cols={2}>` (or `<CardGroup>`) holding `<Card title="\u2026" href="\u2026" icon="\u2026">` | side-by-side cards that stack on a phone; `icon` is a glyph from the model reference (a select option\'s mark), and a name it lacks is refused with the nearest |\n| a table whose header cells are all empty | a table with no header row (a key/value table) |\n| `<Accordion title="\u2026">`, `<AccordionGroup>`, or `<details>` with a one-line `<summary>\u2026</summary>` | a section that opens on press |\n\nEach tag stands on a line of its own, and a tag line inside a fenced code block is code. Attributes are text in double quotes or a number in braces; nothing in a tag runs. A write that adds a tag that does not close, nests wrongly, carries an attribute the tag does not take, or names a component this list lacks (`<Frame>`, `<CodeGroup>`) is refused with the lines to fix; lowercase HTML (`<br>`) stays text.\n\nA chart\'s numbers live in its mermaid source, which agents read and grep like any text: write them there once, not again in a table.\n';
|
|
@@ -21524,6 +21524,8 @@ __export(expression_string_exports, {
|
|
|
21524
21524
|
numberToWords: () => numberToWords,
|
|
21525
21525
|
padEnd: () => padEnd,
|
|
21526
21526
|
padStart: () => padStart,
|
|
21527
|
+
removeChars: () => removeChars,
|
|
21528
|
+
removeSpaces: () => removeSpaces,
|
|
21527
21529
|
repeat: () => repeat,
|
|
21528
21530
|
replace: () => replace,
|
|
21529
21531
|
replaceAll: () => replaceAll,
|
|
@@ -21996,6 +21998,29 @@ function replaceAll(str, search, replacement) {
|
|
|
21996
21998
|
assertStringLength("replaceAll", str.length + matches * (replacement.length - search.length));
|
|
21997
21999
|
return str.split(search).join(replacement);
|
|
21998
22000
|
}
|
|
22001
|
+
function removeChars(text, chars) {
|
|
22002
|
+
if (text == null) return "";
|
|
22003
|
+
if (typeof text !== "string") {
|
|
22004
|
+
throw new ExpressionArgumentError("removeChars: expected string, got " + typeof text);
|
|
22005
|
+
}
|
|
22006
|
+
if (typeof chars !== "string") {
|
|
22007
|
+
throw new ExpressionArgumentError("removeChars: chars must be a string");
|
|
22008
|
+
}
|
|
22009
|
+
if (chars === "") return text;
|
|
22010
|
+
const removed = new Set(chars);
|
|
22011
|
+
let kept = "";
|
|
22012
|
+
for (const char of text) {
|
|
22013
|
+
if (!removed.has(char)) kept += char;
|
|
22014
|
+
}
|
|
22015
|
+
return kept;
|
|
22016
|
+
}
|
|
22017
|
+
function removeSpaces(text) {
|
|
22018
|
+
if (text == null) return "";
|
|
22019
|
+
if (typeof text !== "string") {
|
|
22020
|
+
throw new ExpressionArgumentError("removeSpaces: expected string, got " + typeof text);
|
|
22021
|
+
}
|
|
22022
|
+
return text.replace(/\s/gu, "");
|
|
22023
|
+
}
|
|
21999
22024
|
function matchCount(str, search) {
|
|
22000
22025
|
if (search === "") return Math.max(0, str.length - 1);
|
|
22001
22026
|
let matches = 0;
|
|
@@ -25147,6 +25172,8 @@ var HELPER_COST = {
|
|
|
25147
25172
|
capitalize: "builds",
|
|
25148
25173
|
replace: "builds",
|
|
25149
25174
|
replaceAll: "builds",
|
|
25175
|
+
removeChars: "builds",
|
|
25176
|
+
removeSpaces: "builds",
|
|
25150
25177
|
startsWith: "reads",
|
|
25151
25178
|
endsWith: "reads",
|
|
25152
25179
|
substring: "builds",
|
|
@@ -27025,7 +27052,7 @@ var MODEL_RULES = {
|
|
|
27025
27052
|
sentence: "A List's `create` asks every field its entity requires that nothing else fills \u2014 a `default`, `starts`, the record or folder it files the row under, or a `default_from` over a link it fills \u2014 or the server refuses every add."
|
|
27026
27053
|
},
|
|
27027
27054
|
"app.books": {
|
|
27028
|
-
sentence: "A register App's Books each name another entity once (`entity`) \u2014 never the App's own \u2014 whose fields line up with the register List's by alias, else by `fields` (a field the List reads, or one an Export prints \u2192 one of the Book's): read as one column type (a day apart from a moment), one value or several alike, a link to the same entity. Each Book holds the title, the status, the folder (`scope`), what `where` keeps and every Check locking the rows; the List is a table, cards or a plan, whose `at`, `across` and `up` line up like any field. Two Books holding a field of their own under one alias hold it as one kind of value. An Export's `books` each name the List's entity or one of its Books, once, and one of them holds each field its `where` and `per` narrow by. A Book's `column: true` draws the column naming each row's table in a List that has Books."
|
|
27055
|
+
sentence: "A register App's Books each name another entity once (`entity`) \u2014 never the App's own \u2014 whose fields line up with the register List's by alias, else by `fields` (a field the List reads, or one an Export prints \u2192 one of the Book's): read as one column type (a day apart from a moment), one value or several alike, a link to the same entity. Each Book holds the title, the status where the List shows one, the folder (`scope`), what `where` keeps and every Check locking the rows; the List is a table, cards or a plan, whose `at`, `across` and `up` line up like any field. Two Books holding a field of their own under one alias hold it as one kind of value. An Export's `books` each name the List's entity or one of its Books, once, and one of them holds each field its `where` and `per` narrow by. A Book's `column: true` draws the column naming each row's table in a List that has Books."
|
|
27029
27056
|
},
|
|
27030
27057
|
"app.book-option-kin": {
|
|
27031
27058
|
sentence: "A Book's option labelled as one of the App's, under another alias, reads apart from it: the List lists both and counts each on its own.",
|