@lotics/cli 0.245.0 → 0.246.0
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/probe_page.js +87 -31
- package/dist/src/cli.js +30 -9
- package/docs/cli_reference.md +1 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -52,7 +52,7 @@ var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__
|
|
|
52
52
|
var define_LOTICS_KIT_VERSIONS_default;
|
|
53
53
|
var init_define_LOTICS_KIT_VERSIONS = __esm({
|
|
54
54
|
"<define:__LOTICS_KIT_VERSIONS__>"() {
|
|
55
|
-
define_LOTICS_KIT_VERSIONS_default = { runtime: "0.
|
|
55
|
+
define_LOTICS_KIT_VERSIONS_default = { runtime: "0.32.0" };
|
|
56
56
|
}
|
|
57
57
|
});
|
|
58
58
|
|
|
@@ -55352,7 +55352,7 @@ function resultSideEffects(result) {
|
|
|
55352
55352
|
|
|
55353
55353
|
// src/version.ts
|
|
55354
55354
|
init_define_LOTICS_KIT_VERSIONS();
|
|
55355
|
-
var VERSION = "0.
|
|
55355
|
+
var VERSION = "0.246.0";
|
|
55356
55356
|
|
|
55357
55357
|
// src/timezone.ts
|
|
55358
55358
|
init_define_LOTICS_KIT_VERSIONS();
|
|
@@ -55375,7 +55375,7 @@ import path6 from "node:path";
|
|
|
55375
55375
|
import { fileURLToPath } from "node:url";
|
|
55376
55376
|
|
|
55377
55377
|
// src/model_reference.md
|
|
55378
|
-
var model_reference_default = '# The Lotics workspace model (`model.json`)\n\nOne JSON file describing a workspace: its tables, fields, options, views, roles,\nfirst rows, and the apps planned over them. The full form spells it out; the\n`from` form names a published preset and carries only what this business differs\nby (\xA7 Starting from a preset \u2014 prefer it whenever a preset fits the trade). Each\n\xA7 is `lotics docs model/<section>`.\n\n**What a model composes with**\n\n- **Entities and fields** (\xA7 Entity, \xA7 Field) \u2014 the tables, their columns, options and links.\n- **Roles** (\xA7 Field roles) \u2014 what each field MEANS: its name, its stage, its amount, its\n deadline. One declaration per field, read by every screen over the entity.\n- **Shapes** (\xA7 Apps and screens) \u2014 the question a register answers, as named slots the\n roles fill.\n- **Clauses** (\xA7 Apps and screens) \u2014 everything the author states about what is on a\n screen. What no clause states is drawn in its minimal form and printed `(default)`.\n\n**The working order**\n\n1. Name the people and each one\'s JOB \u2014 the work they alone decide or write.\n2. The entities and fields those jobs touch; a role on every field a screen reads.\n3. One app per job in `apps[]`, each one register over one entity.\n4. `lotics scaffold check model.json` \u2014 offline, every problem in one run.\n5. `lotics app preview model.json#<app> --shots <dir>` \u2014 each app drawn from `rows`, nothing created.\n6. `lotics workspace build model.json --deploy` \u2014 the tables, then every app, live\n (`lotics setup model.json --email you@company.com` first where no account exists).\n\n## The rules\n\n- **At least one entity, at most 50.** More tables than that is a data model\n being designed, not scaffolded \u2014 scaffold the rest in a second call.\n- **Adoption is explicit.** `lotics setup` REFUSES an entity whose `label`\n already names a table in the workspace, naming every colliding label at once.\n `lotics scaffold apply` adopts those tables and adds the fields, options and\n views they are missing. Nothing is ever modified or deleted, so applying the\n same model twice creates nothing the second time.\n- **Adoption is by LABEL, not alias.** Change an entity\'s `label` and the next\n run asks for a NEW table beside the old one. Renaming a FIELD is\n `lotics field rename <table> <field> "<new label>" --model <this file>`, which\n moves the platform, this file and every app bound to it together; deleting is\n `lotics run delete_table`. Neither goes through the file.\n- **The file\'s own majority is the language.** A model names no locale \u2014 which\n language it is in is what it mostly says, and `scaffold check` notes the label\n written the other way. The generated screens read the kit\'s pack, and a\n generated WRITE cannot: its refusals run on the server, so they are worded in\n that same majority. Mix the two and the workspace answers in two languages.\n- **`lotics scaffold diff model.json` says where the file and the workspace have\n come apart**, joined on label, entity then field \u2014 the join adoption itself\n makes. It exits 1 on any difference.\n- **Rows land only where every bound table is empty.** One table already holding\n records and no rows are written anywhere, and the result says\n `rows_skipped: true`: sample rows landing among a customer\'s real ones cannot\n be told apart from them.\n- **`lotics scaffold check` decides all of it offline**, and reports every\n problem in one run rather than the first: an alias that resolves to nothing, a\n link whose pair is not symmetric, and the rows themselves \u2014 a field the entity\n does not declare, an option alias the field does not declare, a link naming no\n row in the file, a `ref` used twice, a date that is not one, a value on a\n platform-computed field, and a files cell that is neither a relative path\n beside this file nor a `fil_` id.\n\n## Top level\n\nEvery table of keys on this page is generated from the schema `scaffold check`\nparses the file with, so it is the whole of what a key may hold. The first is the\nfull form; the second names a preset instead of restating one (\xA7 Starting from a\npreset).\n\n<!-- generated:start top-level -->\n\n#### Model file\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entities` | list of [Entity](#entity) | yes | The tables this model creates, with their fields, options and views |\n| `roles` | list of [Role](#role) | no | Workspace groups to create; members are added to them afterwards |\n| `templates` | list of [Template](#template) | no | Document templates whose content travels inline (html or email) |\n| `connections` | list of [Connection](#connection) | no | Connected accounts the workflow bodies push through, each named by alias and bound by provider |\n| `table_workflows` | list of [Automation](#automation) | no | Automations a table carries, fired by every write to it whichever door made it |\n| `rows` | map of alias \u2192 list of [Row](#row) | no | First records, keyed by entity alias \u2014 written only where every table they land in is empty |\n| `field_roles` | map of alias \u2192 map of alias \u2192 [Role declaration](#role-declaration) \\| `null` | no | Entity alias \u2192 field alias \u2192 the role that field plays on every screen over its entity |\n| `write_rules` | map of alias \u2192 [Entity write rules](#entity-write-rules) | no | Entity alias \u2192 what a create of that entity finds, copies and refuses |\n| `apps` | list of [App](#app) | no | The apps this workspace will have, each ONE register over an entity and the records it opens |\n| `preset` | [Preset](#preset) | no | The trade\'s branches, for a model published to be read; `setup` and `apply` scaffold the base alone |\n| `apply` | list of [Package entry](#package-entry) | no | Published packages copied in once this model\'s tables exist, in this order |\n\n#### From file\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | text | yes | The preset this model starts from, by its slug |\n| `variants` | list of text | no | The preset\'s variant slugs to merge onto its base, in order |\n| `rename` | map of alias \u2192 [Binding](#binding) | no | Entity alias \u2192 what this business calls that table and its fields |\n| `entities` | list of [Entity](#entity) | no | Tables this business has that the preset does not declare |\n| `rows` | map of alias \u2192 list of [Row](#row) | no | First records, keyed by entity alias \u2014 written only where every table they land in is empty |\n| `field_roles` | map of alias \u2192 map of alias \u2192 [Role declaration](#role-declaration) \\| `null` | no | Roles on the preset\'s fields and this business\'s own, over whatever the preset declares |\n| `write_rules` | map of alias \u2192 [Entity write rules](#entity-write-rules) | no | Create-time clauses on the preset\'s entities and this business\'s own, over whatever the preset declares |\n| `apps` | list of [App](#app) | no | The apps this workspace will have, each ONE register over an entity and the records it opens |\n| `apply` | list of [Package entry](#package-entry) | no | Published packages copied in once this model\'s tables exist, in this order |\n\n<!-- generated:end top-level -->\n\n**A model may not carry** `fixtures`, `knowledge` or `knowledge_expects`, and no\n`excel` / `word` / `pdf-form` template: each of those is content that lives in a\npublished bundle, which a model has none of. `apps` here is a plan of screens,\nnever built code. An unknown top-level key is an error, never ignored.\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 the file cross-references\nitself; they are never shown to anyone. `label` is what a person sees.\n\nLabels must be unique within their namespace \u2014 two entities, two fields on one\nentity, two options on one field, two views on one entity, two roles or two\ntemplates cannot share a label, because scaffold matches by label.\n\n## Entity\n\n<!-- generated:start entity -->\n\n#### Entity\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable entity alias, unique within the contract |\n| `label` | text | yes | Display label used as the table name at scaffold time |\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 |\n| `writes` | `false` | no | false: only the workspace\'s automations write this table\'s rows \u2014 no app opens or edits one |\n| `fields` | list of [Field](#field) (at least one) | yes | The table\'s columns |\n| `read_scope` | [Read scope](#read-scope) | no | Which rows a member reads. Absent, every member with access to the table reads every row. |\n| `unique` | list of list of alias (at least one) (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 of cardinality "one"). 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\n#### Read scope\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` | alias | yes | A role alias: whoever is in the group it binds to reads the row |\n\n#### Read scope by member\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `through` | list of alias (1\u20133) | no | select_record_link aliases from this entity outward, each of cardinality "one" \u2014 the clause\'s field is on the entity the last hop lands on |\n| `field` | alias | yes | A select_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 | select_record_link aliases from this entity outward, each of cardinality "one" \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 entity -->\n\n**`unique` is a set of values no two live rows share.** Each entry names fields\nof this entity holding ONE value \u2014 text, number, date, a single select, a link\nof cardinality `"one"` \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, and `scaffold\ncheck` prints it under *Who writes what* as `unique: Bed + Sown on`.\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. `scaffold check` prints it beside the table\nunder *Who writes what* and REFUSES a model where a table some create opens \u2014 a\nregister\'s own, or a record section\'s add \u2014 states none.\n\n**`writes: false` says only the workspace\'s automations write these rows** \u2014 a\nlog of what the system sent, a copy of what another system holds. No app opens,\nedits or files one: a record listing them keeps the section and opens each row\nat rest, with no Add; a screen over the entity operates at most the rows its\nrecord owns; a party of it is picked, never found or minted; and the generator\nwrites no create, no update and no `lotics.writes` entry for it. `scaffold\ncheck` prints it under *Who writes what* as `written by automations` and never\nnotes it as created nowhere. A `lifecycle` on it is refused \u2014 a row walked\nthrough stages is worked by a person \u2014 and so is a publish desk over it. Absent,\npeople write the rows; `true` is not a value.\n\n**`read_scope` is a ROW rule, enforced by the platform.** It is resolved at\nscaffold into the table\'s own row filters, so an app, a workflow reading for a\nviewer, and the API all answer the same rows \u2014 a per-record visibility field the\napp merely honours is a convention, not a gate. A `"self"` clause reads a column\nof one member or several, and every role alias and option alias a clause names\nmust be one this model declares. Scaffold writes the rule onto a table it\nCREATES; a table it adopted that ALREADY CARRIES a rule keeps that one, because\nthe rule is the workspace\'s own statement about its rows \u2014 and a run whose model\nstates a different rule reports the entity rather than leaving the claim silent.\n\n**An app may state that its sharing is its read gate: `"reads": "shared"`.** An\napp reads as its owner, so the rule reaches a viewer only as the predicate every\nquery and editor guard of every app over the entity carries \u2014 right for a desk of\none\'s own rows, wrong for a desk whose audience its sharing already decides, where\nwidening the rule meant a role group nobody remembers to fill. Stated on an APP,\nits queries, pickers and guards carry no entity\'s rule and whoever the app is\nshared with reads and writes every row it draws; every other app and the table\'s\nown filters keep the rule. Share it deliberately. Refused on an app none of whose\ntables states a `read_scope`; `scaffold check` prints it on the app\'s line and\nnames the app beside the rule it is exempt from.\n\n**The rows under a private record INHERIT its rule.** An entity that states no\n`read_scope` and hangs under one that does \u2014 through its `parent` link, over one\nhop or several \u2014 is read by the ancestor\'s rule, answered through that link, on\nits table\'s own filters and in every query and guard alike; nothing is restated,\nso a child needs none of the ancestor\'s columns. Stating a rule on the child\nkeeps that one instead, an ancestor with no rule passes nothing down, and a row\nhanging further under the scoped one than a row filter reaches is refused by\nname \u2014 state a rule on it. So is a hop over a link that names more than one row:\nthe rule would admit a reader any one of them admits while the editor\'s guard\nreads the first, so give the link `"cardinality": "one"` or state a rule on the\nchild.\n\n**ONLY the `parent` role is walked.** A register a scoped record reaches by any\nother link \u2014 the rows that NAME it \u2014 is read by that record\'s id with no rule\ntravelling to it, so it is refused until it states one of its own.\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 (a default is not applied to them), and a required\nLINK is written with its row: the entity it names is created first, and entities\nwhose required links name each other are refused, since none of their rows could\never be created.\n\n<!-- generated:start field -->\n\n#### Field\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"text"` \\| `"number"` \\| `"date"` \\| `"boolean"` \\| `"select"` \\| `"select_member"` \\| `"select_record_link"` \\| `"files"` \\| `"formula"` \\| `"rollup"` \\| `"lookup"` \\| `"autonumber"` | yes | What the field holds \u2014 each type takes the further keys its own section lists |\n| `alias` | alias | yes | Stable local alias, unique within the entity |\n| `label` | text | yes | Display label used as the field name at scaffold time |\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. Scaffold writes it onto the field, and every write path \u2014 create, update, an agent\'s tool call, a workflow\'s set \u2014 refuses the row by field name. |\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\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| `format` | `"number"` \\| `"currency"` \\| `"percentage"` | no | What the figure is \u2014 a plain number, money in `currency`, or a percent |\n| `currency` | text | no | ISO 4217 code |\n\n<!-- generated:end field-number -->\n\n`format` is what the number IS, and every surface reads it: `currency` prints as\nmoney in the code the row or the field states, `percentage` as a whole percent\nwith its sign. An ABSENT number is drawn absent \u2014 the one exception is a\n`sum` or count rollup the plan reads as a **`measure`**: that is the thing\naccumulated toward a bound, so nothing accumulated yet is zero and the meter\ndraws it. The same rollup read as an `amount` keeps its blank, and so does every\nother role: nothing added to what a row is WORTH means unpriced, not free. A\n`min`, an `avg`, a percentage of nothing and every FORMULA stay blank in any\nrole, because none of them has an answer to give. This is why the pair on one\nscreen reads two ways \u2014 what has come in against what is owed \u2014 and why a\nmeasure\'s own LIMIT, an amount, leaves an unquoted row out of the count rather\nthan reporting it as nothing collected. **AND WHERE THAT LIMIT IS ABSENT \u2014 OR\nZERO \u2014 THERE IS NO LEVEL AT ALL**: a level is a reading AGAINST a bound, so a row\nthat states no bound, or a bound of nothing, draws nothing \u2014 cell, fact and all \u2014\nrather than a numerator whose whole meaning was the comparison. "Collected 0"\nbeside a blank total reads as money against a job worth nothing, and "0 of 0"\nagainst a count of nothing owed claims a comparison nobody can make. A measure the model gives no limit is a plain figure\nand is unaffected. A share is stored in percent units \u2014 68.1 is 68.1 % \u2014 and the\ncolumn, the fact behind it, the meter it is judged by and the figure over the\nregister all say so.\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\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\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\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) | yes | The colour the option\'s badge is drawn in |\n| `mark` | [mark](#select) | no | The option\'s own mark, drawn in place of its colour dot wherever the option is shown: the channel it is ({kind: "brand", name: one of facebook, instagram, threads, meta, tiktok, google-ads, zalo, linkedin, x, google-meet, youtube}) or a kit glyph ({kind: "icon", name: "wrench"}). 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\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`.\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\nlookup of the select on another entity: the channel 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`), or a glyph from the kit\'s curated Lucide set\n(`{"kind": "icon", "name": "wrench"}`; a name outside it is refused naming the\nnearest ones). 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. `apply` writes the\nmarks onto the table, sets one an adopted option lacks, and reports one it wears\ndifferently rather than overwrite it.\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### `select_member`\n\nA person picker over the workspace\'s members. No default: a model cannot name\nmembers of a workspace that does not exist yet. 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-select_member -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `multi` | boolean | no | Allow multiple selections |\n\n<!-- generated:end field-select_member -->\n\n### `select_record_link`\n\n<!-- generated:start field-select_record_link -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `target_entity` | alias | yes | Alias of the entity this field links to |\n| `sync_both_ways` | boolean | no | Create a paired link field on the target entity for bidirectional sync |\n| `paired_field_alias` | alias | no | The pair edge of a bidirectional link: the field alias ON THE TARGET ENTITY that is this link\'s sync partner. Both sides of a pair carry it, each naming the other. Scaffold creates whichever side it reaches first WITH the pairing (the platform auto-creates the partner) and binds the partner alias to the auto-created field \u2014 without this edge the two contract fields would be created independently and collide with the auto-created partner. |\n| `cardinality` | `"one"` \\| `"many"` | no | How many linked records this field holds. Default \'many\'. \'one\' holds a single row and needs no partner; where the link IS paired, the partner side holds many. |\n| `display_field_aliases` | list of alias | no | Field aliases on the target entity shown as the link\'s display text / picker columns |\n\n<!-- generated:end field-select_record_link -->\n\nA two-way link is declared on BOTH sides, each naming the other as its\n`paired_field_alias`; the pair must be symmetric or the model is refused.\n\n**A single-valued link needs no partner.** `"cardinality": "one"` on its own is a\nlink that holds one row \u2014 one customer on an invoice, one project on a device \u2014\nand nothing is created on the target. The mirror invariant belongs to a PAIRED\nlink: pair a link when the target\'s own record should list what points at it, and\nleave it unpaired when it should not. Either way the record plan draws the\nrelation as a section on the side it points at, so an unpaired link costs the\ntarget nothing.\n\n### `files`\n\nNo keys beyond every field\'s; a row attaches documents to it (\xA7 Rows).\n\n### `formula`\n\n<!-- generated:start field-formula -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `formula` | [Formula](#formula) | yes | Formula config. The expression references other fields on the SAME entity by alias in braces, e.g. `{quantity} * {unit_price}`. |\n\n#### 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| `format` | `"number"` \\| `"currency"` \\| `"percentage"` \\| `"link"` | no | Display format. \'number\' / \'currency\' / \'percentage\' for numeric results; \'link\' for text-output formulas that return a URL \u2014 renders the result as a clickable link. |\n| `currency` | text | no | ISO 4217 currency code, e.g. USD, VND, EUR |\n| `output_type` | `"number"` \\| `"text"` \\| `"date"` \\| `"datetime"` \\| `"boolean"` | no | What the expression YIELDS \u2014 the kind the platform infers at write time, declared here so the offline checks can read it. `format` beside it is how that result is drawn, not what it is. Ignored on the wire (the platform re-infers it); `lotics scaffold export` writes the inferred 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 neither it nor a `format` says nothing about its\nresult, and every rule that needs one refuses it by name.\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 model 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 copy writes 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 `check` refuses it and names the token. A select looked up from another\nentity is tested there, in a formula of its own, and that result looked up.\n\n### `rollup`\n\nAggregates the records reached through a link on this entity.\n\n<!-- generated:start field-rollup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `source_field_alias` | alias | yes | Alias of a select_record_link field on this entity to roll up from |\n| `aggregate_option` | [aggregation](#rollup) | yes | Aggregation operation. Its `field_key` names a field alias on the linked entity. |\n| `filter` | [filter](#views) group | no | Only linked records matching this filter are aggregated. 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\n<!-- generated:end field-rollup -->\n\n`aggregate_option` is `{ "operation": \u2026, "field_key": \u2026 }` \u2014 `field_key` a field\nalias on the linked entity (`count` may omit it), and `operation` 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 anything.\n\n### `lookup`\n\nDisplays a field from the linked records.\n\n<!-- generated:start field-lookup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `source_field_alias` | alias | yes | Alias of a select_record_link field on this entity to look up through |\n| `lookup_field_alias` | alias | yes | Alias of the field on the linked entity to display |\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_key` | text | yes | A field alias on the linked entity 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\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\n<!-- generated:end field-autonumber -->\n\n## Views\n\nSaved views live under the entity they belong to. Every field reference is a\nfield ALIAS on that entity.\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## Roles\n\nA role becomes a workspace group.\n\n<!-- generated:start roles -->\n\n#### Role\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable role alias, bound to a workspace group when the starter is copied |\n| `label` | text | yes | The group\'s name in the workspace |\n\n<!-- generated:end roles -->\n\n## Templates\n\nOnly inline `html` and `email` templates \u2014 the rest are file-backed and a model\nhas no bytes. `{{name}}` in the content is filled from the workflow\'s data.\n\n<!-- generated:start templates -->\n\n#### Template\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"html"` \\| `"email"` | yes | html is a page a workflow renders to a PDF; email is a message a workflow sends |\n| `alias` | alias | yes | Stable template alias, unique within the contract |\n| `label` | text | yes | The template\'s name in the workspace |\n| `content_sha256` | text | no | sha256 (64-char lowercase hex) of the template content \u2014 inline: the utf-8 content string; file-backed: the bytes_ref bytes. The modified-detection reference. Optional in the schema so contracts published before template sha still parse; REQUIRED at publish (validatePackageContract). |\n| `content` | text | yes | Inline template content; placeholders reference field aliases |\n\n<!-- generated:end templates -->\n\nA paper that has to look like a counterparty produced it \u2014 an official letter,\nan acceptance minute, a supplier\'s bill \u2014 is the same `html` template with a\nshell around the body: a letterhead, a reference line, a seal and a signature\nblock, and paper grain over everything. One shell, many bodies; the data is the\nonly thing that changes, so a workflow can re-issue it over any record.\n\n```jsonc\n{ "alias": "cong_van", "label": "C\xF4ng v\u0103n", "type": "html",\n "content": "\u2026the page below, as one JSON string\u2026" }\n```\n\n```html\n<style>\n .sheet{position:relative;width:718px;padding:44px 58px 30px;background:#fbfaf6;color:#111;font:14.2px/1.5 \'Liberation Serif\',serif}\n .grain{position:absolute;inset:0;opacity:.34;mix-blend-mode:multiply;background:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'140\' height=\'140\'><filter id=\'f\'><feTurbulence baseFrequency=\'.9\' numOctaves=\'2\'/><feColorMatrix values=\'0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .35 0\'/></filter><rect width=\'140\' height=\'140\' filter=\'url(%23f)\'/></svg>")}\n .top{display:flex;text-align:center;font-size:13.4px} .top>div{flex:1} .u{display:inline-block;border-bottom:1px solid #111;font-weight:700}\n .ref{display:flex;text-align:center;font-size:13.4px;margin-top:6px} .ref>div{flex:1} .ref .r{font-style:italic}\n h1{text-align:center;font-size:15.6px;margin:26px 0 18px} p{text-align:justify;text-indent:26px;margin:0 0 9px}\n .sig{display:flex;margin-top:20px} .sig .l{flex:1} .sig .r{width:290px;text-align:center;position:relative}\n .sig .nm{font-weight:700;margin-top:96px} .seal{position:absolute;left:4px;top:8px;width:166px;height:166px;opacity:.66;mix-blend-mode:multiply;transform:rotate(-17deg)}\n </style>\n <div class=\'sheet\'><div class=\'grain\'></div>\n <div class=\'top\'><div><b>{{issuer_parent}}</b><br><span class=\'u\'>{{issuer}}</span></div>\n <div><b>C\u1ED8NG H\xD2A X\xC3 H\u1ED8I CH\u1EE6 NGH\u0128A VI\u1EC6T NAM</b><br><span class=\'u\'>\u0110\u1ED9c l\u1EADp - T\u1EF1 do - H\u1EA1nh ph\xFAc</span></div></div>\n <div class=\'ref\'><div>S\u1ED1: {{number}}</div><div class=\'r\'>{{place}}, ng\xE0y {{day}} th\xE1ng {{month}} n\u0103m {{year}}</div></div>\n <h1>{{title}}</h1>\n <p>K\xEDnh g\u1EEDi: {{recipient}}.</p>\n {{{body}}}\n <div class=\'sig\'><div class=\'l\'><b>N\u01A1i nh\u1EADn:</b><br>- Nh\u01B0 tr\xEAn;<br>- L\u01B0u VT.</div>\n <div class=\'r\'><img class=\'seal\' src=\'{{seal_url}}\'><b>{{signer_title}}</b><div class=\'nm\'>{{signer}}</div></div></div>\n </div>\n```\n\n`lotics preview <file.html>` renders any such page to a PNG the way a demo\'s\nprops are made, sized to its content, so a paper can be looked at before it is\nput in a template.\n\n## Rows\n\nFirst records, keyed by entity alias. Up to 200 rows per entity and 2000 across\nthe model, attaching at most 2000 documents between them \u2014 a real data set\nbelongs in an import, not a model.\n\n```jsonc\n"rows": {\n "customer": [\n { "ref": "acme", "fields": { "name": "Acme Trading", "tier": "gold" } }\n ]\n}\n```\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\nA `ref` is lowercase letters, digits and underscores, and is never persisted.\n\nA `files` cell attaches documents: paths relative to this file (no `..`, never\nabsolute), which `check` proves exist and `apply` uploads into the workspace\nbefore any row is written \u2014 a paperwork business seeds its papers with its\nrows. The server accepts only `fil_` ids of files this workspace owns, which is\nwhat the upload leaves behind. After a run that wrote rows, the WORKSPACE holds\nwhich record each row became, under the row\'s own `<entity>:<ref>`:\n`delete_records` over them is how a seeded set is reset, and applying again\nre-dates it.\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| `select_record_link` | `"<entity-alias>:<ref>"` naming another row in this file \u2014 `"customer:acme"`, or an array for several |\n| `select_member` | `"self"` only \u2014 the person applying the model |\n| `files` | paths beside this file \u2014 `["scans/pccc_letter.png"]` \u2014 uploaded by `apply`/`setup` before the rows are posted; or `fil_` ids of files already in this workspace |\n| `formula`, `rollup`, `lookup`, `autonumber` | not allowed \u2014 the platform writes these |\n\n**Documents can be attached on their own, afterwards.** Rows land only into empty\ntables, so a model whose binaries were added after the first apply has no second\napply to carry them: `lotics scaffold apply model.json --documents` writes ONLY\nthe `files` cells, onto the records the first run created, found by each row\'s\nown `ref` in the binding the workspace holds \u2014 so a row added to or removed from\nthe file since changes nothing about the rest. Running it twice attaches nothing\nthe second time: the same path keeps the same file, and a cell is a set.\n\n### Relative dates\n\nA date cell holds a literal `YYYY-MM-DD`, or an expression relative to the day\nthe model is applied, 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\n`@month-start` exists because `@today-N` cannot promise a month: applied on the\n2nd, `@today-3` lands in the previous one.\n\n## Field roles\n\n`field_roles` names the reporting role a field plays on its entity \u2014 keyed by\nentity alias, then field alias \u2014 so every screen over the entity agrees on\nwhich column names the row and which select is the stage. A shape\'s slot binds\nto it (\xA7 Apps and screens). Like `rows` and `apps`, it is this file\'s: `check`\nproves it and the workspace never sees it. Each role sits on the types that can\nanswer it:\n\n| Role | On | Meaning |\n|---|---|---|\n| `identity` | `text`, `select_record_link`, `formula` | what a PERSON calls the row \u2014 the register\'s first column; a link where the row is "the product, at this branch"; a formula where the name is computed, and then it may not be `format`ted as a figure. NEVER an autonumber: a minted code is a `reference`. One per entity |\n| `reference` | `text`, `autonumber`, `formula` | the key the SYSTEM files the row under \u2014 an order number, a container code \u2014 drawn as the supporting line under the name rather than as a column of its own, and leading where the entity has no `identity` at all. One per entity |\n| `mark` | `files`, or a `lookup`/`rollup` that resolves to one | the row\'s picture \u2014 its own, or the linked record\'s. NEVER A COLUMN: a register draws words, so a mark is projected only where a surface leads with one \u2014 the subject rung of a picture-led grid, and a record\'s itinerary. A child entity\'s mark reaches a record\'s run and nothing else. REQUIRED on an entity some `party` link names and on the item an `offering_register` sells: both are drawn by their picture, initials until one is attached. One per entity |\n| `lifecycle` | single `select` | the ordered stages a row walks; option order is the order. Which of them END the flow is `outcomes` \u2014 each with the fields reaching it `asks` and, for the one a closing step reaches, its `verb` \u2014 which ending each is `verdicts`, which parts of the flow they group into is `phases`, and `history` keeps the day each one was reached. One per entity |\n| `category` | single `select`, a `lookup` that resolves to one, or a text `formula` stating its `values` | what the row IS \u2014 a kind, a service, a book. One badge in the option\'s own colour \u2014 or its own `mark` (\xA7 `select`) \u2014 with no ladder behind it and no flow to advance. Often a fact about the thing the row books rather than about the row, and then it is the lookup that reads it. Worked out, it is a formula: `{"role": "category", "values": [{"label": "Late", "color": "red"}, {"label": "On time", "color": "green"}]}` lists the words it answers, each worn as an option is (`mark` too); a word left out \u2014 its answer for "nothing yet" \u2014 draws nothing, and it fills no slot (a slot reads live options), so it is drawn as a `columns` entry. Repeats: a row can be of two kinds of thing |\n| `measure` | `number`, `formula`, `rollup` | a level read against a limit \u2014 see `against` and `alert`. Money where the model says so, and then it prints as money |\n| `expected_set` | `select` | its OPTIONS are the required set (documents, checks, services); an option no row has is a gap to show, not nothing. A multi-select draws as a ring and a fraction wherever it is drawn \u2014 absence is the information, and a list of what IS there says nothing about what is missing. |\n| `amount` | `number`, `formula`, `rollup` | THE money of a ledger row; `signed_by` says which way it moved, `against` names the reference it is quoted off. One per entity |\n| `when` | `date`, or a `formula`/`rollup`/`lookup` that yields one | the ledger or timeline date \u2014 stated, or derived: the day money MOVED is a formula over the two columns that could hold it. `due` makes it a DEADLINE; without it the date is the plain day it is. `until` names the stage of this entity\'s own `lifecycle` at which the countdown is spent, and says `due` by saying so. `from` names the date the deadline\'s window opens on. One per entity |\n| `slot` | single `select`, or a `datetime` `date` | where in the DAY a row sits \u2014 the run it is read in under its day\'s heading on an itinerary. A select\'s option order IS the run\'s order; a time is ordered by the clock. A plain `date` is refused: that is the day itself. One per entity |\n| `party` | `select_record_link`, `select_member` | WHO THE ROW IS FOR \u2014 the counterparty it was transacted with, or the workspace MEMBER whose work it is (a shift, an assignment, a timesheet period). A member is drawn as the person and opens nothing: they have no record in the app. One per entity \u2014 and left off where the `identity` IS the link to them: the row is then NAMED by that record, and a strip under the title would state them twice |\n| `parent` | `select_record_link` | the record this row belongs to \u2014 a line\'s order, a paper\'s case. The parent\'s record shows these rows; the row shows the parent as a fact. One per entity, and it links to an entity this model declares |\n| `contact` | `text` | a way to reach a party \u2014 an address, a number, the town it is in. The record draws them as ONE row of links under the name, on a page and in a drawer alike, so the role sits on every field that is one of them. A row carrying one is SOMEBODY, so a screen over it may lead with the mark (\xA7 `presentation`). Repeats |\n| `currency` | single `select` whose option LABELS are ISO 4217 codes | the money THIS ROW\'s figures are in. Every amount and every money measure on the entity is then printed per row rather than in one code for the whole column, and the select is not drawn as a fact of its own. One per entity |\n| `verdict` | `boolean`, `formula` | a settled pass/fail \u2014 ticked, or computed. TRUE is the PASSING side wherever it is drawn, a register\'s risk flag included, so a field whose true means trouble is the same fact asked the other way round. One per entity |\n| `obligation` | `date` | a day something is OWED by \u2014 a cut-off, a permit expiry, a payment due. It counts down by definition. `satisfied_by` names what CLOSES it (or `until`, the stage the record stops owing it at), `label` what is owed. Repeats: a row owes several things, and a shape that fans them draws one row each |\n| `expires` | `date`, or a `formula`/`rollup`/`lookup` that yields one | the day something the row HOLDS runs out \u2014 a licence, a certificate, an inspection. Read as a COUNTDOWN, never as the date: the days left while it holds, and a renewal owed once it has passed. `warn` (required) is how many days ahead it starts to need attention, which nothing about a date says. Repeats: a machine carries an inspection and an insurance |\n| `origin` | `text`, single `select`, or a one-row `select_record_link` | where the row STARTS. Declared with a `destination` \u2014 a route has two ends, and one alone is refused \u2014 and then every surface over the entity draws the two as ONE reading, `Warehouse 4 \u2192 Site B`: on the register\'s supporting line after the key, and as one column on the child registers and party histories that list these rows. The two fields are then not facts of their own, and a `columns` clause naming either end is refused. One per entity, holding ONE place |\n| `destination` | `text`, single `select`, or a one-row `select_record_link` | where the row ENDS \u2014 the other half of the `origin`\'s route, and read only with it. One per entity, holding ONE place |\n| `body` | `text` | the words a dated entry SAYS \u2014 the note of a call, the text of a message \u2014 plain or markdown alike: the role makes the row an entry, never the format. Undeclared, a log reads the entity\'s first `markdown` text as the entry. One per entity |\n| `recording` | `files` | the audio or video a dated entry IS the record of \u2014 a call, a site walk, a lesson \u2014 played wherever it is read, on the entry and on the record it opens, never listed among its papers: a log\'s attachments are then its first OTHER pile. One per entity |\n| `verbatim` | `text` | what was literally said or written on a dated entry \u2014 a transcript, a pasted exchange \u2014 drawn behind one disclosure wherever it is read, under the entry\'s words and at the foot of its band on the record, never as the words. Refused on the entry\'s own words (its `body`, else the entity\'s first `markdown` text): declare those as the `body`. One per entity |\n\n**`due` is what makes a `when` a deadline.** A date a row is merely filed by \u2014 the\nday a lead arrived, the day a movement happened \u2014 is neither early nor late,\nhowever long ago it was. Without the clause every desk drew every `when` as a\ncountdown and a two-month-old lead read "40 days overdue" on a record nobody was\nlate on. So a bare `when` is a plain date, and `{ "role": "when", "due": true }`\nis the one that counts down. An `obligation` needs no clause: a day something is\nowed by is a deadline already.\n\n**`until` stops a countdown where the work is DONE.** A deadline counts down so\nsomebody acts on it, and a row that has arrived needs nothing: without the clause\na delivered order is tinted for a date it met, and every settled row past its day\njoins the urgent ones in the reader\'s glance. It names one option of the entity\'s\n`lifecycle`; at that stage, at every stage past it and at every `outcome`, the\ndate is drawn as the plain day it is. On a `when` it also says the date is `due`.\n\n**`from` opens a deadline\'s window.** `{ "role": "when", "due": true, "from":\n"starts_on" }` names the date field on the same entity the window opens on. The\nrecord\'s band then reads the window \u2014 both days and how many are left \u2014 and a\ntarget read against the deadline is paced by how far through the window today\nis. The band only reads the two days; each one a person states stays a fact.\nIt takes a `date` on the same entity \u2014 a stamped `derive_from` day included \u2014\nand only on a `when` that counts down.\n\n**`verdicts` says which ending is the refusal.** `outcomes` names the doors\nthat end the flow and nothing about which of them the flow is walked for, and\nno label can say it: "Cancelled" and "Delivered" are both plain statements.\n`{ "outcomes": ["won", "lost"], "verdicts": { "won": "pass", "lost": "fail" } }`\nkeeps the list and states the ending beside the doors that have one; the\nladder, the register\'s stage column and the strip then draw a row in a `fail`\noutcome as settled and refused rather than as the furthest rung it reached,\nand moving a row into one asks first. An outcome left out is where the row\nstands and neither. Only an outcome takes a verdict, and only on a\n`lifecycle`.\n\n**`at` files a value under the rung it belongs to.** A reason a row was\nrefused, the day it was closed, the tracking number it is shipped under: each\nis stated when the row REACHES a stage, and a create that asked for it asks\nthe reader what went wrong with a row they are opening. `{ "at": "cancelled" }`\nnames the option of this entity\'s own `lifecycle`; the create then never asks\nit, the record states it once the row has reached that rung (or holds a value),\nand `scaffold check` prints it under **Who writes what**. It stands without a\n`role` on a field that has no job on a screen, and beside one on a field that\nhas. Refused on the rung a row opens at, on a `required` field, and on the\nroles a row is opened with \u2014 `identity`, `lifecycle`, `parent`.\n\n"One per entity" is one COLUMN, never one value on screen. A second counterparty\nor a second parent stays a ROLELESS `select_record_link`: which field fills a\nslot is the screen\'s answer (\xA7 Apps and screens), and the extra link is a fact on\nthe row with its own section on the record, headed by that link\'s own label. A\nrole naming two fields would move the ambiguity into every shape that reads it.\nThe same holds for a link from an entity to ITSELF \u2014 the row a line sits under in\na breakdown, the same item\'s lines in the periods before: it takes no role, and\nthe clause that reads it names it (`nest`, `depends_on`, the rollup a claim\'s\n`previous` sums over).\n\nA COMPUTED field is admitted where its RESULT is what the role needs, and\nrefused where it is not: `identity` and `reference` need `text`, `when` a `date`,\n`mark` `files`. A formula\'s result is its `output_type`; a rollup\'s and a\nlookup\'s is read through the walk. So a formula yielding a number is refused in a\nname\'s place, a lookup landing on anything but `files` is refused as a `mark`, and\na formula declaring NO result is refused by all four \u2014 naming `output_type` as\nthe remedy, because a value drawn as a type nobody stated is the wrong cell with\nnothing saying so. The result also decides the DEVICE every screen draws the value\nwith: a rollup taking the `latest` of a set of dates is drawn as a date, a lookup\nof a select as the words it holds.\n\nA bare role name is the common form. A role that is read against a SECOND field\ntakes the object form, whose keys are these:\n\n<!-- generated:start field-roles -->\n\n#### Role declaration\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `role` | [role](#field-roles) | no | What the field answers for every screen over its entity. Absent only where the declaration states `at` and nothing else |\n| `values` | list of [Category value](#category-value) (at least one) | no | For a category on a text formula: every word the formula answers that NAMES something, each drawn as a chip in its colour (and glyph). A word left out \u2014 the formula\'s answer for "nothing yet" \u2014 draws nothing. A select\'s options are already its values |\n| `against` | alias \\| number | no | For a measure: the limit it is read against \u2014 a constant, or a field on the same entity by alias whose value is a number (a number field, or a formula or rollup whose result is one). For an amount: the REFERENCE it is quoted off \u2014 the list price, the going rate \u2014 by alias, never a constant, and with no alert |\n| `alert` | `"over"` \\| `"under"` | no | With against: which side of the limit needs attention \u2014 over a capacity, under a minimum |\n| `counts` | `"days"` \\| `"hours"` | no | For a measure: WHAT the number counts, where the unit changes how a surface draws the row rather than how the figure reads. "days" makes the row span that many days of a run, so a stop of 3 is drawn across three of its days instead of only the one it starts on. "hours" is how long the row TAKES inside its day, stated beside it rather than spread across the run |\n| `reading` | `"fill"` \\| `"threshold"` | no | With against: what that limit IS. "fill" (the default) is a WHOLE the level is a share of \u2014 6 of 8 lines shipped. "threshold" is an alarm the level stays one side of \u2014 a reorder minimum, a margin floor \u2014 and every reading on the safe side is inside it, so there is no share to draw |\n| `gate` | alias \\| number | no | For a measure read "under" a limit it fills toward: the share of that limit, in percent, at which the row PASSES \u2014 a constant, or a percentage field on the same entity by alias. Absent, the row passes at the whole limit |\n| `outcomes` | list of (alias \\| [Outcome](#outcome)) | no | For a lifecycle: the options that END the flow \u2014 a row in one has arrived, not advanced. Each is its alias, or `{"stage": \u2026, "asks": [fields decided on arrival], "verb": the closing step\'s button}` |\n| `verdicts` | map of alias \u2192 `"pass"` \\| `"fail"` | no | For a lifecycle: outcome alias \u2192 which ending it is \u2014 "pass" the close the flow is walked for, "fail" the refusal. An outcome with no entry is where the row stands and neither |\n| `phases` | list of [Phase](#phase) (at least one) | no | For a lifecycle: the stages grouped into the parts of the flow they belong to, in order. Every stage that is not an outcome falls in exactly one; the outcomes may be placed in one or left out, and are then read as a last part of their own |\n| `history` | `true` \\| alias | no | For a lifecycle: keep every stage this row reached, with the day it reached it and who moved it. The model derives the table \u2014 `true` names it `<entity>_history`, an alias names it \u2014 and no other file declares it |\n| `due` | `true` | no | For a when: this date is a DEADLINE the row counts down to, and a row past it is late. Absent, it is the plain day it is \u2014 the day a lead arrived is neither early nor late |\n| `warn` | integer | no | For an expires: how many days ahead of the day it runs out the row reads as needing attention \u2014 the window a renewal is started in |\n| `until` | alias | no | For a when or an obligation: the option of this entity\'s lifecycle the countdown STOPS at \u2014 at or past that stage, and at every outcome, the date is drawn plain. On a when it also says the date is a deadline |\n| `at` | alias | no | The FLOW option of this entity\'s lifecycle this field\'s value belongs to \u2014 it is stated once the row reaches that rung, never when the row is opened. Never an outcome: what an ending decides is that outcome\'s `asks` |\n| `from` | alias | no | For a when that counts down: the date field on the same entity the window OPENS on \u2014 the countdown then reads the window from that day to this one, and how far through it the row should be by today |\n| `signed_by` | alias | no | For an amount: the select on the same entity that says which direction it moved |\n| `outflow` | list of alias | no | With signed_by: the options of that select that spend \u2014 the amount reads negative under them |\n| `required_by` | [Required by](#required-by) | no | For an expected_set: the select whose value decides which entries a row owes |\n| `answered_by` | alias | no | For an expected_set on a multi-select: the entity whose rows ANSWER it \u2014 rows hanging under this record by their `parent` link, each filing one entry through its own single-select expected_set. The options CHOSEN here are then what the record owes, not what it holds, and those rows are counted against them rather than against every option of their own select |\n| `label` | text | no | For an obligation: what is OWED, as the reader says it \u2014 absent, the date field\'s own label |\n| `satisfied_by` | alias | no | For an obligation: the field on the same entity that CLOSES it \u2014 the date it was done, or the file that proves it. Where nothing is stamped, `until` names the stage the record stops owing it at instead |\n\n#### Category value\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | Display label for the option |\n| `color` | [colour](#select) | yes | The colour the option\'s badge is drawn in |\n| `mark` | [mark](#select) | no | The option\'s own mark, drawn in place of its colour dot wherever the option is shown: the channel it is ({kind: "brand", name: one of facebook, instagram, threads, meta, tiktok, google-ads, zalo, linkedin, x, google-meet, youtube}) or a kit glyph ({kind: "icon", name: "wrench"}). Every option of a field has one, or none does. A mark a reader does not draw falls back to the dot |\n\n#### Outcome\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `stage` | alias | yes | The option of this lifecycle the row arrives at |\n| `asks` | list of (alias \\| [Outcome ask](#outcome-ask)) (at least one) | no | Fields of this entity decided when the row reaches this stage \u2014 asked then, and never a standing fact. Each is its alias, or `{"field": alias, "required": true}` where the move cannot land without it |\n| `verb` | text | no | This stage is reached by a closing step drawn as the record\'s last section, its one button labelled by this verb; absent, the stage is a door of the status menu |\n\n#### Outcome ask\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field of this entity asked when the row reaches the stage |\n| `required` | `true` | yes | The move to this stage is refused until the field has a value \u2014 the ending cannot be recorded without it |\n\n#### Phase\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | What this part of the flow is called, as the reader says it |\n| `stages` | list of alias (at least one) | yes | The option aliases of this lifecycle that fall under it, in order |\n\n#### Required by\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | `"own"` \\| `"parent"` | no | WHOSE field decides it, which the set\'s own shape settles: a multi-select holds the whole set, so "own" (the default, this entity\'s field); a single select is one entry per row, so "parent" \u2014 the record these rows hang under, reached through this entity\'s `parent` link. The other way round is refused |\n| `field` | alias | yes | The field whose value decides which entries of this set are required \u2014 a single select, or a yes/no answered on that row |\n| `options` | map of alias \u2192 list of alias | yes | That field\'s option alias \u2014 or "true"/"false" where it is a yes/no \u2014 \u2192 the option aliases of THIS set required under it. A value with no entry is omitted, not empty. |\n\n<!-- generated:end field-roles -->\n\nWhat the tables do not say \u2014 the pairs, and what each is refused for:\n\n- **`measure`**: `against` and `alert` come together. `counts` is stated, never\n inferred \u2014 nothing about a `3` says whether it is nights, pallets or hours, and\n a quantity drawn across a week is a run nobody can read. `gate` passes a row\n short of its whole limit \u2014 `90` passes it at nine tenths of its target \u2014 and is\n refused over a capacity and beside `reading: "threshold"`, which has no share\n to pass at.\n- **`lifecycle`**: not every option may be an `outcome`, and terminal-ness\n belongs here, never written into a stage\'s label. A stage in two `phases` is\n drawn twice and a stage in none vanishes off the ladder; which stages belong\n together is the business\'s answer. `history` is declared BESIDE `phases` and\n refused without them: the rungs a ladder draws are the live select\'s options,\n which nothing reading a file or an app\'s spec can see, so `phases` is the one\n statement of what the dates have to cover. The table it derives \u2014 the link back\n to this record, the stage, the moment it was reached and who moved it \u2014 is seen\n by `check`, `apply`, `diff` and the app generator exactly as a written one is,\n and a file that also writes it by hand is refused. Its rows are appended by the\n two table automations the flag also derives (\xA7 Table automations) \u2014 one on the\n create a row opens with, one on every update that moves the stage \u2014 so a rung is\n dated whichever door moved it. **A\n first apply backfills nothing** \u2014 rows that existed before the flag walked their\n stages before that table existed. The honest backfill is ONE opening row per\n existing parent, at its FIRST stage, dated when that parent was created; it is a\n create per parent \u2014 `lotics run create_records` \u2014 and `scaffold apply` prints it\n as the work it did not do.\n An outcome\'s `asks` are the fields DECIDED on arrival \u2014 why it was lost, what\n was awarded: asked when the record moves there, written in the move\'s own\n save, never a standing fact (a band naming one is refused), and read after the\n stage on its status. Each is one a person states, never `required` on the\n field, the name or the lifecycle; `{"field": \u2026, "required": true}` holds the\n move until it has a value, refused by the save whoever calls it. `verb` makes the ending a CLOSING STEP: the\n record\'s last section, after every row it owns, one button of those words,\n confirmed naming the record and the asked fields, then stage and answers\n written together. Once there it reads as settled \u2014 when and by whom where a\n `history` is kept \u2014 with a quiet Reopen to the flow\'s last stage; a screen that\n does not write the record draws neither button. One verb per lifecycle; an\n ending with none is a door of the status menu. A field\'s `at` names the FLOW\n rung its value arrives at, never an outcome \u2014 what an ending decides is its\n `asks`, so `at` naming one is refused.\n- **`amount`**: its `against` is never a constant (a price typed into the model\n ages with nothing to update it) and never beside an `alert` \u2014 beating a\n reference is the point \u2014 and the catalogue item\'s record draws the pair on one\n axis with the gap said in words. `signed_by` and `outflow` come together, and\n `outflow` is some of that select\'s options, never all. Without them every shape\n over the ledger adds both directions together, and the trend plots one rising\n line.\n- **`expected_set`**: without `required_by` the denominator is every option, so a\n set whose entries are mutually exclusive by kind reads `1 of 4` on every\n complete row. A multi-select holds the whole set on one row and is narrowed by\n that row (`from` omitted); a single select is one entry per row and takes\n `from: "parent"` \u2014 which documents an order owes is the ORDER\'s type, and the\n papers carry no column that says it. The other way round is refused rather than\n left conditioning nothing. Under `answered_by`, every option the set can\n require is one the answering rows can file, it takes no `required_by`, and it\n fills no slot of a register: a row holds what it owes, never how much of it the\n rows below have answered.\n- **`expires`**: `warn` is its window, the days ahead of the day it runs out\n that a renewal is started in. A monitored set\'s `level` bound to one draws each\n unit\'s days left \u2014 tinted inside the window, and read as owed past the day \u2014\n and `summary.above: "counts"` counts the units inside the window or past it. A\n formula of days left against a number is the same fact drawn as a meter of\n 1 287 of 60.\n- **`obligation`**: `satisfied_by` or `until`, never neither \u2014 without one every\n obligation the business ever met stays on the desk.\n\n```jsonc\n"field_roles": {\n "product": { "name": "identity", "photo": "mark" },\n "stock": { "on_hand": { "role": "measure", "against": "minimum", "alert": "under" },\n "uptime": { "role": "measure", "against": 80, "alert": "under" } },\n "order": { "stage": { "role": "lifecycle", "outcomes": ["delivered", "cancelled"],\n "verdicts": { "delivered": "pass", "cancelled": "fail" },\n // Two pieces of work, and every step dated.\n "phases": [{ "label": "Taking it on", "stages": ["placed", "confirmed"] },\n { "label": "Getting it out", "stages": ["picked", "shipped"] }],\n "history": true },\n // A DEADLINE COUNTS DOWN \u2014 `payment.paid_on` below is a plain day.\n "ship_by": { "role": "when", "due": true },\n // The day the papers are owed by, and the day they went.\n "papers_due": { "role": "obligation", "label": "File the papers", "satisfied_by": "papers_sent" },\n // Nothing stamps a deposit paid \u2014 the order\'s own stage ends it.\n "deposit_due": { "role": "obligation", "label": "Take the deposit", "until": "delivered" },\n // Stated when an order is cancelled, never when it is placed.\n "cancel_reason": { "at": "cancelled" } },\n // WHICH PAPERS THIS ONE OWES IS THE ORDER\'S OWN TYPE, and no document says it.\n "document": { "order": "parent",\n "kind": { "role": "expected_set",\n "required_by": { "from": "parent", "field": "kind",\n "options": { "export": ["invoice", "customs"] } } } },\n // `kind` is the direction select \u2014 options `received` and `paid_out`, no role of its own.\n // Every figure on a quotation is in the currency that row names.\n "quote": { "unit": "currency", "total": "amount" },\n // What the price is quoted off, so the item\'s page reads one against the other.\n "service": { "price": { "role": "amount", "against": "list_price" },\n "kind": "category" },\n // The day the money moved is neither early nor late, so it stays a bare `when`.\n "payment": { "paid_on": "when",\n "value": { "role": "amount", "signed_by": "kind", "outflow": ["paid_out"] },\n // A receipt owes a receipt voucher; a payment owes an invoice and a payment voucher.\n "papers": { "role": "expected_set",\n "required_by": { "field": "kind",\n "options": { "received": ["receipt_voucher"],\n "paid_out": ["invoice", "payment_voucher"] } } } },\n // A CHECK THAT FAILED OWES ITS CONSEQUENCE \u2014 keyed by the row\'s own answer,\n // and the answer that owes nothing is omitted rather than named with an empty list.\n "check": { "passed": "verdict",\n "evidence": { "role": "expected_set",\n "required_by": { "field": "passed",\n "options": { "false": ["photo", "owner", "due"] } } } },\n // WHAT THIS JOB NEEDS IS CHOSEN ON THE JOB, and the certificates filed under it\n // answer that choice \u2014 a certificate\'s `kind` holds every option `needs` does.\n "job": { "needs": { "role": "expected_set", "answered_by": "certificate" } },\n "certificate": { "job": "parent", "kind": "expected_set", "scan": "mark" }\n}\n```\n\nIn a file that starts from a preset (\xA7 Starting from a preset), `field_roles`\nmay name the preset\'s fields as well as this business\'s own; a role the preset\ndeclares itself is kept unless this file names the same field, and `null`\nclears it.\n\n## Write rules\n\n`write_rules` is what only a CREATE meets \u2014 keyed by entity alias, like\n`field_roles`, and this file\'s in the same way: `check` proves it and the\nworkspace never sees it. An update names one field that moved; a create takes a\ndraft whole, so it has to know which row a name already belongs to, where a\nvalue comes from when nobody types it, and what a figure or a picker may hold.\nThe generator writes one `create_<entity>` workflow and one `New<Entity>Dialog`\nper entity an app can operate, from these clauses and the fields\' own\n`required`.\n\n```jsonc\n"write_rules": {\n // A customer is RECOGNISED by their address. Where an entity names a customer\n // as its `party`, the create takes the email instead of a picker, reuses the\n // row it matches and mints one only where nothing does \u2014 so the book never\n // grows a second Acme because somebody typed the name differently.\n "customer": { "natural_key": ["email"] },\n "order_line": {\n "fields": {\n // A line of nothing is not a line. Refused on create and on update, at\n // the control the figure was typed into.\n "quantity": { "min": 1, "max": 9999 },\n // The price is fixed at the moment of ordering \u2014 COPIED off the product,\n // not looked up for ever after, so the price list moving next week does\n // not silently reprice an order already placed.\n "unit_price": { "default_from": "product.price" },\n // And nothing is sold off an empty shelf. The picker reads only the rows\n // that answer this, and the write refuses the same rows again \u2014 a caller\n // who never opened the picker is bound by it too.\n "product": {\n "options_where": {\n "node_type": "group", "logic": "and",\n "children": [{ "node_type": "condition", "type": "number",\n "field_key": "in_stock", "operator": "greater_than", "value": 0 }]\n }\n }\n }\n }\n}\n```\n\n<!-- generated:start write-rules -->\n\n#### Entity write rules\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `natural_key` | list of alias (at least one) | no | The field aliases a row of this entity is RECOGNISED by. A create that names this entity as its party matches on them and reuses the row it finds, minting one only where nothing matches. |\n| `fields` | map of alias \u2192 [Field write rule](#field-write-rule) | no | Field alias \u2192 what that field\'s value is copied from, bounded by or picked among |\n\n#### Field write rule\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default_from` | text | no | Copy this field\'s value from the linked row at CREATE time \u2014 `<link alias>.<field alias>`, the link being a one-row link on this entity. The value is copied rather than looked up, so the source changing later leaves the row alone. |\n| `min` | number | no | Refuse a create or update whose figure is below this. |\n| `max` | number | no | Refuse a create or update whose figure is above this. |\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 entity\'s own fields, each `field_key` a field alias. It narrows the picker\'s read and is refused again where the write lands. |\n\n<!-- generated:end write-rules -->\n\n- A `natural_key` is `text` or `number`, because a person types it back, and a\n `text` key declares `unique: true` on the field itself \u2014 two rows sharing it\n would make find-or-create pick whichever the read answered first.\n- A `default_from` link is `required`, because there has to be a row to read, and\n the two field types must match.\n- An `options_where` link is `required`, since the rows it may point at are\n refused again where the write lands.\n\nA field\'s own `required` is not here: the contract carries it, every write path\nrefuses the row by field name from it, and the generated panel stands its commit\ndown until the same set is filled. `unique` is the text field\'s own clause\n(\xA7 `text`) \u2014 the create says so at the control before the column does.\n\nWhat a create asks is what a person STATES, so an entity stating `writes:\nfalse` (\xA7 Entity) has no create at all. A `default_from` field is not drawn,\na `lifecycle` opens at its first rung, and the stamp an obligation\'s\n`satisfied_by` names is not asked (the row is only now taking that on) \u2014 one\nfact, one place.\n\n**Whose row it is.** An entity\'s owner column is the `select_member` column a\nlens reads (\xA7 `filters`) in ANY app of the model \u2014 the one opened on `"mine"`\nwhere lenses name several \u2014 else the one `"self"` clause of its `read_scope`\nover its own column. None where a lens and that rule name different columns, or\nseveral are named and nothing picks one. Every generated create of a row of that\nentity writes whoever ran it there: the register\'s and a section\'s, a party the\nkey mints, a line a file lands, and the touch and next act a queue\'s log opens.\nWhere the draft asks that column, it opens on the reader, who may hand the row\nto somebody else, and a blank is the reader too \u2014 so it is never a required\ninput, whatever the column says. So a new row is in its\ncreator\'s MINE, and is edited like any other column afterwards. A column the\nplan fills another way (a `default_from`, a value stated at a later rung) is\nnot written.\n\nA field the workspace writes (a formula, a rollup, a lookup, an\nautonumber, a date with `derive_from`) is never an input of a create or an\nupdate, a write in a generated body, or an entry in `lotics.writes`, and its\nrecord fact has no editor. Of the files columns it asks for exactly ONE, the\n`mark`: that pile IS the row, so a register leading with the picture would\notherwise open a faceless row, while every other pile is papers gathered onto a\nrow that exists and a panel asking for all of them is a filing cabinet with a\nSave button. The screen narrows that set where it states a `create` (\xA7 Apps and\nscreens). `scaffold check` prints every clause under its entity in **Who writes\nwhat**.\n\n**The `parent` is prefilled only in the door that mounts the act.** A row opened\nfrom the record it hangs under takes that record from the door it was pressed\nin, so the link is neither asked for nor drawn. The same entity\'s act on its own\nregister has no such row, so there the parent is an ordinary picker the draft\ncannot be committed without \u2014 a payment opened from a money app belongs to a\ncase either way.\n\n## Table automations\n\n`table_workflows` are what a TABLE does on every write to it \u2014 whichever door\nmade the write: an app, the table explorer, `lotics run`, a chat agent, another\napp over the same table. A fact that has to hold for every row is kept here,\nnever in the one write path an app owns.\n\n<!-- generated:start automations -->\n\n#### Automation\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable automation alias, unique within the file \u2014 what a workspace remembers the automation it provisioned became |\n| `label` | text | yes | What the automation is called on its table |\n| `description` | text | no | What it does, in a sentence shown beside its name |\n| `entity` | alias | yes | The entity whose table it fires on |\n| `trigger` | `"before_create"` \\| `"after_create"` \\| `"before_update"` \\| `"after_update"` \\| `"before_delete"` \\| `"after_delete"` | yes | The write it fires on \u2014 `after_*` runs once the write has landed, `before_*` runs inside it and may refuse it |\n| `watch` | list of alias (at least one) | no | Fields of that entity an update has to change for it to fire; an `*_update` trigger only. Absent: every write fires it |\n| `body` | text | yes | Alias-form JS-subset workflow source, as an app workflow\'s: `@@entity:x@@`, `@@field:x.y@@`, `@@option:x.y:z@@` and `@@connection:x@@` resolve where it is applied, and `trigger.record_id` names the row that fired it |\n\n<!-- generated:end automations -->\n\n- Every `@@\u2026@@` a `body` names must be declared in this file. Beside\n `trigger.record_id`, a body reads `record`, the row as the write left it,\n `changes`, what an update moved, and `runtime.triggered_by_member_id`, the\n member the write came from. It is verified where it is applied, as an author\'s\n own would be.\n- A lifecycle\'s `history` DERIVES two of them (\xA7 Field roles); a file that also\n declares one under a derived alias is refused unless it is exactly what the\n flag derives.\n\n`scaffold apply` provisions each on its entity\'s table and REMEMBERS which one it\nis: a later apply rewrites that automation in place \u2014 body, gate, event, name \u2014\nand never adds a second one beside it. One the workspace has since removed is a\nrefusal naming `lotics scaffold unbind automation <alias>` \u2014 except one that\nwrites a `writes: false` table, which is restored and rewritten (found by its\nlabel too where nothing binds it), or added afresh when schema its archived body\nnames was deleted since. An automation\nthat writes a `writes: false` table is HELD \u2014 neither created nor rewritten \u2014\nwhile a deployed app workflow writes that table itself: the apply names each app\nand workflow alias, applies everything else, and exits 1; `lotics app\nregenerate` then `lotics app deploy` on each, and apply again. `scaffold check`\nprints every automation under its entity in **Who writes what**; `scaffold diff`\nnames each one without counting it, because the workspace\'s export carries no\nbody to compare, and exits 1 on one the apply would hold; `workspace build`\napplies a model that declares one on every run for the same reason.\n\n## Connections\n\nAn automation that pushes to a connected service names the account it pushes\nthrough by ALIAS \u2014 `connected_account_id: "@@connection:books@@"` \u2014 never by a\n`cac_` id: an account is a credential of one workspace, and the file is applied\nin others.\n\n<!-- generated:start connections -->\n\n#### Connection\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable connection alias; a body names the account as `@@connection:<alias>@@` wherever it states a `connected_account_id` |\n| `provider` | `"wininvoice"` \\| `"outlook"` \\| `"gmail"` \\| `"google_drive"` \\| `"facebook"` \\| `"zalo"` \\| `"threads"` \\| `"instagram"` \\| `"x"` \\| `"linkedin"` \\| `"payos"` \\| `"misa"` \\| `"lark"` \\| `"fedex"` \\| `"kiotviet"` | yes | The service the account is of; where it lands, the alias binds to the account the request names, else to the one account of this provider the applier can use |\n\n<!-- generated:end connections -->\n\n- Every `@@connection:\u2026@@` a body names is declared here, and every connection\n declared here is named by a body.\n- The applying member needs `--connection <alias>=<cac_id>` on `scaffold apply`,\n `library init` or `app upgrade` where they can use two or more accounts of the\n provider; with none named, or no account at all, the refusal lists the\n provider, the alias and each candidate before the first table is written \u2014\n which account a push lands in is never a guess. The workspace remembers the\n account bound, so a later apply pushes through it whatever was connected\n since. A bound account since archived is a refusal naming\n `lotics scaffold unbind connection <alias>`.\n\n## Apps and screens\n\n`apps` is the plan: each app as ONE REGISTER \u2014 a SHAPE over an ENTITY \u2014 under\n`screen`, printed back by `lotics scaffold check` with the field in every slot\nbefore a screen exists.\n\n### Shapes\n\n**THE AUTHOR COMPOSES; THE PLATFORM DRAWS EVERY PIECE.** Three things decide\nevery screen and every record: the SHAPE, whose slots are the table below; the\nROLES the entities declare (\xA7 Field roles), which fill those slots and decide\nwhat each child\'s rows become; and the CLAUSES, the keys of the App and Screen\ntables, which are how the author states what is on the screen \u2014 a register\'s\ncolumns, a record\'s sections and their order, a lifecycle drawn as a ladder or\nas a status, the bands its facts fall into, what an outcome asks. How a piece\nLOOKS \u2014 a row, a party, a status, an empty state \u2014 is the runtime\'s and no\nclause styles it. Where a clause is not stated the plan draws the minimal form\nand `scaffold check` marks it `(default)`, so a clause nobody wrote is never read\nback as one somebody did; `--why` prints the rule behind every binding, section\nand default. `lotics docs clauses` lists every clause with an app that states it.\n\n| Shape | Answers | Required | Also fills | Record | Tabs |\n|---|---|---|---|---|---|\n| `lifecycle_desk` | what is stuck, what do I move next | `lifecycle`, `identity` | `mark`, `party`, `amount`, `measure` (level, one or several), `when` | page, or drawer where the entity carries `parent` | the lifecycle\'s stages |\n| `party_register` | who is this, our history, is there a risk | `identity` | `mark`, `contact`, `measure` (worth), `verdict` (risk) | page | none |\n| `offering_register` | what do we offer, at what price, can I sell it | `identity` | `mark`, `amount` (price), `measure` (availability) | page | none |\n| `transaction_ledger` | does this period reconcile, what is unexplained | `when`, `amount` | `identity` (reference \u2014 the line\'s own number), `party`, `lifecycle` (classification), `expected_set` (document) | drawer | none |\n| `monitored_asset_set` | what needs attention, is that number normal | `identity`, `measure` (level, one or several) or `expires` (level, as the days left) | `mark`, `lifecycle` | drawer | none |\n| `obligation_desk` | what is due next, and has it been done | `identity`, and at least one `obligation` on the entity | `when` (runway) | page | none |\n| `trend_deep_dive` | how did the period go, and why | `when` | `measure`, `amount` | drawer | the series, where the model names a select |\n| `reconciliation_desk` | what does not match, and by how much | `identity` (reference), and two figures \u2014 `ours` and `theirs`, each an `amount` or a `measure` | `category` (reason), `verdict`, `when` | drawer, on the PAIR | the dated runs, where the model names a select |\n| `entry_matrix` | one value per subject per period | `identity` (subject), `across` \u2014 a `when`, or a `category` for a fixed column set | `measure` (value), `lifecycle` (state), `category` (note) | drawer, on the cell\'s row | none |\n| `guided_run` | complete an ordered sequence, a step at a time | `identity` (step) | `slot` (sequence), `capture` \u2014 a `measure`, `verdict` or `expected_set` \u2014 `verdict` (gate), `mark` (media) | page: the run IS the record | none |\n| `live_board` | is everything OK, right now | `lifecycle` (state), `identity` | `verdict` (exception), `when` (since), `measure` (level, one or several), `category` (where) | drawer | none |\n| `group_register` | what does each group come to, and what is behind it | `subject` \u2014 a `category`, `party` or `when` | `amount`, `measure`, `identity` (member) | page: a row is an aggregate, and the door is the SET behind it | none |\n| `media_set` | scan a body of pictures by where they belong | `mark` (picture), `identity` | `category` or `party` (group), `when`, `verdict` (current against superseded) | drawer | none |\n| `day_sheet` | what happened on each day | `when` (the day) | `lifecycle` (state), `measure` (level, one or several), `amount`, `category` (note) | page: a day composes several child logs | none |\n| `resource_schedule` | what is on each resource, in what order, under what ceiling | `lane` \u2014 a `party` or a `parent` \u2014 `identity`, `when` (the start) | `measure` (duration), and `load` and `bulk`, each a `measure` read against a ceiling of the LANE\'s \u2014 a vehicle fills by weight and by room and stops at whichever runs out first; `lifecycle` (stage) | drawer, over the lanes | none |\n| `worksheet` | priced lines the reader edits, totalling to one figure | `identity` | `category` (group), `measure` (quantity), and `cost` and `sell`, each an `amount` or a `measure` | drawer, per line | the versions or the jobs, where the model names a select |\n\n**What a child\'s rows become.** One section per LINK into the record, and what\nthe CHILD declares decides its kind \u2014 the first line it answers wins.\n\n| Printed as | The child declares | The reader gets |\n|---|---|---|\n| `documents:` | an `expected_set` select, one entry per row | the desk of what is owed, each row\'s files attached to it |\n| `schedule:` | an `obligation` closed by a DATE, on rows with no `lifecycle` of their own | what is planned against time and what slipped \u2014 the ordered stops, the promised day against the day it happened |\n| `log:` | a `body` (else a `markdown` text) and a day, on rows with no `lifecycle` of their own | what happened, in order \u2014 each entry read in full in the words it states, its selects and the parties it was with on its line, its `recording` played under the words and its `verbatim` a disclosure behind them, and a composer that writes the next one, asking what the entry shows. With a `verdict` the rows are a correspondence: read oldest first, the reply marked with it the answer, and whose move it is at the foot; without one, newest first under day heads. An entry the child NAMES (`identity`) opens a record of its own |\n| `itinerary:` | a `when` and a `lifecycle`, on rows a JOB\'s record OWNS | the day heads the run, one stop per row \u2014 its name, its supporting lines, its stage as the status at the right \u2014 and the sum under it |\n| `book:` | an `amount` that is `signed_by` a select, on rows the record owns (`parent`) \u2014 or a `ledger` clause | the book of movements, closing on its total read against the record\'s `rollup` that sums them, where it carries one \u2014 neither figure is then a fact |\n| `lines:` | anything else the record owns (`parent`) | the register of the rows it is made of |\n| `history:` | anything else NAMED BY it \u2014 the link is their `party` or their `identity` | the same rows read as that record\'s own history \u2014 on a PROFILE, every one of them stands open at its latest 5, headed by how many there are in all, with the rest a press away |\n| `via <link>:` | a link carrying neither role | the register, headed by that link\'s own label |\n| `related:` | rows of an entity this app lists NOWHERE | a counted tab of its own, and a press that opens their register |\n\nA child carrying the BODY and no day is a plain register, and `scaffold check`\nnotes the day it is one short of. A LOG\'S DAY is the child\'s `when`, unless that\n`when` counts down (`due`, `until`) \u2014 then the first `date` no role claims. A\nchild carrying a `lifecycle` is never a log: it is a thing being worked.\n\n**A child register\'s columns** follow the roles in one order \u2014 identity \xB7 when \xB7\nlifecycle \xB7 category \xB7 amount \xB7 measure \xB7 expected_set \xB7 party \xB7 contact \xB7\nverdict \u2014 the day ahead of the counterparty, which repeats on every row;\n`{"of": "<child>", "columns": [\u2026]}` in `sections` names them instead. Where one child reaches a record\nthrough two links that would read alike, each section says the link it is read\nthrough (`schedule: Stops via Billed to`).\n\n### One app per job\n\n**ONE APP IS ONE REGISTER AND THE RECORDS IT OPENS.** A second register beside\nit is a second job on one page: the reader arrives on whichever the nav listed\nfirst and decides, every time, which of the two they came for. So an app has one\ndestination and everything else about a row is DISCLOSED by opening it \u2014 the\nfacts where the plan places them, the children as lists, the related as counts, one primary act, and\na filter or a group where a strip of destinations would have been. What used to\nbe a second screen is a section of the record, or another app; a model still\nsaying `screens` is refused by name.\n\n**The unit of an app is a JOB, not a person and not a table.** A job has its\nown outcome (something exists or is settled when it is done), its own subject\n(the record it advances), and an end that does not wait on the rest of the\nwork. Two tasks are ONE job when neither finishes without the other and both\nadvance the same record. One app per job, and that is what makes an app a\nwrite-ownership boundary: what the job settles is its app\'s alone to write,\nplus everything it must read to settle it well. A person holding several jobs\nopens several apps \u2014 one app for everything one person does is the dump. A step\nof the job is a section of the record, never a register beside it; a reference\nthe job needs at hand is read inside the record it is about.\n\n**Two people signing is two jobs**, because the outcome belongs to the signer:\nthe desk that prepares against the gate that releases. So is a different\ncadence \u2014 reference data edited monthly and read by the public site, beside a\ndaily desk \u2014 and a different reader, the owner\'s read-only questions. Device,\nplace and step never split a job. A super app holds more than one job; an\nover-split holds less than one, a job cut by device, place or step. The count\nper workspace falls out of the jobs, typically two to six, and is never the\ninput: review the plan app by app, naming who holds it, the job in one\nsentence, what it writes and what it reads.\n\n**A party is not a job.** The people and companies a job\'s rows name \u2014 the\ncustomers, the suppliers, the carriers \u2014 are not a job of their own, however\nmany rows name them: nobody\'s day is "the customer book". They live inside the\napp whose rows name them: a create takes the party by its natural key, the row\'s\nname opens the party\'s own record (a profile \u2014 how to reach them, what is live\nwith them, their history, their log), and its facts are edited there. A\nparty earns an app of its own only where someone\'s job IS the relationship, with\nan outcome of its own \u2014 an account plan signed, a supplier qualified \u2014 never\n"keeping the book". Two apps for one seller, the deals and the customers, is the\ndump cut in two.\n\n*A two-van appliance repair shop.* The technician\'s job is the call-out: the\noutcome is a visit done, the subject is the call-out record, and quoting it and\nscheduling it are one job, because neither finishes without the other and both\nadvance that record. The owner holds two of his own \u2014 billing the month\n(outcome invoiced, subject the invoice, settled against visits already closed)\nand the price list the booking page quotes from, edited monthly. Three jobs,\nthree apps, and the owner opens two of them.\n\n**Inside a job the caps hold**: six flow stages on a desk, eight facts to a\nrecord\'s band. Past them, look for the second job hiding inside. And a job\'s app\nis DENSE: every create the job needs lives in it, so its user never leaves it to\ncorrect a figure the screen in front of them is stating \u2014 three workflows is a\nscreen, not a desk.\n\n**A handoff is a stage change.** Where one job ends the record moves stage and\nthe next job\'s desk opens on it; a field two jobs must both write is declared\nshared at the split, naming the stage each may write it in.\n\n**Name an app for the WORK, in the words of the role that opens it** \u2014 the\ndesk\'s own noun for what it settles, never who it is for. A title naming a person\nor a department says nothing about what the one who opened it came to do, and it\nis wrong the day the org chart moves; a title in the owner\'s words is one the\nclerk opening it does not use.\n\n\n```jsonc\n"apps": [\n {\n "alias": "order_desk", "name": "Order desk",\n "description": "\u2026", "icon": "briefcase", "theme": { "color": "blue" },\n "screen":\n { "alias": "orders", "label": "Orders", "shape": "lifecycle_desk", "entity": "order",\n "record": "drawer", "tabs": "stage", "writes": false, "period": "due_date",\n "filters": ["kind",\n { "label": "Qu\xE1 h\u1EA1n l\u01B0u", "predicates": [\n { "label": "\u0110\xE3 qu\xE1", "tone": "red",\n "where": { "node_type": "group", "logic": "and", "children": [\n { "node_type": "condition", "field_key": "owed", "operator": "greater_than", "value": 0 }] } }] }],\n "summary": { "totals": ["total"], "above": ["total", { "field": "owed", "at": "end" }],\n "ageing": { "amount": "owed", "due": "due_date", "buckets": [30, 60, 90] },\n "trend": { "field": "total", "direction": "up" } },\n "facts": { "groups": [{ "caption": "Pricing", "fields": ["rate", "surcharge"] }] },\n "ladder": true,\n "presentation": { "lead": "none", "density": "dense" },\n "acts": { "row": [{ "label": "Issue the note", "template": "debit_note", "place": "cta", "confirm": true },\n { "kind": "agent", "label": "Read the papers", "agent": "reader", "fills": ["ref", "due_date"] },\n { "kind": "workflow", "label": "Hand it to dispatch", "workflow": "hand_to_dispatch",\n "inputs": { "order_id": "record", "owed_by": "due_date" },\n "asks": { "crew": "crew" },\n "needs": ["site_phone", "site_email"] }],\n "record": [{ "label": "Print the file", "template": "dossier" }],\n "selection": [{ "label": "Statement", "template": "statement" }],\n "export": true,\n "import": { "kind": "import", "label": "Upload the sheet",\n "entity": "order", "key": "code", "columns": ["rate"] } },\n "section_acts": { "line": [{ "label": "Chase the lines", "template": "chaser" }] },\n "sections": [{ "of": "stop", "heading": "The move", "facts": ["pack_on", "deliver_on"] },\n "attachments",\n { "of": "line", "draw": "worksheet", "cost": "buy_rate", "sell": "rate" },\n { "of": "payment", "draw": "ledger", "heading": "Money in" },\n { "of": "visit", "draw": "log", "evidence": ["photos"], "lines": [["kind", "site"]] },\n { "of": "pinning", "draw": "publish", \u2026 },\n { "of": "chase", "draw": "acts", \u2026 }],\n "slots": { "identity": "code",\n "subject": ["customer", "project"],\n "stage": { "field": "state", "quick": true } },\n "columns": ["source", "owner"],\n "create": ["code", "customer", "due_date"] }\n }\n]\n```\n\n### App and screen keys\n\n<!-- generated:start apps -->\n\n#### App\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable app alias, unique within the contract |\n| `name` | text | yes | What the app is called wherever its members open it |\n| `description` | text | no | What the app is for, in a sentence shown beside its name |\n| `icon` | text | no | A Lucide icon name, kebab-case |\n| `theme` | [Theme](#theme) | no | The app\'s colour |\n| `scope` | [Scope](#scope) | no | Narrow every read of this app to one row of an entity, the pick shared with every app that names it |\n| `reads` | `"shared"` | no | "shared": this app\'s queries and writes carry no entity\'s read_scope \u2014 whoever the app is shared with reads and writes every row it draws. Absent, each read_scope binds the viewer. |\n| `screen` | [Screen](#screen) | yes | The app\'s one register and the records its rows open |\n\n#### Theme\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `color` | [colour](#select) \\| `null` | no | Theme color for the app |\n\n#### Scope\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | alias | yes | The entity every read of this app narrows to ONE row of |\n| `param` | text | yes | The param this app\'s reads take the picked row by |\n\n#### Screen\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable screen alias, unique within the app |\n| `label` | text | yes | What the screen is called in the app |\n| `shape` | [shape](#apps-and-screens), or `"custom"` | yes | A shape, or "custom" with its own `roles` |\n| `entity` | alias | yes | The entity whose rows this screen is over |\n| `record` | `"drawer"` \\| `"page"` \\| `"expand"` \\| [Record clause](#record-clause) | no | How one record opens from the list \u2014 "drawer", "page", or "expand" to reveal it in the row \u2014 or `{"door", "brief", "tabs"}` placing its sections too; absent, the shape decides the door and the roles the rest |\n| `tabs` | alias \\| `null` | no | The entity\'s lifecycle select, whose stages are the tab strip; null for none; absent, the shape decides. Any other select is a `filters` entry \u2014 except on a shape whose strip means something of its own (a reconciliation\'s runs, a trend\'s series, a worksheet\'s versions), which takes any select |\n| `slots` | map of text \u2192 (alias \\| list of alias (at least 2) \\| [Quick slot](#quick-slot) \\| `null`) | no | Slot \u2192 the field that fills it, where roles alone cannot decide; null leaves an optional slot unbound |\n| `roles` | map of text \u2192 [role](#field-roles) | no | For "custom" only: slot \u2192 the role that fills it |\n| `columns` | list of (alias \\| [Chip column](#chip-column)) (1\u20134) | no | Extra fields drawn after the slots, in this order \u2014 at most 4, each holding ONE value; never a files field, and never a field a slot already draws. `{"field", "age", "more"}` draws a lookup of a related row\'s category as that category\'s chip |\n| `sources` | list of alias (at least one) | no | Other entities whose rows this register reads beside its own, in ONE read sorted by its order \u2014 each declares a field for every role the register\'s slots draw and, under the same alias, every other field it draws, orders or narrows by; a row opens in its own entity\'s record, and the create and row acts are this screen\'s entity\'s |\n| `create` | list of alias (at least one) | no | The fields a new row is asked for, in this order \u2014 at most 12, each a field of this entity a draft can ask for, and every field the row cannot be written without among them; absent, the draft asks for every field a person states |\n| `writes` | boolean \\| `"children"` \\| `"record"` | no | false makes this screen\'s record read-only \u2014 no field editor, no stage advance; "children" draws the record at rest and leaves the rows it owns operable; "record" leaves the record operable and draws the rows it owns at rest; absent, both are operable |\n| `acts` | [Acts](#acts) | no | The papers this register makes \u2014 from one row, from a ticked set, and from the whole view |\n| `section_acts` | map of alias \u2192 list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | Acts on the RECORD\'s own sections, keyed by the alias each section is derived from \u2014 a field alias (its progress, its prose, its set, its charge, its files) or a child entity\'s alias (its register, its desk, its run, its log) |\n| `sections` | list of (alias \\| [Worksheet section](#worksheet-section) \\| [Ledger section](#ledger-section) \\| [Itinerary section](#itinerary-section) \\| [Claim section](#claim-section) \\| [Tree section](#tree-section) \\| [Gantt section](#gantt-section) \\| [Curve section](#curve-section) \\| [Log section](#log-section) \\| [Publish section](#publish-section) \\| [Acts section](#acts-section) \\| [Section heading](#section-heading)) | no | The RECORD\'s sections, drawn in exactly this order \u2014 each the alias a section is derived from (a child entity\'s rows; a field\'s files, prose, charge, set or ladder), or `{"of": <child>, \u2026}` with how those rows are drawn; a section left out is not drawn, `[]` draws none, and a counted child is named after every open section. Absent, every section the record derives, in the recipe\'s order. The facts are banded by `facts`, and a closing step always ends the record |\n| `filters` | list of (alias \\| [Lens](#lens) \\| [Member lens](#member-lens)) (at least one) | no | The chips beside the search \u2014 a single-select or select_member field\'s alias, `{"field": \u2026, "default": "mine"}` to open a member field on the reader\'s own rows, or a lens this model states as predicates over the entity\'s fields |\n| `facts` | [Facts](#facts) | no | How the record\'s facts are BANDED \u2014 where the aside\'s fields group under captions |\n| `ladder` | `true` | no | The flow drawn as a ladder of dated rungs, for a long flow whose path the reader needs to see; absent, the stage is the record header\'s status |\n| `summary` | [Summary](#summary) | no | What the rows in view come to, stated before or after their names |\n| `period` | alias | no | A date field on the entity the reader narrows the view by; absent, the view is every row |\n| `presentation` | [Presentation](#presentation) | no | How this screen and the record it opens are DRAWN, where the shape\'s own answer is not the one wanted |\n\n<!-- generated:end apps -->\n\n### Slots\n\nA shape is a proven screen with named SLOTS, each filled by a field carrying a\nrole (\xA7 Field roles). A slot with exactly one candidate on the entity binds by itself;\ntwo candidates need naming in `slots`; a field fills one slot; a required slot\nwith none is refused \u2014 a lifecycle desk over an entity with no `lifecycle`\nselect cannot be built. `"slots": { "party": null }` leaves a slot the shape can\ndo without UNBOUND: the register draws no column for it and gives the width to\n`columns`, while the role goes on being read everywhere else. A required slot\nrefuses it.\n\n**A LIST IS A HIERARCHY, not a set of columns.** A group register\'s `subject`\ntakes one \u2014 `"subject": ["customer", "project", "order"]`, outermost first \u2014 and\nthe register becomes one the reader walks DOWN: it folds by the first tier, a\nnode narrows to the tier inside it, the last tier opens the set behind the\nfigure, and a trail says where they are. Every tier is a field of this entity\ncarrying a role that slot accepts, and none of them fills a second slot. It is\nthe slot\'s own answer that decides: a list on any other is refused, because two\ncolumns in one cell is not a fold. Without it the same business needs a register\nper depth, and the same money is folded twice with nothing saying it is the same\nmoney.\n\n**AND ON A SLOT READ AGAINST A LIMIT A LIST IS SEVERAL MEASURES** \u2014\n`"level": ["received", "invoiced"]`, at most four, each drawn as a named meter\nline inside the ONE column and each read against its OWN `against`/`alert`. A\nrow is regularly scored against more than one target at once, and the second\nreading put in `columns` instead draws a figure bare with the comparison handed\nback to the reader. Every entry states a limit \u2014 a bare measure in the list is\nrefused, naming the clause \u2014 and `quick` is refused beside one, because a typed\nfigure is one cell. The FIRST leads: it is what `summary.above: "counts"` counts\npast-limit rows on and what a live board ranks its exceptions by, both of which\nname one set.\n\n**A QUICK SLOT IS FOR A DECISION THE READER MAKES FROM THE ROW ALONE.**\n`{"field": \u2026, "quick": true}` makes the column the CONTROL for its value,\nwritten as a diff through the record\'s own update, blockers and an outcome\'s\nconfirm included. Only a `lifecycle`, a `verdict` or a `measure` (a reading\ntaken per row IS the row \u2014 a timesheet\'s hours); `"order": "sequence"` makes a\nlifecycle\'s picker a walk with the next step marked. Refused under\n`writes: false`.\n\n<!-- generated:start apps-slots -->\n\n#### Quick slot\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field this slot takes |\n| `quick` | `true` | yes | Draw the column as its OWN editor \u2014 for a decision the reader makes from the row alone |\n| `order` | `"sequence"` | no | A lifecycle whose stages are a WALK: the cell advances to the next one rather than offering them all |\n\n<!-- generated:end apps-slots -->\n\n**THE SLOTS ARE THE ROW\'S ANSWER; `columns` IS ITS CONTEXT.** `"columns":\n["nguon", "phu_trach", "nhu_cau"]` names extra fields drawn after every slot,\nin this order, each by what its column is and under its column\'s name \u2014 except a\nchip (a select, a category formula\'s word, a lookup of either), which names\nitself \u2014 and shed before any slot on a narrow screen. **A COUNT IS NOT A\nCOLUMN**: a `rollup` counting rows reads on the name\'s supporting line as the\nthing it counts (`3 b\xE1o gi\xE1 \u0111ang m\u1EDF`), takes no seat, and opens those rows where\nthe record lists them. A fifth fact worth the width is a slot the shape is\nmissing \u2014 say so rather than widening this clause.\n\n**A RELATED ROW\'S KIND IS A CHIP.** An entry `{"field": "latest_kind", "age":\n"latest_on", "more": "item_count"}` draws a `lookup` of a related row\'s\n`category` as that category\'s chip \u2014 the option\'s colour and mark (\xA7 `select`),\nor a category formula\'s word as its `values` wear it \u2014 followed by how long ago that row happened (`age`, a date read\nover the SAME link, a lookup or a rollup) and how many OTHER rows the link names\n(`more`, a count rollup over the same link, drawn `+N`). Three columns drawn apart\nare a word, a date and a small integer the reader joins by eye. It takes one seat.\nThe chip is ONE row: across a link naming several, the lookup is ordered\n(`order_by`), and `age` dates the row that order picks \u2014 a `latest` (for `desc`)\nor `earliest` (for `asc`) rollup of the order\'s own date, or a lookup ordered the\nsame way. Refused: a field that is no lookup of a category, an unordered lookup\nacross a link naming several rows, an `age` or `more` read over another link or\nthrough a rollup `filter`, an `age` that is not a date or dates another row, a\n`more` that is not a count, an entry stating neither, and a rider named again as a\ncolumn of its own.\n\n**THE DRAFT ASKS WHAT FILES THE ROW; THE RECORD ASKS THE REST.** `"create":\n["ma_ho_so", "khach_hang", "han_xu_ly"]` names the fields a new row is asked\nfor, in order; absent, every field a person states. A row written over days is\nnamed, dated and filed the moment it is taken, and the rest is edited in place on\nthe record. It narrows this entity\'s own draft \u2014 rows a record adds under itself\nare the section\'s. A link the row stands on (`parent`, `party`) is asked as one\nrow. Refused: a field no draft asks (computed, minted, the `lifecycle`, a\n`default_from` target, an obligation\'s stamp, any pile but the `mark`), and a\nlist omitting what the row cannot be written without \u2014 a `required` field, the\nlink it hangs under, or a party\'s recognising key. Whose row it is may be left\nout \u2014 required, or the member column its `read_scope` names its only reader by \u2014\nsince the create writes whoever ran it there (**Whose row it is**, \xA7 Write\nrules); a member column nothing stamps is refused like any other.\n\n### Records and scope\n\n**A record is one of five KINDS, and the shape plus the roles decide which.**\nEvery one of them is ONE READING COLUMN, and the ORDER of that column is the\nwhole of what the kind means.\n\n| Kind | The column, top to bottom |\n|---|---|\n| WORK RECORD \u2014 a `lifecycle_desk` over rows that belong to nothing and that nothing transacts with | the one act that moves it \xB7 what it IS \xB7 the papers it owes \xB7 the rows it is made of \xB7 the book of what it came to |\n| LINE \u2014 the same shape over rows carrying `parent` | the arithmetic behind its amount \xB7 the rows and papers under it \xB7 its particulars, last (the key it is filed by is the line under its title) |\n| PROFILE \u2014 a `party_register`, and any shape over an entity whose rows other entities are NAMED by (their `party` or their `identity`) | where it stands \xB7 its history, OPEN, grouped by year \xB7 the rest of its registers \xB7 its facts (the ways to reach it are under its name, its settlement is the band, and a `when` it owes closes that band as a countdown) |\n| CATALOGUE ITEM \u2014 an `offering_register` | its own words (the picture leads the header at media scale, the price is read against its reference in the band) \xB7 what uses it \xB7 its facts |\n| EVIDENCE \u2014 a `transaction_ledger` | the PROOF \xB7 what the paper is, compactly \xB7 the rest \xB7 the links to the record it settles and the party it was with |\n\nNothing in the file states the kind: a clause that could say it could say the\nwrong one, and the model already says it \u2014 `parent` says whether a row stands\nalone, and a link NAMING this record (`party`, `identity`) says whether other\nrows are ABOUT it. So a desk over accounts\nwalking their own stages opens a PROFILE: the quotes and the invoices naming\neach account are what its record is read for, and a work record would have\ncounted them in one line at the foot. `record`\noverrides the DOOR where the shape\'s own answer is not the one wanted \u2014 and its\nthird value, `"expand"`, is not a door at all: the row REVEALS what it holds in\nplace, where there is nothing behind it worth navigating to. A row read that way\nreads as a LINE whatever the shape would have opened: the arithmetic\nbehind its amount, then its particulars, because there is no header above it to\nhave stated the name first. It is REFUSED for two reasons, and they are not the\nsame refusal. On a shape whose record is a page by nature \u2014 a `guided_run`, a\n`group_register`, a `day_sheet`, an `obligation_desk` \u2014 because each of those\nstates the opposite where its door is declared: what its row leads to is read\nwhole, not as four more columns than the register had room for. And on a shape\nthat draws its rows with a device of its OWN \u2014 an `entry_matrix`, a `media_set` \u2014\nbecause a cell of a grid and a tile on a wall have no band under them for the row\nto reveal into. For the same reason a revealed register is drawn as a TABLE: a\n`presentation.layout` of anything else is refused, and the reader is not offered\nthe arrangement either, since it would take away the only door the register has.\n\n**`scope` \u2014 the one subject every read of the app narrows to.** Stated on the\nAPP rather than on its screen, because it is not a filter: a record opened under\nit stays under it, the pick survives closing the app, and every app declaring the\nsame `entity` shares one pick per viewer \u2014 which is the whole value, and what\nstops a construction workspace asking which project twelve times.\n\nThe register\'s own entity has to REACH it \u2014 its own rows, or rows that name one\nthrough a link \u2014 or the switcher is a control that changes nothing. Until a\nsubject is picked the app reads NOTHING and says which one it is waiting for: an\nunscoped read over a scoped app is every row in the workspace drawn as one\nproject\'s work, which looks correct.\n\n**ONE RECORD SURFACE PER ENTITY, AND ITS KIND IS THE ENTITY\'S.** An app is one\nregister, so the record its rows open is the register\'s own and the kind comes\nfrom that register\'s shape. Another app over the same entity opens the same kind\nof record: a payment read from a cash book and one read from a trend are both\nmovements, and both read proof first. A shape with no kind of its own (a trend, a\nmonitored set, a `custom` screen) says only where the record opens.\n\n**A NAMED PARTY HAS A RECORD SURFACE IN THE APP.** One register, but the party\nits rows name \u2014 by `identity` or by `party` \u2014 opens a record of its own, a\nPROFILE, from the cell that names it and from the record\'s header: the ways to\nreach them, what is live with them, every register that names them at its latest\nfive, their log, their facts. It is the app\'s to write as far as the job\nneeds \u2014 the party\'s facts and its log \u2014 and the writers matrix says so; the\nregister\'s own record stays the register\'s. A party named through a link\ncarrying no role opens nothing, and neither does a party that is a MEMBER \u2014 a\nperson of this workspace has no row in any book the app carries, so the cell and\nthe header state them and stop.\n\n**Two things a register states about a row beyond which field fills which slot.**\nA `measure` is read AGAINST its limit (`against`/`alert`) where the shape\'s slot\ntakes one \u2014 `level`, on a lifecycle desk, a monitored set, a live board and a day\nsheet, a schedule\'s `load` and `bulk`, and every slot of a\n`custom` screen, which composes the frame\'s own column; `worth`, `availability`\nand a trend\'s headline are plain figures, and a limit there would read as a meter\nin the record over a number the row printed bare. And the name carries a\nSUPPORTING LINE where the entity states a key to file the row under: the `parent`\nit belongs to, its `reference`, an `autonumber`, a text formula \u2014 in that order,\nfirst hit wins, never a field a person types prose into. Every record heads with\nthat same key, so the register and the door it opens name one row one way.\n\n**The act that opens a row belongs to a register that LISTS the entity\'s rows**\n\u2014 a desk, a register, a ledger, a monitored set, or a `custom` screen. A shape\nwhose rows are not records has neither that act nor the door a counted tab\nelsewhere leads to: an obligation desk\'s rows are deadlines and a trend\'s are\nperiods, so a create pressed there would open a row the screen cannot show. An\nentity whose only app is one of those has no create at all \u2014 give it an app whose\nregister its rows are read in.\n\n**An `obligation_desk`\'s rows are not its entity\'s rows.** The shape fans the\nentity\'s `obligation` fields out \u2014 one row per thing still owed, which is one\nwhose date is SET and whose `satisfied_by` is empty \u2014 so a l\xF4 with four cut-offs\nis four rows, and the three it has met are not there at all. The row\'s name is\nthe obligation\'s, its supporting line the record\'s `identity`, and the door is\nthat record. A `when` bound to `runway` is what the countdown\'s ring is measured\nfrom. Over an entity declaring no `obligation` the shape is refused: there is\nnothing to count down to.\n\n### Register keys\n\n**What a register carries beyond its columns.** Clauses the runtime draws;\nevery one is optional and a screen that states none gets a register of\ncolumns and nothing else. Their keys:\n\n<!-- generated:start apps-register -->\n\n#### Chip column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A lookup of this entity reading a related row\'s `category` \u2014 drawn as that category\'s chip, in its option\'s colour and glyph |\n| `age` | alias | no | A date of this entity dating the row the chip\'s ordered lookup picks \u2014 a `latest`/`earliest` rollup of the order\'s date, or a lookup ordered the same way \u2014 drawn after the chip as how long ago it was |\n| `more` | alias | no | A count rollup of this entity over the SAME link \u2014 drawn beside the chip as how many OTHER rows there are |\n\n#### Member lens\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A select_member field of the entity \u2014 who the row is for |\n| `default` | `"mine"` | yes | The register opens narrowed to the reader\'s own rows |\n\n#### Lens\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | What the chip is called |\n| `predicates` | list of [Predicate](#predicate) (at least one) | yes | The sets it offers, in this order \u2014 ONE is a chip the reader toggles |\n| `sort` | [Lens order](#lens-order) | no | The order the rows are read in while the lens is on |\n\n#### Lens order\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A date, number or text of this entity \u2014 its own, or a lookup or rollup |\n| `order` | `"asc"` \\| `"desc"` | no | "asc" (the default) puts the soonest or smallest first; a row holding none goes last |\n\n#### Predicate\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | What this predicate is called in the chip\'s list |\n| `tone` | [colour](#select) | no | The colour it wears, from the option palette; absent, the kit\'s neutral |\n| `where` | [filter](#views) | yes | An `and` group of plain conditions over the entity\'s OWN fields, each `field_key` a field alias \u2014 the rows this predicate keeps |\n\n#### Summary\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `totals` | list of alias (at least one) | no | Number fields the whole view adds up \u2014 under the column that draws one, else in a band beneath the register |\n| `above` | `"counts"` \\| list of (alias \\| [Stock](#stock)) (at least one) | no | A band of figures over the register: "counts" for how many rows and how many need attention, or number fields \u2014 each added up over the window, or `{"field": \u2026, "at": "end"}` for a stock STANDING at its end |\n| `ageing` | [Ageing](#ageing) | no | How old the money over this register is \u2014 the amount split by how many days past its date each row is |\n| `trend` | [Trend](#trend) | no | Which way this figure moved across the window the reader is reading \u2014 needs a `period` to be a window of |\n| `curve` | [Curve](#curve) | no | Planned against actual, each added up to date over the rows\' dates and drawn as two lines \u2014 read along the `period`, else the `when` |\n\n#### Stock\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The number field the reading is in |\n| `at` | `"end"` | yes | The reading STANDING at the window\'s end, rather than the sum of the readings in it |\n\n#### Ageing\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `amount` | alias | yes | The figure each bucket adds up \u2014 what is still owed on the row |\n| `due` | alias | yes | The date the row was owed by; a row is bucketed by how far past it is |\n| `buckets` | list of integer (at least one) | no | The bucket edges, in days past due, ascending \u2014 absent, 30, 60, 90 |\n\n#### Trend\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The number this register adds up in each bucket of the window |\n| `direction` | `"up"` \\| `"down"` | yes | Which way is GOOD \u2014 a book wants `up`, a backlog `down`; the arrow follows the change and the colour follows this |\n\n#### Curve\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `planned` | alias | yes | The number each row was PLANNED to come to |\n| `actual` | alias | yes | The number each row DID come to \u2014 empty on a row that has not happened |\n\n#### Facts\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `groups` | list of [Facts group](#facts-group) (at least one) | yes | The named bands of the record\'s facts, in this order \u2014 every field a person states is in one, and a field the platform works out is drawn only where one names it |\n\n#### Facts group\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `caption` | text | yes | What the fields under it have in common, as the reader says it |\n| `alias` | alias | no | What `record.brief` and `record.tabs` name this band by |\n| `fields` | list of alias (at least one) | yes | The facts of this band, in this order |\n| `last` | `true` | no | Read after the record\'s own work \u2014 its acts, its rows, its correspondence \u2014 instead of before them |\n| `first` | `true` | no | Read before everything else on the record, its stage included \u2014 the facts its stage is judged by |\n| `closes` | alias | no | The outcome of this entity\'s lifecycle whose closing step this band IS \u2014 drawn as the record\'s last section, above the outcome\'s asked fields and its verb. The outcome states the verb |\n\n#### Presentation\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `lead` | `"mark"` \\| `"picture"` \\| `"paper"` \\| `"figure"` \\| `"none"` | no | What each row leads with; absent, what the shape\'s rows are decides |\n| `density` | `"roomy"` \\| `"dense"` | no | How many lines a row\'s subject may take; absent, what the shape\'s rows are decides |\n| `layout` | `"table"` \\| `"list"` \\| `"cards"` \\| `"gallery"` \\| `"board"` \\| `"calendar"` \\| `"timeline"` \\| `"gantt"` \\| `"tree"` | no | How the rows are arranged; absent, a table |\n| `line` | list of alias (1\u20132) | no | The register row\'s supporting line, in this order \u2014 at most 2 fields of this entity, each a plain text or a single select; absent, the key the row is filed under |\n| `until` | alias | no | For a gantt: the date field each bar is drawn TO; absent, the bar runs for the days its measure counts |\n| `nest` | alias | no | A one-row link from this entity to itself \u2014 the row each row sits under; affords `layout: "tree"` and nests a gantt\'s bars |\n| `depends_on` | alias | no | For a gantt: a link from this entity to itself naming the rows that must finish before a row starts |\n| `baseline` | [Baseline](#baseline) | no | For a gantt: the date fields the row was PLANNED to start and end on, drawn under its bar |\n\n#### Baseline\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | yes | The date field the row was planned to start on |\n| `end` | alias | yes | The date field the row was planned to end on |\n\n<!-- generated:end apps-register -->\n\n### Period and filters\n\nWhat each draws, and what it is refused for:\n\n- **`period`** \u2014 a field on the entity whose value is a DATE. The toolbar gains a\n date-range control (this month to start), and the rows it keeps are what the\n register AND every figure below are computed over, so a band can never be over a\n different window than the rows under it. Any such field, not only the `when`\n role, and however the date got there: one table is read two ways (a cost ledger\n by the day a line arose, the cash book over the same table by the day it\n settled), a role is one field\'s, and the day money MOVED is often a formula over\n the two columns that could hold it \u2014 which then declares `output_type: "date"`.\n A `trend_deep_dive` brings its own period and takes none here; a `live_board`\n is PERIODLESS and takes none either, for the opposite reason \u2014 it answers what\n is true NOW, and a date range over it draws what was true in the window in the\n live state\'s own colours.\n- **`filters`** \u2014 the chips beside the search. A single-`select` field\'s alias\n \u2014 or a `lookup` of one, read as the looked-up row\'s option \u2014 offers that\n field\'s own live options; a category formula\'s alias (or a lookup of one)\n offers its `values`, each a chip in the word\'s colour; it is the lens a register is read through\n over and above the one band its strip gives, so the strip\'s own field is\n refused here (one dimension, one control) and so is a multi-select (a row would\n answer the chip several ways at once). The rows the register and every figure\n below are computed over are what the chips AND the period kept.\n A `select_member` field\'s alias is the third form and asks one question \u2014 whose\n rows these are: two peers, MINE and ALL, read against the person looking rather\n than a directory of everybody. `{"field": \u2026, "default": "mine"}` opens the\n register on the reader\'s own; that form on any other type is refused. Alone of\n the chips it narrows the READ (a `member` param on the register\'s query), so a\n desk that opens on the reader\'s work opens on all of it rather than on\n whichever of their rows the first page happened to hold. The column it reads\n says whose a row is, so every create writes whoever ran it there\n (**Whose row it is**, \xA7 Write rules).\n A DERIVED LENS is the other form, where the sets a reader narrows by are ones\n the MODEL names and no column holds: rate validity, plan urgency, free-time\n overrun. Each `where` is an `and` group of plain conditions over the entity\'s\n own fields, each `field_key` a field alias, and it is read off the row, so the\n operators are the comparisons a value answers \u2014 `number` and `text` equality,\n `number` ordering, `boolean` equality, `select` `has_any_of`/`has_none_of`, and\n `is_empty`/`is_not_empty` on any of them. Nothing DATED: a relative point needs\n a clock, and a screen states one date control (`period`), so a second beside it\n would be two windows with nothing saying which a figure was computed over \u2014 a\n date question is asked as a column the model derives and read here as a number\n or a yes/no. `tone` is the option palette\'s, because the chip draws these\n exactly as it draws a select\'s options. ONE predicate is a chip the reader\n toggles rather than a list of one. `"sort": {"field": \u2026, "order": "asc"}`\n reads the rows in that field\'s order while the lens is on \u2014 a worklist, owed\n soonest first, a row holding none last; refused over a field that is not a\n date, number or text of this entity.\n\n### The summary band\n\n- **`summary`** \u2014 what the rows in view come to. `above` is a band OVER the\n register: `"counts"` for how many rows are in view and, for the FIRST\n `measure` the model gives a limit, how many are past it (accented only when\n one is) \u2014 one such set, because a band naming three is the dashboard a\n register is not; a list of number-field aliases for what they add up to, and a\n `percentage` named there is refused, since a share summed over the rows in\n view is a figure of no kind (draw it as a column, whose own total reads the\n mean). A figure written `{"field": \u2026, "at": "end"}` is a STOCK instead: the\n reading standing at the END of the window rather than the sum of the readings\n in it, which is the only way opening + in \u2212 out = closing reconciles \u2014 a month\n of daily closing stocks added together is thirty warehouses. The band labels it\n as the snapshot it is, and it is refused on a screen that states no `period`,\n since there is then no window for it to stand at the end of.\n `ageing` stands in that same\n band: an `amount` split by how many days past its `due` date each row is,\n drawn as a `Breakdown` of the edges in `buckets` (30, 60 and 90 days unless the\n model states its own, ascending). The ladder is the kit\'s \u2014 its boundary, its\n words and its ramp \u2014 and it opens with what is NOT YET DUE, because a bar of\n overdue bands alone is full at every input: a book with one late invoice and a\n book that has gone entirely bad would paint the same solid width. Against the\n current money the bar\'s own shape is the reading. A row whose date is empty is\n in no band. `totals` is what the whole\n view adds up, and it is drawn in that SAME band above the rows: a register\n states one aggregate, over the rows the reader can see, in one place, and a\n closing line under them is a second place to look and a second question about\n which rows it covers. A closing line belongs to the one device that IS a\n statement \u2014 a record\'s `Ledger`, whose balance is what it is read for, labelled\n by its amount\'s own word. Where a shape has no band above (a trend, whose own\n band is the chart) the figure stands under the register instead. A\n `transaction_ledger` sums its amount column itself and takes `above` alone. An\n `obligation_desk`\'s rows are DEADLINES and not records, so its band states\n `"counts"` \u2014 how many are open \u2014 and a figure list, an `ageing` or a `trend`\n over it is\n refused: a l\xF4 with three cut-offs is three rows, and its freight added over\n them is three times the freight.\n `trend` states WHICH WAY that figure moved across the window: the rows in view are cut into the window\'s own\n buckets \u2014 days over a month, weeks over half a year, months beyond \u2014 and the\n later half of the CLOSED ones is read against the earlier half, drawn beside\n the figures as one line, so a window the reader is still inside states the\n movement of the days that have run rather than a fall into the ones that have\n not. `direction` is which way is GOOD, which nothing about the number\n says: a book wants its takings rising and a backlog wants its days falling, and\n the same arrow is a win on one and an alarm on the other. It needs a `period`\n to be a window of, and it is refused on a shape that draws its own (a\n `trend_deep_dive` already draws the series) or that reads none at all (a\n `live_board` answers what is true now). A window whose earlier half took\n nothing draws no line: there is no base for a percentage, and 0 % would claim a\n steadiness nobody measured.\n `curve` is `{"planned": \u2026, "actual": \u2026}` \u2014 two number fields of the row, each\n added up to date along the rows\' own date and drawn in that band as two lines:\n the S-curve a schedule or a budget is judged by, where a row is a period or a\n piece of work and the running total is the curve\'s own arithmetic. The date is\n the screen\'s `period` where it states one, else its `when`; a screen reading\n its rows by no date is refused. The actual line stops at the last date a row\n stated an actual figure \u2014 drawn on past it, the weeks nobody has worked yet\n would read as a stall. Refused on the shapes a `trend` is refused on, over the\n same reasons, and against itself.\n\n### Sources and fact groups\n\n- **`sources`** \u2014 other entities whose rows this register draws beside its own:\n one subject standing in two books (stock on the shelf and on consignment, a\n claim raised by the site and by the office), read once rather than as rows that\n look duplicated. Each book lines up a field for every slot the register draws,\n by the role the slot reads, and \u2014 under the same alias and type, a day beside a\n day and a moment beside a moment \u2014 every other field the register draws,\n orders or narrows by, and the `currency` a drawn amount is read in; a\n select\'s options must be among the register\'s own.\n What it lacks or holds differently is refused by name. Every book is read in\n ONE read with the register\'s own rows, sorted once by the register\'s day, else\n by its name or the day each row was created \u2014 so a page is the true first rows\n of all of them, and a register with none of the three is refused. A column\n names the book each row stands in. A narrowing a book has no column for \u2014 a\n record\'s door onto the register, a link the book does not carry \u2014 reads none of\n that book\'s rows while it is given. A book\'s row opens its own entity\'s record,\n so the door is a page; the create, the row acts and a `quick` column are the\n register\'s own entity\'s and a book\'s row carries none of them, which is why\n `acts.selection`, `acts.import`, a slot of several fields and an app `scope` are\n refused beside it. A figure computed over the rows \u2014 a line\'s share of the\n whole, the change since the row before \u2014 is not a clause: over a register read a\n page at a time, it would be a reading of the page. A value of one row is a\n `formula` field of the entity.\n- **`facts`** \u2014 how the RECORD\'s facts are banded. `groups` is a list of\n `{caption, fields}`: what a set of facts has in common is a sentence about the\n business and nothing derives it, so a plan that states one gets exactly those\n bands, in its own order, captioned. EACH BAND IS A PART of the record \u2014 its own\n heading, placed by the anatomy (\xA7 Record anatomy) as a section is, and named\n there by its `alias`: under one heading the reader is handed every particular\n at once, in a caption weight that cannot out-rank the heading above it.\n Everything the plan leaves unnamed is the LAST band and carries no heading,\n since there is no sentence to head "whatever no group claimed" with \u2014 ONE band,\n even where the record\'s own recipe bands what was left in two (an evidence\n record reads its particulars before the record and the party it was with):\n both are groups inside it, because the word the kit heads "the rest" with\n cannot name two parts. A band marked `last: true` is read\n AFTER the record\'s own work \u2014 its acts, its rows, its correspondence \u2014 for the\n particulars a reader checks once they know what is owed (who it is with, where\n it came from). `first: true` reads a band ahead of everything, the stage\n included \u2014 the facts a verdict is taken on, read before the verdict; a band\n both first and last is refused. `closes: "<outcome>"` makes a band the body of\n that outcome\'s closing step, drawn last above the fields it asks and its verb\n (its fields edited while the record is open, a formula read): refused on an\n outcome naming no verb, on a stage that ends nothing, beside `first`/`last`,\n and twice for one outcome. Where `sections` lists the record\'s sections, they\n are drawn in that order whatever each is drawn as \u2014 a log of the evidence above\n the acts it decides.\n A band of more than eight facts is NOTED: a section that long is two. A field the header, the stage, a required\n set, the files, a book\'s balance or a register below already draws, or a figure the band states that nobody\n writes, is not a fact and is refused. A band of nothing but notes (a markdown text, a `verbatim`) is refused: a\n note sits at the foot of the band it annotates. Named bands PLACE the facts, and nothing is folded: they hold\n EVERY fact a person states \u2014 one the groups leave out is refused by name \u2014 and what the platform mints, stamps\n or works out is drawn only where a band names it. An entity whose record shows `rate`, `surcharge`, a markdown\n `note` and a `created_on` stamp is banded as `[{ "caption": "Pricing", "fields": ["rate", "surcharge",\n "note"] }]` \u2014 the stamp is the header\'s.\n\n### How a screen is drawn\n\n- **`presentation`** \u2014 how the screen and the record it opens are DRAWN, where\n what the rows are is not the answer wanted. `lead` is what each row leads\n with: `"mark"` the subject\'s own mark, always spent because a party\'s initials\n stand in for a picture nobody uploaded; `"picture"` the photograph, spent only\n on the rows that carry one and, on the record, at media scale above the name;\n `"paper"` the row\'s own files, drawn as the cards a pile of documents is read\n as; `"figure"` the amount, with no gutter at all; `"none"` neither. `density` is\n how many lines a row\'s subject may take \u2014 `"roomy"` two, `"dense"` one.\n Both default, and the DEFAULT is what the rows are: a party register leads\n with the mark, an offering register with the picture, a ledger with its\n figure, and a desk \u2014 rows that are work still to do, scanned \u2014 stands its rows\n one line each while a record\'s own rows are read roomy. So state it only to\n differ, and a plan that states nothing still gets screens that vary. A lead\n the rows cannot carry is refused: a mark, a picture or a paper where no column\n is drawn as the rows\' mark, a\n figure where no column is the amount, and the other subject\'s mark (which of\n the two a row wears is the shape\'s, so the register and the record it opens\n cannot call one row two kinds of thing). A row carrying a `contact` is\n SOMEBODY even where nothing names it yet, so `"mark"` \u2014 initials where no\n picture was uploaded \u2014 is admitted over it; the default stays the picture. A\n record opened as a drawer leads its bar with that mark where `lead` is\n `"mark"`, and its files are then no section of their own.\n `line` is what the line under a register row\'s name says, in place of the key\n the row is filed under: at most two fields of the entity, each plain words (a\n `text` with no `format`, or `"text"`) or ONE option of a closed list (a single\n `select`) \u2014 where somebody works and what they do there, say. It is also what\n the row is found by. The record keeps its key. Refused: notation, a link, a\n field holding several values, the row\'s own name, and a field a slot, a column\n or a chip already draws.\n `layout` is how the rows are ARRANGED \u2014 `"table"`, `"list"`, `"cards"`,\n `"gallery"`, `"board"`, `"calendar"`, `"timeline"`, `"gantt"`, `"tree"` \u2014 and\n it is AFFORDED by what the rows carry, not by the shape: every register affords\n a table and a list, a `mark` affords the two picture-led grids, a `lifecycle`\n affords a board, a `when` affords a calendar and a timeline, and a `when`\n beside a `measure` that `counts` DAYS affords a gantt, because a bar with no\n length is a calendar drawn sideways and a bar measured in money or litres is a\n length of another kind. `until` names the date field a bar ENDS on, where the\n rows state one, and the days measure is what the bar runs for without it. A\n layout the bound roles do not afford is refused, naming the ones they do.\n `nest` names a one-row link from the entity to ITSELF \u2014 the row each row sits\n under, a work breakdown\'s or a cost plan\'s \u2014 and it is what affords `"tree"`:\n the rows drawn under the row they name, each folding what it holds, the MONEY\n each row states or works out from its own columns summed up the tree. A rollup\n is drawn as the row holds it (it already adds up rows of its own, and summed up\n the tree a parent carrying one would count its children twice), and so is a\n quantity, since two units have no sum. A row naming no row above it, or one\n the read does not hold, stands at the top. On a gantt the same `nest` folds a\n row\'s bars under a summary that spans them, a row stating no day of its own\n included. Two more clauses are the gantt\'s\n own and refused anywhere a bar cannot be drawn: `depends_on`, a link from the\n entity to itself naming the rows that must FINISH before a row starts \u2014 each an\n arrow into its start, toned where the sequence is broken \u2014 and `baseline`,\n `{"start": \u2026, "end": \u2026}`, the two date fields the plan was FIXED in, drawn as a\n ghost under the bar so a slip reads as the gap. A baseline is never the day\n the bar itself starts on: the plan kept apart from the date that moves is the\n whole of what it shows. A row not yet started has no bar of its own, and is\n drawn across its plan until it starts.\n\n ```jsonc\n // A work breakdown, laid out as its outline and scheduled against its plan.\n "presentation": { "layout": "gantt", "nest": "part_of", "depends_on": "after",\n "baseline": { "start": "planned_start", "end": "planned_finish" } }\n ```\n\n### Handing work on\n\n- **`acts`** \u2014 where this screen hands work on: `row` is the \u22EF menu on every row,\n `record` is the \u22EF on the RECORD\'s own page header \u2014 the same reach as a row\'s,\n standing where the work is open instead of while a list is scanned, and refused\n on a record that opens as a drawer, which has no header of its own and whose\n row already carries the register\'s menu \u2014 and `selection` is the bar over the\n TICKED rows. `export` is the third reach \u2014\n the WHOLE VIEW, saved in one press: `true` writes the rows as the register drew\n them, in the columns it drew, as an `.xlsx`, and\n `{"template": "\u2026"}` says the layout is the trade\'s and makes the paper with a\n workflow like any other. It is never a list of columns: which columns the\n export carries is what the screen already draws, and a second statement of it\n disagrees the moment a slot moves \u2014 the generator reads them off the drawn\n slots and writes them into the app, so the sheet\'s headings are the labels the\n register drew them under.\n `import` is the reach OPPOSITE it \u2014 a file turned into rows. The file is\n mapped column by column, each row is validated, what would change is\n previewed, and the commit UPSERTS by `key` \u2014 which is why a sheet sent twice\n moves no counts. That key is one of the entity\'s own `natural_key` fields\n (\xA7 Write rules), and an entity declaring none is refused: without a key the\n second run is a second set of the same rows. `entity` is the register\'s own \u2014\n an app is one register, and rows filed into another entity are a count nobody\n on this screen can check. `columns` is what the sheet may fill, in that order;\n absent, it is every column a person states IN WORDS. A computed one is refused\n because the workspace writes it, and a link, a member or a files column is\n refused because a cell of a sheet is words and those hold a row of another\n table or bytes \u2014 a file reaches another table through the party pattern (a\n `natural_key` on the target, named rather than picked), which is a create\'s.\n The generator writes the whole verb, as it does for a paper: an\n `import_<entity>` workflow that finds the row by `key` and then changes it or\n opens it, answering which of the two, and the staged run over it \u2014 drop, map,\n the per-row verdict, the commit. A line whose cell the column cannot hold is\n refused with the reason and the rest of the sheet still lands. A file MINTS\n rows of the register, which is the Add many times over, so a screen stating\n `writes: false` or `"children"` \u2014 which opens no row of its own \u2014 refuses the\n clause; the `export` beside it stays, because it only reads.\n Both spreadsheet reaches are read and written IN THE BROWSER, so the generated\n `src/main.tsx` imports `@lotics/app-runtime/sheets` wherever the plan states\n one of them and not otherwise; an app that has neither carries no spreadsheet\n engine at all. An act names ONE of four things, never two:\n - **`template`** \u2014 an `html` template this model declares (\xA7 Templates). The\n paper is filled from ONE row in `row`, and from the whole ticked set in\n `selection`. The generator writes the whole verb \u2014 a `generate_<template>`\n workflow that reads the row and hands the file back, and a menu item that\n opens it \u2014 so there is nothing left to bind. An `email` template is sent\n rather than opened and is refused here; a file-backed template (`excel`,\n `word`, `pdf-form`) is bytes a model has none of, so those stay the author\'s\n own `lotics app workflow set`. Two screens over DIFFERENT entities cannot\n name one template: one paper is filled from one kind of row.\n - **`{"kind": "agent", \u2026}`** \u2014 a run over ONE row. `agent` is an alias the APP\n declares (`package.json#lotics.agents`), because an agent is prose and tools\n rather than anything a workspace model can state \u2014 `lotics app check` refuses\n one nothing declares, exactly as it refuses a workflow nothing bound. `fills`\n is the fields the run may write: pressed, the reader watches the run, reviews\n what it proposes field by field against what the row holds now, and applies\n the ones they kept as ONE write through the record\'s own update. A run can\n reach nothing outside `fills`, a computed column there is refused (the\n workspace writes those), and a screen stating `writes: false` refuses an\n agent act outright. It belongs in `row`; the selection bar makes ONE paper\n from many rows, and a run per ticked row is the row\'s own act many times.\n - **`{"kind": "workflow", "workflow": "\u2026", "inputs": {\u2026}}`** \u2014 the work HANDED\n ON: a lead becomes an opportunity, an order is split into the orders placed\n on its suppliers, a request is closed. `workflow` is an alias the APP binds\n (`package.json#lotics.workflows`), because the rule behind it is the\n business\'s and no model states one \u2014 so the generator writes NO body and\n `lotics app check` refuses an alias nothing bound, exactly as it refuses an\n agent nothing declares. `inputs` is what the press hands that body: the\n workflow\'s own input name \u2192 `"record"` for the row it was pressed on, or a\n field of this entity for a value off that row. The value is what the\n workspace HOLDS \u2014 an option\'s key, a linked row\'s id, the figure \u2014 never the\n rendering. A files column is refused (the body reads the row\'s files off the\n row it is handed), a column that is not a field of the entity is refused by\n name, and one input short of the declaration is refused at the press, so the\n whole act is dropped rather than shipped half-bound. It runs ON the press:\n there is nothing to review first, which is the agent arm\'s law, and the\n body\'s own sentence is what the reader is told either way. It stands in\n `row`, in `record` and on a `section_acts` band \u2014 each reaches ONE row \u2014 and\n is refused over the ticked set, where one act makes one paper from many. The\n row it reaches is the RECORD\'s, so a screen stating `writes: false` or\n `"children"` refuses a hand-off on every one of those surfaces, exactly as it\n refuses an agent act.\n - **`{"kind": "sibling", "app": "\u2026", "record": "\u2026"}`** \u2014 a record opened in\n ANOTHER app of this plan, in place: the one door out of an app. `app` is the\n sibling\'s alias in `apps`, never an id \u2014 `lotics app create --from` binds\n each app to its alias, and a regeneration writes the app it became off the\n workspace\'s binding. `record` is a one-link of this entity to the sibling\'s\n register entity, or `"record"` for the row itself where both apps register\n one entity. It lands on the sibling\'s own record PAGE, so a sibling whose\n records open in a drawer is refused, as are this app itself and an alias the\n plan does not declare. While no app was made for the sibling the verb stands\n down and says so, and a row whose link is empty stands it down naming the\n link; outside Lotics, where there is no sibling to open, it is not drawn. It\n only opens, so a resting record keeps it; like a hand-off it reaches ONE row\n and is refused over the ticked set.\n - **`{"kind": "recording", "workflow": "\u2026", "inputs": {\u2026}}`** \u2014 a call, a\n visit, a walk-through RECORDED onto the row. Pressed, the product\'s own\n capture dialog asks which of the microphone, the system sound and the screen\n to take; the member records; once the words are transcribed the platform runs\n `workflow` with `inputs` and fills its `recording` input itself \u2014 the audio,\n the video where the screen was kept, the transcript (`""` for a silent\n recording), the length and when capture started. It is a hand-off whose run\n waits for the transcript, so it takes the `workflow` arm\'s rules \u2014 an alias\n the APP binds, `inputs` read off the row, `when`, `needs` and `place`, the\n same three reaches, refused over the ticked set and on a screen stating\n `writes: false` or `"children"` \u2014 less three: no `confirm` (the capture\n dialog is the confirmation), no `asks` and no `moves`. An input named\n `recording` is refused: the platform alone fills it. The body is the\n author\'s, and `lotics app check` refuses one whose declaration does not\n carry `recording` exactly as\n `{"type": "object", "fields": {"session_id": {"type": "text"}, "audio": {"type": "file"}, "video": {"type": "file", "required": false}, "transcript": {"type": "text"}, "duration_seconds": {"type": "number"}, "recorded_at": {"type": "datetime"}}}`,\n printing that declaration. The body files `video` where it is present and\n `audio` otherwise \u2014 never both, they carry the same sound \u2014 into the row\'s\n `recording` field, and writes `transcript` into its `verbatim` field\n (\xA7 Field roles). An app opened\n outside the product, or on an instance without transcription, does not draw\n the act. `scaffold check` prints it as `"<label>" [records \u2192 <alias>(\u2026)] [you\n write the body]`.\n\n **`"when"` stands an act down until the work is ready.** `{ "field": "<the\n entity\'s lifecycle>", "in": ["<stage>", \u2026] }` offers the verb only while the\n row stands at one of those stages. A verb that cannot work yet is worse than\n no verb: the reader presses it, the body refuses, and nothing on the screen\n ever said the work was not ready. Readiness is what a lifecycle states, so the\n field named is this entity\'s `lifecycle` and nothing else \u2014 a category says\n what a row is and a verdict answers a question, and neither moves. Refused: a\n field that is not the lifecycle, a stage it does not declare, and every stage\n at once, which is the act with no condition.\n\n **`"place": "cta"` draws an act ON the row** instead of in its \u22EF \u2014 the one verb\n a reader presses without opening a menu first. At most one per screen and every\n other act stays in the menu, because the trailing gutter is paid for by every\n row: a second is refused with *one verb on the row, the rest in its menu*. The\n ticked set has no row of its own, so `cta` is refused there.\n\n **`"confirm": true` asks before the act runs**, with the act\'s own label as the\n commit word \u2014 for a press that cannot be taken back: something goes public,\n something leaves the building, a figure is filed with somebody else. Everything\n else runs on the press, because a dialog in front of a reversible act is one\n readers learn to dismiss, and then dismiss in front of the one that mattered.\n It reads the same wherever the verb stands \u2014 a row\'s \u22EF, a row\'s own button, a\n record\'s menu, a section\'s band \u2014 and `scaffold check` prints it as\n `[asks first]`.\n\n **`"needs"` stands an act down until the row HOLDS what it works with.**\n `["<field>", \u2026]` offers the verb only while at least one of those fields holds a\n value \u2014 `when` asks where the row stands, this asks what it holds. A hand-off\n to somebody who reaches a person needs a way to reach them; a paper sent by\n post needs an address. ANY of them, because one person is reached several ways\n and one is enough to start. The verb is drawn standing down and names what it\n waits for, rather than pressed into a refusal. Any field answers, a lookup\n included \u2014 only whether it holds a value is read. Refused: a field this entity\n has not got, the same field twice, and `needs` over the ticked set, where rows\n hold values in some and not in others. `scaffold check` prints it as\n `[needs <field> or <field>]`.\n\n **`"asks"` is what the reader STATES before a hand-off runs** \u2014 the workflow\'s\n own input name \u2192 a field of this entity whose editor the act\'s panel draws,\n starting at the row\'s value. A hand-off to a person is to WHICH person, and the\n row cannot answer that: its value is who holds it now. The run is handed what\n the reader stated beside its `inputs`. The editor is one the panel draws with\n nothing else in hand \u2014 a text, a number, a date, a yes/no, a select or a member\n \u2014 so a link (a picker and a read of its own), a pile and a computed field are\n refused, and so is an input both handed and asked. `workflow` acts only.\n\n **`"moves"` is the stage the body lands the row at**, where a hand-off moves\n it at all \u2014 an option of this entity\'s lifecycle, and the ACT\'s alone: no\n stage picker, ladder, board or verdict moves a row there, or past it, because\n a row moved there by hand stands with none of what the body writes beside the\n move. Refused: a stage the lifecycle has not got. `scaffold check` prints it\n as `[only way to <stage>]`.\n\n **`"sets"` is the fields of this record the body writes.** One that no draft\n (`create`), closing band, outcome ask, agent `fills` or act panel (`asks`) of\n the app writes is drawn read-only on the record: its value is what the body\n wrote, and an editor beside it would state it behind the body\'s back. An\n unstated `create` asks every field a person states, so nothing is read-only\n there. Refused: a field this entity has not got \u2014 another entity\'s included \u2014\n and a column the workspace computes. `scaffold check` prints such a fact as\n `[read-only: "<label>" sets it]`.\n\n A hand-off\'s BODY is still the author\'s: `lotics app workflow set <alias>`\n binds it, and `scaffold check` prints the act as `"<label>" \u2192 <alias>(\u2026) [you\n write the body]` so it is read as work owed rather than as a verb already\n wired.\n\n A `selection` act is a `template` and nothing else: an act over many rows makes\n ONE paper from them. The generator turns the register\'s checkbox gutter\n on, emits a `generate_<template>` workflow taking the ticked ids, and the body\n reads each row and passes them as `rows` \u2014 which is what the template iterates\n (`{{#each rows}}`). One template is one paper AND one reach: naming the same\n one from a row\'s \u22EF and from the bar is refused, because those are two bodies.\n A shape whose rows are DERIVED rather than records \u2014 an `obligation_desk` \u2014\n has no set to tick and refuses the clause. The paper is handed BACK, never\n filed: a ticked set can hold rows of three different parents, so there is no\n record on which "this document belongs here" is true.\n\n<!-- generated:start apps-acts -->\n\n#### Acts\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `row` | list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | The acts in every row\'s \u22EF menu, in this order |\n| `record` | list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | The acts in the RECORD\'s own header menu, in this order \u2014 the same reach as a row\'s, with the work open |\n| `selection` | list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | The acts over the TICKED rows \u2014 each a `template`, generating ONE document over the set |\n| `export` | `true` \\| [Export template](#export-template) | no | Save the rows in view \u2014 true for a workbook of the drawn columns, or a template this model declares |\n| `import` | [Import](#import) | no | Turn a file into rows \u2014 mapped, validated per row, previewed, then upserted by the entity\'s natural key |\n\n#### Paper act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `template` | alias | yes | A template alias this model declares; a row\'s act generates that document from the row and opens it |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n| `confirm` | boolean | no | Ask before this act runs, with its own label as the commit word \u2014 for a press that cannot be taken back |\n\n#### Agent act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"agent"` | yes | A run of an app agent over the row, whose proposed values the reader reviews before one write |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `agent` | alias | yes | An agent alias the APP declares (package.json#lotics.agents) |\n| `fills` | list of alias (at least one) | yes | The fields of this entity the run may write \u2014 reviewed by the reader, then written as ONE update |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n| `confirm` | boolean | no | Ask before this act runs, with its own label as the commit word \u2014 for a press that cannot be taken back |\n\n#### Workflow act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"workflow"` | yes | The row handed to a workflow the app binds, run on the press |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `workflow` | alias | yes | A workflow alias the APP declares and binds (package.json#lotics.workflows) |\n| `inputs` | map of alias \u2192 alias | yes | What the run is handed: the workflow\'s own input name \u2192 the value on the row it is pressed on |\n| `asks` | map of alias \u2192 alias | no | What the reader states in the act\'s panel before it runs: the workflow\'s own input name \u2192 the field whose editor asks it |\n| `moves` | alias | no | The stage of this entity\'s lifecycle the body lands the row at \u2014 the act\'s alone: no stage picker, ladder or board of this app moves a row there |\n| `sets` | list of alias (at least one) | no | The fields of this entity the body writes \u2014 read-only on the record where no draft, closing step, outcome, agent or act panel of this app writes them |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n| `confirm` | boolean | no | Ask before this act runs, with its own label as the commit word \u2014 for a press that cannot be taken back |\n\n#### Sibling act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"sibling"` | yes | A record of this row opened in a sibling app of this plan, in place |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `app` | alias | yes | The sibling app, by its alias in this plan\'s `apps`; the app it became is read off the workspace\'s binding |\n| `record` | alias | yes | A one-link of this entity to the sibling\'s register entity, or "record" for the row itself where both apps register one entity |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n\n#### Recording act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"recording"` | yes | A call, a visit, a walk-through recorded onto the row and filed through a workflow once transcribed |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `workflow` | alias | yes | A workflow alias the APP binds (package.json#lotics.workflows), whose contract declares the platform\'s `recording` input |\n| `inputs` | map of alias \u2192 alias | yes | What the run is handed beside `recording`: the workflow\'s own input name \u2192 the value on the row it is pressed on |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n\n#### Act condition\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The lifecycle field of this entity the state is read off |\n| `in` | list of alias (at least one) | yes | The option aliases of that lifecycle the act is offered at |\n\n#### Export template\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `template` | alias | yes | A template alias this model declares, filled from the rows in view |\n\n#### Import\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"import"` | yes | A spreadsheet turned into rows of this register |\n| `label` | text | yes | The act\'s own words, as the reader reads them over the register |\n| `entity` | alias | yes | The entity the file\'s rows land in |\n| `key` | alias | yes | The field a row is matched on \u2014 one of that entity\'s `natural_key` fields (\xA7 Write rules) |\n| `columns` | list of alias (at least one) | no | The fields the file may fill, in this order \u2014 absent, every field a person states |\n\n<!-- generated:end apps-acts -->\n\n### Which sections a record draws\n\n**`section_acts` puts a verb WHERE ITS EFFECT LANDS.** "Chase the paperwork"\nbelongs on the papers, not in the register\'s \u22EF two surfaces away. It is keyed by\nthe alias each section is DERIVED from \u2014 a field\'s (its ladder, its prose, its\nrequired set, its charge, one of its files) or a CHILD ENTITY\'s (its register,\nits desk, its run, its log) \u2014 because the sections come off the roles and the\nkey the app addresses one by does not exist until it is built. An alias naming no\nsection is refused, listing the ones that do; the facts band is what every other\nsection left over, so nothing addresses it. Each act is a `template`, an `agent`,\na `workflow` or a `recording`, exactly as a row\'s is \u2014 the reach is the same one row \u2014 and\n`place: "cta"` is refused: a section is a band with a heading and has no row to\ndraw a verb on. `scaffold check` prints\nwhich section each alias landed on, which is the half an author cannot see in\ntheir own file.\n\n**`sections` says WHICH of those sections this app\'s record draws, in what\norder, and how** \u2014 one ordered list, each entry the alias `section_acts` would\naddress a section by: a child entity alias draws that child\'s rows, a field alias\n(its files, its prose, its charge, its set, its ladder) draws that field\'s own\nsection, and `{"of": "<child>", \u2026}` draws that child\'s rows the way the entry\nsays (below). Absent, the record draws every section it derives, in its recipe\'s\norder \u2014 `scaffold check` prints `sections: every one the record derives (default)`.\nPresent, it draws exactly the listed sections in exactly that order, in the\nplaces the record\'s recipe gives the set of them; a section left out is not\ndrawn, a link into a child left out is not drawn as a fact either, and `[]` draws\nnone. Two apps over one entity read it for different jobs: the site\'s daily log\nbelongs on the site team\'s record and not on the owner\'s. The facts are no entry\n\u2014 `facts.groups[].first`/`last` place their bands \u2014 and **a closing step always\nends the tab it is read in**, after every band there: the one fixed rule. A\ncounted child (a relation drawn as a counted tab) reads with the other\ncounted tabs after every section, so it is listed after them; of the registers\nof rows the record does not own, the first listed is the one standing open.\nRefused, each naming the fix: a name that is no section of the record, one listed\ntwice, settings on a field\'s own section, `ladder: true` with the flow left out,\na counted child listed ahead of a section, and no child at all on a screen whose\n`writes` is `"children"`. A `children` list or a `sections` map is refused with\nthe array that draws the same record, ready to paste.\n\nWhere `sections` is absent, a screen whose `writes` is `"children"` leads with\nthe sections whose rows the reader writes, a log composed in place first; a\nstated `sections` is drawn as it stands.\n\n```jsonc\n"sections": ["task", "contract_scan", "claim"] // the schedule, the contract\'s pile, then the claims\n```\n\n### Record anatomy\n\n**A record reads in three parts, one anatomy on a drawer and a page** \u2014 the\nhead, the brief standing under it at rest, and the tabs one press away. A page\nadds width only: the brief stands beside the tabs.\n\n**The head** is the mark and name, the stage with its move menu, the ways to\nreach the subject, the line under the name, and ONE primary act. Where the queue\nmarked `cta` has an act waiting, that act leads, done through the queue\'s own\nreach and outcome sheet. Otherwise the head does what the plan decided for the\nstage the record stands at:\n\n- an act whose `when` names that stage (the row\'s `cta` act first) leads there;\n- else the stage\'s own move, taken the way the plan lands it: the closing step\n whose ending it reaches (the head opens its confirm), the workflow act that\n `moves` there, the queue whose logged touch `moves` there (the head opens its\n add), and a bare write \u2014 asking what that stage asks \u2014 only where nothing owns\n the move. Past the flow\'s last stage the move is to the ending such an act or\n the closing step reaches, else the first ending no verdict marks `fail`;\n- a status with no ending (no `outcomes`, no `phases`) is a menu and moves\n nowhere; a record reached to read \u2014 a trend\'s or a group\'s drill-down, a party\n named by the register\'s rows, a row whose stages a queue or a publish desk\n above it writes \u2014 derives no move;\n- at a stage none of this reaches, the row\'s ungated `cta` act, else \u2014 on a\n screen with `writes: "children"` \u2014 the add of the one register it adds to,\n whose tab then opens first.\n\nElse none \u2014 never a placeholder. A second queue marked `cta` is refused, and a\nstage the flow walks through whose head is bare while the closing step waits\nthere is noted.\n\n**The brief** states only what the primary act needs: for a queue, why now \u2014\nthe newest entry of the first log people write (a log only automations append\nis a receipt, read under Activity) \u2014 and the next act\'s words; for a closing\nstep, the band it `closes`, standing from the stage the step is the head\'s move\nand, once closed, as the outcome with when and by whom; and the band placed\n`first`. A record whose head carries no queue briefs no log. Nothing in the\nbrief repeats the head \u2014 a band read first naming a field the head draws is\nrefused \u2014 and the tab open at rest is the first one repeating nothing the brief\ndraws. A band in the brief stands as one row of values, each opening its control\nor its whole reading on a press; a log\'s entry as its headline, two lines and\none picture, the rest a press away.\n\n**The tabs**, in order: the registers whose rows make up the record (priced\nlines, a book, rows carrying an amount or rolled up into the record), each its\nown tab; Activity \u2014 every log and queue, and the stage changes a lifecycle with\n`history: true` keeps; Details \u2014 every other band (`last` ones included), the\nrecord\'s own sections with its picture first, rows of its own kind (a nesting,\na dependency) named from its side, and a closing step no head carries last;\neach other register it owns; Related \u2014 the rows that merely name it, stacked and\ncounted (one such relation is its own tab); History, last, only where no log\nholds the stage changes. Past four tabs, owned registers fold into Related, the\nlast first. A tab with nothing to show is not drawn, and a countable one states\nits count.\n\n**`record: {"door", "brief", "tabs"}` places the parts instead**, each named by\nthe alias `sections` addresses its section by, the lifecycle by its field (its\nkept stage changes and its closing step), or a band by its `facts.groups[].alias`.\n`brief` alone keeps the derived tabs over what it does not hold whole; `tabs`\nplaces every part the brief does not hold whole \u2014 a log or a queue in the brief\nshows its latest part there, so it still needs a tab. Refused, each naming the\nfix: a name that is no section or band of the record, a part in two tabs or in\nboth the brief and a tab, a part placed nowhere, two tabs sharing a label, and a\nband alias that is also a section\'s. `scaffold check` prints each record\'s\n`anatomy:` line \u2014 head, brief and tabs, `(default)` where the roles decided.\n\n<!-- generated:start apps-anatomy -->\n\n#### Record clause\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `door` | `"drawer"` \\| `"page"` \\| `"expand"` | no | "drawer", "page", or "expand"; absent, the shape decides |\n| `brief` | list of alias | no | What stands under the head at rest, in this order \u2014 a log by its newest entry, a queue by its next act\'s words; absent, derived from the roles |\n| `tabs` | list of [Record tab](#record-tab) (at least one) | no | The tabs, in this order \u2014 every section and band not wholly in the brief is in one; absent, derived from the roles |\n\n#### Record tab\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | The tab\'s word |\n| `of` | list of alias (at least one) | yes | The sections and bands it holds, in this order |\n\n<!-- generated:end apps-anatomy -->\n\n```jsonc\n"record": { "door": "page", "brief": ["delivery"], "tabs": [{ "label": "Paperwork", "of": ["papers", "terms"] }] }\n```\n\n### How a section draws its rows\n\n**An object entry says HOW a child\'s rows are DRAWN**, its `of` naming the CHILD\nENTITY \u2014 every reading is over a child\'s ROWS, so settings on a field\'s own\nsection are refused. An entry here OUTRANKS the table above:\nthe derivations there recognise a shape off columns that are also ordinary\ncolumns, so a markdown field holding a refusal reads as somebody\'s message. Where\nthis entry names the reading, it is the reading. The fragments below show one\nentry each; a record\'s list names every section it draws.\n\nEvery reading takes an optional `heading` \u2014 what the section is headed on the\nrecord \u2014 and a register, a book or a tree takes `columns`: the child\'s fields it\ndraws, in order, at most four, each one its role or its type draws as a column\n(absent, the roles decide). The child\'s label names the SET its table holds ("Placements", "Tasks");\na section is read as the question it answers ("Where it goes", "Waiting on us"),\nand where the two differ the heading says the second. `{"heading": "\u2026"}` ALONE\nrenames any section of the rows this record OWNS \u2014 a log, a correspondence, a\nregister \u2014 without changing how the roles draw it; on rows the record merely\nlists it is refused, naming the rows it owns.\n\n`facts` names fields of THE RECORD drawn at the head of the section, above its\nrows \u2014 the facts those rows are read against: a job\'s window over its visits, a\nquote\'s figure over its payments. One section, because a reader reads the rows\nagainst them; a band of the same dates elsewhere on the record is read apart\nfrom the rows it frames. A field named here is filed here: it leaves the\nrecord\'s facts, counts as filed for `facts.groups`, and the clause\'s `heading`\nnames the section it and the rows make. It stands alone or beside `ledger`,\n`itinerary`, `worksheet`, `claim`, `tree`, `gantt` and `curve` \u2014 a register, a\nrun or a book of rows the record OWNS; a log, a schedule, a desk and a queue draw\nno head. Refused: a name that is no field of the record, a field another section\nalready states (the header\'s name, the stage, a pile), the limit a fact\'s level\nalready reads its measure against, one named twice or also in a band \u2014 naming\nboth \u2014 and a clause over rows the record does not own or draws as one counted\nline.\n\n```jsonc\n// A removal job: the move\'s window frames its stops, read top to bottom as one section.\n"sections": [{ "of": "stop", "heading": "The move", "facts": ["pack_on", "deliver_on", "crew_size"] }, \u2026]\n```\n\n`"draw": "ledger"` reads the rows as a book of movements where the roles alone\nwould read a register. A book derives no head from its rows: the record\'s\nfigures are the header band\'s and the facts\', and a figure is drawn once per\nrecord \u2014 `facts` moves one of them over the book, out of the band.\nThe book closes on its own total, read AGAINST what the record OWES \u2014 the limit\nthe record\'s `rollup` over these rows is a `measure` against (`field_roles`:\n`{ "role": "measure", "against": "<owed>" }`) \u2014 where the record carries one;\nthe binder emits that column as the section\'s `against`, and the balance line\nunder the rows reads the total against it. A total read against its own sum\nwould balance at zero by construction, so a rollup no measure reads closes the\nbook on its total alone. A `summary` on this clause is refused naming `against`.\n`"draw": "worksheet"`: the child\'s rows\nare PRICED LINES the reader works down in place, each part of the job footed and\nthe sheet closing under them. `cost` and `sell` name the child\'s two figures,\nwhich is the one thing the roles cannot decide \u2014 an entity carries one `amount`,\nand a line that is costed and sold carries two. Everything else is read off the\nchild\'s own roles: what a line is FOR is its `identity`, how many it is for is\nthe `measure` the pair left, and the part it falls under is its `category`. The\nMARGIN is on none of them, because it is arithmetic over the pair.\n\n`"draw": "claim"`: the child\'s rows are the LINES OF ONE PERIOD\'S CLAIM against\na priced schedule \u2014 each read as what the contract holds, what the periods\nbefore claimed, what this one claims, what that comes to to date, the share done\nand what is left, with a retention held back under the total. Four figures, and\nthree of them are quantities no role tells apart, so the clause names each:\n`quantity` (THIS period\'s, the one cell the reader edits \u2014 a `number` nothing\ncomputes, written through the line\'s own update), `contract` and `price`, and\n`previous` \u2014 what the periods before claimed on the line, cumulative. Every\namount is arithmetic over those four, so no column states one; `unit` optionally\nnames what the line is counted in, the line\'s `reference` is its code, and\n`retention` names a percentage field of THIS record, the share held back.\n\n`previous` is DERIVED, and a `number` there is refused: a cumulative somebody\ntypes is right until the first earlier period is corrected, and wrong on every\nclaim after it with nothing saying so. It is a `rollup` SUMMING THIS PERIOD\'S\nQUANTITY OVER THE SAME ITEM\'S EARLIER LINES \u2014 a line per contract item per\nperiod, each linked (a many-row link of the line to its own entity) to that\nitem\'s line in every period before. Never a lookup of the prior line\'s own\nrunning total: that total is worked out from the lookup, so the column is\ncomputed from itself, has no order to compute in, and is refused as a cycle.\n\n```json\n{\n "entities": [\n { "alias": "item", "label": "Items", "fields": [\n { "alias": "name", "label": "Item", "type": "text", "required": true },\n { "alias": "unit", "label": "Unit", "type": "text" },\n { "alias": "quantity", "label": "Contract quantity", "type": "number" },\n { "alias": "price", "label": "Unit price", "type": "number", "format": "currency", "currency": "USD" } ] },\n { "alias": "claim", "label": "Claims", "singular": "Claim", "fields": [\n { "alias": "name", "label": "Claim", "type": "text", "required": true },\n { "alias": "stage", "label": "Stage", "type": "select", "options": [\n { "alias": "draft", "label": "Draft", "color": "slate" },\n { "alias": "certified", "label": "Certified", "color": "green" } ] },\n { "alias": "retention", "label": "Retention", "type": "number", "format": "percentage" },\n { "alias": "lines", "label": "Claim lines", "type": "select_record_link", "target_entity": "claim_line",\n "cardinality": "many", "sync_both_ways": true, "paired_field_alias": "claim" } ] },\n { "alias": "claim_line", "label": "Claim lines", "singular": "Claim line", "fields": [\n { "alias": "claim", "label": "Claim", "type": "select_record_link", "target_entity": "claim",\n "cardinality": "one", "required": true, "sync_both_ways": true, "paired_field_alias": "lines" },\n { "alias": "item", "label": "Item", "type": "select_record_link", "target_entity": "item",\n "cardinality": "one", "required": true, "display_field_aliases": ["name"] },\n { "alias": "earlier", "label": "Earlier lines", "type": "select_record_link", "target_entity": "claim_line" },\n { "alias": "quantity", "label": "This period", "type": "number" },\n { "alias": "claimed_before", "label": "Claimed before", "type": "rollup", "source_field_alias": "earlier",\n "aggregate_option": { "operation": "sum", "field_key": "quantity" } },\n { "alias": "contract_quantity", "label": "Contract quantity", "type": "lookup",\n "source_field_alias": "item", "lookup_field_alias": "quantity" },\n { "alias": "unit_price", "label": "Unit price", "type": "lookup", "source_field_alias": "item", "lookup_field_alias": "price" },\n { "alias": "unit", "label": "Unit", "type": "lookup", "source_field_alias": "item", "lookup_field_alias": "unit" } ] }\n ],\n "field_roles": {\n "item": { "name": "identity" },\n "claim": { "name": "identity",\n "stage": { "role": "lifecycle", "outcomes": ["certified"],\n "phases": [{ "label": "Claiming", "stages": ["draft"] }], "history": true } },\n "claim_line": { "item": "identity", "claim": "parent" }\n },\n "apps": [\n { "alias": "claims", "name": "Claims",\n "screen": { "alias": "claims", "label": "Claims", "shape": "lifecycle_desk", "entity": "claim",\n "sections": [{ "of": "claim_line", "draw": "claim", "quantity": "quantity", "contract": "contract_quantity",\n "price": "unit_price", "previous": "claimed_before", "unit": "unit", "retention": "retention" }] } }\n ]\n}\n```\n\nThe workflow that opens a period writes its lines and links each to the same\nitem\'s lines before it; from then on a correction to any period reaches every\nlater claim through the rollup by itself. The draw is over rows the record OWNS,\nlike a sheet\'s.\n\n### Trees, bars, curves, logs and sheets\n\n`"draw": "tree"`: the rows nested under each other through `nest`, a one-row\nlink of the CHILD to itself \u2014 a record\'s breakdown drawn as its outline, each\nrow folding what it holds and the money summed up the tree the way a register\'s\n`layout: "tree"` sums it. Unlike the other draws it is an arrangement and not a\nwrite, so it reads rows reaching the record through any link; a relation the\nrecord only counts has no rows here to nest and is refused.\n\n`"draw": "gantt"`: the rows as bars across the calendar \u2014 a record\'s own\nschedule, drawn by the rules a register\'s `layout: "gantt"` is. Each bar starts\non the child\'s `when` and runs the days its `measure` that `counts` days states,\nor to `until`; `nest`, `depends_on` and `baseline` fold, link and ghost the bars\nexactly as the register\'s clauses of the same names do, over the child\'s own\ncolumns, and are refused for the same reasons. Rows none of which a day or a\nplan places are listed as a register lists them.\n\n`"draw": "curve"`: two number fields of the rows, `planned` and `actual`, each\nadded up to date along the child\'s `when` and drawn as two lines \u2014 the S-curve of\na record\'s own work, read as `summary.curve` reads a register\'s. The actual line\nstops at the last day a row stated one, and a curve against itself, over rows\nwith no `when` or over anything but numbers is refused. Both are arrangements\nlike a tree: they read the rows through any link the record lists them by.\n\n```jsonc\n// A job\'s work breakdown as its schedule, and its value earned against plan.\n"sections": [{ "of": "task", "draw": "gantt", "nest": "part_of", "depends_on": "after",\n "baseline": { "start": "planned_start", "end": "planned_finish" } }, \u2026]\n"sections": [{ "of": "task", "draw": "curve", "planned": "planned_value", "actual": "earned_value" }, \u2026]\n```\n\n`"draw": "log"` and `"draw": "itinerary"` PLACE a child\'s fields on the entry\nits rows are read as, where the roles\' placement is not the reading. A log\'s\nentry and a run\'s stop are ONE anatomy \u2014 a `name` that leads, `words` under it,\nat most two supporting `lines` of at most four fields each, a `badge` at the\nright that is one select\'s option, a `figure` at the head, `evidence` shown\nunder the words and `detail` behind one fold \u2014 and the roles decide every rung\nby themselves. On a LOG the `identity` leads, the `body` is the words, the\n`recording` is the evidence, the `verbatim` the detail, the selects, the parties\nand the `contact` make the one line, the `amount` is the figure, and nothing\nwears the badge \u2014 an entry has no stage. On a RUN the `identity` leads, the\n`slot` and the first `party` make the first line, the `amount` is a second line\nof money words, and the `lifecycle` wears the badge. The `category` is on no\nrung: the name and the mark already say what the stop is and its own record has\nthe rest, so a clause places it where a business reads its stops by kind. The\nentry\'s day is its axis and never a run of a line.\n\nSo the clause is for the field a role cannot place: a photo that is the\nevidence rather than an attachment, a headcount worn as the `figure`, a line read\nby two words instead of five, a stop\'s hours or its kind on its line.\nEach rung named replaces that rung whole; a rung left out keeps the roles\'\nanswer minus whatever the clause placed elsewhere, so a field is drawn once on\nan entry \u2014 an `amount` the clause reads on a line is no longer the figure at the\nhead. A field the clause places is never also a fact under the words, and one\nthe roles placed on a rung the clause rewrote without it is a fact again \u2014 a\n`verbatim` left off the `detail` is read by its label, and folds nothing. A\nfield the child lacks, one placed on two rungs, the entry\'s own day on any rung\n(the surface that stacks the entries draws it already), a `badge` that is not a\nsingle select, a `figure` that is not a number, and on a run the stage on any\nrung but the badge or a line, or a `badge` naming another select while no line\nreads the stage, are each refused naming the places. `words` may make rows a log that the roles would not \u2014 a plain text with\nno `body` role \u2014 but nothing makes rows with no day one.\n\n```jsonc\n// A site diary: the photos are what the entry shows, the crew count sits at its head,\n// and the line says only what kind of visit it was and where.\n"sections": [{ "of": "visit", "draw": "log", "evidence": ["photos"], "figure": "crew", "lines": [["kind", "site"]] }, \u2026]\n```\n\n```jsonc\n// A cleaning round: the stop is read by its hours and the flat, and the stage stays on the badge.\n"sections": [{ "of": "visit", "draw": "itinerary", "lines": [["hours", "flat"]] }, \u2026]\n// A fitting run read by its stage on the line, with the kind of job as the badge.\n"sections": [{ "of": "fitting", "draw": "itinerary", "lines": [["window", "fitter", "status"], ["line_total"]], "badge": "handling" }, \u2026]\n```\n\nA sheet is the WRITE side of a price, so it is the one child section whose cells\nare editors: a figure typed into a cell is written as a diff through the child\'s\nown update, which is the same editor the line\'s own drawer saves through. Every\nother child section is read where it is drawn and edited in the row\'s drawer. A\nline is ADDED from the section\'s heading as every child\'s row is, with the record\nit hangs under as the act\'s own context.\n\n### Publish sections\n\n`"draw": "publish"`: the child\'s rows are ONE RECORD STANDING ON N\nDESTINATIONS, and the section is drawn as the strip of those destinations rather\nthan as a register of the rows. A register of them is the same record listed once\nper destination with a stage beside it; what a reader wants is every destination\nthey COULD reach, each wearing what has happened there, and the one press that\nsends the ones that are ready.\n\n```jsonc\n"sections": [{\n "of": "pinning",\n "draw": "publish",\n "states": { "queued": "waiting", "published": "up", "failed": "refused", "by_hand": "by_hand" },\n "text": "wording",\n "permalink": "address",\n "error": "fault",\n "link": "source",\n "destination": {\n "network": { "field": "surface", "options": { "page": "facebook", "feed": "instagram" } },\n "connected": "account",\n "bodies": { "field": "tongue", "options": { "near": "wording_near", "far": "wording_far" } },\n "offered": { "field": "standing", "in": ["open"] }\n },\n "publish": "send_pinnings",\n "when": { "field": "state", "in": ["ready"] }\n}, \u2026]\n```\n\nA destination is queued by opening a row, which is why `queued` is the stage the\nchild\'s flow opens at. A network that takes a card OR a picture takes neither\ntwice, so a plan whose records always carry both a `link` and a `mark` leaves the\ndesk nothing it can send there.\n\n`when` is an act\'s `when` (\xA7 Apps and screens, `acts`), read off THIS record\'s\nown lifecycle: a piece still being written is not one to send, and the desk\'s\nPublish is the one press on the page that cannot be taken back. The same refusals\nhold \u2014 a field that is not this entity\'s lifecycle, a stage it does not declare.\n\nEverything else is read off the roles: the child\'s `parent` (the record), its\n`party` (the destination entity), its `lifecycle` and its `when`; the\ndestination\'s `identity`, `mark`, `contact` (where a post put up by hand is\npasted) and `category` (what the strip folds the chips under); and this record\'s\nown `mark` (the pile the post carries). The destinations are read WHOLE, through\na query of their own, because the strip\'s point is the ones this record has not\ngone to yet \u2014 which the child\'s own read structurally cannot answer with. The\nREGISTER\'s row wears one mark per destination its record has reached, read one\npage of rows at a time; the face and the network each mark wears are read across\nthe `party` link by that read itself, so the model declares no lookup for them.\n\nA network the INSTANCE is not configured for is refused when the send\'s body is\nbound (`tool_capability_unavailable`): a check over a file cannot know which\nproviders this instance holds keys for, so the map is proven against the kit\'s\nnetworks alone and the rest is answered where the body is.\n\nA chip IS the draft, which is why the section mounts no Add: pressing an empty\ndestination opens a row, pressing a queued one takes it back, and both are\nworkflows the generator writes and binds. The create takes the RECORD and the\nDESTINATION and nothing else \u2014 every other column of one of these rows is what\nthe send writes back \u2014 and it reads the desk first, refusing a destination this\nrecord already stands on: a surface guards a second press for the life of a\nmount, and a second tab is a second mount. The withdrawal is the ONE generated\nbody that deletes, and it reads the stage first and refuses anything past the\nqueue. A delete names no column, so `app create` declares the child\'s TABLE in\n`package.json#lotics.deletes` beside the columns in `lotics.writes`, and `lotics\napp check` refuses a body that deletes from a table nothing there names. The SEND\nis the author\'s, like every other hand-off: `lotics app check`\nrefuses an alias nothing bound, and the press hands it the record and the rows it\nis sending.\n\nRefused: an alias naming no register of this record, rows the record does not\nOWN for any draw but a tree, a gantt or a curve (a line added to a history\nbelongs to the job it is about, not to what is reading it), a figure that is not a field of the child or whose role is neither\n`amount` nor `measure`, a sheet naming neither figure, one figure priced as both,\na book headed with a `summary`, and a placement over a section this record\ndraws as a register rather than as a run. A desk is refused\nfor rows that name no destination or carry no lifecycle, a state naming an option\nthat lifecycle has not got, a `queued` stage that is not the one the flow opens\nat, a destination that names no row of itself, a network the kit cannot preview,\nan option of the destination\'s own selects it has not got, and a body, an\noverride, a permalink, a refusal, a card address or a connected account that is\nnot a text field of the entity it is named on. A SECOND desk on one record is\nrefused too: the strip is every destination that record can reach, so two are two\nanswers to one question, and a row of the register wears the marks of one.\n\n### Section keys\n\n<!-- generated:start apps-sections -->\n\n#### Worksheet section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"worksheet"` | yes | Priced lines the reader works down in place, each part footed and the sheet closing under them |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `cost` | alias | no | The child\'s field holding what a line COSTS \u2014 the base the margin is taken against |\n| `sell` | alias | no | The child\'s field holding what it SELLS for \u2014 the figure the sheet is read for |\n\n#### Ledger section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"ledger"` | yes | A book of movements \u2014 closes on its total, read against the record\'s rollup that sums it |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `columns` | list of alias (1\u20134) | no | The child\'s fields this register draws as its columns, in this order \u2014 at most 4, each one its role or its type draws as a column; absent, the child\'s roles decide |\n\n#### Itinerary section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"itinerary"` | yes | A run of stops read a day at a time \u2014 stated to place the child\'s fields on the stop where the roles\' placement is not the reading |\n| `name` | alias | no | The child\'s field that NAMES the entry and leads it; absent, its `identity` |\n| `words` | alias | no | The child\'s text field the entry SAYS; absent, its `body`, else (on a log) its first markdown text |\n| `lines` | list of list of alias (1\u20134) (1\u20132) | no | The entry\'s supporting lines, at most 2, each the child\'s fields it reads in order, at most 4; absent, a log reads its selects, the parties it was with and the address it reached them at, and a stop reads its slot and its party, with its amount on a second line |\n| `badge` | alias | no | The child\'s select drawn as the entry\'s status at the right \u2014 a lifecycle or a category; absent, a stop\'s lifecycle, and nothing on a log |\n| `figure` | alias | no | The child\'s number worn at the trailing end of the entry\'s head; absent, a log\'s amount, and nothing on a stop |\n| `evidence` | list of alias (at least one) | no | The child\'s fields drawn under the words as what the entry shows, a playable file played; absent, its `recording` |\n| `detail` | list of alias (at least one) | no | The child\'s fields kept behind one fold under the words; absent, its `verbatim` |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n\n#### Log section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"log"` | yes | Dated entries read in the words each states \u2014 stated to place the child\'s fields on the entry where the roles\' placement is not the reading |\n| `name` | alias | no | The child\'s field that NAMES the entry and leads it; absent, its `identity` |\n| `words` | alias | no | The child\'s text field the entry SAYS; absent, its `body`, else (on a log) its first markdown text |\n| `lines` | list of list of alias (1\u20134) (1\u20132) | no | The entry\'s supporting lines, at most 2, each the child\'s fields it reads in order, at most 4; absent, a log reads its selects, the parties it was with and the address it reached them at, and a stop reads its slot and its party, with its amount on a second line |\n| `badge` | alias | no | The child\'s select drawn as the entry\'s status at the right \u2014 a lifecycle or a category; absent, a stop\'s lifecycle, and nothing on a log |\n| `figure` | alias | no | The child\'s number worn at the trailing end of the entry\'s head; absent, a log\'s amount, and nothing on a stop |\n| `evidence` | list of alias (at least one) | no | The child\'s fields drawn under the words as what the entry shows, a playable file played; absent, its `recording` |\n| `detail` | list of alias (at least one) | no | The child\'s fields kept behind one fold under the words; absent, its `verbatim` |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n\n#### Claim section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"claim"` | yes | Lines claimed against a priced schedule, one period at a time \u2014 the contract, before, now, to date and what is left |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `quantity` | alias | yes | The child\'s number field THIS period\'s quantity is stated in \u2014 the one cell the reader edits |\n| `contract` | alias | yes | The child\'s field holding the quantity the contract prices the line for |\n| `price` | alias | yes | The child\'s field holding the line\'s unit price |\n| `previous` | alias | yes | The child\'s DERIVED field holding what earlier periods claimed on the line, cumulative \u2014 a rollup summing `quantity` over the same item\'s earlier lines, never a number somebody types |\n| `unit` | alias | no | The child\'s field naming what the line is counted in |\n| `retention` | alias | no | A percentage field of THIS record \u2014 the share held back from what is claimed |\n\n#### Tree section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"tree"` | yes | The rows nested under each other, each money figure summed up the tree |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `columns` | list of alias (1\u20134) | no | The child\'s fields this register draws as its columns, in this order \u2014 at most 4, each one its role or its type draws as a column; absent, the child\'s roles decide |\n| `nest` | alias | yes | The child\'s one-row link to its own entity \u2014 the row each row sits under |\n\n#### Gantt section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"gantt"` | yes | The rows as bars across the calendar \u2014 from each row\'s `when`, for the days its measure counts |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `until` | alias | no | The child\'s date field each bar is drawn TO; absent, a bar runs for the days its measure counts |\n| `nest` | alias | no | The child\'s one-row link to its own entity \u2014 folds a row\'s bars under the row it sits under |\n| `depends_on` | alias | no | The child\'s link to its own entity naming the rows that must finish before a row starts |\n| `baseline` | [Baseline](#baseline) | no | The child\'s date fields each row was PLANNED to start and end on, drawn under its bar |\n\n#### Curve section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"curve"` | yes | Two figures of the rows, each added up to date along the rows\' `when` and drawn as two lines |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `planned` | alias | yes | The child\'s number each row was PLANNED to come to |\n| `actual` | alias | yes | The child\'s number each row DID come to \u2014 empty on a row that has not happened |\n\n#### Publish section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"publish"` | yes | One row per destination this record stands on \u2014 the strip, the preview, and the press that sends them; the record\'s facts read after it, unless a band is placed `first` or `last` |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `states` | [Publish states](#publish-states) | yes | Option aliases of the child\'s own lifecycle \u2014 all four, because each is a different thing the chip says |\n| `text` | alias | no | A plain text on the child \u2014 what this one destination goes out with instead of the body |\n| `permalink` | alias | no | A link-formatted text on the child \u2014 where the post landed |\n| `error` | alias | no | A text on the child holding the platform\'s own refusal |\n| `link` | alias | no | A link-formatted text of THIS entity \u2014 the address the post carries as its card |\n| `destination` | [Destination](#destination) | yes | The entity this child\'s party link names, read as the places a post can go \u2014 what reaches each, and what each receives |\n| `publish` | alias | yes | The workflow alias the APP binds (package.json#lotics.workflows) \u2014 the generator writes no body, and `lotics app check` refuses an alias nothing bound |\n| `when` | [Act condition](#act-condition) | no | Offer the desk\'s Publish only while THIS record stands at one of these stages |\n\n#### Publish states\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `queued` | alias | yes | Waiting to go out \u2014 the stage the child\'s flow OPENS at |\n| `published` | alias | yes | Sent through the API |\n| `failed` | alias | yes | The platform refused it |\n| `by_hand` | alias | yes | Posted by a person, and marked so here |\n\n#### Destination\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `network` | [Destination network](#destination-network) | yes | Which destinations the platform can reach through an API, and as what |\n| `connected` | alias | no | A text on the destination holding the account the platform posts as \u2014 empty means not connected, and the post is made by hand |\n| `bodies` | alias \\| [Bodies by destination](#bodies-by-destination) | yes | The text each destination goes out with \u2014 one field of this entity, or the destination\'s own select choosing between several |\n| `offered` | [Offered destinations](#offered-destinations) | no | Which destination rows the strip offers; absent, every row |\n\n#### Destination network\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select on the DESTINATION entity saying what kind of surface a row is |\n| `options` | map of alias \u2192 `"facebook"` \\| `"instagram"` \\| `"threads"` \\| `"x"` \\| `"linkedin"` | yes | Option alias \u2192 the network the kit previews and publishes it as; an option this map leaves out is posted by hand |\n\n#### Bodies by destination\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select on the DESTINATION entity deciding which body it receives |\n| `options` | map of alias \u2192 alias | yes | Option alias \u2192 a text field of THIS entity |\n\n#### Offered destinations\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select on the destination |\n| `in` | list of alias (at least one) | yes | Its option aliases the strip offers a row at |\n\n#### Acts section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"acts"` | yes | Acts waiting for a person \u2014 the words, where to reach them, and the sheet that records what came of it |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `states` | [Acts states](#acts-states) | yes | Option aliases of the child\'s own lifecycle \u2014 all three, because each is a different way an act ends |\n| `verb` | alias | yes | A single select on the child saying which act this is \u2014 its glyph, its label, and how it is reached |\n| `words` | alias | yes | A text on the child \u2014 the words to send, copied and pasted where the act is done |\n| `why` | alias | no | A text on the child \u2014 why this is worth doing now |\n| `reason` | alias | no | A text or a single select on the child \u2014 why it was passed over |\n| `reach` | map of alias \u2192 [Reach](#reach) | no | Verb option \u2192 where that act is done; a verb this map leaves out is done wherever the reader already is |\n| `withhold` | alias | no | A yes/no of THIS entity \u2014 while it reads yes, no act reaches out, and the reach verb says why |\n| `log` | [Acts log](#acts-log) | yes | What Done writes \u2014 the touch an act becomes, the answer the sheet asks, and the next step |\n| `cta` | `true` | no | The record\'s NEXT waiting act leads its head and is worn by its register row as the verb \u2014 the one press that reaches the person; one queue of a record carries it |\n\n#### Acts states\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `queued` | alias | yes | Waiting to be done \u2014 the stage the child\'s flow OPENS at |\n| `done` | alias | yes | Done, and recorded as the touch it became |\n| `skipped` | alias | yes | Passed over on purpose |\n\n#### Reach\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `at` | alias \\| text | yes | A field of THIS entity whose value is the address \u2014 its own, or a lookup of the person\'s \u2014 or "<link of the act>.<field>", an address on the row the act names |\n| `by` | `"link"` \\| `"phone"` \\| `"sms"` \\| `"email"` \\| `"zalo"` \\| `"whatsapp"` | yes | How that address is opened |\n\n#### Acts log\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `became` | alias | yes | The child\'s link to the entity whose rows record what an act became \u2014 the touch this act turns into |\n| `outcome` | alias | yes | A single select on the touch \u2014 what came of it, drawn as the sheet\'s one-tap choices |\n| `outcomes` | map of alias \u2192 list of alias (at least one) | no | Verb option \u2192 the options of `outcome` the sheet offers on that verb; a verb this map leaves out offers every one |\n| `note` | alias | no | A text on the touch \u2014 what was said; the act\'s own words stand in where the reader writes none |\n| `next` | [Next step](#next-step) | no | The next step the sheet asks for, written on the touch and queued as the next act |\n| `set` | map of alias \u2192 (alias \\| map of alias \u2192 alias) | no | Single selects on the touch the verb decides \u2014 a constant option, or one per verb |\n| `ask` | map of alias \u2192 list of alias (at least one) | no | Verb option \u2192 selects on the touch the sheet asks as the act is closed, written with the touch; a verb this map leaves out asks none of them |\n| `copy` | map of alias \u2192 text | no | Links on the touch \u2192 the link they are copied from, read one hop across a one-row link of the act |\n| `silent` | list of alias (at least one) | no | Verb options whose doing leaves nothing anybody answers \u2014 marked done, and no touch is written |\n| `moves` | [Log moves](#log-moves) | no | A touch logged while THIS record stands at one of `from` moves it to `to`, in the same run that writes the touch |\n\n#### Next step\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `step` | alias | yes | A text on the touch \u2014 the next step, which is also queued as the next act |\n| `on` | alias | no | A date on the touch \u2014 when that next step is due |\n\n#### Log moves\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | list of alias (at least one) | yes | Stages of THIS entity\'s lifecycle a logged touch moves the record out of |\n| `to` | alias | yes | The stage of THIS entity\'s lifecycle it lands at |\n\n#### Section heading\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | absent | no | Never stated \u2014 the section keeps the draw its roles give it |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `columns` | list of alias (1\u20134) | no | The child\'s fields this register draws as its columns, in this order \u2014 at most 4, each one its role or its type draws as a column; absent, the child\'s roles decide |\n\n<!-- generated:end apps-sections -->\n\n### The acts queue\n\n`"draw": "acts"`: the child\'s rows are ACTS WAITING FOR A PERSON \u2014 a reply to\nwrite, a call to make, a message to send \u2014 and the section is drawn as a queue of\ncards rather than a register: the verb, the words already written, why now, how\nlong is left, where to reach them, and the ways an act ends. An act is an\nintention and what HAPPENED is a row of its own (the touch), so Done opens a\nsheet \u2014 what came of it, what was said, the next step \u2014 and one write files the\ntouch and closes the act.\n\n```jsonc\n"sections": [{\n "of": "task",\n "draw": "acts",\n "heading": "Waiting on us", // optional\n // The child\'s own lifecycle, all three \u2014 `queued` is the stage its flow OPENS\n // at (an act is queued by opening a row).\n "states": { "queued": "waiting", "done": "done", "skipped": "passed" },\n "verb": "verb", // a single select on the child \u2014 which act this is\n "words": "words", // a text on the child \u2014 what to send\n "why": "why_now", // optional \u2014 a text on the child, why this is worth doing now\n "reason": "pass_reason", // optional \u2014 a text or single select on the child, why it was passed over\n // Where each verb is done: a field of THIS record holding the address \u2014 its\n // own, or a lookup of the person\'s \u2014 or "<link of the act>.<field>", an address\n // on the row the act names; and how it is opened: "link" as it stands, "phone"\n // dialled, "sms" / "email" composed, "zalo" / "whatsapp" a chat opened on the\n // number. A verb left out is done where the reader already is.\n "reach": { "reply": { "at": "on_post.url", "by": "link" }, "call": { "at": "prospect_phone", "by": "phone" } },\n "withhold": "prospect_quiet", // optional \u2014 a yes/no of THIS record; while it reads yes, no act reaches out\n "log": {\n "became": "became", // the child\'s link to the touch it becomes\n "outcome": "outcome", // a single select on the touch \u2014 the sheet\'s one-tap answer\n // optional \u2014 per verb, the answers of `outcome` it can come to; the sheet offers only those and the body refuses the rest\n "outcomes": { "reply": ["answered", "no_reply"] },\n "note": "said", // optional \u2014 a text on the touch; the act\'s own words stand in where none is written\n "next": { "step": "next_step", "on": "next_on" }, // optional \u2014 texts/dates on the touch; a stated step is queued as the next act\n "set": { "direction": "out", "channel": { "reply": "in_public", "call": "phone" } }, // optional \u2014 touch selects the verb decides\n "silent": ["like"], // optional \u2014 verbs whose doing leaves nothing to answer: closed, and no touch written\n // optional \u2014 selects on the touch the sheet asks, per verb: one tap for a single select, a toggle per option for a multi-select\n "ask": { "reply": ["approach", "carried"] },\n // optional \u2014 links on the touch copied from a row the act names: "<link of the act>.<link on that row>"\n "copy": { "venue": "on_post.venue" },\n "moves": { "from": ["fresh"], "to": "working" } // optional \u2014 stages of THIS record\'s lifecycle: a touch logged at one of `from` lands it at `to`\n },\n "cta": true // optional \u2014 the register\'s row wears this record\'s NEXT waiting act as its verb\n}, \u2026]\n```\n\nEverything else is read off the roles: the child\'s `parent` (the record), its\n`party` (who the act is aimed at), its `lifecycle` and its `when` (the day it\nstops being worth doing, counted down on the card); the touch\'s link to this\nrecord, its link to the same party where it keeps one (the one carrying `party`\nwhere it names that entity twice), its `when` (stamped with the moment it is\nlogged), its member (who did it \u2014 the caller, never a value anybody states) and\nits text `identity` (the act\'s `why`, else its words). Where the act names\nnobody, it is done with this record\'s own `party` onto that entity: an act queued\nbefore the person was known is still done with them, and the next act is queued\nwith them.\n\n**AN ACT IS OFTEN ABOUT A ROW OF ITS OWN** \u2014 the post a reply is left under, the\nmessage it answers \u2014 and that row holds where the act is done and where the touch\nit becomes happened. A path `"<link of the act>.<field>"` reads one hop across a\nlink of the ACT holding ONE row (`cardinality: "one"`): in `reach.at` the field is\nthe address, read across the link by every read of the acts, so the card and the\nregister row wearing the next act both open it; in `log.copy` the field is a link\non that row, copied onto the touch\'s link to the same entity as the act is closed.\n`log.ask` names selects of the touch the sheet asks on the verbs listed \u2014 how it\nwas done, what it carried \u2014 each written with the touch, and only on those verbs.\n\nRefused beside the rest below: a path across a link holding several rows or onto a\nfield that row has not got, an address there that holds no words, an asked column\nthat is no select of the touch or one the log already writes, an asked verb that\nis `silent` or no option, an `outcomes` verb that is `silent` or no option or an\nanswer `outcome` does not offer, a copied column that is no link of the touch or is the\ntouch\'s own link to this record or to the person, a copy between links to\ndifferent entities, and a copy of a link naming several rows onto one that holds\none.\n\nThe generator writes and binds three bodies per queue. `create_<child>` opens an\nact from the section\'s heading \u2014 the verb (required), the words, why now and the\nday, never the person or the touch. `log_<child>` is Done: it opens the touch,\nsets the act done AND points it at that touch in ONE update \u2014 a row reading done\nwith nothing it became is exactly the gap it closes, and a table workflow waiting\non that state does not fire a second time \u2014 then queues the next act where the\nsheet states a step, by the verb the reader picked (this one\'s where they picked\nnone). With `moves`, the same run moves the record the act is under to `to`\nwhere it stood at one of `from` \u2014 read before anything is written, and moved\nonly where a touch is: reaching somebody for the first time is a stage of the\nrecord\'s own work, and a stage set by hand afterwards is one nobody sets. A\n`silent` verb\'s act is set done alone and reads done with nothing it became \u2014\nthe state such a table workflow fires on, and no move. `skip_<child>` passes it over,\nwriting the reason only where one is sent; passed-over acts are KEPT, because they\nare evidence about what the queue gets wrong. Both refuse an act that is no\nlonger waiting, read before anything is written. With `cta`, the\nregister reads the waiting acts of every row on the page in one read, soonest due\nfirst, and each row wears its next one as its verb \u2014 the one press that reaches\nthe person; that read and the touches\' own (whose live options colour the sheet)\nare declared beside the record\'s.\n\nA queue is worked IN PLACE on every door, the way a correspondence is from its\ncomposer: an act is added from the heading, its words corrected and it is closed\nfrom its own card, so a record that opens as a drawer carries the queue whole \u2014\nthe sheet that closes an act opens over the drawer.\n\nRefused: a queue on a screen stating `writes: false` (every way an act ends is a\nwrite of its row), rows with no lifecycle, a state naming an option it has not got,\na `queued` stage that is not the one the flow opens at, a verb that is not a\nsingle select beside the lifecycle, words, why or a reason that is not a text (or\nsingle select, for the reason) of the child, a `reach` key that is not a verb\noption or an address that is not a field of THIS record holding words, a\n`withhold` that is not a yes/no of this record, `cta` beside a row act already\nplaced there, and a log whose touch cannot be written: a `became` that is not a\nlink, a touch naming this record through no link or through two, an outcome that\nis not a single select of the touch, a note or step that is not a text, a day that\nis not a date, one column named for two things the log writes (the touch\'s name\nand day, what was said, the next step and its day), a `set` over a column the log\nalready writes or naming an option either select has not got, a `silent` verb\nthat is not an option, and a `moves` that names a stage this record\'s lifecycle\nhas not got (or a record with none), names its `to` among its `from`, lands at a\nstage that ENDS the flow (where the work ends is somebody\'s decision, not a\nreply\'s), lands at a stage only a workflow act\'s `moves` reaches, or moves a\nrecord the screen draws at rest (`writes: "children"`). A SECOND\nqueue on one record is refused: what a record is waiting on is one list read\nsoonest first.\n\n### Notes, printed clauses and custom screens\n\n**A CHILD NOBODY REGISTERS IS A NOTE, not a refusal.** Where a record lists a\nchild entity no app is a register of, `scaffold check` says `"<entity>" has no\nregister \u2014 its rows are read here and created nowhere` and the plan printout\nmarks that entity `(no app)`. Rows can arrive from an import or a workflow, so\nit is a reading rather than a rule; what it catches is the register somebody\nmeant to plan and did not. Rows only the automations write (\xA7 Entity) are\nexempt, since nothing should create them. A section the record ADDS to is\nexempt, because its heading is where those rows are typed: the rows a record\nowns on a PAGE, a log on any door \u2014 an entry is written where it is read \u2014 a\nqueue of acts on any door, and a sheet, which opens its own lines. A record\nopening in a DRAWER adds none of its other owned rows, so those are noted like\nanybody else\'s.\n\n`scaffold check` prints every clause a screen states, on its own line under the\nslots, spelling the screen\'s `writes` clause `operable` \u2014 the app manifest\'s\n`lotics.writes` keeps that word, and the two are different facts: `app create`\nSEEDS the manifest from the fields the screens\' own editors write, and from then\non the app owns the declaration `lotics app check` holds its bodies to.\n\nEach shape is a `@lotics/ui` component of the same name (`LifecycleDesk`,\n`PartyRegister`, \u2026) whose props are these slots, so once the tables exist\n`lotics app create <name> --from <this file>#<app alias>` scaffolds the app with\none screen per entry, each slot reading the field the plan bound.\n\n`"shape": "custom"` is the screen no registry row covers: it declares its own\n`roles` (slot name \u2192 role), and it is emitted on `ShapeFrame` \u2014 the one anatomy\nthe six shapes above are each a configuration of \u2014 so its strip, search,\nordinal, fit budget, empty and waiting states and record door are the same ones\nthey have. It opens the record its `record` says, drawer or page, like any other.\nA bound `lifecycle` slot IS its strip, the stages in the field\'s order as on a\ndesk, so such a screen names no `tabs`. Only a lifecycle earns a strip \u2014 each\ntab a queue of work at one stage \u2014 and a screen naming any other select as\n`tabs` is refused: a kind, a book, a channel is a `filters` entry beside the\nsearch. The three shapes whose strip means something of their own take any\nselect: a reconciliation\'s dated runs, a trend\'s series, a worksheet\'s versions.\n\n### An app is its files\n\n**An app IS its `app.json`.** `create --from` writes the bound plan \u2014 every\nscreen with its shape and slots, the record each row opens with its sections in\nthe archetype\'s order, the create panels, the acts \u2014 as one JSON document, plus\na five-line `src/main.tsx` that mounts `@lotics/app-runtime` over it and one\n`src/workflows/<alias>.ts` per write. There is no screen source: the runtime\ndraws the spec, so a kit correction reaches every app with its next install.\nWhere the plan has no word for what a screen or a section IS \u2014 or for what a\npaper act should ASK before it is made \u2014 the spec names one of the app\'s own\ncomponents and `src/components/index.ts` registers it, the one file under `src/`\na regeneration never rewrites. `lotics app eject <screen|<section key>|<act\nlabel>>` writes each of those, starting from what the runtime already drew. What\nthe APP does rather than what a screen draws \u2014 a bridge to the page it is framed\nin, a listener \u2014 is one component `app.json#root` names: mounted once around\nevery route, and carried over by every regeneration, since the plan has no word\nfor it.\n\n**How a spec reads a row.** Each query projects its columns under the alias the\nworkspace mints from the field\'s LABEL \u2014 never the alias this file keys it\nunder, which is the model\'s own namespace and which the workspace never saw. So\na field this file calls `partner` and labels `\u0110\u1ED1i t\xE1c` is referenced as\n`doi_tac` throughout the spec. Every id in it is live: the `tbl_` a screen is\nover, the `fld_` of each column, and the `opt_` of every option a role names.\n\n**Every query is described, in the model\'s own language.** The line an agent\nchooses between aliases by is written from this file\'s nouns \u2014 the entity\'s\nlabel, the screen\'s, the link\'s \u2014 and the sentence around them is the language\nthe file is mostly written in, decided the same way a generated write\'s refusals\nare. Hand-editing one is erased by the next regeneration; `lotics app check`\nrefuses an alias that carries none.\n\nA screen is that list and the RECORD it opens, and the record comes off the same\nroles \u2014 nothing to declare for it. It opens with a **header**: the entity\'s\n`identity` as the record\'s name \u2014 whether or not the list has a column for it, so\na ledger\'s and a trend\'s records are named too \u2014 the `mark` beside it on a PAGE,\nwhich is what the subject is recognised by (so the pile below is the papers that\nare left, and a `presentation.lead` spending that gutter on something else leaves\nthe picture in the pile), the key the row is filed under \u2014 else the `when`\nthis screen reads \u2014 under it, the day the platform stamped it made (a date\n`derive_from: "created_at"`) as its `stamp`, the `lifecycle` as ONE status, and\nONE headline figure (the `amount` the screen reads, else its `measure`). Nothing\nthe header states is a fact as well. BOTH DOORS state the name: which door a\nscreen uses is a layout answer, and what the record is ABOUT is not. The status\ncarries its outcomes on every record the app draws, a party\'s and a child\'s too,\nand a stage only an act reaches is chosen by running that act.\n\n### Record header and facts\n\n**A JOB\'S HEADER ALSO STATES WHO IT IS FOR, AND WHO IS WORKING IT.** Where the\nrecord is the work itself \u2014 a desk\'s rows that belong to nothing, opened on a\npage \u2014 its `party` link is under the name, beside the ways to reach the subject,\nand is then not a fact as well: a page headed by a code alone said nothing about\nwhose work it is. A record opening in a DRAWER has no row of links to hang it\non, so its party stays a fact; so does a LINE\'s, whose register files it under\nthe thing it belongs to. A `select_member` `party` on a job is the person in\ncharge: drawn as the person beside the status, on either door, and in no section.\nA one-link to an entity declaring a `mark` is drawn as that party \u2014 its logo,\nelse its initials \u2014 wherever a record page states it, the face read across the\nlink.\n\n**A record whose figures are a SENTENCE states them under the header, in a\nband**, and the header then states neither of them: a job reads what it comes to,\nhow far through it is (the `measure` against its limit), what is left of it (a\nformula that is exactly `{amount} - {measure}`) and how long there is (an\n`obligation`, else its `when`, counted down); a party reads its settlement, the\n`amount` and `measure` roles it carries in the order the model declares them; a\ncatalogue item reads its `amount` against the reference that amount names. Two\nfigures are the floor and four the ceiling \u2014 under two, the header keeps its one,\nsave a lone `measure` filling toward its limit (any `reading` but `"threshold"`): it\nstands in the band by itself, because its track, its pass mark and what is still\nowed are the record\'s goal, read whole.\nA band is a PAGE\'s strip, so a screen whose `record` is a drawer keeps every\nfigure where a drawer reads them. The band only READS: a figure nobody writes is\nstated there alone, and every one a person states \u2014 a limit, a pass mark, a\nwindow\'s days, a typed amount \u2014 stays a fact and is changed among the facts. The\nday a countdown counts to is not the provenance line above it. Then\nits sections, in the order its KIND reads them: the\n**facts** (every field neither the header, the band nor another section owns, the\nrow\'s own `parent` among them \u2014 led by the ones THIS screen\'s slots bound, then\nthe key it is filed under, then the order the model declares them in \u2014 a\n`markdown` field among them, drawn across the grid\'s whole row rather than\nwrapped to a third of it), the record\'s own **body** (that same markdown, taken\nas a section instead, ONLY where the kind leads with it: a catalogue item IS its\ndescription, and a job is not its notes), the **charge** (where the `amount` is a formula over exactly one\nquantity and one money field, the line states `quantity \xD7 unit price = amount`\nand neither figure is typed twice \u2014 the section is headed by the amount\'s own\nlabel and the closing row says what the figure IS, so the word is said once),\nthe **progress**, only where the screen states `ladder: true` \u2014 for a long flow\nwhose path the reader needs to see (the stages as a ladder of rungs, grouped into\nits `phases` and each dated where the lifecycle keeps a `history`; the header\nthen states no status), a\n**required set** (a multi-select `expected_set`, or a child entity whose rows\ncarry one entry of it each \u2014 that child\'s `files` field is what a paper attaches\nto), the record\'s **own rows** (any other child, its role-bound fields as\ncolumns), and its **files** (every `files` field the header does not lead with,\nthe `mark` first where it keeps one \u2014 a section like\nany other, read in Details straight after the facts it is the evidence for,\nexcept on an EVIDENCE record, which OPENS on the paper it exists for; the\nheading carries the Add verb, and stands it down while the pile is empty, where\nthe drop well is already the door; and a `verdict` whose FORMULA reads one of\nthose files fields is the pile\'s OWN state rather than a fact row \u2014 the file it\nis short, drawn as a ghost). A child is\nan entity that LINKS here, ONE SECTION PER LINK: the rows it owns (`parent`), the\nrows NAMED by it (their `party` or their `identity` \u2014 so this record\'s history),\nand the rows reaching it through a link carrying neither role, headed by that\nlink\'s own label. A child hanging off two records therefore declares one `parent` and\nleaves the second relationship a plain link, which still gets its section.\n\n**THE LADDER\'S DATES ARE WRITTEN BY THE TABLE, NOT BY THE APP.** A lifecycle that\nkeeps a `history` derives the table automations that append its rows\n(\xA7 Table automations), so the generated `update_<entity>` writes the stage like\nany other field and the generated `create_<entity>` writes nothing to the\nhistory: a rung is dated by the write that moved it, from whichever door made it,\nand an app that appended too would date one move twice.\n\n**THE ROWS A RECORD OWNS ARE ADDED AND OPENED FROM THEIR SECTION.** A `parent`\nsection\'s heading carries the Add for its rows, handing the record as the\nparent \u2014 filled, never asked. Every row opens its own record \u2014 a line of a book,\na paper on a desk, a stop on a schedule alike: where this app already routes a\npage for those rows (the register\'s own, under one of them) it is navigated to;\notherwise it is a DRAWER mounted on the record it belongs to, stepped \u25C0 \u25B6 over\nthe section\'s rows as they are drawn, with the row\'s framed editors, its stage\nand its files, saving through `update_<child>` \u2014 the one editor those rows have,\nwhich any control the section draws over a row writes through too. Never the\n`parent` link itself: it is the key the row is FILED under, stated by the record\nthe drawer was opened from, so it is neither a fact of the drawer nor an input\nof that editor. ONE LEVEL, and only from a PAGE: a child\'s own children draw as\nthe kit\'s panel of the row, with no drawer and no Add, and a register whose own\nrecord is a DRAWER is the same for the children it owns \u2014 a drawer mounts no\ndrawer. The rule is about RECORDS INSIDE RECORDS, not about rows of one kind\nnested by a link of their own: a tree (`layout: "tree"`, `"draw": "tree"`) is one\nlist of one entity, and each of its rows opens the one record surface that entity\nalready has, at whatever depth it is drawn. **A ROW IS ADDED WHERE IT CAN BE OPENED AGAIN**, so those sections lose\ntheir Add with their drawer; a row of the register\'s OWN kind keeps it, because\nthe register\'s list opens it. A correspondence\'s entries open nothing anywhere:\nthe composer under them is the section\'s Add whatever door the record has, and\nthe answer mark is one field of the reply\'s own editor. A queue\'s acts are the\nsame on a drawer: the heading\'s Add opens one, and its card saves its words\nthrough `update_<child>`. The rows a record is merely NAMED by, and those reaching it\nthrough a roleless link, are read only \u2014 a history, never a place to open or\nadd.\n\n### Time axis, logs and books\n\n**TWO CHILDREN ARE READ ON A TIME AXIS RATHER THAN AS A REGISTER**, and the\nmodel says which by the roles it declares on them. A child carrying an\n`obligation` whose `satisfied_by` is a DATE \u2014 and no `lifecycle`, because an\nentity with stages of its own is a JOB being worked and its rows under another\nrecord are that record\'s lines or its history \u2014 is a **schedule**: what is\nplanned against time, and what slipped. Its rows are the ordered stops, each\nstating the day it was promised for and the day it happened, with the delta\nnamed where the rung was late \u2014 drawn as a register those are two date columns\nand the reader subtracts them by eye, which is exactly what "three days late at\ndischarge" costs to learn. An obligation closed by a FILE says nothing about\nwhen, so those rows stay the register they were.\n\n**A body and a day is a LOG** \u2014 what happened, in order: the entry in the words\nsomebody wrote, filed under the day they wrote it. The body is the child\'s\n`body`, else its first `markdown` text; the format never decides, so a plain-text\nnote declared `body` is an entry and a markdown remarks field on a job is not\n(a `lifecycle` keeps it a register, as it keeps a schedule one). A register of\nentries is a table whose one useful column is the one it cannot draw, so the rows\nare read as one time axis instead: one head per entry, the words under it, and\neverything else the row states placed on the entry. WHO WROTE IT is the\n`select_member` the row names \u2014 a log is kept by the people who work the\nrecord \u2014 and the parties on it are who it was WITH, drawn on the entry\'s line\nbeside the `contact` it reached them at and the files that came with it. The\nother selects are words on that line; the `recording` plays under the words and\nthe `verbatim` waits behind one fold; everything else a person states on the\nentry \u2014 the day\'s headcount, the gear on site, what went wrong \u2014 is drawn under\nits words by its own label, where that entry states it: an entry is the whole\ntouch. Its Add is a composer asking exactly that \u2014 the entry, its name where the\nchild names one, the recording and the verbatim, the day (opening on today), the\nentry\'s own selects and those facts; the record it hangs under is not asked.\n\n**A `verdict` on those rows makes the log a CORRESPONDENCE** \u2014 a question, its\nreplies, and the one reply marked with that yes/no as the ANSWER. It is read\nfrom the question, oldest first, where a log is read from its latest entry under\nday heads. Where no member keeps it the first `party` is who wrote each entry,\nbecause there the other side writes too; a SECOND party is who the entry was\nleft WITH, and the latest one names the ball in court: while nothing answers,\nthe record says whose move it is and counts its own deadline (an `obligation`,\nelse its `when`) against them. Whose move and the clock are read only under the\nrecord that OWNS the messages \u2014 under the party who wrote them the same rows\nare several questions at once, and the last of them says nothing about any \u2014 but\nthe answer mark is a fact of the reply wherever it is read, and it is one field\nof the reply\'s own editor. A NAMED row is an entry that OPENS \u2014 a visit or an\ninspection is a thing in its own right, read in full on a record of its own,\nmounted on the page it is read from as a register\'s row is \u2014 where an entry\nnothing names is corrected where it stands. An entity only the automations\nwrite (`writes: false`) opens that record at rest and composes nothing.\n\n**A PROFILE\'S LADDER LEADS.** A profile whose screen states `ladder: true`\nreads `progress` first, right under the band, and its histories follow.\n\n**A RELATION THE RECORD NEVER ACTS ON IS A COUNTED TAB.** Every register that\nmerely NAMES the record is a tab of its own: its label, how many rows name this\nrecord (a COUNT, never a page of rows measured), and a press that opens the\nregister owning them narrowed to this record. An entity this app lists nowhere\nis a count with no door, and a tab counting none is not drawn. The one\nexception is a PROFILE, whose history IS its body and stands open in its tab.\n\n**Rows the record owns whose `amount` is `signed_by` a direction are a\nBOOK**, drawn as a statement \u2014 each line dated and signed, the balance under\nthem where there is more than one line to add up, labelled by the amount\'s own\nword \u2014 where the same rows on the party they\nwere transacted with stay that party\'s history, which is scanned for the line\nshort of its paperwork rather than struck to a balance. **Rows that carry a\n`when` read as RUNS**: a party\'s history under the year \u2014 and only where the rows\nin hand span more than one, since a subhead over every line of a book names the\nyear and separates nothing \u2014 and a job\'s own rows under the day they fall on,\nwhere some day holds more than one (a row per day keeps its date column), and\nwhich, where those rows also carry a `lifecycle`, is an ITINERARY rather than a\ngrouped table: the day heads the run, each row is one line (its `slot`, its name,\nits party, its stage, its `category` and `reference`, its amount), the figures end\non one edge and the run closes with their sum \u2014 unless the record\'s own BAND is a\nsum rollup over exactly these rows, which states it once already. A `category`\npaints the node the run is read down, in the option\'s own colour; a `reference`\ncloses the line with the code somebody quotes on the phone. **A `mark` LEADS the\nline with the stop\'s own picture** \u2014 a venue, a vehicle, a machine \u2014 and it is the one\nplace on a record where a child entity\'s mark is drawn at all, since no register\ncolumn is ever a picture; a line whose subject holds none is marked with its\n`category`\'s glyph instead, so the run keeps one left edge. All three are\nPROJECTED for the run whether or not the register\'s column budget would have\ndrawn them, and a mark is projected NOWHERE ELSE.\nA line\'s mark is most often the SUBJECT\'s rather than the line\'s own \u2014 the\nservice, the product, the asset it is a line of \u2014 which is a `lookup` through\nthat link onto the subject\'s own `files`, given the role on the line.\n**EACH DAY\'S HEAD CARRIES THE SECTION\'S OWN ADD** as well, handing the panel that\nday, so a line made from Tuesday opens with Tuesday answered; the heading keeps\nthe verb too, for the first line and for a day nothing is planned on yet. **THE\nKEY LEAVES THE COLUMNS where the head states the whole of it** \u2014 a day head does,\na year head states four digits of the date and the day is what the column is\nstill there to carry.\n**A run is READ FROM THE END THE READER WANTS**: a job\'s own rows and an itinerary\nascending, a book, a history and a log descending.\n**A REGISTER WHOSE `when` IS A DEADLINE IS READ FROM THE NEAR END** whatever its\nshape does \u2014 `due` (or an `until` that says where the countdown stops) makes the\ndate a promise, so the rows in breach of it lead and a row nobody dated falls\nlast. A plain `when` keeps the shape\'s own end. `scaffold check` prints the\npromise (`order: Theo d\xF5i ti\u1EBFp, overdue first`).\n\n**A DESK OVER AN ENTITY THAT CARRIES A `slot` IS ITSELF A DAY\'S RUN.** Its own\nregister is drawn under one subhead per day, sorted by that day and then by the\npart of it, and the date column goes \u2014 the subhead states it once for the whole\nrun, and a column repeating it down every line is the same value twice. Without\nit an itinerary was one flat list in whatever order the rows arrived.\nA document desk keeps its head either way \u2014 the entries it owes are its\ncontent, filed or not. The name and whatever figure is left to it are the header\'s\nalone \u2014 it states them in full, so a fact for either would be the same sentence\ntwice.\n\n### Where a field is drawn\n\n**NOTHING IS FOLDED: A FIELD IS DRAWN WHERE IT IS PLACED.** Everything a person\ntypes or picks is shown, empty or not, because absence is work somebody owes. An\n`autonumber`, a date the platform stamps (`derive_from`) and a date a formula\nworks out that no role names are what the SYSTEM wrote: the key under the name\nand the `created_at` stamp are the header\'s, and the rest are drawn only where a\n`facts` band or a section\'s `facts` names them \u2014 unplaced, they are not drawn.\n\n**A `contact` field whose kind the\nmodel decides** \u2014 an email, a number, a place, a `link`-formatted text, or a\nMESSAGING APP its label names (WhatsApp, Zalo, Telegram, Viber, WeChat, Line,\nMessenger) \u2014 leaves the facts too, for the row of links under the name on a\nrecord that opens as a page; one whose kind nothing decides stays a labelled\nfact, because a scheme nobody stated is a link that opens nothing. A handle is\ndrawn under its app\'s name \u2014 the app is what identifies the person there, and a\nbare number beside an envelope says the wrong thing \u2014 and it dials only where its\nvalue IS a number.\n`check` prints the record under each screen\'s slots:\n\n```\n Orders \u2014 lifecycle desk over Orders (12 rows) \xB7 page \xB7 tabs: Stage (New \u2192 Quoted \u2192 Confirmed \u2192 Shipped \u2192 Done)\n stage Stage \xB7 identity Order no. \xB7 mark Photo \xB7 party Customer \xB7 amount Total \xB7 level (none \u2014 no field declares "measure") \xB7 when Due\n record: header (Order no. \xB7 Customer) \xB7 band (Total \xB7 Shipped against Lines \xB7 Due counting down) \xB7 status: Stage (in the header) \xB7 facts (Ship to) \xB7 files: Photos \xB7 documents: Papers (Kind: 4 required) \xB7 itinerary: Order lines by Ship by at Window, read by Window \xB7 Handling / Line total, status Status (Product \xB7 Quantity \xB7 Line total) \xB7 related: Visits (count via Order)\n```\n\nThe ORDER of that line is the record\'s own, top to bottom, and it is the KIND\'s,\nand every entry of it is a part the anatomy places (its `anatomy:` line says\nwhere), `status:` excepted \u2014 the stage beside the name. `related:` comes last \u2014\na register the record never acts on, a counted tab of its own.\nA child register whose `amount` is `signed_by` a direction is a BOOK of movements\n\u2014 what the record came to rather than what it is made of \u2014 so it follows the\nrows the job is made of rather than standing among them.\n\n**A section over a child is named by the CHILD\'s own label**, because the reader\nalready knows which record they are on: "\u0110\u01A1n h\xE0ng", not "\u0110\u01A1n h\xE0ng \u2014 Kh\xE1ch h\xE0ng".\nWhere two links from one entity reach this record, that label names two sections\nand each takes its own link\'s label as a qualifier \u2014 `H\u1ED3 s\u01A1 (\u0110\u01A1n h\xE0ng)` and\n`H\u1ED3 s\u01A1 (\u0110\u01A1n g\u1ED1c)`.\n\nIn that line `documents:` is a `RecordExpectedSet`, and `lines:` a `RecordChildren` over the rows\nthis record owns \u2014 `history:` where they NAME it instead, as their `party` or their `identity`, capped at its latest 5\nwhere the record is a profile (`latest 5 of N by <day>`, the whole of them behind the foot); a required set\nwhose entries are a CHILD entity carrying files takes `kind="files"`, and the entity\'s own\nmulti-select takes `kind="items"`, since nothing attaches to an option.\n\n**A section that knows its size says so.** Where a `measure` on the record is a\n`count` rollup over the very link a rows section hangs on, the limit it is read\n`against` is how many rows that section is OWED: the printout adds `\u2014 expects\n<limit>` and the screen draws that many ghost rows until the first one lands,\ninstead of "nothing here" two bands under a count saying three are outstanding.\n\nA figure printed as `Total (USD)` is MONEY in the currency named \u2014 a `formula`\nstates it in `formula.currency`, and a `rollup` or a `lookup` inherits it from the\nfield it reads, so read those brackets: a rate that should be in dollars and is\nprinted bare will be drawn in the workspace\'s own money. Where the entity carries\na `currency` role, the ROW\'s code outranks the field\'s on every line that states\none, and a line stating none falls back to the field\'s.\n\n### Operating a record\n\n**A record surface can be OPERATED, and that is the default.** Every screen with\na record gets a workflow that writes its editable facts \u2014 every field a person\nstates \u2014 so the record\'s values are edited in place and its stage is advanced\nfrom its status, or its ladder. `"writes": false` makes one screen a reader: use it\nfor a screen over rows another desk owns, never as the default. A desk nobody\ncan act on is a viewer of state somebody must go and set somewhere else. A\nreader keeps the acts that only READ the row \u2014 its papers and its `export` \u2014\nand refuses every one that writes it: the quick column, the agent act, the\nhand-off on any surface, and the file an `import` would mint rows from.\n\n**`"writes": "children"` splits the record from the rows under it.** The record\nis drawn exactly as `false` draws it \u2014 the same refusals, and the register opens\nno row of its own \u2014 while every section listing the rows that record OWNS keeps\nits Add, its drawer and its own editor. Use it where one desk states the plan\nand another logs against it: the hours typed under a period the office set, the\nitems ticked under a job somebody else opened.\n\n**`"writes": "record"` is the mirror.** The record keeps every editor, its stage,\nits files, its quick column and its acts, and the register opens rows of its\nown; every section listing the rows that record OWNS is drawn at rest \u2014 no Add,\nno drawer that saves, no composer. Use it on the desk that decides the plan and\nreads what other desks log under it. `lotics app check` refuses a child create\nor a child editor under it, and the app\'s `package.json#lotics.writes` is seeded\nwithout the child tables.\n\n**Which facts those are is the FIELD\'s answer.** Text, number, date, yes/no and\nselect are typed or picked. A `"cardinality": "one"` link is RE-POINTED, through\na picker over the target entity named by its `display_field_aliases` \u2014 else the\ntarget\'s `identity` \u2014 narrowed by what the reader types and carrying that\nentity\'s own `read_scope` and the link\'s `options_where`, both refused again\nwhere the save lands, so a link the plan gives neither column is read instead\nof offering an empty list. The record\'s fact, a move\'s ask and a closing step\'s\nfield draw the one picker a create\'s draft asks with. A files field is ATTACHED\nTO and DETACHED FROM, as the delta rather than the pile, so two readers filing\nat once each keep their file. A link naming SEVERAL rows is picked through the\nsame picker and written the same way \u2014 the generated editor takes the rows added\nand the rows taken off, never the list \u2014 so two readers adding at once each keep\ntheirs. An `autonumber` and every computed field are read: they are the\nplatform\'s to write.\n\n**A one-link is that fact WHATEVER its sync.** `sync_both_ways` is one relation\nwith a field on each side, and the sides are not the same surface: the record\nthat names ONE row states it as a fact, and the MANY side is the register on the\nother entity\'s record. Declaring both directions does not give this record a\nregister of the single row its own fact already names.\n\n**The write is the record SURFACE\'s.** Its inputs are the fields that surface\noffers to save \u2014 the facts with an editor, the lifecycle its status or ladder\nadvances, the key under the name and the figure a book is read against where a\nperson states them, a files section over a stated field, and what the BAND\nstates: its countdown\'s date, and a meter\'s limit where a person types it \u2014\nnever every field a person could in principle type. What\nthe header states, a limit a level folds into a meter among the FACTS, a required\nset with a section of its own and the pile a page\'s mark draws are read there, so\nno input is declared for them. A second screen over the same entity draws no\nsurface of its own and adds nothing.\n\nWhere two operable screens of one plan reach the same entity, `scaffold check`\nnames them and the apps they belong to: two desks writing one record is a\ndecision, and the split is stated on the FIELD in each app\'s\n`package.json#lotics.writes` rather than left to whoever edits second.\n\nThat workflow lands in the app as source, like every other: its body in\n`src/workflows/update_<entity>.ts` and its declaration in\n`package.json#lotics.workflows`. The two together are the binding \u2014 `lotics app\ndeploy` pushes whatever differs from what it last saw live \u2014 so the write is\nversion-controlled and travels with the app rather than being bound by hand\nafterwards.\n\nA `custom` screen declares its slots under `roles` (slot \u2192 role) and they bind\nthe same way:\n\n```jsonc\n{ "alias": "readings", "label": "Readings", "shape": "custom", "entity": "reading",\n "roles": { "subject": "identity", "reading": "measure" } }\n```\n\n## Applying packages\n\n`apply` copies published packages into the workspace AFTER the model\'s own\ntables exist \u2014 apps over the tables you just described, and any tables of their\nown they still need. Ordered, and run by `lotics setup` and `lotics scaffold\napply` alike.\n\n```jsonc\n"apply": [\n {\n "package": "apg_k3nf82ldpq",\n "bind": { "company": { "label": "Customers", "fields": { "name": "Company name" } } },\n "no_sample_data": true\n }\n]\n```\n\n<!-- generated:start apply -->\n\n#### Package entry\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `package` | text | yes | The package id to copy in |\n| `bind` | map of alias \u2192 [Binding](#binding) | no | Entity alias \u2192 the labels this workspace calls that entity and its fields |\n| `no_sample_data` | boolean | no | Decline the package\'s sample records |\n\n#### Binding\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | no | The workspace\'s own table name for this entity. |\n| `fields` | map of alias \u2192 text | no | Field alias \u2192 the workspace\'s own field name for it. |\n\n<!-- generated:end apply -->\n\n`bind` holds the LABELS this workspace uses: scaffold adopts by label, so binding points the package at the\ntables the model created instead of a second set beside them. Only naming\nmoves \u2014 a bound field must be the TYPE the package declares, or the copy is\nrefused. `lotics library list` is the shelf, and `lotics library show <apg_id>`\nlists the aliases to bind.\n\nEntries run in the order they are written, because a later one may bind onto a\ntable an earlier one created. **A refused entry stops the run and the entries\nbefore it stay** \u2014 they are separate copies, committed as they land, so the\nrefusal names them rather than leaving a caller to re-run the file and copy them\ntwice.\n\n### What a run remembers\n\nThe WORKSPACE remembers what each entity, field, select option, role and\ntemplate alias became here, plus which record each first row landed on and the\nfile each document path was uploaded as. The model file itself holds no live id\n\u2014 it is the portable half, and the same file applies to a demo workspace and to\na customer\'s \u2014 so the join lives where the things it names live, and one model\napplied to two workspaces holds two bindings that know nothing of each other.\n\nIt is the workspace\'s and not the file\'s because a model file is never\ncommitted: memory kept beside it is one author\'s disk, absent for a teammate, on\na second machine or after a delete \u2014 and every one of those goes quietly back to\nbinding by label, which is what grows the second table.\n\nEvery later verb binds through it: `apply`, `diff`, `app create --from`,\n`app regenerate --from` and `workspace build` take the remembered id first and\nfall back to a label only for an alias nothing has bound \u2014 a field you have just\nadded, or a workspace nothing has applied this model to. That is what makes a\nrelabel on EITHER side a rename rather than one thing the workspace lacks and\none the model lacks: `diff` prints one line under the alias\n(`order.state: label: model "Stage" \xB7 workspace "Giai \u0111o\u1EA1n"`), and `apply` binds\nthe thing it has always meant, reports the move and names the command that makes\nthe two agree \u2014 which side is right is yours to decide, not a reason to refuse\nthe run.\n\nA bound target the workspace no longer holds IS a refusal, by name: re-binding\nto whatever carries that label today is how the model comes to point at\nsomebody else\'s table. `lotics run restore_table` puts a deleted table back, and\n`lotics scaffold unbind <kind> <alias>` forgets the binding \u2014 after which the\nnext apply creates a new one and nothing joins it to what was there.\n\n## Presets\n\nA preset is a trade\'s model, published to be READ. An assistant reads it, asks\nat most two questions, picks a variant and writes a `model.json` from it \u2014\nnothing is copied, and a preset is a file rather than anything a workspace\ninstalls.\n\n```jsonc\n"preset": {\n "name": "Field service",\n "description": "Jobs, the crew that runs them, and what each one billed.",\n "questions": ["Do you dispatch crews, or one person per job?"],\n "variants": {\n "crews": {\n "when": "work is dispatched to crews rather than to one person",\n "entities": [ /* tables this branch ADDS */ ],\n "fields": { "job": [ /* fields this branch ADDS to `job` */ ] }\n }\n }\n}\n```\n\n<!-- generated:start presets -->\n\n#### Preset\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `name` | text | yes | The trade, as the shelf lists it |\n| `description` | text | yes | What the trade\'s workspace keeps, in a sentence |\n| `questions` | list of text (at most 2) | yes | What to ask before choosing a variant \u2014 at most two |\n| `variants` | map of alias \u2192 [Variant](#variant) | yes | Slug \u2192 a branch of the model, taken where its `when` describes the business |\n\n#### Variant\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `when` | text | yes | The condition, in the words a person would use, that selects this variant |\n| `entities` | list of [Entity](#entity) | no | Tables this variant adds |\n| `fields` | map of alias \u2192 list of [Field](#field) | no | Entity alias \u2192 the fields this variant adds to that entity |\n\n<!-- generated:end presets -->\n\nVariants are **additive only**: a branch adds entities and fields and never\nremoves them, so the base is a model in its own right rather than a draft.\n`lotics scaffold check` proves the base AND every variant merged onto it, so a\npreset ships with every branch already proven \u2014 the branch nobody took is the\none that fails in the workspace of whoever takes it.\n\n`preset` is not scaffolded. `lotics setup` and `lotics scaffold apply` ignore\nit and create the base model\'s tables.\n\n`lotics scaffold export` prints a workspace that already works as one of these\nfiles \u2014 the starting point for a preset or for another business\'s model, never a\nsource of truth: it carries one business\'s words and stops describing that\nworkspace the moment either changes.\n\n## Starting from a preset\n\n`lotics library list` is the shelf of them and `lotics library show <slug>`\nprints one whole: its questions, every table as `alias \xB7 label` with each field\nas `alias:type`, and each variant as `slug \xB7 when` followed by the tables and\nfields that branch adds. When one of them is the trade in front of you, do not\ntranscribe it \u2014 name it:\n\n```jsonc\n{\n "from": "field_service",\n "variants": ["crews"],\n "rename": { "job": { "label": "\u0110\u01A1n h\xE0ng", "fields": { "code": "M\xE3 \u0111\u01A1n" } } },\n "entities": [ /* a table this business has that the preset does not */ ],\n "rows": { "job": [ { "ref": "j1", "fields": { "code": "J-1" } } ] },\n "field_roles": { "job": { "code": "identity" } },\n "write_rules": { "customer": { "natural_key": ["email"] } },\n "apps": [ /* the screens this business\'s apps will have */ ]\n}\n```\n\n- **`from`** is the preset\'s SLUG \u2014 its own file name, a lowercase slug. Naming\n it is what makes `entities` optional; every other rule on this page is\n unchanged, because the file is resolved into the full form and then checked and\n applied exactly as one. A slug nothing serves is refused with the ones there\n are, never resolved against something else.\n- **`variants`** names the branches to merge onto the base, in order. Pick the\n one whose `when` describes what the person said; a slug the preset does not\n declare is refused rather than ignored.\n- **`rename`** is keyed by the preset\'s entity alias and holds the labels this\n business uses \u2014 the same shape `apply[].bind` takes, and the same rule: only\n naming moves. An alias the preset does not declare, and a label that is\n already another table\'s, are both refused.\n- **`entities`** are added after the rename, already in this business\'s own\n words.\n- **`rows`**, **`field_roles`**, **`write_rules`**, **`apps`** and\n **`apply`** mean exactly what they mean in the full form \u2014 `"rows"` are this\n business\'s real first records,\n `"field_roles"` may name the preset\'s fields as well as its own (a role the\n preset declares itself is kept unless this file names the same field, or\n clears it with `null`), `"write_rules"` the create-time clauses on either\n (an entity this file names replaces the preset\'s whole entry for it),\n `"apps"` the screens it will have (a preset carries none), `"apply"` the\n packages copied in once its tables exist.\n\nThis is the ONE thing on this page that needs the network: `check` reads the\npreset it names, once. Everything after that read is the same offline check.\n\nWrite the full form when no preset is the trade.\n\n## A complete model\n\n```json\n{\n "entities": [\n {\n "alias": "customer",\n "label": "Customers",\n "singular": "Customer",\n "fields": [\n { "alias": "name", "label": "Name", "type": "text", "required": true },\n { "alias": "logo", "label": "Logo", "type": "files" },\n {\n "alias": "tier",\n "label": "Tier",\n "type": "select",\n "options": [\n { "alias": "standard", "label": "Standard", "color": "slate" },\n { "alias": "gold", "label": "Gold", "color": "amber" }\n ],\n "default": ["standard"]\n },\n {\n "alias": "orders",\n "label": "Orders",\n "type": "select_record_link",\n "target_entity": "order",\n "cardinality": "many",\n "sync_both_ways": true,\n "paired_field_alias": "customer",\n "display_field_aliases": ["code"]\n },\n {\n "alias": "total_ordered",\n "label": "Total ordered",\n "type": "rollup",\n "source_field_alias": "orders",\n "aggregate_option": { "operation": "sum", "field_key": "amount" }\n }\n ],\n "views": [\n {\n "alias": "gold",\n "label": "Gold customers",\n "filters": {\n "node_type": "condition",\n "type": "select",\n "field_key": "tier",\n "operator": "has_any_of",\n "value": ["gold"]\n },\n "sort": [{ "field_key": "name", "order": "asc" }]\n }\n ]\n },\n {\n "alias": "order",\n "label": "Orders",\n "singular": "Order",\n "fields": [\n { "alias": "code", "label": "Order no.", "type": "text", "unique": true },\n { "alias": "placed_on", "label": "Placed on", "type": "date", "format": "date" },\n {\n "alias": "amount",\n "label": "Amount",\n "type": "number",\n "format": "currency",\n "currency": "VND"\n },\n {\n "alias": "total",\n "label": "Total with VAT",\n "type": "formula",\n "formula": { "expression": "{amount} * 1.1", "format": "currency", "currency": "VND" }\n },\n {\n "alias": "customer",\n "label": "Customer",\n "type": "select_record_link",\n "target_entity": "customer",\n "cardinality": "one",\n "sync_both_ways": true,\n "paired_field_alias": "orders",\n "display_field_aliases": ["name"]\n }\n ]\n }\n ],\n "roles": [{ "alias": "sales", "label": "Sales" }],\n "field_roles": {\n "customer": { "name": "identity", "logo": "mark" },\n "order": { "code": "identity", "placed_on": "when", "amount": "amount", "customer": "party" }\n },\n "apps": [\n {\n "alias": "customers",\n "name": "Customers",\n "screen": { "alias": "customers", "label": "Customers", "shape": "party_register", "entity": "customer" }\n },\n {\n "alias": "orders",\n "name": "Orders",\n "screen": { "alias": "orders", "label": "Orders", "shape": "transaction_ledger", "entity": "order" }\n }\n ],\n "rows": {\n "customer": [\n { "ref": "acme", "fields": { "name": "Acme Trading", "tier": "gold" } },\n { "ref": "bluebird", "fields": { "name": "Bluebird Foods", "tier": "standard" } }\n ],\n "order": [\n {\n "ref": "so_1001",\n "fields": {\n "code": "SO-1001",\n "placed_on": "@month-start+2",\n "amount": 4200000,\n "customer": "customer:acme"\n }\n },\n {\n "ref": "so_1002",\n "fields": {\n "code": "SO-1002",\n "placed_on": "@today-3",\n "amount": 1150000,\n "customer": "customer:bluebird"\n }\n }\n ]\n }\n}\n```\n\n`lotics scaffold check` on this file reports\n`2 tables, 10 fields, 2 links, 1 view, 1 role, 4 rows, 2 apps`, then\nthe plan:\n\n```\nCustomers\n Customers \u2014 party register over Customers (2 rows) \xB7 page (default) \xB7 tabs: none (default)\n identity Name \xB7 mark Logo \xB7 contact (none \u2014 no field declares "contact") \xB7 worth (none \u2014 no field declares "measure") \xB7 risk (none \u2014 no field declares "verdict")\n order: Name, A to Z (default) \xB7 presentation: leads with the mark (default), roomy (default), as a table (default) (of table, list, cards, gallery) \xB7 read (default: 30 rows a page) \xB7 period: none, every row (default) \xB7 operable: the record and its rows (default)\n anatomy: head none \xB7 brief nothing (default) \xB7 tabs Details (facts) \xB7 Orders (Orders [order]) (default)\n record: header (Name \xB7 Logo) \xB7 presentation: leads with the mark (default), roomy (default) \xB7 sections: every one the record derives (default) \xB7 history: Orders (latest 5 (default) of N by Placed on) (Order no. \xB7 Placed on \xB7 Amount (VND)), by year (default) \xB7 facts (Tier \xB7 Total ordered (VND))\nOrders\n Orders \u2014 transaction ledger over Orders (2 rows) \xB7 drawer (default) \xB7 tabs: none (default)\n when Placed on \xB7 amount Amount (VND) \xB7 reference Order no. \xB7 party Customer \xB7 classification (none \u2014 no field declares "lifecycle") \xB7 document (none \u2014 no field declares "expected_set" or "verdict")\n order: Placed on, newest first (default) \xB7 presentation: leads with the figure (default), roomy (default), as a table (default) (of table, list, calendar, timeline) \xB7 read (default: 30 rows a page) \xB7 period: none, every row (default) \xB7 operable: the record and its rows (default)\n anatomy: head none \xB7 brief nothing (default) \xB7 tabs Details (facts) (default)\n record: header (Order no. \xB7 Placed on \xB7 Amount (VND)) \xB7 presentation: leads with the figure (default), roomy (default) \xB7 facts (Total with VAT (VND) \xB7 Customer)\n party: Customers \u2014 opens from Customer (profile: header (Name \xB7 Logo) \xB7 presentation: leads with the mark (default), roomy (default) \xB7 sections: every one the record derives (default) \xB7 history: Orders (latest 5 (default) of N by Placed on) (Order no. \xB7 Placed on \xB7 Amount (VND)), by year (default) \u2014 foot: the whole register, narrowed to this record \xB7 facts (Tier \xB7 Total ordered (VND)))\nWho writes what:\n Customers (customer, one: Customer): Customers \xB7 written from the rows of Orders: facts\n Name: required\n asks: every field a person states (default)\n Logo: attached at create \u2014 the row\'s own face, and the only pile a draft asks for\n Orders (order, one: Order): Orders\n asks: every field a person states (default)\nWhat the rows would show:\n rows.customer.logo: no row carries a picture \u2014 every register over it shows no mark\n```\n\nA `party:` line is the record a NAME on a row opens. A party is not a job, so it\ngets no app of its own \u2014 but a clerk reading the orders book presses the\ncustomer and corrects the phone number there, so the app whose rows name a party\ncarries that party\'s own record beside its register\'s. It is printed in the same\ngrammar the `record:` line is, because it is the same kind of surface: a profile,\non a page, its histories capped. Which cells are the door is what `opens from`\nsays \u2014 `the name` where the register\'s subject IS the party.\n\nThe block before the last is the WRITERS matrix \u2014 one line per record surface the plan\nleaves operable, the word one of its rows is called, and the app whose register\nopens it. Part of the verdict rather than a diagnostic beside it: which desk\nchanges a table is answerable from the plan, and two apps on one line is the\nsplit to state in each of their `package.json#lotics.writes`. A\n`written from the rows of \u2026` clause is the other reach: the party that register\nnames, and what the app can change on it \u2014 counted apart, because several\nregisters naming one account is the model working as designed rather than a\nsplit to declare. A publish desk\'s rows get a clause of their own, because the\npress that withdraws one DELETES it \u2014 the app declares that table in\n`package.json#lotics.deletes`, and `lotics app check` refuses a body that deletes\nfrom a table it does not name. Indented under each entity are the clauses only a\ncreate meets: the cells a row cannot stand without, what its draft asks for, and\nwhat `write_rules` state.\nThe last block is what the first `rows` would show: no customer carries a logo\nyet, so every register over them draws initials where the picture goes.\n\nEvery `(none)` is a slot no field fills, and it says WHY: no field declares the\nrole, or several could and the plan named none of them (`(none \u2014 a, b; name\none)`). Read it as the screen a person will see. The customer\'s mark is the\nparty\'s face, so the header takes it; the customer\'s `history:` is the orders\nthat name it as their party, and **a link is a section, not a fact**:\n`Customers.Orders` leaves the customer\'s facts because the section lists the same\nrelation. Both records state their name in the header and nowhere else, and\n`Total ordered (VND)` is the rollup inheriting the currency of the column it\nsums: money is read off the model, never off the field\'s own line.\n';
|
|
55378
|
+
var model_reference_default = '# The Lotics workspace model (`model.json`)\n\nOne JSON file describing a workspace: its tables, fields, options, views, roles,\nfirst rows, and the apps planned over them. The full form spells it out; the\n`from` form names a published preset and carries only what this business differs\nby (\xA7 Starting from a preset \u2014 prefer it whenever a preset fits the trade). Each\n\xA7 is `lotics docs model/<section>`.\n\n**What a model composes with**\n\n- **Entities and fields** (\xA7 Entity, \xA7 Field) \u2014 the tables, their columns, options and links.\n- **Roles** (\xA7 Field roles) \u2014 what each field MEANS: its name, its stage, its amount, its\n deadline. One declaration per field, read by every screen over the entity.\n- **Shapes** (\xA7 Apps and screens) \u2014 the question a register answers, as named slots the\n roles fill.\n- **Clauses** (\xA7 Apps and screens) \u2014 everything the author states about what is on a\n screen. What no clause states is drawn in its minimal form and printed `(default)`.\n\n**The working order**\n\n1. Name the people and each one\'s JOB \u2014 the work they alone decide or write.\n2. The entities and fields those jobs touch; a role on every field a screen reads.\n3. One app per job in `apps[]`, each one register over one entity.\n4. `lotics scaffold check model.json` \u2014 offline, every problem in one run.\n5. `lotics app preview model.json#<app> --shots <dir>` \u2014 each app drawn from `rows`, nothing created.\n6. `lotics workspace build model.json --deploy` \u2014 the tables, then every app, live\n (`lotics setup model.json --email you@company.com` first where no account exists).\n\n## The rules\n\n- **At least one entity, at most 50.** More tables than that is a data model\n being designed, not scaffolded \u2014 scaffold the rest in a second call.\n- **Adoption is explicit.** `lotics setup` REFUSES an entity whose `label`\n already names a table in the workspace, naming every colliding label at once.\n `lotics scaffold apply` adopts those tables and adds the fields, options and\n views they are missing. Nothing is ever modified or deleted, so applying the\n same model twice creates nothing the second time.\n- **Adoption is by LABEL, not alias.** Change an entity\'s `label` and the next\n run asks for a NEW table beside the old one. Renaming a FIELD is\n `lotics field rename <table> <field> "<new label>" --model <this file>`, which\n moves the platform, this file and every app bound to it together; deleting is\n `lotics run delete_table`. Neither goes through the file.\n- **The file\'s own majority is the language.** A model names no locale \u2014 which\n language it is in is what it mostly says, and `scaffold check` notes the label\n written the other way. The generated screens read the kit\'s pack, and a\n generated WRITE cannot: its refusals run on the server, so they are worded in\n that same majority. Mix the two and the workspace answers in two languages.\n- **`lotics scaffold diff model.json` says where the file and the workspace have\n come apart**, joined on label, entity then field \u2014 the join adoption itself\n makes. It exits 1 on any difference.\n- **Rows land only where every bound table is empty.** One table already holding\n records and no rows are written anywhere, and the result says\n `rows_skipped: true`: sample rows landing among a customer\'s real ones cannot\n be told apart from them.\n- **`lotics scaffold check` decides all of it offline**, and reports every\n problem in one run rather than the first: an alias that resolves to nothing, a\n link whose pair is not symmetric, and the rows themselves \u2014 a field the entity\n does not declare, an option alias the field does not declare, a link naming no\n row in the file, a `ref` used twice, a date that is not one, a value on a\n platform-computed field, and a files cell that is neither a relative path\n beside this file nor a `fil_` id.\n\n## Top level\n\nEvery table of keys on this page is generated from the schema `scaffold check`\nparses the file with, so it is the whole of what a key may hold. The first is the\nfull form; the second names a preset instead of restating one (\xA7 Starting from a\npreset).\n\n<!-- generated:start top-level -->\n\n#### Model file\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entities` | list of [Entity](#entity) | yes | The tables this model creates, with their fields, options and views |\n| `roles` | list of [Role](#role) | no | Workspace groups to create; members are added to them afterwards |\n| `templates` | list of [Template](#template) | no | Document templates whose content travels inline (html or email) |\n| `connections` | list of [Connection](#connection) | no | Connected accounts the workflow bodies push through, each named by alias and bound by provider |\n| `table_workflows` | list of [Automation](#automation) | no | Automations a table carries, fired by every write to it whichever door made it |\n| `rows` | map of alias \u2192 list of [Row](#row) | no | First records, keyed by entity alias \u2014 written only where every table they land in is empty |\n| `field_roles` | map of alias \u2192 map of alias \u2192 [Role declaration](#role-declaration) \\| `null` | no | Entity alias \u2192 field alias \u2192 the role that field plays on every screen over its entity |\n| `write_rules` | map of alias \u2192 [Entity write rules](#entity-write-rules) | no | Entity alias \u2192 what a create of that entity finds, copies and refuses |\n| `apps` | list of [App](#app) | no | The apps this workspace will have, each ONE register over an entity and the records it opens |\n| `preset` | [Preset](#preset) | no | The trade\'s branches, for a model published to be read; `setup` and `apply` scaffold the base alone |\n| `apply` | list of [Package entry](#package-entry) | no | Published packages copied in once this model\'s tables exist, in this order |\n\n#### From file\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | text | yes | The preset this model starts from, by its slug |\n| `variants` | list of text | no | The preset\'s variant slugs to merge onto its base, in order |\n| `rename` | map of alias \u2192 [Binding](#binding) | no | Entity alias \u2192 what this business calls that table and its fields |\n| `entities` | list of [Entity](#entity) | no | Tables this business has that the preset does not declare |\n| `rows` | map of alias \u2192 list of [Row](#row) | no | First records, keyed by entity alias \u2014 written only where every table they land in is empty |\n| `field_roles` | map of alias \u2192 map of alias \u2192 [Role declaration](#role-declaration) \\| `null` | no | Roles on the preset\'s fields and this business\'s own, over whatever the preset declares |\n| `write_rules` | map of alias \u2192 [Entity write rules](#entity-write-rules) | no | Create-time clauses on the preset\'s entities and this business\'s own, over whatever the preset declares |\n| `apps` | list of [App](#app) | no | The apps this workspace will have, each ONE register over an entity and the records it opens |\n| `apply` | list of [Package entry](#package-entry) | no | Published packages copied in once this model\'s tables exist, in this order |\n\n<!-- generated:end top-level -->\n\n**A model may not carry** `fixtures`, `knowledge` or `knowledge_expects`, and no\n`excel` / `word` / `pdf-form` template: each of those is content that lives in a\npublished bundle, which a model has none of. `apps` here is a plan of screens,\nnever built code. An unknown top-level key is an error, never ignored.\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 the file cross-references\nitself; they are never shown to anyone. `label` is what a person sees.\n\nLabels must be unique within their namespace \u2014 two entities, two fields on one\nentity, two options on one field, two views on one entity, two roles or two\ntemplates cannot share a label, because scaffold matches by label.\n\n## Entity\n\n<!-- generated:start entity -->\n\n#### Entity\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable entity alias, unique within the contract |\n| `label` | text | yes | Display label used as the table name at scaffold time |\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 |\n| `writes` | `false` | no | false: only the workspace\'s automations write this table\'s rows \u2014 no app opens or edits one |\n| `fields` | list of [Field](#field) (at least one) | yes | The table\'s columns |\n| `read_scope` | [Read scope](#read-scope) | no | Which rows a member reads. Absent, every member with access to the table reads every row. |\n| `unique` | list of list of alias (at least one) (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 of cardinality "one"). 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\n#### Read scope\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` | alias | yes | A role alias: whoever is in the group it binds to reads the row |\n\n#### Read scope by member\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `through` | list of alias (1\u20133) | no | select_record_link aliases from this entity outward, each of cardinality "one" \u2014 the clause\'s field is on the entity the last hop lands on |\n| `field` | alias | yes | A select_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 | select_record_link aliases from this entity outward, each of cardinality "one" \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 entity -->\n\n**`unique` is a set of values no two live rows share.** Each entry names fields\nof this entity holding ONE value \u2014 text, number, date, a single select, a link\nof cardinality `"one"` \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, and `scaffold\ncheck` prints it under *Who writes what* as `unique: Bed + Sown on`.\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. `scaffold check` prints it beside the table\nunder *Who writes what* and REFUSES a model where a table some create opens \u2014 a\nregister\'s own, or a record section\'s add \u2014 states none.\n\n**`writes: false` says only the workspace\'s automations write these rows** \u2014 a\nlog of what the system sent, a copy of what another system holds. No app opens,\nedits or files one: a record listing them keeps the section and opens each row\nat rest, with no Add; a screen over the entity operates at most the rows its\nrecord owns; a party of it is picked, never found or minted; and the generator\nwrites no create, no update and no `lotics.writes` entry for it. `scaffold\ncheck` prints it under *Who writes what* as `written by automations` and never\nnotes it as created nowhere. A `lifecycle` on it is refused \u2014 a row walked\nthrough stages is worked by a person \u2014 and so is a publish desk over it. Absent,\npeople write the rows; `true` is not a value.\n\n**`read_scope` is a ROW rule, enforced by the platform.** It is resolved at\nscaffold into the table\'s own row filters, so an app, a workflow reading for a\nviewer, and the API all answer the same rows \u2014 a per-record visibility field the\napp merely honours is a convention, not a gate. A `"self"` clause reads a column\nof one member or several, and every role alias and option alias a clause names\nmust be one this model declares. Scaffold writes the rule onto a table it\nCREATES; a table it adopted that ALREADY CARRIES a rule keeps that one, because\nthe rule is the workspace\'s own statement about its rows \u2014 and a run whose model\nstates a different rule reports the entity rather than leaving the claim silent.\n\n**An app may state that its sharing is its read gate: `"reads": "shared"`.** An\napp reads as its owner, so the rule reaches a viewer only as the predicate every\nquery and editor guard of every app over the entity carries \u2014 right for a desk of\none\'s own rows, wrong for a desk whose audience its sharing already decides, where\nwidening the rule meant a role group nobody remembers to fill. Stated on an APP,\nits queries, pickers and guards carry no entity\'s rule and whoever the app is\nshared with reads and writes every row it draws; every other app and the table\'s\nown filters keep the rule. Share it deliberately. Refused on an app none of whose\ntables states a `read_scope`; `scaffold check` prints it on the app\'s line and\nnames the app beside the rule it is exempt from.\n\n**The rows under a private record INHERIT its rule.** An entity that states no\n`read_scope` and hangs under one that does \u2014 through its `parent` link, over one\nhop or several \u2014 is read by the ancestor\'s rule, answered through that link, on\nits table\'s own filters and in every query and guard alike; nothing is restated,\nso a child needs none of the ancestor\'s columns. Stating a rule on the child\nkeeps that one instead, an ancestor with no rule passes nothing down, and a row\nhanging further under the scoped one than a row filter reaches is refused by\nname \u2014 state a rule on it. So is a hop over a link that names more than one row:\nthe rule would admit a reader any one of them admits while the editor\'s guard\nreads the first, so give the link `"cardinality": "one"` or state a rule on the\nchild.\n\n**ONLY the `parent` role is walked.** A register a scoped record reaches by any\nother link \u2014 the rows that NAME it \u2014 is read by that record\'s id with no rule\ntravelling to it, so it is refused until it states one of its own.\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 (a default is not applied to them), and a required\nLINK is written with its row: the entity it names is created first, and entities\nwhose required links name each other are refused, since none of their rows could\never be created.\n\n<!-- generated:start field -->\n\n#### Field\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"text"` \\| `"number"` \\| `"date"` \\| `"boolean"` \\| `"select"` \\| `"select_member"` \\| `"select_record_link"` \\| `"files"` \\| `"formula"` \\| `"rollup"` \\| `"lookup"` \\| `"autonumber"` | yes | What the field holds \u2014 each type takes the further keys its own section lists |\n| `alias` | alias | yes | Stable local alias, unique within the entity |\n| `label` | text | yes | Display label used as the field name at scaffold time |\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. Scaffold writes it onto the field, and every write path \u2014 create, update, an agent\'s tool call, a workflow\'s set \u2014 refuses the row by field name. |\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\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| `format` | `"number"` \\| `"currency"` \\| `"percentage"` | no | What the figure is \u2014 a plain number, money in `currency`, or a percent |\n| `currency` | text | no | ISO 4217 code |\n\n<!-- generated:end field-number -->\n\n`format` is what the number IS, and every surface reads it: `currency` prints as\nmoney in the code the row or the field states, `percentage` as a whole percent\nwith its sign. An ABSENT number is drawn absent \u2014 the one exception is a\n`sum` or count rollup the plan reads as a **`measure`**: that is the thing\naccumulated toward a bound, so nothing accumulated yet is zero and the meter\ndraws it. The same rollup read as an `amount` keeps its blank, and so does every\nother role: nothing added to what a row is WORTH means unpriced, not free. A\n`min`, an `avg`, a percentage of nothing and every FORMULA stay blank in any\nrole, because none of them has an answer to give. This is why the pair on one\nscreen reads two ways \u2014 what has come in against what is owed \u2014 and why a\nmeasure\'s own LIMIT, an amount, leaves an unquoted row out of the count rather\nthan reporting it as nothing collected. **AND WHERE THAT LIMIT IS ABSENT \u2014 OR\nZERO \u2014 THERE IS NO LEVEL AT ALL**: a level is a reading AGAINST a bound, so a row\nthat states no bound, or a bound of nothing, draws nothing \u2014 cell, fact and all \u2014\nrather than a numerator whose whole meaning was the comparison. "Collected 0"\nbeside a blank total reads as money against a job worth nothing, and "0 of 0"\nagainst a count of nothing owed claims a comparison nobody can make. A measure the model gives no limit is a plain figure\nand is unaffected. A share is stored in percent units \u2014 68.1 is 68.1 % \u2014 and the\ncolumn, the fact behind it, the meter it is judged by and the figure over the\nregister all say so.\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\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\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\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) | yes | The colour the option\'s badge is drawn in |\n| `mark` | [mark](#select) | no | The option\'s own mark, drawn in place of its colour dot wherever the option is shown: the channel it is ({kind: "brand", name: one of facebook, instagram, threads, meta, tiktok, google-ads, zalo, linkedin, x, google-meet, youtube}) or a kit glyph ({kind: "icon", name: "wrench"}). 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\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`.\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\nlookup of the select on another entity: the channel 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`), or a glyph from the kit\'s curated Lucide set\n(`{"kind": "icon", "name": "wrench"}`; a name outside it is refused naming the\nnearest ones). 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. `apply` writes the\nmarks onto the table, sets one an adopted option lacks, and reports one it wears\ndifferently rather than overwrite it.\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### `select_member`\n\nA person picker over the workspace\'s members. No default: a model cannot name\nmembers of a workspace that does not exist yet. 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-select_member -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `multi` | boolean | no | Allow multiple selections |\n\n<!-- generated:end field-select_member -->\n\n### `select_record_link`\n\n<!-- generated:start field-select_record_link -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `target_entity` | alias | yes | Alias of the entity this field links to |\n| `sync_both_ways` | boolean | no | Create a paired link field on the target entity for bidirectional sync |\n| `paired_field_alias` | alias | no | The pair edge of a bidirectional link: the field alias ON THE TARGET ENTITY that is this link\'s sync partner. Both sides of a pair carry it, each naming the other. Scaffold creates whichever side it reaches first WITH the pairing (the platform auto-creates the partner) and binds the partner alias to the auto-created field \u2014 without this edge the two contract fields would be created independently and collide with the auto-created partner. |\n| `cardinality` | `"one"` \\| `"many"` | no | How many linked records this field holds. Default \'many\'. \'one\' holds a single row and needs no partner; where the link IS paired, the partner side holds many. |\n| `display_field_aliases` | list of alias | no | Field aliases on the target entity shown as the link\'s display text / picker columns |\n\n<!-- generated:end field-select_record_link -->\n\nA two-way link is declared on BOTH sides, each naming the other as its\n`paired_field_alias`; the pair must be symmetric or the model is refused.\n\n**A single-valued link needs no partner.** `"cardinality": "one"` on its own is a\nlink that holds one row \u2014 one customer on an invoice, one project on a device \u2014\nand nothing is created on the target. The mirror invariant belongs to a PAIRED\nlink: pair a link when the target\'s own record should list what points at it, and\nleave it unpaired when it should not. Either way the record plan draws the\nrelation as a section on the side it points at, so an unpaired link costs the\ntarget nothing.\n\n### `files`\n\nNo keys beyond every field\'s; a row attaches documents to it (\xA7 Rows).\n\n### `formula`\n\n<!-- generated:start field-formula -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `formula` | [Formula](#formula) | yes | Formula config. The expression references other fields on the SAME entity by alias in braces, e.g. `{quantity} * {unit_price}`. |\n\n#### 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| `format` | `"number"` \\| `"currency"` \\| `"percentage"` \\| `"link"` | no | Display format. \'number\' / \'currency\' / \'percentage\' for numeric results; \'link\' for text-output formulas that return a URL \u2014 renders the result as a clickable link. |\n| `currency` | text | no | ISO 4217 currency code, e.g. USD, VND, EUR |\n| `output_type` | `"number"` \\| `"text"` \\| `"date"` \\| `"datetime"` \\| `"boolean"` | no | What the expression YIELDS \u2014 the kind the platform infers at write time, declared here so the offline checks can read it. `format` beside it is how that result is drawn, not what it is. Ignored on the wire (the platform re-infers it); `lotics scaffold export` writes the inferred 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 neither it nor a `format` says nothing about its\nresult, and every rule that needs one refuses it by name.\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 model 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 copy writes 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 `check` refuses it and names the token. A select looked up from another\nentity is tested there, in a formula of its own, and that result looked up.\n\n### `rollup`\n\nAggregates the records reached through a link on this entity.\n\n<!-- generated:start field-rollup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `source_field_alias` | alias | yes | Alias of a select_record_link field on this entity to roll up from |\n| `aggregate_option` | [aggregation](#rollup) | yes | Aggregation operation. Its `field_key` names a field alias on the linked entity. |\n| `filter` | [filter](#views) group | no | Only linked records matching this filter are aggregated. 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\n<!-- generated:end field-rollup -->\n\n`aggregate_option` is `{ "operation": \u2026, "field_key": \u2026 }` \u2014 `field_key` a field\nalias on the linked entity (`count` may omit it), and `operation` 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 anything.\n\n### `lookup`\n\nDisplays a field from the linked records.\n\n<!-- generated:start field-lookup -->\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `source_field_alias` | alias | yes | Alias of a select_record_link field on this entity to look up through |\n| `lookup_field_alias` | alias | yes | Alias of the field on the linked entity to display |\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_key` | text | yes | A field alias on the linked entity 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\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\n<!-- generated:end field-autonumber -->\n\n## Views\n\nSaved views live under the entity they belong to. Every field reference is a\nfield ALIAS on that entity.\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## Roles\n\nA role becomes a workspace group.\n\n<!-- generated:start roles -->\n\n#### Role\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable role alias, bound to a workspace group when the starter is copied |\n| `label` | text | yes | The group\'s name in the workspace |\n\n<!-- generated:end roles -->\n\n## Templates\n\nOnly inline `html` and `email` templates \u2014 the rest are file-backed and a model\nhas no bytes. `{{name}}` in the content is filled from the workflow\'s data.\n\n<!-- generated:start templates -->\n\n#### Template\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `type` | `"html"` \\| `"email"` | yes | html is a page a workflow renders to a PDF; email is a message a workflow sends |\n| `alias` | alias | yes | Stable template alias, unique within the contract |\n| `label` | text | yes | The template\'s name in the workspace |\n| `content_sha256` | text | no | sha256 (64-char lowercase hex) of the template content \u2014 inline: the utf-8 content string; file-backed: the bytes_ref bytes. The modified-detection reference. Optional in the schema so contracts published before template sha still parse; REQUIRED at publish (validatePackageContract). |\n| `content` | text | yes | Inline template content; placeholders reference field aliases |\n\n<!-- generated:end templates -->\n\nA paper that has to look like a counterparty produced it \u2014 an official letter,\nan acceptance minute, a supplier\'s bill \u2014 is the same `html` template with a\nshell around the body: a letterhead, a reference line, a seal and a signature\nblock, and paper grain over everything. One shell, many bodies; the data is the\nonly thing that changes, so a workflow can re-issue it over any record.\n\n```jsonc\n{ "alias": "cong_van", "label": "C\xF4ng v\u0103n", "type": "html",\n "content": "\u2026the page below, as one JSON string\u2026" }\n```\n\n```html\n<style>\n .sheet{position:relative;width:718px;padding:44px 58px 30px;background:#fbfaf6;color:#111;font:14.2px/1.5 \'Liberation Serif\',serif}\n .grain{position:absolute;inset:0;opacity:.34;mix-blend-mode:multiply;background:url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'140\' height=\'140\'><filter id=\'f\'><feTurbulence baseFrequency=\'.9\' numOctaves=\'2\'/><feColorMatrix values=\'0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .35 0\'/></filter><rect width=\'140\' height=\'140\' filter=\'url(%23f)\'/></svg>")}\n .top{display:flex;text-align:center;font-size:13.4px} .top>div{flex:1} .u{display:inline-block;border-bottom:1px solid #111;font-weight:700}\n .ref{display:flex;text-align:center;font-size:13.4px;margin-top:6px} .ref>div{flex:1} .ref .r{font-style:italic}\n h1{text-align:center;font-size:15.6px;margin:26px 0 18px} p{text-align:justify;text-indent:26px;margin:0 0 9px}\n .sig{display:flex;margin-top:20px} .sig .l{flex:1} .sig .r{width:290px;text-align:center;position:relative}\n .sig .nm{font-weight:700;margin-top:96px} .seal{position:absolute;left:4px;top:8px;width:166px;height:166px;opacity:.66;mix-blend-mode:multiply;transform:rotate(-17deg)}\n </style>\n <div class=\'sheet\'><div class=\'grain\'></div>\n <div class=\'top\'><div><b>{{issuer_parent}}</b><br><span class=\'u\'>{{issuer}}</span></div>\n <div><b>C\u1ED8NG H\xD2A X\xC3 H\u1ED8I CH\u1EE6 NGH\u0128A VI\u1EC6T NAM</b><br><span class=\'u\'>\u0110\u1ED9c l\u1EADp - T\u1EF1 do - H\u1EA1nh ph\xFAc</span></div></div>\n <div class=\'ref\'><div>S\u1ED1: {{number}}</div><div class=\'r\'>{{place}}, ng\xE0y {{day}} th\xE1ng {{month}} n\u0103m {{year}}</div></div>\n <h1>{{title}}</h1>\n <p>K\xEDnh g\u1EEDi: {{recipient}}.</p>\n {{{body}}}\n <div class=\'sig\'><div class=\'l\'><b>N\u01A1i nh\u1EADn:</b><br>- Nh\u01B0 tr\xEAn;<br>- L\u01B0u VT.</div>\n <div class=\'r\'><img class=\'seal\' src=\'{{seal_url}}\'><b>{{signer_title}}</b><div class=\'nm\'>{{signer}}</div></div></div>\n </div>\n```\n\n`lotics preview <file.html>` renders any such page to a PNG the way a demo\'s\nprops are made, sized to its content, so a paper can be looked at before it is\nput in a template.\n\n## Rows\n\nFirst records, keyed by entity alias. Up to 200 rows per entity and 2000 across\nthe model, attaching at most 2000 documents between them \u2014 a real data set\nbelongs in an import, not a model.\n\n```jsonc\n"rows": {\n "customer": [\n { "ref": "acme", "fields": { "name": "Acme Trading", "tier": "gold" } }\n ]\n}\n```\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\nA `ref` is lowercase letters, digits and underscores, and is never persisted.\n\nA `files` cell attaches documents: paths relative to this file (no `..`, never\nabsolute), which `check` proves exist and `apply` uploads into the workspace\nbefore any row is written \u2014 a paperwork business seeds its papers with its\nrows. The server accepts only `fil_` ids of files this workspace owns, which is\nwhat the upload leaves behind. After a run that wrote rows, the WORKSPACE holds\nwhich record each row became, under the row\'s own `<entity>:<ref>`:\n`delete_records` over them is how a seeded set is reset, and applying again\nre-dates it.\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| `select_record_link` | `"<entity-alias>:<ref>"` naming another row in this file \u2014 `"customer:acme"`, or an array for several |\n| `select_member` | `"self"` only \u2014 the person applying the model |\n| `files` | paths beside this file \u2014 `["scans/pccc_letter.png"]` \u2014 uploaded by `apply`/`setup` before the rows are posted; or `fil_` ids of files already in this workspace |\n| `formula`, `rollup`, `lookup`, `autonumber` | not allowed \u2014 the platform writes these |\n\n**Documents can be attached on their own, afterwards.** Rows land only into empty\ntables, so a model whose binaries were added after the first apply has no second\napply to carry them: `lotics scaffold apply model.json --documents` writes ONLY\nthe `files` cells, onto the records the first run created, found by each row\'s\nown `ref` in the binding the workspace holds \u2014 so a row added to or removed from\nthe file since changes nothing about the rest. Running it twice attaches nothing\nthe second time: the same path keeps the same file, and a cell is a set.\n\n### Relative dates\n\nA date cell holds a literal `YYYY-MM-DD`, or an expression relative to the day\nthe model is applied, 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\n`@month-start` exists because `@today-N` cannot promise a month: applied on the\n2nd, `@today-3` lands in the previous one.\n\n## Field roles\n\n`field_roles` names the reporting role a field plays on its entity \u2014 keyed by\nentity alias, then field alias \u2014 so every screen over the entity agrees on\nwhich column names the row and which select is the stage. A shape\'s slot binds\nto it (\xA7 Apps and screens). Like `rows` and `apps`, it is this file\'s: `check`\nproves it and the workspace never sees it. Each role sits on the types that can\nanswer it:\n\n| Role | On | Meaning |\n|---|---|---|\n| `identity` | `text`, `select_record_link`, `formula` | what a PERSON calls the row \u2014 the register\'s first column; a link where the row is "the product, at this branch"; a formula where the name is computed, and then it may not be `format`ted as a figure. NEVER an autonumber: a minted code is a `reference`. One per entity |\n| `reference` | `text`, `autonumber`, `formula` | the key the SYSTEM files the row under \u2014 an order number, a container code \u2014 drawn as the supporting line under the name rather than as a column of its own, and leading where the entity has no `identity` at all. One per entity |\n| `mark` | `files`, or a `lookup`/`rollup` that resolves to one | the row\'s picture \u2014 its own, or the linked record\'s. NEVER A COLUMN: a register draws words, so a mark is projected only where a surface leads with one \u2014 the subject rung of a picture-led grid, and a record\'s itinerary. A child entity\'s mark reaches a record\'s run and nothing else. REQUIRED on an entity some `party` link names and on the item an `offering_register` sells: both are drawn by their picture, initials until one is attached. One per entity |\n| `lifecycle` | single `select` | the ordered stages a row walks; option order is the order. Which of them END the flow is `outcomes` \u2014 each with the fields reaching it `asks` and, for the one a closing step reaches, its `verb` \u2014 which ending each is `verdicts`, which parts of the flow they group into is `phases`, and `history` keeps the day each one was reached. One per entity |\n| `category` | single `select`, a `lookup` that resolves to one, or a text `formula` stating its `values` | what the row IS \u2014 a kind, a service, a book. One badge in the option\'s own colour \u2014 or its own `mark` (\xA7 `select`) \u2014 with no ladder behind it and no flow to advance. Often a fact about the thing the row books rather than about the row, and then it is the lookup that reads it. Worked out, it is a formula: `{"role": "category", "values": [{"label": "Late", "color": "red"}, {"label": "On time", "color": "green"}]}` lists the words it answers, each worn as an option is (`mark` too); a word left out \u2014 its answer for "nothing yet" \u2014 draws nothing, and it fills no slot (a slot reads live options), so it is drawn as a `columns` entry. Repeats: a row can be of two kinds of thing |\n| `measure` | `number`, `formula`, `rollup` | a level read against a limit \u2014 see `against` and `alert`. Money where the model says so, and then it prints as money |\n| `expected_set` | `select` | its OPTIONS are the required set (documents, checks, services); an option no row has is a gap to show, not nothing. A multi-select draws as a ring and a fraction wherever it is drawn \u2014 absence is the information, and a list of what IS there says nothing about what is missing. |\n| `amount` | `number`, `formula`, `rollup` | THE money of a ledger row; `signed_by` says which way it moved, `against` names the reference it is quoted off. One per entity |\n| `when` | `date`, or a `formula`/`rollup`/`lookup` that yields one | the ledger or timeline date \u2014 stated, or derived: the day money MOVED is a formula over the two columns that could hold it. `due` makes it a DEADLINE; without it the date is the plain day it is. `until` names the stage of this entity\'s own `lifecycle` at which the countdown is spent, and says `due` by saying so. `from` names the date the deadline\'s window opens on. One per entity |\n| `slot` | single `select`, or a `datetime` `date` | where in the DAY a row sits \u2014 the run it is read in under its day\'s heading on an itinerary. A select\'s option order IS the run\'s order; a time is ordered by the clock. A plain `date` is refused: that is the day itself. One per entity |\n| `party` | `select_record_link`, `select_member` | WHO THE ROW IS FOR \u2014 the counterparty it was transacted with, or the workspace MEMBER whose work it is (a shift, an assignment, a timesheet period). A member is drawn as the person and opens nothing: they have no record in the app. One per entity \u2014 and left off where the `identity` IS the link to them: the row is then NAMED by that record, and a strip under the title would state them twice |\n| `parent` | `select_record_link` | the record this row belongs to \u2014 a line\'s order, a paper\'s case. The parent\'s record shows these rows; the row shows the parent as a fact. One per entity, and it links to an entity this model declares |\n| `contact` | `text` | a way to reach a party \u2014 an address, a number, the town it is in. The record draws them as ONE row of links under the name, on a page and in a drawer alike, so the role sits on every field that is one of them. A row carrying one is SOMEBODY, so a screen over it may lead with the mark (\xA7 `presentation`). Repeats |\n| `currency` | single `select` whose option LABELS are ISO 4217 codes | the money THIS ROW\'s figures are in. Every amount and every money measure on the entity is then printed per row rather than in one code for the whole column, and the select is not drawn as a fact of its own. One per entity |\n| `verdict` | `boolean`, `formula` | a settled pass/fail \u2014 ticked, or computed. TRUE is the PASSING side wherever it is drawn, a register\'s risk flag included, so a field whose true means trouble is the same fact asked the other way round. One per entity |\n| `obligation` | `date` | a day something is OWED by \u2014 a cut-off, a permit expiry, a payment due. It counts down by definition. `satisfied_by` names what CLOSES it (or `until`, the stage the record stops owing it at), `label` what is owed. Repeats: a row owes several things, and a shape that fans them draws one row each |\n| `expires` | `date`, or a `formula`/`rollup`/`lookup` that yields one | the day something the row HOLDS runs out \u2014 a licence, a certificate, an inspection. Read as a COUNTDOWN, never as the date: the days left while it holds, and a renewal owed once it has passed. `warn` (required) is how many days ahead it starts to need attention, which nothing about a date says. Repeats: a machine carries an inspection and an insurance |\n| `origin` | `text`, single `select`, or a one-row `select_record_link` | where the row STARTS. Declared with a `destination` \u2014 a route has two ends, and one alone is refused \u2014 and then every surface over the entity draws the two as ONE reading, `Warehouse 4 \u2192 Site B`: on the register\'s supporting line after the key, and as one column on the child registers and party histories that list these rows. The two fields are then not facts of their own, and a `columns` clause naming either end is refused. One per entity, holding ONE place |\n| `destination` | `text`, single `select`, or a one-row `select_record_link` | where the row ENDS \u2014 the other half of the `origin`\'s route, and read only with it. One per entity, holding ONE place |\n| `body` | `text` | the words a dated entry SAYS \u2014 the note of a call, the text of a message \u2014 plain or markdown alike: the role makes the row an entry, never the format. Undeclared, a log reads the entity\'s first `markdown` text as the entry. One per entity |\n| `recording` | `files` | the audio or video a dated entry IS the record of \u2014 a call, a site walk, a lesson \u2014 played wherever it is read, on the entry and on the record it opens, never listed among its papers: a log\'s attachments are then its first OTHER pile. One per entity |\n| `verbatim` | `text` | what was literally said or written on a dated entry \u2014 a transcript, a pasted exchange \u2014 drawn behind one disclosure wherever it is read, under the entry\'s words and at the foot of its band on the record, never as the words. Refused on the entry\'s own words (its `body`, else the entity\'s first `markdown` text): declare those as the `body`. One per entity |\n\n**`due` is what makes a `when` a deadline.** A date a row is merely filed by \u2014 the\nday a lead arrived, the day a movement happened \u2014 is neither early nor late,\nhowever long ago it was. Without the clause every desk drew every `when` as a\ncountdown and a two-month-old lead read "40 days overdue" on a record nobody was\nlate on. So a bare `when` is a plain date, and `{ "role": "when", "due": true }`\nis the one that counts down. An `obligation` needs no clause: a day something is\nowed by is a deadline already.\n\n**`until` stops a countdown where the work is DONE.** A deadline counts down so\nsomebody acts on it, and a row that has arrived needs nothing: without the clause\na delivered order is tinted for a date it met, and every settled row past its day\njoins the urgent ones in the reader\'s glance. It names one option of the entity\'s\n`lifecycle`; at that stage, at every stage past it and at every `outcome`, the\ndate is drawn as the plain day it is. On a `when` it also says the date is `due`.\n\n**`from` opens a deadline\'s window.** `{ "role": "when", "due": true, "from":\n"starts_on" }` names the date field on the same entity the window opens on. The\nrecord\'s band then reads the window \u2014 both days and how many are left \u2014 and a\ntarget read against the deadline is paced by how far through the window today\nis. The band only reads the two days; each one a person states stays a fact.\nIt takes a `date` on the same entity \u2014 a stamped `derive_from` day included \u2014\nand only on a `when` that counts down.\n\n**`verdicts` says which ending is the refusal.** `outcomes` names the doors\nthat end the flow and nothing about which of them the flow is walked for, and\nno label can say it: "Cancelled" and "Delivered" are both plain statements.\n`{ "outcomes": ["won", "lost"], "verdicts": { "won": "pass", "lost": "fail" } }`\nkeeps the list and states the ending beside the doors that have one; the\nladder, the register\'s stage column and the strip then draw a row in a `fail`\noutcome as settled and refused rather than as the furthest rung it reached,\nand moving a row into one asks first. An outcome left out is where the row\nstands and neither. Only an outcome takes a verdict, and only on a\n`lifecycle`.\n\n**`at` files a value under the rung it belongs to.** A reason a row was\nrefused, the day it was closed, the tracking number it is shipped under: each\nis stated when the row REACHES a stage, and a create that asked for it asks\nthe reader what went wrong with a row they are opening. `{ "at": "cancelled" }`\nnames the option of this entity\'s own `lifecycle`; the create then never asks\nit, the record states it once the row has reached that rung (or holds a value),\nand `scaffold check` prints it under **Who writes what**. It stands without a\n`role` on a field that has no job on a screen, and beside one on a field that\nhas. Refused on the rung a row opens at, on a `required` field, and on the\nroles a row is opened with \u2014 `identity`, `lifecycle`, `parent`.\n\n"One per entity" is one COLUMN, never one value on screen. A second counterparty\nor a second parent stays a ROLELESS `select_record_link`: which field fills a\nslot is the screen\'s answer (\xA7 Apps and screens), and the extra link is a fact on\nthe row with its own section on the record, headed by that link\'s own label. A\nrole naming two fields would move the ambiguity into every shape that reads it.\nThe same holds for a link from an entity to ITSELF \u2014 the row a line sits under in\na breakdown, the same item\'s lines in the periods before: it takes no role, and\nthe clause that reads it names it (`nest`, `depends_on`, the rollup a claim\'s\n`previous` sums over).\n\nA COMPUTED field is admitted where its RESULT is what the role needs, and\nrefused where it is not: `identity` and `reference` need `text`, `when` a `date`,\n`mark` `files`. A formula\'s result is its `output_type`; a rollup\'s and a\nlookup\'s is read through the walk. So a formula yielding a number is refused in a\nname\'s place, a lookup landing on anything but `files` is refused as a `mark`, and\na formula declaring NO result is refused by all four \u2014 naming `output_type` as\nthe remedy, because a value drawn as a type nobody stated is the wrong cell with\nnothing saying so. The result also decides the DEVICE every screen draws the value\nwith: a rollup taking the `latest` of a set of dates is drawn as a date, a lookup\nof a select as the words it holds.\n\nA bare role name is the common form. A role that is read against a SECOND field\ntakes the object form, whose keys are these:\n\n<!-- generated:start field-roles -->\n\n#### Role declaration\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `role` | [role](#field-roles) | no | What the field answers for every screen over its entity. Absent only where the declaration states `at` and nothing else |\n| `values` | list of [Category value](#category-value) (at least one) | no | For a category on a text formula: every word the formula answers that NAMES something, each drawn as a chip in its colour (and glyph). A word left out \u2014 the formula\'s answer for "nothing yet" \u2014 draws nothing. A select\'s options are already its values |\n| `against` | alias \\| number | no | For a measure: the limit it is read against \u2014 a constant, or a field on the same entity by alias whose value is a number (a number field, or a formula or rollup whose result is one). For an amount: the REFERENCE it is quoted off \u2014 the list price, the going rate \u2014 by alias, never a constant, and with no alert |\n| `alert` | `"over"` \\| `"under"` | no | With against: which side of the limit needs attention \u2014 over a capacity, under a minimum |\n| `counts` | `"days"` \\| `"hours"` | no | For a measure: WHAT the number counts, where the unit changes how a surface draws the row rather than how the figure reads. "days" makes the row span that many days of a run, so a stop of 3 is drawn across three of its days instead of only the one it starts on. "hours" is how long the row TAKES inside its day, stated beside it rather than spread across the run |\n| `reading` | `"fill"` \\| `"threshold"` | no | With against: what that limit IS. "fill" (the default) is a WHOLE the level is a share of \u2014 6 of 8 lines shipped. "threshold" is an alarm the level stays one side of \u2014 a reorder minimum, a margin floor \u2014 and every reading on the safe side is inside it, so there is no share to draw |\n| `gate` | alias \\| number | no | For a measure read "under" a limit it fills toward: the share of that limit, in percent, at which the row PASSES \u2014 a constant, or a percentage field on the same entity by alias. Absent, the row passes at the whole limit |\n| `outcomes` | list of (alias \\| [Outcome](#outcome)) | no | For a lifecycle: the options that END the flow \u2014 a row in one has arrived, not advanced. Each is its alias, or `{"stage": \u2026, "asks": [fields decided on arrival], "verb": the closing step\'s button}` |\n| `verdicts` | map of alias \u2192 `"pass"` \\| `"fail"` | no | For a lifecycle: outcome alias \u2192 which ending it is \u2014 "pass" the close the flow is walked for, "fail" the refusal. An outcome with no entry is where the row stands and neither |\n| `phases` | list of [Phase](#phase) (at least one) | no | For a lifecycle: the stages grouped into the parts of the flow they belong to, in order. Every stage that is not an outcome falls in exactly one; the outcomes may be placed in one or left out, and are then read as a last part of their own |\n| `history` | `true` \\| alias | no | For a lifecycle: keep every stage this row reached, with the day it reached it and who moved it. The model derives the table \u2014 `true` names it `<entity>_history`, an alias names it \u2014 and no other file declares it |\n| `due` | `true` | no | For a when: this date is a DEADLINE the row counts down to, and a row past it is late. Absent, it is the plain day it is \u2014 the day a lead arrived is neither early nor late |\n| `warn` | integer | no | For an expires: how many days ahead of the day it runs out the row reads as needing attention \u2014 the window a renewal is started in |\n| `until` | alias | no | For a when or an obligation: the option of this entity\'s lifecycle the countdown STOPS at \u2014 at or past that stage, and at every outcome, the date is drawn plain. On a when it also says the date is a deadline |\n| `at` | alias | no | The FLOW option of this entity\'s lifecycle this field\'s value belongs to \u2014 it is stated once the row reaches that rung, never when the row is opened. Never an outcome: what an ending decides is that outcome\'s `asks` |\n| `from` | alias | no | For a when that counts down: the date field on the same entity the window OPENS on \u2014 the countdown then reads the window from that day to this one, and how far through it the row should be by today |\n| `signed_by` | alias | no | For an amount: the select on the same entity that says which direction it moved |\n| `outflow` | list of alias | no | With signed_by: the options of that select that spend \u2014 the amount reads negative under them |\n| `required_by` | [Required by](#required-by) | no | For an expected_set: the select whose value decides which entries a row owes |\n| `answered_by` | alias | no | For an expected_set on a multi-select: the entity whose rows ANSWER it \u2014 rows hanging under this record by their `parent` link, each filing one entry through its own single-select expected_set. The options CHOSEN here are then what the record owes, not what it holds, and those rows are counted against them rather than against every option of their own select |\n| `label` | text | no | For an obligation: what is OWED, as the reader says it \u2014 absent, the date field\'s own label |\n| `satisfied_by` | alias | no | For an obligation: the field on the same entity that CLOSES it \u2014 the date it was done, or the file that proves it. Where nothing is stamped, `until` names the stage the record stops owing it at instead |\n\n#### Category value\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | Display label for the option |\n| `color` | [colour](#select) | yes | The colour the option\'s badge is drawn in |\n| `mark` | [mark](#select) | no | The option\'s own mark, drawn in place of its colour dot wherever the option is shown: the channel it is ({kind: "brand", name: one of facebook, instagram, threads, meta, tiktok, google-ads, zalo, linkedin, x, google-meet, youtube}) or a kit glyph ({kind: "icon", name: "wrench"}). Every option of a field has one, or none does. A mark a reader does not draw falls back to the dot |\n\n#### Outcome\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `stage` | alias | yes | The option of this lifecycle the row arrives at |\n| `asks` | list of (alias \\| [Outcome ask](#outcome-ask)) (at least one) | no | Fields of this entity decided when the row reaches this stage \u2014 asked then, and never a standing fact. Each is its alias, or `{"field": alias, "required": true}` where the move cannot land without it |\n| `verb` | text | no | This stage is reached by a closing step drawn as the record\'s last section, its one button labelled by this verb; absent, the stage is a door of the status menu |\n\n#### Outcome ask\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field of this entity asked when the row reaches the stage |\n| `required` | `true` | yes | The move to this stage is refused until the field has a value \u2014 the ending cannot be recorded without it |\n\n#### Phase\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | What this part of the flow is called, as the reader says it |\n| `stages` | list of alias (at least one) | yes | The option aliases of this lifecycle that fall under it, in order |\n\n#### Required by\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | `"own"` \\| `"parent"` | no | WHOSE field decides it, which the set\'s own shape settles: a multi-select holds the whole set, so "own" (the default, this entity\'s field); a single select is one entry per row, so "parent" \u2014 the record these rows hang under, reached through this entity\'s `parent` link. The other way round is refused |\n| `field` | alias | yes | The field whose value decides which entries of this set are required \u2014 a single select, or a yes/no answered on that row |\n| `options` | map of alias \u2192 list of alias | yes | That field\'s option alias \u2014 or "true"/"false" where it is a yes/no \u2014 \u2192 the option aliases of THIS set required under it. A value with no entry is omitted, not empty. |\n\n<!-- generated:end field-roles -->\n\nWhat the tables do not say \u2014 the pairs, and what each is refused for:\n\n- **`measure`**: `against` and `alert` come together. `counts` is stated, never\n inferred \u2014 nothing about a `3` says whether it is nights, pallets or hours, and\n a quantity drawn across a week is a run nobody can read. `gate` passes a row\n short of its whole limit \u2014 `90` passes it at nine tenths of its target \u2014 and is\n refused over a capacity and beside `reading: "threshold"`, which has no share\n to pass at.\n- **`lifecycle`**: not every option may be an `outcome`, and terminal-ness\n belongs here, never written into a stage\'s label. A stage in two `phases` is\n drawn twice and a stage in none vanishes off the ladder; which stages belong\n together is the business\'s answer. `history` is declared BESIDE `phases` and\n refused without them: the rungs a ladder draws are the live select\'s options,\n which nothing reading a file or an app\'s spec can see, so `phases` is the one\n statement of what the dates have to cover. The table it derives \u2014 the link back\n to this record, the stage, the moment it was reached and who moved it \u2014 is seen\n by `check`, `apply`, `diff` and the app generator exactly as a written one is,\n and a file that also writes it by hand is refused. Its rows are appended by the\n two table automations the flag also derives (\xA7 Table automations) \u2014 one on the\n create a row opens with, one on every update that moves the stage \u2014 so a rung is\n dated whichever door moved it. **A\n first apply backfills nothing** \u2014 rows that existed before the flag walked their\n stages before that table existed. The honest backfill is ONE opening row per\n existing parent, at its FIRST stage, dated when that parent was created; it is a\n create per parent \u2014 `lotics run create_records` \u2014 and `scaffold apply` prints it\n as the work it did not do.\n An outcome\'s `asks` are the fields DECIDED on arrival \u2014 why it was lost, what\n was awarded: asked when the record moves there, written in the move\'s own\n save, never a standing fact (a band naming one is refused), and read after the\n stage on its status. Each is one a person states, never `required` on the\n field, the name or the lifecycle; `{"field": \u2026, "required": true}` holds the\n move until it has a value, refused by the save whoever calls it. `verb` makes the ending a CLOSING STEP: the\n record\'s last section, after every row it owns, one button of those words,\n confirmed naming the record and the asked fields, then stage and answers\n written together. Once there it reads as settled \u2014 when and by whom where a\n `history` is kept \u2014 with a quiet Reopen to the flow\'s last stage; a screen that\n does not write the record draws neither button. One verb per lifecycle; an\n ending with none is a door of the status menu. A field\'s `at` names the FLOW\n rung its value arrives at, never an outcome \u2014 what an ending decides is its\n `asks`, so `at` naming one is refused.\n- **`amount`**: its `against` is never a constant (a price typed into the model\n ages with nothing to update it) and never beside an `alert` \u2014 beating a\n reference is the point \u2014 and the catalogue item\'s record draws the pair on one\n axis with the gap said in words. `signed_by` and `outflow` come together, and\n `outflow` is some of that select\'s options, never all. Without them every shape\n over the ledger adds both directions together, and the trend plots one rising\n line.\n- **`expected_set`**: without `required_by` the denominator is every option, so a\n set whose entries are mutually exclusive by kind reads `1 of 4` on every\n complete row. A multi-select holds the whole set on one row and is narrowed by\n that row (`from` omitted); a single select is one entry per row and takes\n `from: "parent"` \u2014 which documents an order owes is the ORDER\'s type, and the\n papers carry no column that says it. The other way round is refused rather than\n left conditioning nothing. Under `answered_by`, every option the set can\n require is one the answering rows can file, it takes no `required_by`, and it\n fills no slot of a register: a row holds what it owes, never how much of it the\n rows below have answered.\n- **`expires`**: `warn` is its window, the days ahead of the day it runs out\n that a renewal is started in. A monitored set\'s `level` bound to one draws each\n unit\'s days left \u2014 tinted inside the window, and read as owed past the day \u2014\n and `summary.above: "counts"` counts the units inside the window or past it. A\n formula of days left against a number is the same fact drawn as a meter of\n 1 287 of 60.\n- **`obligation`**: `satisfied_by` or `until`, never neither \u2014 without one every\n obligation the business ever met stays on the desk.\n\n```jsonc\n"field_roles": {\n "product": { "name": "identity", "photo": "mark" },\n "stock": { "on_hand": { "role": "measure", "against": "minimum", "alert": "under" },\n "uptime": { "role": "measure", "against": 80, "alert": "under" } },\n "order": { "stage": { "role": "lifecycle", "outcomes": ["delivered", "cancelled"],\n "verdicts": { "delivered": "pass", "cancelled": "fail" },\n // Two pieces of work, and every step dated.\n "phases": [{ "label": "Taking it on", "stages": ["placed", "confirmed"] },\n { "label": "Getting it out", "stages": ["picked", "shipped"] }],\n "history": true },\n // A DEADLINE COUNTS DOWN \u2014 `payment.paid_on` below is a plain day.\n "ship_by": { "role": "when", "due": true },\n // The day the papers are owed by, and the day they went.\n "papers_due": { "role": "obligation", "label": "File the papers", "satisfied_by": "papers_sent" },\n // Nothing stamps a deposit paid \u2014 the order\'s own stage ends it.\n "deposit_due": { "role": "obligation", "label": "Take the deposit", "until": "delivered" },\n // Stated when an order is cancelled, never when it is placed.\n "cancel_reason": { "at": "cancelled" } },\n // WHICH PAPERS THIS ONE OWES IS THE ORDER\'S OWN TYPE, and no document says it.\n "document": { "order": "parent",\n "kind": { "role": "expected_set",\n "required_by": { "from": "parent", "field": "kind",\n "options": { "export": ["invoice", "customs"] } } } },\n // `kind` is the direction select \u2014 options `received` and `paid_out`, no role of its own.\n // Every figure on a quotation is in the currency that row names.\n "quote": { "unit": "currency", "total": "amount" },\n // What the price is quoted off, so the item\'s page reads one against the other.\n "service": { "price": { "role": "amount", "against": "list_price" },\n "kind": "category" },\n // The day the money moved is neither early nor late, so it stays a bare `when`.\n "payment": { "paid_on": "when",\n "value": { "role": "amount", "signed_by": "kind", "outflow": ["paid_out"] },\n // A receipt owes a receipt voucher; a payment owes an invoice and a payment voucher.\n "papers": { "role": "expected_set",\n "required_by": { "field": "kind",\n "options": { "received": ["receipt_voucher"],\n "paid_out": ["invoice", "payment_voucher"] } } } },\n // A CHECK THAT FAILED OWES ITS CONSEQUENCE \u2014 keyed by the row\'s own answer,\n // and the answer that owes nothing is omitted rather than named with an empty list.\n "check": { "passed": "verdict",\n "evidence": { "role": "expected_set",\n "required_by": { "field": "passed",\n "options": { "false": ["photo", "owner", "due"] } } } },\n // WHAT THIS JOB NEEDS IS CHOSEN ON THE JOB, and the certificates filed under it\n // answer that choice \u2014 a certificate\'s `kind` holds every option `needs` does.\n "job": { "needs": { "role": "expected_set", "answered_by": "certificate" } },\n "certificate": { "job": "parent", "kind": "expected_set", "scan": "mark" }\n}\n```\n\nIn a file that starts from a preset (\xA7 Starting from a preset), `field_roles`\nmay name the preset\'s fields as well as this business\'s own; a role the preset\ndeclares itself is kept unless this file names the same field, and `null`\nclears it.\n\n## Write rules\n\n`write_rules` is what only a CREATE meets \u2014 keyed by entity alias, like\n`field_roles`, and this file\'s in the same way: `check` proves it and the\nworkspace never sees it. An update names one field that moved; a create takes a\ndraft whole, so it has to know which row a name already belongs to, where a\nvalue comes from when nobody types it, and what a figure or a picker may hold.\nThe generator writes one `create_<entity>` workflow and one `New<Entity>Dialog`\nper entity an app can operate, from these clauses and the fields\' own\n`required`.\n\n```jsonc\n"write_rules": {\n // A customer is RECOGNISED by their address. Where an entity names a customer\n // as its `party`, the create takes the email instead of a picker, reuses the\n // row it matches and mints one only where nothing does \u2014 so the book never\n // grows a second Acme because somebody typed the name differently.\n "customer": { "natural_key": ["email"] },\n "order_line": {\n "fields": {\n // A line of nothing is not a line. Refused on create and on update, at\n // the control the figure was typed into.\n "quantity": { "min": 1, "max": 9999 },\n // The price is fixed at the moment of ordering \u2014 COPIED off the product,\n // not looked up for ever after, so the price list moving next week does\n // not silently reprice an order already placed.\n "unit_price": { "default_from": "product.price" },\n // And nothing is sold off an empty shelf. The picker reads only the rows\n // that answer this, and the write refuses the same rows again \u2014 a caller\n // who never opened the picker is bound by it too.\n "product": {\n "options_where": {\n "node_type": "group", "logic": "and",\n "children": [{ "node_type": "condition", "type": "number",\n "field_key": "in_stock", "operator": "greater_than", "value": 0 }]\n }\n }\n }\n }\n}\n```\n\n<!-- generated:start write-rules -->\n\n#### Entity write rules\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `natural_key` | list of alias (at least one) | no | The field aliases a row of this entity is RECOGNISED by. A create that names this entity as its party matches on them and reuses the row it finds, minting one only where nothing matches. |\n| `fields` | map of alias \u2192 [Field write rule](#field-write-rule) | no | Field alias \u2192 what that field\'s value is copied from, bounded by or picked among |\n\n#### Field write rule\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `default_from` | text | no | Copy this field\'s value from the linked row at CREATE time \u2014 `<link alias>.<field alias>`, the link being a one-row link on this entity. The value is copied rather than looked up, so the source changing later leaves the row alone. |\n| `min` | number | no | Refuse a create or update whose figure is below this. |\n| `max` | number | no | Refuse a create or update whose figure is above this. |\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 entity\'s own fields, each `field_key` a field alias. It narrows the picker\'s read and is refused again where the write lands. |\n\n<!-- generated:end write-rules -->\n\n- A `natural_key` is `text` or `number`, because a person types it back, and a\n `text` key declares `unique: true` on the field itself \u2014 two rows sharing it\n would make find-or-create pick whichever the read answered first.\n- A `default_from` link is `required`, because there has to be a row to read, and\n the two field types must match.\n- An `options_where` link is `required`, since the rows it may point at are\n refused again where the write lands.\n\nA field\'s own `required` is not here: the contract carries it, every write path\nrefuses the row by field name from it, and the generated panel stands its commit\ndown until the same set is filled. `unique` is the text field\'s own clause\n(\xA7 `text`) \u2014 the create says so at the control before the column does.\n\nWhat a create asks is what a person STATES, so an entity stating `writes:\nfalse` (\xA7 Entity) has no create at all. A `default_from` field is not drawn,\na `lifecycle` opens at its first rung, and the stamp an obligation\'s\n`satisfied_by` names is not asked (the row is only now taking that on) \u2014 one\nfact, one place.\n\n**Whose row it is.** An entity\'s owner column is the `select_member` column a\nlens reads (\xA7 `filters`) in ANY app of the model \u2014 the one opened on `"mine"`\nwhere lenses name several \u2014 else the one `"self"` clause of its `read_scope`\nover its own column. None where a lens and that rule name different columns, or\nseveral are named and nothing picks one. Every generated create of a row of that\nentity writes whoever ran it there: the register\'s and a section\'s, a party the\nkey mints, a line a file lands, and the touch and next act a queue\'s log opens.\nWhere the draft asks that column, it opens on the reader, who may hand the row\nto somebody else, and a blank is the reader too \u2014 so it is never a required\ninput, whatever the column says. So a new row is in its\ncreator\'s MINE, and is edited like any other column afterwards. A column the\nplan fills another way (a `default_from`, a value stated at a later rung) is\nnot written.\n\nA field the workspace writes (a formula, a rollup, a lookup, an\nautonumber, a date with `derive_from`) is never an input of a create or an\nupdate, a write in a generated body, or an entry in `lotics.writes`, and its\nrecord fact has no editor. Of the files columns it asks for exactly ONE, the\n`mark`: that pile IS the row, so a register leading with the picture would\notherwise open a faceless row, while every other pile is papers gathered onto a\nrow that exists and a panel asking for all of them is a filing cabinet with a\nSave button. The screen narrows that set where it states a `create` (\xA7 Apps and\nscreens). `scaffold check` prints every clause under its entity in **Who writes\nwhat**.\n\n**The `parent` is prefilled only in the door that mounts the act.** A row opened\nfrom the record it hangs under takes that record from the door it was pressed\nin, so the link is neither asked for nor drawn. The same entity\'s act on its own\nregister has no such row, so there the parent is an ordinary picker the draft\ncannot be committed without \u2014 a payment opened from a money app belongs to a\ncase either way.\n\n## Table automations\n\n`table_workflows` are what a TABLE does on every write to it \u2014 whichever door\nmade the write: an app, the table explorer, `lotics run`, a chat agent, another\napp over the same table. A fact that has to hold for every row is kept here,\nnever in the one write path an app owns.\n\n<!-- generated:start automations -->\n\n#### Automation\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable automation alias, unique within the file \u2014 what a workspace remembers the automation it provisioned became |\n| `label` | text | yes | What the automation is called on its table |\n| `description` | text | no | What it does, in a sentence shown beside its name |\n| `entity` | alias | yes | The entity whose table it fires on |\n| `trigger` | `"before_create"` \\| `"after_create"` \\| `"before_update"` \\| `"after_update"` \\| `"before_delete"` \\| `"after_delete"` | yes | The write it fires on \u2014 `after_*` runs once the write has landed, `before_*` runs inside it and may refuse it |\n| `watch` | list of alias (at least one) | no | Fields of that entity an update has to change for it to fire; an `*_update` trigger only. Absent: every write fires it |\n| `body` | text | yes | Alias-form JS-subset workflow source, as an app workflow\'s: `@@entity:x@@`, `@@field:x.y@@`, `@@option:x.y:z@@` and `@@connection:x@@` resolve where it is applied, and `trigger.record_id` names the row that fired it |\n\n<!-- generated:end automations -->\n\n- Every `@@\u2026@@` a `body` names must be declared in this file. Beside\n `trigger.record_id`, a body reads `record`, the row as the write left it,\n `changes`, what an update moved, and `runtime.triggered_by_member_id`, the\n member the write came from. It is verified where it is applied, as an author\'s\n own would be.\n- A lifecycle\'s `history` DERIVES two of them (\xA7 Field roles); a file that also\n declares one under a derived alias is refused unless it is exactly what the\n flag derives.\n\n`scaffold apply` provisions each on its entity\'s table and REMEMBERS which one it\nis: a later apply rewrites that automation in place \u2014 body, gate, event, name \u2014\nand never adds a second one beside it. One the workspace has since removed is a\nrefusal naming `lotics scaffold unbind automation <alias>` \u2014 except one that\nwrites a `writes: false` table, which is restored and rewritten (found by its\nlabel too where nothing binds it), or added afresh when schema its archived body\nnames was deleted since. An automation\nthat writes a `writes: false` table is HELD \u2014 neither created nor rewritten \u2014\nwhile a deployed app workflow writes that table itself: the apply names each app\nand workflow alias, applies everything else, and exits 1; `lotics app\nregenerate` then `lotics app deploy` on each, and apply again. `scaffold check`\nprints every automation under its entity in **Who writes what**; `scaffold diff`\nnames each one without counting it, because the workspace\'s export carries no\nbody to compare, and exits 1 on one the apply would hold; `workspace build`\napplies a model that declares one on every run for the same reason.\n\n## Connections\n\nAn automation that pushes to a connected service names the account it pushes\nthrough by ALIAS \u2014 `connected_account_id: "@@connection:books@@"` \u2014 never by a\n`cac_` id: an account is a credential of one workspace, and the file is applied\nin others.\n\n<!-- generated:start connections -->\n\n#### Connection\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable connection alias; a body names the account as `@@connection:<alias>@@` wherever it states a `connected_account_id` |\n| `provider` | `"wininvoice"` \\| `"outlook"` \\| `"gmail"` \\| `"google_drive"` \\| `"facebook"` \\| `"zalo"` \\| `"threads"` \\| `"instagram"` \\| `"x"` \\| `"linkedin"` \\| `"payos"` \\| `"misa"` \\| `"lark"` \\| `"fedex"` \\| `"kiotviet"` | yes | The service the account is of; where it lands, the alias binds to the account the request names, else to the one account of this provider the applier can use |\n\n<!-- generated:end connections -->\n\n- Every `@@connection:\u2026@@` a body names is declared here, and every connection\n declared here is named by a body.\n- The applying member needs `--connection <alias>=<cac_id>` on `scaffold apply`,\n `library init` or `app upgrade` where they can use two or more accounts of the\n provider; with none named, or no account at all, the refusal lists the\n provider, the alias and each candidate before the first table is written \u2014\n which account a push lands in is never a guess. The workspace remembers the\n account bound, so a later apply pushes through it whatever was connected\n since. A bound account since archived is a refusal naming\n `lotics scaffold unbind connection <alias>`.\n\n## Apps and screens\n\n`apps` is the plan: each app as ONE REGISTER \u2014 a SHAPE over an ENTITY \u2014 under\n`screen`, printed back by `lotics scaffold check` with the field in every slot\nbefore a screen exists.\n\n### Shapes\n\n**THE AUTHOR COMPOSES; THE PLATFORM DRAWS EVERY PIECE.** Three things decide\nevery screen and every record: the SHAPE, whose slots are the table below; the\nROLES the entities declare (\xA7 Field roles), which fill those slots and decide\nwhat each child\'s rows become; and the CLAUSES, the keys of the App and Screen\ntables, which are how the author states what is on the screen \u2014 a register\'s\ncolumns, a record\'s sections and their order, a lifecycle drawn as a ladder or\nas a status, the bands its facts fall into, what an outcome asks. How a piece\nLOOKS \u2014 a row, a party, a status, an empty state \u2014 is the runtime\'s and no\nclause styles it. Where a clause is not stated the plan draws the minimal form\nand `scaffold check` marks it `(default)`, so a clause nobody wrote is never read\nback as one somebody did; `--why` prints the rule behind every binding, section\nand default. `lotics docs clauses` lists every clause with an app that states it.\n\n| Shape | Answers | Required | Also fills | Record | Tabs |\n|---|---|---|---|---|---|\n| `lifecycle_desk` | what is stuck, what do I move next | `lifecycle`, `identity` | `mark`, `party`, `amount`, `measure` (level, one or several), `when` | page, or drawer where the entity carries `parent` | the lifecycle\'s stages |\n| `party_register` | who is this, our history, is there a risk | `identity` | `mark`, `contact`, `measure` (worth), `verdict` (risk) | page | none |\n| `offering_register` | what do we offer, at what price, can I sell it | `identity` | `mark`, `amount` (price), `measure` (availability) | page | none |\n| `transaction_ledger` | does this period reconcile, what is unexplained | `when`, `amount` | `identity` (reference \u2014 the line\'s own number), `party`, `lifecycle` (classification), `expected_set` (document) | drawer | none |\n| `monitored_asset_set` | what needs attention, is that number normal | `identity`, `measure` (level, one or several) or `expires` (level, as the days left) | `mark`, `lifecycle` | drawer | none |\n| `obligation_desk` | what is due next, and has it been done | `identity`, and at least one `obligation` on the entity | `when` (runway) | page | none |\n| `trend_deep_dive` | how did the period go, and why | `when` | `measure`, `amount` | drawer | the series, where the model names a select |\n| `reconciliation_desk` | what does not match, and by how much | `identity` (reference), and two figures \u2014 `ours` and `theirs`, each an `amount` or a `measure` | `category` (reason), `verdict`, `when` | drawer, on the PAIR | the dated runs, where the model names a select |\n| `entry_matrix` | one value per subject per period | `identity` (subject), `across` \u2014 a `when`, or a `category` for a fixed column set | `measure` (value), `lifecycle` (state), `category` (note) | drawer, on the cell\'s row | none |\n| `guided_run` | complete an ordered sequence, a step at a time | `identity` (step) | `slot` (sequence), `capture` \u2014 a `measure`, `verdict` or `expected_set` \u2014 `verdict` (gate), `mark` (media) | page: the run IS the record | none |\n| `live_board` | is everything OK, right now | `lifecycle` (state), `identity` | `verdict` (exception), `when` (since), `measure` (level, one or several), `category` (where) | drawer | none |\n| `group_register` | what does each group come to, and what is behind it | `subject` \u2014 a `category`, `party` or `when` | `amount`, `measure`, `identity` (member) | page: a row is an aggregate, and the door is the SET behind it | none |\n| `media_set` | scan a body of pictures by where they belong | `mark` (picture), `identity` | `category` or `party` (group), `when`, `verdict` (current against superseded) | drawer | none |\n| `day_sheet` | what happened on each day | `when` (the day) | `lifecycle` (state), `measure` (level, one or several), `amount`, `category` (note) | page: a day composes several child logs | none |\n| `resource_schedule` | what is on each resource, in what order, under what ceiling | `lane` \u2014 a `party` or a `parent` \u2014 `identity`, `when` (the start) | `measure` (duration), and `load` and `bulk`, each a `measure` read against a ceiling of the LANE\'s \u2014 a vehicle fills by weight and by room and stops at whichever runs out first; `lifecycle` (stage) | drawer, over the lanes | none |\n| `worksheet` | priced lines the reader edits, totalling to one figure | `identity` | `category` (group), `measure` (quantity), and `cost` and `sell`, each an `amount` or a `measure` | drawer, per line | the versions or the jobs, where the model names a select |\n\n**What a child\'s rows become.** One section per LINK into the record, and what\nthe CHILD declares decides its kind \u2014 the first line it answers wins.\n\n| Printed as | The child declares | The reader gets |\n|---|---|---|\n| `documents:` | an `expected_set` select, one entry per row | the desk of what is owed, each row\'s files attached to it |\n| `schedule:` | an `obligation` closed by a DATE, on rows with no `lifecycle` of their own | what is planned against time and what slipped \u2014 the ordered stops, the promised day against the day it happened |\n| `log:` | a `body` (else a `markdown` text) and a day, on rows with no `lifecycle` of their own | what happened, in order \u2014 each entry read in full in the words it states, its selects and the parties it was with on its line, its `recording` played under the words and its `verbatim` a disclosure behind them, and a composer that writes the next one, asking what the entry shows. With a `verdict` the rows are a correspondence: read oldest first, the reply marked with it the answer, and whose move it is at the foot; without one, newest first under day heads. An entry the child NAMES (`identity`) opens a record of its own |\n| `itinerary:` | a `when` and a `lifecycle`, on rows a JOB\'s record OWNS | the day heads the run, one stop per row \u2014 its name, its supporting lines, its stage as the status at the right \u2014 and the sum under it |\n| `book:` | an `amount` that is `signed_by` a select, on rows the record owns (`parent`) \u2014 or a `ledger` clause | the book of movements, closing on its total read against the record\'s `rollup` that sums them, where it carries one \u2014 neither figure is then a fact |\n| `lines:` | anything else the record owns (`parent`) | the register of the rows it is made of |\n| `history:` | anything else NAMED BY it \u2014 the link is their `party` or their `identity` | the same rows read as that record\'s own history \u2014 on a PROFILE, every one of them stands open at its latest 5, headed by how many there are in all, with the rest a press away |\n| `via <link>:` | a link carrying neither role | the register, headed by that link\'s own label |\n| `related:` | rows of an entity this app lists NOWHERE | a counted tab of its own, and a press that opens their register |\n\nA child carrying the BODY and no day is a plain register, and `scaffold check`\nnotes the day it is one short of. A LOG\'S DAY is the child\'s `when`, unless that\n`when` counts down (`due`, `until`) \u2014 then the first `date` no role claims. A\nchild carrying a `lifecycle` is never a log: it is a thing being worked.\n\n**A child register\'s columns** follow the roles in one order \u2014 identity \xB7 when \xB7\nlifecycle \xB7 category \xB7 amount \xB7 measure \xB7 expected_set \xB7 party \xB7 contact \xB7\nverdict \u2014 the day ahead of the counterparty, which repeats on every row;\n`{"of": "<child>", "columns": [\u2026]}` in `sections` names them instead. Where one child reaches a record\nthrough two links that would read alike, each section says the link it is read\nthrough (`schedule: Stops via Billed to`).\n\n### One app per job\n\n**ONE APP IS ONE REGISTER AND THE RECORDS IT OPENS.** A second register beside\nit is a second job on one page: the reader arrives on whichever the nav listed\nfirst and decides, every time, which of the two they came for. So an app has one\ndestination and everything else about a row is DISCLOSED by opening it \u2014 the\nfacts where the plan places them, the children as lists, the related as counts, one primary act, and\na filter or a group where a strip of destinations would have been. What used to\nbe a second screen is a section of the record, or another app; a model still\nsaying `screens` is refused by name.\n\n**The unit of an app is a JOB, not a person and not a table.** A job has its\nown outcome (something exists or is settled when it is done), its own subject\n(the record it advances), and an end that does not wait on the rest of the\nwork. Two tasks are ONE job when neither finishes without the other and both\nadvance the same record. One app per job, and that is what makes an app a\nwrite-ownership boundary: what the job settles is its app\'s alone to write,\nplus everything it must read to settle it well. A person holding several jobs\nopens several apps \u2014 one app for everything one person does is the dump. A step\nof the job is a section of the record, never a register beside it; a reference\nthe job needs at hand is read inside the record it is about.\n\n**Two people signing is two jobs**, because the outcome belongs to the signer:\nthe desk that prepares against the gate that releases. So is a different\ncadence \u2014 reference data edited monthly and read by the public site, beside a\ndaily desk \u2014 and a different reader, the owner\'s read-only questions. Device,\nplace and step never split a job. A super app holds more than one job; an\nover-split holds less than one, a job cut by device, place or step. The count\nper workspace falls out of the jobs, typically two to six, and is never the\ninput: review the plan app by app, naming who holds it, the job in one\nsentence, what it writes and what it reads.\n\n**A party is not a job.** The people and companies a job\'s rows name \u2014 the\ncustomers, the suppliers, the carriers \u2014 are not a job of their own, however\nmany rows name them: nobody\'s day is "the customer book". They live inside the\napp whose rows name them: a create takes the party by its natural key, the row\'s\nname opens the party\'s own record (a profile \u2014 how to reach them, what is live\nwith them, their history, their log), and its facts are edited there. A\nparty earns an app of its own only where someone\'s job IS the relationship, with\nan outcome of its own \u2014 an account plan signed, a supplier qualified \u2014 never\n"keeping the book". Two apps for one seller, the deals and the customers, is the\ndump cut in two.\n\n*A two-van appliance repair shop.* The technician\'s job is the call-out: the\noutcome is a visit done, the subject is the call-out record, and quoting it and\nscheduling it are one job, because neither finishes without the other and both\nadvance that record. The owner holds two of his own \u2014 billing the month\n(outcome invoiced, subject the invoice, settled against visits already closed)\nand the price list the booking page quotes from, edited monthly. Three jobs,\nthree apps, and the owner opens two of them.\n\n**Inside a job the caps hold**: six flow stages on a desk, eight facts to a\nrecord\'s band. Past them, look for the second job hiding inside. And a job\'s app\nis DENSE: every create the job needs lives in it, so its user never leaves it to\ncorrect a figure the screen in front of them is stating \u2014 three workflows is a\nscreen, not a desk.\n\n**A handoff is a stage change.** Where one job ends the record moves stage and\nthe next job\'s desk opens on it; a field two jobs must both write is declared\nshared at the split, naming the stage each may write it in.\n\n**Name an app for the WORK, in the words of the role that opens it** \u2014 the\ndesk\'s own noun for what it settles, never who it is for. A title naming a person\nor a department says nothing about what the one who opened it came to do, and it\nis wrong the day the org chart moves; a title in the owner\'s words is one the\nclerk opening it does not use.\n\n\n```jsonc\n"apps": [\n {\n "alias": "order_desk", "name": "Order desk",\n "description": "\u2026", "icon": "briefcase", "theme": { "color": "blue" },\n "screen":\n { "alias": "orders", "label": "Orders", "shape": "lifecycle_desk", "entity": "order",\n "record": "drawer", "tabs": "stage", "writes": false, "period": "due_date",\n "filters": ["kind",\n { "label": "Qu\xE1 h\u1EA1n l\u01B0u", "predicates": [\n { "label": "\u0110\xE3 qu\xE1", "tone": "red",\n "where": { "node_type": "group", "logic": "and", "children": [\n { "node_type": "condition", "field_key": "owed", "operator": "greater_than", "value": 0 }] } }] }],\n "summary": { "totals": ["total"], "above": ["total", { "field": "owed", "at": "end" }],\n "ageing": { "amount": "owed", "due": "due_date", "buckets": [30, 60, 90] },\n "trend": { "field": "total", "direction": "up" } },\n "facts": { "groups": [{ "caption": "Pricing", "fields": ["rate", "surcharge"] }] },\n "ladder": true,\n "presentation": { "lead": "none", "density": "dense" },\n "acts": { "row": [{ "label": "Issue the note", "template": "debit_note", "place": "cta", "confirm": true },\n { "kind": "agent", "label": "Read the papers", "agent": "reader", "fills": ["ref", "due_date"] },\n { "kind": "workflow", "label": "Hand it to dispatch", "workflow": "hand_to_dispatch",\n "inputs": { "order_id": "record", "owed_by": "due_date" },\n "asks": { "crew": "crew" },\n "needs": ["site_phone", "site_email"] }],\n "record": [{ "label": "Print the file", "template": "dossier" }],\n "selection": [{ "label": "Statement", "template": "statement" }],\n "export": true,\n "import": { "kind": "import", "label": "Upload the sheet",\n "entity": "order", "key": "code", "columns": ["rate"] } },\n "section_acts": { "line": [{ "label": "Chase the lines", "template": "chaser" }] },\n "sections": [{ "of": "stop", "heading": "The move", "facts": ["pack_on", "deliver_on"] },\n "attachments",\n { "of": "line", "draw": "worksheet", "cost": "buy_rate", "sell": "rate" },\n { "of": "payment", "draw": "ledger", "heading": "Money in" },\n { "of": "visit", "draw": "log", "evidence": ["photos"], "lines": [["kind", "site"]] },\n { "of": "pinning", "draw": "publish", \u2026 },\n { "of": "chase", "draw": "acts", \u2026 }],\n "slots": { "identity": "code",\n "subject": ["customer", "project"],\n "stage": { "field": "state", "quick": true } },\n "columns": ["source", "owner"],\n "create": ["code", "customer", "due_date"] }\n }\n]\n```\n\n### App and screen keys\n\n<!-- generated:start apps -->\n\n#### App\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable app alias, unique within the contract |\n| `name` | text | yes | What the app is called wherever its members open it |\n| `description` | text | no | What the app is for, in a sentence shown beside its name |\n| `icon` | text | no | A Lucide icon name, kebab-case |\n| `theme` | [Theme](#theme) | no | The app\'s colour |\n| `scope` | [Scope](#scope) | no | Narrow every read of this app to one row of an entity, the pick shared with every app that names it |\n| `reads` | `"shared"` | no | "shared": this app\'s queries and writes carry no entity\'s read_scope \u2014 whoever the app is shared with reads and writes every row it draws. Absent, each read_scope binds the viewer. |\n| `screen` | [Screen](#screen) | yes | The app\'s one register and the records its rows open |\n\n#### Theme\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `color` | [colour](#select) \\| `null` | no | Theme color for the app |\n\n#### Scope\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `entity` | alias | yes | The entity every read of this app narrows to ONE row of |\n| `param` | text | yes | The param this app\'s reads take the picked row by |\n\n#### Screen\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `alias` | alias | yes | Stable screen alias, unique within the app |\n| `label` | text | yes | What the screen is called in the app |\n| `shape` | [shape](#apps-and-screens), or `"custom"` | yes | A shape, or "custom" with its own `roles` |\n| `entity` | alias | yes | The entity whose rows this screen is over |\n| `record` | `"drawer"` \\| `"page"` \\| `"expand"` \\| [Record clause](#record-clause) | no | How one record opens from the list \u2014 "drawer", "page", or "expand" to reveal it in the row \u2014 or `{"door", "brief", "tabs"}` placing its sections too; absent, the shape decides the door and the roles the rest |\n| `tabs` | alias \\| `null` | no | The entity\'s lifecycle select, whose stages are the tab strip; null for none; absent, the shape decides. Any other select is a `filters` entry \u2014 except on a shape whose strip means something of its own (a reconciliation\'s runs, a trend\'s series, a worksheet\'s versions), which takes any select |\n| `slots` | map of text \u2192 (alias \\| list of alias (at least 2) \\| [Quick slot](#quick-slot) \\| `null`) | no | Slot \u2192 the field that fills it, where roles alone cannot decide; null leaves an optional slot unbound |\n| `roles` | map of text \u2192 [role](#field-roles) | no | For "custom" only: slot \u2192 the role that fills it |\n| `columns` | list of (alias \\| [Chip column](#chip-column)) (1\u20134) | no | Extra fields drawn after the slots, in this order \u2014 at most 4, each holding ONE value; never a files field, and never a field a slot already draws. `{"field", "age", "more"}` draws a lookup of a related row\'s category as that category\'s chip |\n| `sources` | list of alias (at least one) | no | Other entities whose rows this register reads beside its own, in ONE read sorted by its order \u2014 each declares a field for every role the register\'s slots draw and, under the same alias, every other field it draws, orders or narrows by; a row opens in its own entity\'s record, and the create and row acts are this screen\'s entity\'s |\n| `create` | list of alias (at least one) | no | The fields a new row is asked for, in this order \u2014 at most 12, each a field of this entity a draft can ask for, and every field the row cannot be written without among them; absent, the draft asks for every field a person states |\n| `writes` | boolean \\| `"children"` \\| `"record"` | no | false makes this screen\'s record read-only \u2014 no field editor, no stage advance; "children" draws the record at rest and leaves the rows it owns operable; "record" leaves the record operable and draws the rows it owns at rest; absent, both are operable |\n| `acts` | [Acts](#acts) | no | The papers this register makes \u2014 from one row, from a ticked set, and from the whole view |\n| `section_acts` | map of alias \u2192 list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | Acts on the RECORD\'s own sections, keyed by the alias each section is derived from \u2014 a field alias (its progress, its prose, its set, its charge, its files) or a child entity\'s alias (its register, its desk, its run, its log) |\n| `sections` | list of (alias \\| [Worksheet section](#worksheet-section) \\| [Ledger section](#ledger-section) \\| [Itinerary section](#itinerary-section) \\| [Claim section](#claim-section) \\| [Tree section](#tree-section) \\| [Gantt section](#gantt-section) \\| [Curve section](#curve-section) \\| [Log section](#log-section) \\| [Publish section](#publish-section) \\| [Acts section](#acts-section) \\| [Section heading](#section-heading)) | no | The RECORD\'s sections, drawn in exactly this order \u2014 each the alias a section is derived from (a child entity\'s rows; a field\'s files, prose, charge, set or ladder), or `{"of": <child>, \u2026}` with how those rows are drawn; a section left out is not drawn, `[]` draws none, and a counted child is named after every open section. Absent, every section the record derives, in the recipe\'s order. The facts are banded by `facts`, and a closing step always ends the record |\n| `filters` | list of (alias \\| [Lens](#lens) \\| [Member lens](#member-lens)) (at least one) | no | The chips beside the search \u2014 a single-select or select_member field\'s alias, `{"field": \u2026, "default": "mine"}` to open a member field on the reader\'s own rows, or a lens this model states as predicates over the entity\'s fields |\n| `facts` | [Facts](#facts) | no | How the record\'s facts are BANDED \u2014 where the aside\'s fields group under captions |\n| `ladder` | `true` | no | The flow drawn as a ladder of dated rungs, for a long flow whose path the reader needs to see; absent, the stage is the record header\'s status |\n| `summary` | [Summary](#summary) | no | What the rows in view come to, stated before or after their names |\n| `period` | alias | no | A date field on the entity the reader narrows the view by; absent, the view is every row |\n| `presentation` | [Presentation](#presentation) | no | How this screen and the record it opens are DRAWN, where the shape\'s own answer is not the one wanted |\n\n<!-- generated:end apps -->\n\n### Slots\n\nA shape is a proven screen with named SLOTS, each filled by a field carrying a\nrole (\xA7 Field roles). A slot with exactly one candidate on the entity binds by itself;\ntwo candidates need naming in `slots`; a field fills one slot; a required slot\nwith none is refused \u2014 a lifecycle desk over an entity with no `lifecycle`\nselect cannot be built. `"slots": { "party": null }` leaves a slot the shape can\ndo without UNBOUND: the register draws no column for it and gives the width to\n`columns`, while the role goes on being read everywhere else. A required slot\nrefuses it.\n\n**A LIST IS A HIERARCHY, not a set of columns.** A group register\'s `subject`\ntakes one \u2014 `"subject": ["customer", "project", "order"]`, outermost first \u2014 and\nthe register becomes one the reader walks DOWN: it folds by the first tier, a\nnode narrows to the tier inside it, the last tier opens the set behind the\nfigure, and a trail says where they are. Every tier is a field of this entity\ncarrying a role that slot accepts, and none of them fills a second slot. It is\nthe slot\'s own answer that decides: a list on any other is refused, because two\ncolumns in one cell is not a fold. Without it the same business needs a register\nper depth, and the same money is folded twice with nothing saying it is the same\nmoney.\n\n**AND ON A SLOT READ AGAINST A LIMIT A LIST IS SEVERAL MEASURES** \u2014\n`"level": ["received", "invoiced"]`, at most four, each drawn as a named meter\nline inside the ONE column and each read against its OWN `against`/`alert`. A\nrow is regularly scored against more than one target at once, and the second\nreading put in `columns` instead draws a figure bare with the comparison handed\nback to the reader. Every entry states a limit \u2014 a bare measure in the list is\nrefused, naming the clause \u2014 and `quick` is refused beside one, because a typed\nfigure is one cell. The FIRST leads: it is what `summary.above: "counts"` counts\npast-limit rows on and what a live board ranks its exceptions by, both of which\nname one set.\n\n**A QUICK SLOT IS FOR A DECISION THE READER MAKES FROM THE ROW ALONE.**\n`{"field": \u2026, "quick": true}` makes the column the CONTROL for its value,\nwritten as a diff through the record\'s own update, blockers and an outcome\'s\nconfirm included. Only a `lifecycle`, a `verdict` or a `measure` (a reading\ntaken per row IS the row \u2014 a timesheet\'s hours); `"order": "sequence"` makes a\nlifecycle\'s picker a walk with the next step marked. Refused under\n`writes: false`.\n\n<!-- generated:start apps-slots -->\n\n#### Quick slot\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The field this slot takes |\n| `quick` | `true` | yes | Draw the column as its OWN editor \u2014 for a decision the reader makes from the row alone |\n| `order` | `"sequence"` | no | A lifecycle whose stages are a WALK: the cell advances to the next one rather than offering them all |\n\n<!-- generated:end apps-slots -->\n\n**THE SLOTS ARE THE ROW\'S ANSWER; `columns` IS ITS CONTEXT.** `"columns":\n["nguon", "phu_trach", "nhu_cau"]` names extra fields drawn after every slot,\nin this order, each by what its column is and under its column\'s name \u2014 except a\nchip (a select, a category formula\'s word, a lookup of either), which names\nitself \u2014 and shed before any slot on a narrow screen. **A COUNT IS NOT A\nCOLUMN**: a `rollup` counting rows reads on the name\'s supporting line as the\nthing it counts (`3 b\xE1o gi\xE1 \u0111ang m\u1EDF`), takes no seat, and opens those rows where\nthe record lists them. A fifth fact worth the width is a slot the shape is\nmissing \u2014 say so rather than widening this clause.\n\n**A RELATED ROW\'S KIND IS A CHIP.** An entry `{"field": "latest_kind", "age":\n"latest_on", "more": "item_count"}` draws a `lookup` of a related row\'s\n`category` as that category\'s chip \u2014 the option\'s colour and mark (\xA7 `select`),\nor a category formula\'s word as its `values` wear it \u2014 followed by how long ago that row happened (`age`, a date read\nover the SAME link, a lookup or a rollup) and how many OTHER rows the link names\n(`more`, a count rollup over the same link, drawn `+N`). Three columns drawn apart\nare a word, a date and a small integer the reader joins by eye. It takes one seat.\nThe chip is ONE row: across a link naming several, the lookup is ordered\n(`order_by`), and `age` dates the row that order picks \u2014 a `latest` (for `desc`)\nor `earliest` (for `asc`) rollup of the order\'s own date, or a lookup ordered the\nsame way. Refused: a field that is no lookup of a category, an unordered lookup\nacross a link naming several rows, an `age` or `more` read over another link or\nthrough a rollup `filter`, an `age` that is not a date or dates another row, a\n`more` that is not a count, an entry stating neither, and a rider named again as a\ncolumn of its own.\n\n**THE DRAFT ASKS WHAT FILES THE ROW; THE RECORD ASKS THE REST.** `"create":\n["ma_ho_so", "khach_hang", "han_xu_ly"]` names the fields a new row is asked\nfor, in order; absent, every field a person states. A row written over days is\nnamed, dated and filed the moment it is taken, and the rest is edited in place on\nthe record. It narrows this entity\'s own draft \u2014 rows a record adds under itself\nare the section\'s. A link the row stands on (`parent`, `party`) is asked as one\nrow. Refused: a field no draft asks (computed, minted, the `lifecycle`, a\n`default_from` target, an obligation\'s stamp, any pile but the `mark`), and a\nlist omitting what the row cannot be written without \u2014 a `required` field, the\nlink it hangs under, or a party\'s recognising key. Whose row it is may be left\nout \u2014 required, or the member column its `read_scope` names its only reader by \u2014\nsince the create writes whoever ran it there (**Whose row it is**, \xA7 Write\nrules); a member column nothing stamps is refused like any other.\n\n### Records and scope\n\n**A record is one of five KINDS, and the shape plus the roles decide which.**\nEvery one of them is ONE READING COLUMN, and the ORDER of that column is the\nwhole of what the kind means.\n\n| Kind | The column, top to bottom |\n|---|---|\n| WORK RECORD \u2014 a `lifecycle_desk` over rows that belong to nothing and that nothing transacts with | the one act that moves it \xB7 what it IS \xB7 the papers it owes \xB7 the rows it is made of \xB7 the book of what it came to |\n| LINE \u2014 the same shape over rows carrying `parent` | the arithmetic behind its amount \xB7 the rows and papers under it \xB7 its particulars, last (the key it is filed by is the line under its title) |\n| PROFILE \u2014 a `party_register`, and any shape over an entity whose rows other entities are NAMED by (their `party` or their `identity`) | where it stands \xB7 its history, OPEN, grouped by year \xB7 the rest of its registers \xB7 its facts (the ways to reach it are under its name, its settlement is the band, and a `when` it owes closes that band as a countdown) |\n| CATALOGUE ITEM \u2014 an `offering_register` | its own words (the picture leads the header at media scale, the price is read against its reference in the band) \xB7 what uses it \xB7 its facts |\n| EVIDENCE \u2014 a `transaction_ledger` | the PROOF \xB7 what the paper is, compactly \xB7 the rest \xB7 the links to the record it settles and the party it was with |\n\nNothing in the file states the kind: a clause that could say it could say the\nwrong one, and the model already says it \u2014 `parent` says whether a row stands\nalone, and a link NAMING this record (`party`, `identity`) says whether other\nrows are ABOUT it. So a desk over accounts\nwalking their own stages opens a PROFILE: the quotes and the invoices naming\neach account are what its record is read for, and a work record would have\ncounted them in one line at the foot. `record`\noverrides the DOOR where the shape\'s own answer is not the one wanted \u2014 and its\nthird value, `"expand"`, is not a door at all: the row REVEALS what it holds in\nplace, where there is nothing behind it worth navigating to. A row read that way\nreads as a LINE whatever the shape would have opened: the arithmetic\nbehind its amount, then its particulars, because there is no header above it to\nhave stated the name first. It is REFUSED for two reasons, and they are not the\nsame refusal. On a shape whose record is a page by nature \u2014 a `guided_run`, a\n`group_register`, a `day_sheet`, an `obligation_desk` \u2014 because each of those\nstates the opposite where its door is declared: what its row leads to is read\nwhole, not as four more columns than the register had room for. And on a shape\nthat draws its rows with a device of its OWN \u2014 an `entry_matrix`, a `media_set` \u2014\nbecause a cell of a grid and a tile on a wall have no band under them for the row\nto reveal into. For the same reason a revealed register is drawn as a TABLE: a\n`presentation.layout` of anything else is refused, and the reader is not offered\nthe arrangement either, since it would take away the only door the register has.\n\n**`scope` \u2014 the one subject every read of the app narrows to.** Stated on the\nAPP rather than on its screen, because it is not a filter: a record opened under\nit stays under it, the pick survives closing the app, and every app declaring the\nsame `entity` shares one pick per viewer \u2014 which is the whole value, and what\nstops a construction workspace asking which project twelve times.\n\nThe register\'s own entity has to REACH it \u2014 its own rows, or rows that name one\nthrough a link \u2014 or the switcher is a control that changes nothing. Until a\nsubject is picked the app reads NOTHING and says which one it is waiting for: an\nunscoped read over a scoped app is every row in the workspace drawn as one\nproject\'s work, which looks correct.\n\n**ONE RECORD SURFACE PER ENTITY, AND ITS KIND IS THE ENTITY\'S.** An app is one\nregister, so the record its rows open is the register\'s own and the kind comes\nfrom that register\'s shape. Another app over the same entity opens the same kind\nof record: a payment read from a cash book and one read from a trend are both\nmovements, and both read proof first. A shape with no kind of its own (a trend, a\nmonitored set, a `custom` screen) says only where the record opens.\n\n**A NAMED PARTY HAS A RECORD SURFACE IN THE APP.** One register, but the party\nits rows name \u2014 by `identity` or by `party` \u2014 opens a record of its own, a\nPROFILE, from the cell that names it and from the record\'s header: the ways to\nreach them, what is live with them, every register that names them at its latest\nfive, their log, their facts. It is the app\'s to write as far as the job\nneeds \u2014 the party\'s facts and its log \u2014 and the writers matrix says so; the\nregister\'s own record stays the register\'s. A party named through a link\ncarrying no role opens nothing, and neither does a party that is a MEMBER \u2014 a\nperson of this workspace has no row in any book the app carries, so the cell and\nthe header state them and stop.\n\n**Two things a register states about a row beyond which field fills which slot.**\nA `measure` is read AGAINST its limit (`against`/`alert`) where the shape\'s slot\ntakes one \u2014 `level`, on a lifecycle desk, a monitored set, a live board and a day\nsheet, a schedule\'s `load` and `bulk`, and every slot of a\n`custom` screen, which composes the frame\'s own column; `worth`, `availability`\nand a trend\'s headline are plain figures, and a limit there would read as a meter\nin the record over a number the row printed bare. And the name carries a\nSUPPORTING LINE where the entity states a key to file the row under: the `parent`\nit belongs to, its `reference`, an `autonumber`, a text formula \u2014 in that order,\nfirst hit wins, never a field a person types prose into. Every record heads with\nthat same key, so the register and the door it opens name one row one way.\n\n**The act that opens a row belongs to a register that LISTS the entity\'s rows**\n\u2014 a desk, a register, a ledger, a monitored set, or a `custom` screen. A shape\nwhose rows are not records has neither that act nor the door a counted tab\nelsewhere leads to: an obligation desk\'s rows are deadlines and a trend\'s are\nperiods, so a create pressed there would open a row the screen cannot show. An\nentity whose only app is one of those has no create at all \u2014 give it an app whose\nregister its rows are read in.\n\n**An `obligation_desk`\'s rows are not its entity\'s rows.** The shape fans the\nentity\'s `obligation` fields out \u2014 one row per thing still owed, which is one\nwhose date is SET and whose `satisfied_by` is empty \u2014 so a l\xF4 with four cut-offs\nis four rows, and the three it has met are not there at all. The row\'s name is\nthe obligation\'s, its supporting line the record\'s `identity`, and the door is\nthat record. A `when` bound to `runway` is what the countdown\'s ring is measured\nfrom. Over an entity declaring no `obligation` the shape is refused: there is\nnothing to count down to.\n\n### Register keys\n\n**What a register carries beyond its columns.** Clauses the runtime draws;\nevery one is optional and a screen that states none gets a register of\ncolumns and nothing else. Their keys:\n\n<!-- generated:start apps-register -->\n\n#### Chip column\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A lookup of this entity reading a related row\'s `category` \u2014 drawn as that category\'s chip, in its option\'s colour and glyph |\n| `age` | alias | no | A date of this entity dating the row the chip\'s ordered lookup picks \u2014 a `latest`/`earliest` rollup of the order\'s date, or a lookup ordered the same way \u2014 drawn after the chip as how long ago it was |\n| `more` | alias | no | A count rollup of this entity over the SAME link \u2014 drawn beside the chip as how many OTHER rows there are |\n\n#### Member lens\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A select_member field of the entity \u2014 who the row is for |\n| `default` | `"mine"` | yes | The register opens narrowed to the reader\'s own rows |\n\n#### Lens\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | What the chip is called |\n| `predicates` | list of [Predicate](#predicate) (at least one) | yes | The sets it offers, in this order \u2014 ONE is a chip the reader toggles |\n| `sort` | [Lens order](#lens-order) | no | The order the rows are read in while the lens is on |\n\n#### Lens order\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A date, number or text of this entity \u2014 its own, or a lookup or rollup |\n| `order` | `"asc"` \\| `"desc"` | no | "asc" (the default) puts the soonest or smallest first; a row holding none goes last |\n\n#### Predicate\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | What this predicate is called in the chip\'s list |\n| `tone` | [colour](#select) | no | The colour it wears, from the option palette; absent, the kit\'s neutral |\n| `where` | [filter](#views) | yes | An `and` group of plain conditions over the entity\'s OWN fields, each `field_key` a field alias \u2014 the rows this predicate keeps |\n\n#### Summary\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `totals` | list of alias (at least one) | no | Number fields the whole view adds up \u2014 under the column that draws one, else in a band beneath the register |\n| `above` | `"counts"` \\| list of (alias \\| [Stock](#stock)) (at least one) | no | A band of figures over the register: "counts" for how many rows and how many need attention, or number fields \u2014 each added up over the window, or `{"field": \u2026, "at": "end"}` for a stock STANDING at its end |\n| `ageing` | [Ageing](#ageing) | no | How old the money over this register is \u2014 the amount split by how many days past its date each row is |\n| `trend` | [Trend](#trend) | no | Which way this figure moved across the window the reader is reading \u2014 needs a `period` to be a window of |\n| `curve` | [Curve](#curve) | no | Planned against actual, each added up to date over the rows\' dates and drawn as two lines \u2014 read along the `period`, else the `when` |\n\n#### Stock\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The number field the reading is in |\n| `at` | `"end"` | yes | The reading STANDING at the window\'s end, rather than the sum of the readings in it |\n\n#### Ageing\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `amount` | alias | yes | The figure each bucket adds up \u2014 what is still owed on the row |\n| `due` | alias | yes | The date the row was owed by; a row is bucketed by how far past it is |\n| `buckets` | list of integer (at least one) | no | The bucket edges, in days past due, ascending \u2014 absent, 30, 60, 90 |\n\n#### Trend\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The number this register adds up in each bucket of the window |\n| `direction` | `"up"` \\| `"down"` | yes | Which way is GOOD \u2014 a book wants `up`, a backlog `down`; the arrow follows the change and the colour follows this |\n\n#### Curve\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `planned` | alias | yes | The number each row was PLANNED to come to |\n| `actual` | alias | yes | The number each row DID come to \u2014 empty on a row that has not happened |\n\n#### Facts\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `groups` | list of [Facts group](#facts-group) (at least one) | yes | The named bands of the record\'s facts, in this order \u2014 every field a person states is in one, and a field the platform works out is drawn only where one names it |\n\n#### Facts group\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `caption` | text | yes | What the fields under it have in common, as the reader says it |\n| `alias` | alias | no | What `record.brief` and `record.tabs` name this band by |\n| `fields` | list of alias (at least one) | yes | The facts of this band, in this order |\n| `last` | `true` | no | Read after the record\'s own work \u2014 its acts, its rows, its correspondence \u2014 instead of before them |\n| `first` | `true` | no | Read before everything else on the record, its stage included \u2014 the facts its stage is judged by |\n| `closes` | alias | no | The outcome of this entity\'s lifecycle whose closing step this band IS \u2014 drawn as the record\'s last section, above the outcome\'s asked fields and its verb. The outcome states the verb |\n\n#### Presentation\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `lead` | `"mark"` \\| `"picture"` \\| `"paper"` \\| `"figure"` \\| `"none"` | no | What each row leads with; absent, what the shape\'s rows are decides |\n| `density` | `"roomy"` \\| `"dense"` | no | How many lines a row\'s subject may take; absent, what the shape\'s rows are decides |\n| `layout` | `"table"` \\| `"list"` \\| `"cards"` \\| `"gallery"` \\| `"board"` \\| `"calendar"` \\| `"timeline"` \\| `"gantt"` \\| `"tree"` | no | How the rows are arranged; absent, a table |\n| `line` | list of alias (1\u20132) | no | The register row\'s supporting line, in this order \u2014 at most 2 fields of this entity, each a plain text or a single select; absent, the key the row is filed under |\n| `until` | alias | no | For a gantt: the date field each bar is drawn TO; absent, the bar runs for the days its measure counts |\n| `nest` | alias | no | A one-row link from this entity to itself \u2014 the row each row sits under; affords `layout: "tree"` and nests a gantt\'s bars |\n| `depends_on` | alias | no | For a gantt: a link from this entity to itself naming the rows that must finish before a row starts |\n| `baseline` | [Baseline](#baseline) | no | For a gantt: the date fields the row was PLANNED to start and end on, drawn under its bar |\n\n#### Baseline\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `start` | alias | yes | The date field the row was planned to start on |\n| `end` | alias | yes | The date field the row was planned to end on |\n\n<!-- generated:end apps-register -->\n\n### Period and filters\n\nWhat each draws, and what it is refused for:\n\n- **`period`** \u2014 a field on the entity whose value is a DATE. The toolbar gains a\n date-range control (this month to start), and the rows it keeps are what the\n register AND every figure below are computed over, so a band can never be over a\n different window than the rows under it. Any such field, not only the `when`\n role, and however the date got there: one table is read two ways (a cost ledger\n by the day a line arose, the cash book over the same table by the day it\n settled), a role is one field\'s, and the day money MOVED is often a formula over\n the two columns that could hold it \u2014 which then declares `output_type: "date"`.\n A `trend_deep_dive` brings its own period and takes none here; a `live_board`\n is PERIODLESS and takes none either, for the opposite reason \u2014 it answers what\n is true NOW, and a date range over it draws what was true in the window in the\n live state\'s own colours.\n- **`filters`** \u2014 the chips beside the search. A single-`select` field\'s alias\n \u2014 or a `lookup` of one, read as the looked-up row\'s option \u2014 offers that\n field\'s own live options; a category formula\'s alias (or a lookup of one)\n offers its `values`, each a chip in the word\'s colour; it is the lens a register is read through\n over and above the one band its strip gives, so the strip\'s own field is\n refused here (one dimension, one control) and so is a multi-select (a row would\n answer the chip several ways at once). The rows the register and every figure\n below are computed over are what the chips AND the period kept.\n A `select_member` field\'s alias is the third form and asks one question \u2014 whose\n rows these are: two peers, MINE and ALL, read against the person looking rather\n than a directory of everybody. `{"field": \u2026, "default": "mine"}` opens the\n register on the reader\'s own; that form on any other type is refused. Alone of\n the chips it narrows the READ (a `member` param on the register\'s query), so a\n desk that opens on the reader\'s work opens on all of it rather than on\n whichever of their rows the first page happened to hold. The column it reads\n says whose a row is, so every create writes whoever ran it there\n (**Whose row it is**, \xA7 Write rules).\n A DERIVED LENS is the other form, where the sets a reader narrows by are ones\n the MODEL names and no column holds: rate validity, plan urgency, free-time\n overrun. Each `where` is an `and` group of plain conditions over the entity\'s\n own fields, each `field_key` a field alias, and it is read off the row, so the\n operators are the comparisons a value answers \u2014 `number` and `text` equality,\n `number` ordering, `boolean` equality, `select` `has_any_of`/`has_none_of`, and\n `is_empty`/`is_not_empty` on any of them. Nothing DATED: a relative point needs\n a clock, and a screen states one date control (`period`), so a second beside it\n would be two windows with nothing saying which a figure was computed over \u2014 a\n date question is asked as a column the model derives and read here as a number\n or a yes/no. `tone` is the option palette\'s, because the chip draws these\n exactly as it draws a select\'s options. ONE predicate is a chip the reader\n toggles rather than a list of one. `"sort": {"field": \u2026, "order": "asc"}`\n reads the rows in that field\'s order while the lens is on \u2014 a worklist, owed\n soonest first, a row holding none last; refused over a field that is not a\n date, number or text of this entity.\n\n### The summary band\n\n- **`summary`** \u2014 what the rows in view come to. `above` is a band OVER the\n register: `"counts"` for how many rows are in view and, for the FIRST\n `measure` the model gives a limit, how many are past it (accented only when\n one is) \u2014 one such set, because a band naming three is the dashboard a\n register is not; a list of number-field aliases for what they add up to, and a\n `percentage` named there is refused, since a share summed over the rows in\n view is a figure of no kind (draw it as a column, whose own total reads the\n mean). A figure written `{"field": \u2026, "at": "end"}` is a STOCK instead: the\n reading standing at the END of the window rather than the sum of the readings\n in it, which is the only way opening + in \u2212 out = closing reconciles \u2014 a month\n of daily closing stocks added together is thirty warehouses. The band labels it\n as the snapshot it is, and it is refused on a screen that states no `period`,\n since there is then no window for it to stand at the end of.\n `ageing` stands in that same\n band: an `amount` split by how many days past its `due` date each row is,\n drawn as a `Breakdown` of the edges in `buckets` (30, 60 and 90 days unless the\n model states its own, ascending). The ladder is the kit\'s \u2014 its boundary, its\n words and its ramp \u2014 and it opens with what is NOT YET DUE, because a bar of\n overdue bands alone is full at every input: a book with one late invoice and a\n book that has gone entirely bad would paint the same solid width. Against the\n current money the bar\'s own shape is the reading. A row whose date is empty is\n in no band. `totals` is what the whole\n view adds up, and it is drawn in that SAME band above the rows: a register\n states one aggregate, over the rows the reader can see, in one place, and a\n closing line under them is a second place to look and a second question about\n which rows it covers. A closing line belongs to the one device that IS a\n statement \u2014 a record\'s `Ledger`, whose balance is what it is read for, labelled\n by its amount\'s own word. Where a shape has no band above (a trend, whose own\n band is the chart) the figure stands under the register instead. A\n `transaction_ledger` sums its amount column itself and takes `above` alone. An\n `obligation_desk`\'s rows are DEADLINES and not records, so its band states\n `"counts"` \u2014 how many are open \u2014 and a figure list, an `ageing` or a `trend`\n over it is\n refused: a l\xF4 with three cut-offs is three rows, and its freight added over\n them is three times the freight.\n `trend` states WHICH WAY that figure moved across the window: the rows in view are cut into the window\'s own\n buckets \u2014 days over a month, weeks over half a year, months beyond \u2014 and the\n later half of the CLOSED ones is read against the earlier half, drawn beside\n the figures as one line, so a window the reader is still inside states the\n movement of the days that have run rather than a fall into the ones that have\n not. `direction` is which way is GOOD, which nothing about the number\n says: a book wants its takings rising and a backlog wants its days falling, and\n the same arrow is a win on one and an alarm on the other. It needs a `period`\n to be a window of, and it is refused on a shape that draws its own (a\n `trend_deep_dive` already draws the series) or that reads none at all (a\n `live_board` answers what is true now). A window whose earlier half took\n nothing draws no line: there is no base for a percentage, and 0 % would claim a\n steadiness nobody measured.\n `curve` is `{"planned": \u2026, "actual": \u2026}` \u2014 two number fields of the row, each\n added up to date along the rows\' own date and drawn in that band as two lines:\n the S-curve a schedule or a budget is judged by, where a row is a period or a\n piece of work and the running total is the curve\'s own arithmetic. The date is\n the screen\'s `period` where it states one, else its `when`; a screen reading\n its rows by no date is refused. The actual line stops at the last date a row\n stated an actual figure \u2014 drawn on past it, the weeks nobody has worked yet\n would read as a stall. Refused on the shapes a `trend` is refused on, over the\n same reasons, and against itself.\n\n### Sources and fact groups\n\n- **`sources`** \u2014 other entities whose rows this register draws beside its own:\n one subject standing in two books (stock on the shelf and on consignment, a\n claim raised by the site and by the office), read once rather than as rows that\n look duplicated. Each book lines up a field for every slot the register draws,\n by the role the slot reads, and \u2014 under the same alias and type, a day beside a\n day and a moment beside a moment \u2014 every other field the register draws,\n orders or narrows by, and the `currency` a drawn amount is read in; a\n select\'s options must be among the register\'s own.\n What it lacks or holds differently is refused by name. Every book is read in\n ONE read with the register\'s own rows, sorted once by the register\'s day, else\n by its name or the day each row was created \u2014 so a page is the true first rows\n of all of them, and a register with none of the three is refused. A column\n names the book each row stands in. A narrowing a book has no column for \u2014 a\n record\'s door onto the register, a link the book does not carry \u2014 reads none of\n that book\'s rows while it is given. A book\'s row opens its own entity\'s record,\n so the door is a page; the create, the row acts and a `quick` column are the\n register\'s own entity\'s and a book\'s row carries none of them, which is why\n `acts.selection`, `acts.import`, a slot of several fields and an app `scope` are\n refused beside it. A figure computed over the rows \u2014 a line\'s share of the\n whole, the change since the row before \u2014 is not a clause: over a register read a\n page at a time, it would be a reading of the page. A value of one row is a\n `formula` field of the entity.\n- **`facts`** \u2014 how the RECORD\'s facts are banded. `groups` is a list of\n `{caption, fields}`: what a set of facts has in common is a sentence about the\n business and nothing derives it, so a plan that states one gets exactly those\n bands, in its own order, captioned. EACH BAND IS A PART of the record \u2014 its own\n heading, placed by the anatomy (\xA7 Record anatomy) as a section is, and named\n there by its `alias`: under one heading the reader is handed every particular\n at once, in a caption weight that cannot out-rank the heading above it.\n Everything the plan leaves unnamed is the LAST band and carries no heading,\n since there is no sentence to head "whatever no group claimed" with \u2014 ONE band,\n even where the record\'s own recipe bands what was left in two (an evidence\n record reads its particulars before the record and the party it was with):\n both are groups inside it, because the word the kit heads "the rest" with\n cannot name two parts. A band marked `last: true` is read\n AFTER the record\'s own work \u2014 its acts, its rows, its correspondence \u2014 for the\n particulars a reader checks once they know what is owed (who it is with, where\n it came from). `first: true` reads a band ahead of everything, the stage\n included \u2014 the facts a verdict is taken on, read before the verdict; a band\n both first and last is refused. `closes: "<outcome>"` makes a band the body of\n that outcome\'s closing step, drawn last above the fields it asks and its verb\n (its fields edited while the record is open, a formula read): refused on an\n outcome naming no verb, on a stage that ends nothing, beside `first`/`last`,\n and twice for one outcome. Where `sections` lists the record\'s sections, they\n are drawn in that order whatever each is drawn as \u2014 a log of the evidence above\n the acts it decides.\n A band of more than eight facts is NOTED: a section that long is two. A field the header, the stage, a required\n set, the files, a book\'s balance or a register below already draws, or a figure the band states that nobody\n writes, is not a fact and is refused. A band of nothing but notes (a markdown text, a `verbatim`) is refused: a\n note sits at the foot of the band it annotates. Named bands PLACE the facts, and nothing is folded: they hold\n EVERY fact a person states \u2014 one the groups leave out is refused by name \u2014 and what the platform mints, stamps\n or works out is drawn only where a band names it. An entity whose record shows `rate`, `surcharge`, a markdown\n `note` and a `created_on` stamp is banded as `[{ "caption": "Pricing", "fields": ["rate", "surcharge",\n "note"] }]` \u2014 the stamp is the header\'s.\n\n### How a screen is drawn\n\n- **`presentation`** \u2014 how the screen and the record it opens are DRAWN, where\n what the rows are is not the answer wanted. `lead` is what each row leads\n with: `"mark"` the subject\'s own mark, always spent because a party\'s initials\n stand in for a picture nobody uploaded; `"picture"` the photograph, spent only\n on the rows that carry one and, on the record, at media scale above the name;\n `"paper"` the row\'s own files, drawn as the cards a pile of documents is read\n as; `"figure"` the amount, with no gutter at all; `"none"` neither. `density` is\n how many lines a row\'s subject may take \u2014 `"roomy"` two, `"dense"` one.\n Both default, and the DEFAULT is what the rows are: a party register leads\n with the mark, an offering register with the picture, a ledger with its\n figure, and a desk \u2014 rows that are work still to do, scanned \u2014 stands its rows\n one line each while a record\'s own rows are read roomy. So state it only to\n differ, and a plan that states nothing still gets screens that vary. A lead\n the rows cannot carry is refused: a mark, a picture or a paper where no column\n is drawn as the rows\' mark, a\n figure where no column is the amount, and the other subject\'s mark (which of\n the two a row wears is the shape\'s, so the register and the record it opens\n cannot call one row two kinds of thing). A row carrying a `contact` is\n SOMEBODY even where nothing names it yet, so `"mark"` \u2014 initials where no\n picture was uploaded \u2014 is admitted over it; the default stays the picture. A\n record opened as a drawer leads its bar with that mark where `lead` is\n `"mark"`, and its files are then no section of their own.\n `line` is what the line under a register row\'s name says, in place of the key\n the row is filed under: at most two fields of the entity, each plain words (a\n `text` with no `format`, or `"text"`) or ONE option of a closed list (a single\n `select`) \u2014 where somebody works and what they do there, say. It is also what\n the row is found by. The record keeps its key. Refused: notation, a link, a\n field holding several values, the row\'s own name, and a field a slot, a column\n or a chip already draws.\n `layout` is how the rows are ARRANGED \u2014 `"table"`, `"list"`, `"cards"`,\n `"gallery"`, `"board"`, `"calendar"`, `"timeline"`, `"gantt"`, `"tree"` \u2014 and\n it is AFFORDED by what the rows carry, not by the shape: every register affords\n a table and a list, a `mark` affords the two picture-led grids, a `lifecycle`\n affords a board, a `when` affords a calendar and a timeline, and a `when`\n beside a `measure` that `counts` DAYS affords a gantt, because a bar with no\n length is a calendar drawn sideways and a bar measured in money or litres is a\n length of another kind. `until` names the date field a bar ENDS on, where the\n rows state one, and the days measure is what the bar runs for without it. A\n layout the bound roles do not afford is refused, naming the ones they do.\n `nest` names a one-row link from the entity to ITSELF \u2014 the row each row sits\n under, a work breakdown\'s or a cost plan\'s \u2014 and it is what affords `"tree"`:\n the rows drawn under the row they name, each folding what it holds, the MONEY\n each row states or works out from its own columns summed up the tree. A rollup\n is drawn as the row holds it (it already adds up rows of its own, and summed up\n the tree a parent carrying one would count its children twice), and so is a\n quantity, since two units have no sum. A row naming no row above it, or one\n the read does not hold, stands at the top. On a gantt the same `nest` folds a\n row\'s bars under a summary that spans them, a row stating no day of its own\n included. Two more clauses are the gantt\'s\n own and refused anywhere a bar cannot be drawn: `depends_on`, a link from the\n entity to itself naming the rows that must FINISH before a row starts \u2014 each an\n arrow into its start, toned where the sequence is broken \u2014 and `baseline`,\n `{"start": \u2026, "end": \u2026}`, the two date fields the plan was FIXED in, drawn as a\n ghost under the bar so a slip reads as the gap. A baseline is never the day\n the bar itself starts on: the plan kept apart from the date that moves is the\n whole of what it shows. A row not yet started has no bar of its own, and is\n drawn across its plan until it starts.\n\n ```jsonc\n // A work breakdown, laid out as its outline and scheduled against its plan.\n "presentation": { "layout": "gantt", "nest": "part_of", "depends_on": "after",\n "baseline": { "start": "planned_start", "end": "planned_finish" } }\n ```\n\n### Handing work on\n\n- **`acts`** \u2014 where this screen hands work on: `row` is the \u22EF menu on every row,\n `record` is the \u22EF on the RECORD\'s own page header \u2014 the same reach as a row\'s,\n standing where the work is open instead of while a list is scanned, and refused\n on a record that opens as a drawer, which has no header of its own and whose\n row already carries the register\'s menu \u2014 and `selection` is the bar over the\n TICKED rows. `export` is the third reach \u2014\n the WHOLE VIEW, saved in one press: `true` writes the rows as the register drew\n them, in the columns it drew, as an `.xlsx`, and\n `{"template": "\u2026"}` says the layout is the trade\'s and makes the paper with a\n workflow like any other. It is never a list of columns: which columns the\n export carries is what the screen already draws, and a second statement of it\n disagrees the moment a slot moves \u2014 the generator reads them off the drawn\n slots and writes them into the app, so the sheet\'s headings are the labels the\n register drew them under.\n `import` is the reach OPPOSITE it \u2014 a file turned into rows. The file is\n mapped column by column, each row is validated, what would change is\n previewed, and the commit UPSERTS by `key` \u2014 which is why a sheet sent twice\n moves no counts. That key is one of the entity\'s own `natural_key` fields\n (\xA7 Write rules), and an entity declaring none is refused: without a key the\n second run is a second set of the same rows. `entity` is the register\'s own \u2014\n an app is one register, and rows filed into another entity are a count nobody\n on this screen can check. `columns` is what the sheet may fill, in that order;\n absent, it is every column a person states IN WORDS. A computed one is refused\n because the workspace writes it, and a link, a member or a files column is\n refused because a cell of a sheet is words and those hold a row of another\n table or bytes \u2014 a file reaches another table through the party pattern (a\n `natural_key` on the target, named rather than picked), which is a create\'s.\n The generator writes the whole verb, as it does for a paper: an\n `import_<entity>` workflow that finds the row by `key` and then changes it or\n opens it, answering which of the two, and the staged run over it \u2014 drop, map,\n the per-row verdict, the commit. A line whose cell the column cannot hold is\n refused with the reason and the rest of the sheet still lands. A file MINTS\n rows of the register, which is the Add many times over, so a screen stating\n `writes: false` or `"children"` \u2014 which opens no row of its own \u2014 refuses the\n clause; the `export` beside it stays, because it only reads.\n Both spreadsheet reaches are read and written IN THE BROWSER, so the generated\n `src/main.tsx` imports `@lotics/app-runtime/sheets` wherever the plan states\n one of them and not otherwise; an app that has neither carries no spreadsheet\n engine at all. An act names ONE of four things, never two:\n - **`template`** \u2014 an `html` template this model declares (\xA7 Templates). The\n paper is filled from ONE row in `row`, and from the whole ticked set in\n `selection`. The generator writes the whole verb \u2014 a `generate_<template>`\n workflow that reads the row and hands the file back, and a menu item that\n opens it \u2014 so there is nothing left to bind. An `email` template is sent\n rather than opened and is refused here; a file-backed template (`excel`,\n `word`, `pdf-form`) is bytes a model has none of, so those stay the author\'s\n own `lotics app workflow set`. Two screens over DIFFERENT entities cannot\n name one template: one paper is filled from one kind of row.\n - **`{"kind": "agent", \u2026}`** \u2014 a run over ONE row. `agent` is an alias the APP\n declares (`package.json#lotics.agents`), because an agent is prose and tools\n rather than anything a workspace model can state \u2014 `lotics app check` refuses\n one nothing declares, exactly as it refuses a workflow nothing bound. `fills`\n is the fields the run may write: pressed, the reader watches the run, reviews\n what it proposes field by field against what the row holds now, and applies\n the ones they kept as ONE write through the record\'s own update. A run can\n reach nothing outside `fills`, a computed column there is refused (the\n workspace writes those), and a screen stating `writes: false` refuses an\n agent act outright. It belongs in `row`; the selection bar makes ONE paper\n from many rows, and a run per ticked row is the row\'s own act many times.\n - **`{"kind": "workflow", "workflow": "\u2026", "inputs": {\u2026}}`** \u2014 the work HANDED\n ON: a lead becomes an opportunity, an order is split into the orders placed\n on its suppliers, a request is closed. `workflow` is an alias the APP binds\n (`package.json#lotics.workflows`), because the rule behind it is the\n business\'s and no model states one \u2014 so the generator writes NO body and\n `lotics app check` refuses an alias nothing bound, exactly as it refuses an\n agent nothing declares. `inputs` is what the press hands that body: the\n workflow\'s own input name \u2192 `"record"` for the row it was pressed on, or a\n field of this entity for a value off that row. The value is what the\n workspace HOLDS \u2014 an option\'s key, a linked row\'s id, the figure \u2014 never the\n rendering. A files column is refused (the body reads the row\'s files off the\n row it is handed), a column that is not a field of the entity is refused by\n name, and one input short of the declaration is refused at the press, so the\n whole act is dropped rather than shipped half-bound. It runs ON the press:\n there is nothing to review first, which is the agent arm\'s law, and the\n body\'s own sentence is what the reader is told either way. It stands in\n `row`, in `record` and on a `section_acts` band \u2014 each reaches ONE row \u2014 and\n is refused over the ticked set, where one act makes one paper from many. The\n row it reaches is the RECORD\'s, so a screen stating `writes: false` or\n `"children"` refuses a hand-off on every one of those surfaces, exactly as it\n refuses an agent act.\n - **`{"kind": "sibling", "app": "\u2026", "record": "\u2026"}`** \u2014 a record opened in\n ANOTHER app of this plan, in place: the one door out of an app. `app` is the\n sibling\'s alias in `apps`, never an id \u2014 `lotics app create --from` binds\n each app to its alias, and a regeneration writes the app it became off the\n workspace\'s binding. `record` is a one-link of this entity to the sibling\'s\n register entity, or `"record"` for the row itself where both apps register\n one entity. It lands on the sibling\'s own record PAGE, so a sibling whose\n records open in a drawer is refused, as are this app itself and an alias the\n plan does not declare. While no app was made for the sibling the verb stands\n down and says so, and a row whose link is empty stands it down naming the\n link; outside Lotics, where there is no sibling to open, it is not drawn. It\n only opens, so a resting record keeps it; like a hand-off it reaches ONE row\n and is refused over the ticked set.\n - **`{"kind": "recording", "workflow": "\u2026", "inputs": {\u2026}}`** \u2014 a call, a\n visit, a walk-through RECORDED onto the row. Pressed, the product\'s own\n capture dialog asks which of the microphone, the system sound and the screen\n to take; the member records; once the words are transcribed the platform runs\n `workflow` with `inputs` and fills its `recording` input itself \u2014 the audio,\n the video where the screen was kept, the transcript (`""` for a silent\n recording), the length and when capture started. It is a hand-off whose run\n waits for the transcript, so it takes the `workflow` arm\'s rules \u2014 an alias\n the APP binds, `inputs` read off the row, `when`, `needs` and `place`, the\n same three reaches, refused over the ticked set and on a screen stating\n `writes: false` or `"children"` \u2014 less three: no `confirm` (the capture\n dialog is the confirmation), no `asks` and no `moves`. An input named\n `recording` is refused: the platform alone fills it. The body is the\n author\'s, and `lotics app check` refuses one whose declaration does not\n carry `recording` exactly as\n `{"type": "object", "fields": {"session_id": {"type": "text"}, "audio": {"type": "file"}, "video": {"type": "file", "required": false}, "transcript": {"type": "text"}, "duration_seconds": {"type": "number"}, "recorded_at": {"type": "datetime"}}}`,\n printing that declaration. The body files `video` where it is present and\n `audio` otherwise \u2014 never both, they carry the same sound \u2014 into the row\'s\n `recording` field, and writes `transcript` into its `verbatim` field\n (\xA7 Field roles). An app opened\n outside the product, or on an instance without transcription, does not draw\n the act. `scaffold check` prints it as `"<label>" [records \u2192 <alias>(\u2026)] [you\n write the body]`.\n\n **`"when"` stands an act down until the work is ready.** `{ "field": "<the\n entity\'s lifecycle>", "in": ["<stage>", \u2026] }` offers the verb only while the\n row stands at one of those stages. A verb that cannot work yet is worse than\n no verb: the reader presses it, the body refuses, and nothing on the screen\n ever said the work was not ready. Readiness is what a lifecycle states, so the\n field named is this entity\'s `lifecycle` and nothing else \u2014 a category says\n what a row is and a verdict answers a question, and neither moves. Refused: a\n field that is not the lifecycle, a stage it does not declare, and every stage\n at once, which is the act with no condition.\n\n **`"place": "cta"` draws an act ON the row** instead of in its \u22EF \u2014 the one verb\n a reader presses without opening a menu first. At most one per screen and every\n other act stays in the menu, because the trailing gutter is paid for by every\n row: a second is refused with *one verb on the row, the rest in its menu*. The\n ticked set has no row of its own, so `cta` is refused there.\n\n **`"confirm": true` asks before the act runs**, with the act\'s own label as the\n commit word \u2014 for a press that cannot be taken back: something goes public,\n something leaves the building, a figure is filed with somebody else. Everything\n else runs on the press, because a dialog in front of a reversible act is one\n readers learn to dismiss, and then dismiss in front of the one that mattered.\n It reads the same wherever the verb stands \u2014 a row\'s \u22EF, a row\'s own button, a\n record\'s menu, a section\'s band \u2014 and `scaffold check` prints it as\n `[asks first]`.\n\n **`"needs"` stands an act down until the row HOLDS what it works with.**\n `["<field>", \u2026]` offers the verb only while at least one of those fields holds a\n value \u2014 `when` asks where the row stands, this asks what it holds. A hand-off\n to somebody who reaches a person needs a way to reach them; a paper sent by\n post needs an address. ANY of them, because one person is reached several ways\n and one is enough to start. The verb is drawn standing down and names what it\n waits for, rather than pressed into a refusal. Any field answers, a lookup\n included \u2014 only whether it holds a value is read. Refused: a field this entity\n has not got, the same field twice, and `needs` over the ticked set, where rows\n hold values in some and not in others. `scaffold check` prints it as\n `[needs <field> or <field>]`.\n\n **`"asks"` is what the reader STATES before a hand-off runs** \u2014 the workflow\'s\n own input name \u2192 a field of this entity whose editor the act\'s panel draws,\n starting at the row\'s value. A hand-off to a person is to WHICH person, and the\n row cannot answer that: its value is who holds it now. The run is handed what\n the reader stated beside its `inputs`. The editor is one the panel draws with\n nothing else in hand \u2014 a text, a number, a date, a yes/no, a select or a member\n \u2014 so a link (a picker and a read of its own), a pile and a computed field are\n refused, and so is an input both handed and asked. `workflow` acts only.\n\n **`"moves"` is the stage the body lands the row at**, where a hand-off moves\n it at all \u2014 an option of this entity\'s lifecycle, and the ACT\'s alone: no\n stage picker, ladder, board or verdict moves a row there, or past it, because\n a row moved there by hand stands with none of what the body writes beside the\n move. Refused: a stage the lifecycle has not got. `scaffold check` prints it\n as `[only way to <stage>]`.\n\n **`"sets"` is the fields of this record the body writes.** One that no draft\n (`create`), closing band, outcome ask, agent `fills` or act panel (`asks`) of\n the app writes is drawn read-only on the record: its value is what the body\n wrote, and an editor beside it would state it behind the body\'s back. An\n unstated `create` asks every field a person states, so nothing is read-only\n there. Refused: a field this entity has not got \u2014 another entity\'s included \u2014\n and a column the workspace computes. `scaffold check` prints such a fact as\n `[read-only: "<label>" sets it]`.\n\n A hand-off\'s BODY is still the author\'s: `lotics app workflow set <alias>`\n binds it, and `scaffold check` prints the act as `"<label>" \u2192 <alias>(\u2026) [you\n write the body]` so it is read as work owed rather than as a verb already\n wired.\n\n A `selection` act is a `template` and nothing else: an act over many rows makes\n ONE paper from them. The generator turns the register\'s checkbox gutter\n on, emits a `generate_<template>` workflow taking the ticked ids, and the body\n reads each row and passes them as `rows` \u2014 which is what the template iterates\n (`{{#each rows}}`). One template is one paper AND one reach: naming the same\n one from a row\'s \u22EF and from the bar is refused, because those are two bodies.\n A shape whose rows are DERIVED rather than records \u2014 an `obligation_desk` \u2014\n has no set to tick and refuses the clause. The paper is handed BACK, never\n filed: a ticked set can hold rows of three different parents, so there is no\n record on which "this document belongs here" is true.\n\n<!-- generated:start apps-acts -->\n\n#### Acts\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `row` | list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | The acts in every row\'s \u22EF menu, in this order |\n| `record` | list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | The acts in the RECORD\'s own header menu, in this order \u2014 the same reach as a row\'s, with the work open |\n| `selection` | list of ([Agent act](#agent-act) \\| [Workflow act](#workflow-act) \\| [Sibling act](#sibling-act) \\| [Recording act](#recording-act) \\| [Paper act](#paper-act)) (at least one) | no | The acts over the TICKED rows \u2014 each a `template`, generating ONE document over the set |\n| `export` | `true` \\| [Export template](#export-template) | no | Save the rows in view \u2014 true for a workbook of the drawn columns, or a template this model declares |\n| `import` | [Import](#import) | no | Turn a file into rows \u2014 mapped, validated per row, previewed, then upserted by the entity\'s natural key |\n\n#### Paper act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `template` | alias | yes | A template alias this model declares; a row\'s act generates that document from the row and opens it |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n| `confirm` | boolean | no | Ask before this act runs, with its own label as the commit word \u2014 for a press that cannot be taken back |\n\n#### Agent act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"agent"` | yes | A run of an app agent over the row, whose proposed values the reader reviews before one write |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `agent` | alias | yes | An agent alias the APP declares (package.json#lotics.agents) |\n| `fills` | list of alias (at least one) | yes | The fields of this entity the run may write \u2014 reviewed by the reader, then written as ONE update |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n| `confirm` | boolean | no | Ask before this act runs, with its own label as the commit word \u2014 for a press that cannot be taken back |\n\n#### Workflow act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"workflow"` | yes | The row handed to a workflow the app binds, run on the press |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `workflow` | alias | yes | A workflow alias the APP declares and binds (package.json#lotics.workflows) |\n| `inputs` | map of alias \u2192 alias | yes | What the run is handed: the workflow\'s own input name \u2192 the value on the row it is pressed on |\n| `asks` | map of alias \u2192 alias | no | What the reader states in the act\'s panel before it runs: the workflow\'s own input name \u2192 the field whose editor asks it |\n| `moves` | alias | no | The stage of this entity\'s lifecycle the body lands the row at \u2014 the act\'s alone: no stage picker, ladder or board of this app moves a row there |\n| `sets` | list of alias (at least one) | no | The fields of this entity the body writes \u2014 read-only on the record where no draft, closing step, outcome, agent or act panel of this app writes them |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n| `confirm` | boolean | no | Ask before this act runs, with its own label as the commit word \u2014 for a press that cannot be taken back |\n\n#### Sibling act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"sibling"` | yes | A record of this row opened in a sibling app of this plan, in place |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `app` | alias | yes | The sibling app, by its alias in this plan\'s `apps`; the app it became is read off the workspace\'s binding |\n| `record` | alias | yes | A one-link of this entity to the sibling\'s register entity, or "record" for the row itself where both apps register one entity |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n\n#### Recording act\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"recording"` | yes | A call, a visit, a walk-through recorded onto the row and filed through a workflow once transcribed |\n| `label` | text | yes | The act\'s own words, as the reader reads them in the menu |\n| `workflow` | alias | yes | A workflow alias the APP binds (package.json#lotics.workflows), whose contract declares the platform\'s `recording` input |\n| `inputs` | map of alias \u2192 alias | yes | What the run is handed beside `recording`: the workflow\'s own input name \u2192 the value on the row it is pressed on |\n| `place` | `"cta"` | no | Draw this act ON the row rather than in its \u22EF menu \u2014 at most one per screen |\n| `when` | [Act condition](#act-condition) | no | Offer this act only while the row stands at one of these stages |\n| `needs` | list of alias (at least one) | no | Fields of this entity \u2014 the act is offered only while at least one of them holds a value |\n\n#### Act condition\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | The lifecycle field of this entity the state is read off |\n| `in` | list of alias (at least one) | yes | The option aliases of that lifecycle the act is offered at |\n\n#### Export template\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `template` | alias | yes | A template alias this model declares, filled from the rows in view |\n\n#### Import\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `kind` | `"import"` | yes | A spreadsheet turned into rows of this register |\n| `label` | text | yes | The act\'s own words, as the reader reads them over the register |\n| `entity` | alias | yes | The entity the file\'s rows land in |\n| `key` | alias | yes | The field a row is matched on \u2014 one of that entity\'s `natural_key` fields (\xA7 Write rules) |\n| `columns` | list of alias (at least one) | no | The fields the file may fill, in this order \u2014 absent, every field a person states |\n\n<!-- generated:end apps-acts -->\n\n### Which sections a record draws\n\n**`section_acts` puts a verb WHERE ITS EFFECT LANDS.** "Chase the paperwork"\nbelongs on the papers, not in the register\'s \u22EF two surfaces away. It is keyed by\nthe alias each section is DERIVED from \u2014 a field\'s (its ladder, its prose, its\nrequired set, its charge, one of its files) or a CHILD ENTITY\'s (its register,\nits desk, its run, its log) \u2014 because the sections come off the roles and the\nkey the app addresses one by does not exist until it is built. An alias naming no\nsection is refused, listing the ones that do; the facts band is what every other\nsection left over, so nothing addresses it. Each act is a `template`, an `agent`,\na `workflow` or a `recording`, exactly as a row\'s is \u2014 the reach is the same one row \u2014 and\n`place: "cta"` is refused: a section is a band with a heading and has no row to\ndraw a verb on. `scaffold check` prints\nwhich section each alias landed on, which is the half an author cannot see in\ntheir own file.\n\n**`sections` says WHICH of those sections this app\'s record draws, in what\norder, and how** \u2014 one ordered list, each entry the alias `section_acts` would\naddress a section by: a child entity alias draws that child\'s rows, a field alias\n(its files, its prose, its charge, its set, its ladder) draws that field\'s own\nsection, and `{"of": "<child>", \u2026}` draws that child\'s rows the way the entry\nsays (below). Absent, the record draws every section it derives, in its recipe\'s\norder \u2014 `scaffold check` prints `sections: every one the record derives (default)`.\nPresent, it draws exactly the listed sections in exactly that order, in the\nplaces the record\'s recipe gives the set of them; a section left out is not\ndrawn, a link into a child left out is not drawn as a fact either, and `[]` draws\nnone. Two apps over one entity read it for different jobs: the site\'s daily log\nbelongs on the site team\'s record and not on the owner\'s. The facts are no entry\n\u2014 `facts.groups[].first`/`last` place their bands \u2014 and **a closing step always\nends the tab it is read in**, after every band there: the one fixed rule. A\ncounted child (a relation drawn as a counted tab) reads with the other\ncounted tabs after every section, so it is listed after them; of the registers\nof rows the record does not own, the first listed is the one standing open.\nRefused, each naming the fix: a name that is no section of the record, one listed\ntwice, settings on a field\'s own section, `ladder: true` with the flow left out,\na counted child listed ahead of a section, and no child at all on a screen whose\n`writes` is `"children"`. A `children` list or a `sections` map is refused with\nthe array that draws the same record, ready to paste.\n\nWhere `sections` is absent, a screen whose `writes` is `"children"` leads with\nthe sections whose rows the reader writes, a log composed in place first; a\nstated `sections` is drawn as it stands.\n\n```jsonc\n"sections": ["task", "contract_scan", "claim"] // the schedule, the contract\'s pile, then the claims\n```\n\n### Record anatomy\n\n**A record reads in three parts, one anatomy on a drawer and a page** \u2014 the\nhead, the brief standing under it at rest, and the tabs one press away. A page\nadds width only: the brief stands beside the tabs.\n\n**The head** is the mark and name, the stage with its move menu, the ways to\nreach the subject, the line under the name, and ONE primary act. Where the queue\nmarked `cta` has an act waiting, that act leads, done through the queue\'s own\nreach and outcome sheet. Otherwise the head does what the plan decided for the\nstage the record stands at:\n\n- an act whose `when` names that stage (the row\'s `cta` act first) leads there;\n- else the stage\'s own move, taken the way the plan lands it: the closing step\n whose ending it reaches (the head opens its confirm), the workflow act that\n `moves` there, the queue whose logged touch `moves` there (the head opens its\n add), and a bare write \u2014 asking what that stage asks \u2014 only where nothing owns\n the move. Past the flow\'s last stage the move is to the ending such an act or\n the closing step reaches, else the first ending no verdict marks `fail`;\n- a status with no ending (no `outcomes`, no `phases`) is a menu and moves\n nowhere; a record reached to read \u2014 a trend\'s or a group\'s drill-down, a party\n named by the register\'s rows, a row whose stages a queue or a publish desk\n above it writes \u2014 derives no move;\n- at a stage none of this reaches, the row\'s ungated `cta` act, else \u2014 on a\n screen with `writes: "children"` \u2014 the add of the one register it adds to,\n whose tab then opens first.\n\nElse none \u2014 never a placeholder. A second queue marked `cta` is refused, and a\nstage the flow walks through whose head is bare while the closing step waits\nthere is noted.\n\n**The brief** states only what the primary act needs: for a queue, why now \u2014\nthe newest entry of the first log people write (a log only automations append\nis a receipt, read under Activity) \u2014 and the next act\'s words; for a closing\nstep, the band it `closes`, standing from the stage the step is the head\'s move\nand, once closed, as the outcome with when and by whom; and the band placed\n`first`. A record whose head carries no queue briefs no log. Every fact is drawn\nonce at rest: nothing in the brief repeats the head \u2014 a band read first naming a\nfield the head draws is refused \u2014 and the first tab, open at rest, takes what it\nalso holds out of the brief, so where Activity opens first the brief is the band\nplaced `first` alone. The head\'s act carried in its queue\'s card there draws its\nwords and verbs, not its name, due date or refusal again. A band in the brief\nstands as one row of values, each opening its control or its whole reading on a\npress; a log\'s entry as its headline, two lines and one picture, the rest a\npress away.\n\n**The tabs**, in order: the registers whose rows make up the record (priced\nlines, a book, rows carrying an amount or rolled up into the record), each its\nown tab; Activity \u2014 every log and queue, and the stage changes a lifecycle with\n`history: true` keeps; Details \u2014 every other band (`last` ones included), the\nrecord\'s own sections with its picture first, rows of its own kind (a nesting,\na dependency) named from its side, and a closing step no head carries last;\neach other register it owns; Related \u2014 the rows that merely name it, stacked and\ncounted (one such relation is its own tab); History, last, only where no log\nholds the stage changes. Past four tabs, owned registers fold into Related, the\nlast first. A tab with nothing to show is not drawn, and a countable one states\nits count.\n\n**`record: {"door", "brief", "tabs"}` places the parts instead**, each named by\nthe alias `sections` addresses its section by, the lifecycle by its field (its\nkept stage changes and its closing step), or a band by its `facts.groups[].alias`.\n`brief` alone keeps the derived tabs over what it does not hold whole; `tabs`\nplaces every part the brief does not hold whole \u2014 a log or a queue in the brief\nshows its latest part there, so it still needs a tab. Refused, each naming the\nfix: a name that is no section or band of the record, a part in two tabs or in\nboth the brief and a tab, a brief naming a log or a queue the first tab holds, a\npart placed nowhere, two tabs sharing a label, and a band alias that is also a\nsection\'s. `scaffold check` prints each record\'s\n`anatomy:` line \u2014 head, brief and tabs, `(default)` where the roles decided.\n\n<!-- generated:start apps-anatomy -->\n\n#### Record clause\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `door` | `"drawer"` \\| `"page"` \\| `"expand"` | no | "drawer", "page", or "expand"; absent, the shape decides |\n| `brief` | list of alias | no | What stands under the head at rest, in this order \u2014 a log by its newest entry, a queue by its next act\'s words; absent, derived from the roles |\n| `tabs` | list of [Record tab](#record-tab) (at least one) | no | The tabs, in this order \u2014 every section and band not wholly in the brief is in one; absent, derived from the roles |\n\n#### Record tab\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | yes | The tab\'s word |\n| `of` | list of alias (at least one) | yes | The sections and bands it holds, in this order |\n\n<!-- generated:end apps-anatomy -->\n\n```jsonc\n"record": { "door": "page", "brief": ["delivery"], "tabs": [{ "label": "Paperwork", "of": ["papers", "terms"] }] }\n```\n\n### How a section draws its rows\n\n**An object entry says HOW a child\'s rows are DRAWN**, its `of` naming the CHILD\nENTITY \u2014 every reading is over a child\'s ROWS, so settings on a field\'s own\nsection are refused. An entry here OUTRANKS the table above:\nthe derivations there recognise a shape off columns that are also ordinary\ncolumns, so a markdown field holding a refusal reads as somebody\'s message. Where\nthis entry names the reading, it is the reading. The fragments below show one\nentry each; a record\'s list names every section it draws.\n\nEvery reading takes an optional `heading` \u2014 what the section is headed on the\nrecord \u2014 and a register, a book or a tree takes `columns`: the child\'s fields it\ndraws, in order, at most four, each one its role or its type draws as a column\n(absent, the roles decide). The child\'s label names the SET its table holds ("Placements", "Tasks");\na section is read as the question it answers ("Where it goes", "Waiting on us"),\nand where the two differ the heading says the second. `{"heading": "\u2026"}` ALONE\nrenames any section of the rows this record OWNS \u2014 a log, a correspondence, a\nregister \u2014 without changing how the roles draw it; on rows the record merely\nlists it is refused, naming the rows it owns.\n\n`facts` names fields of THE RECORD drawn at the head of the section, above its\nrows \u2014 the facts those rows are read against: a job\'s window over its visits, a\nquote\'s figure over its payments. One section, because a reader reads the rows\nagainst them; a band of the same dates elsewhere on the record is read apart\nfrom the rows it frames. A field named here is filed here: it leaves the\nrecord\'s facts, counts as filed for `facts.groups`, and the clause\'s `heading`\nnames the section it and the rows make. It stands alone or beside `ledger`,\n`itinerary`, `worksheet`, `claim`, `tree`, `gantt` and `curve` \u2014 a register, a\nrun or a book of rows the record OWNS; a log, a schedule, a desk and a queue draw\nno head. Refused: a name that is no field of the record, a field another section\nalready states (the header\'s name, the stage, a pile), the limit a fact\'s level\nalready reads its measure against, one named twice or also in a band \u2014 naming\nboth \u2014 and a clause over rows the record does not own or draws as one counted\nline.\n\n```jsonc\n// A removal job: the move\'s window frames its stops, read top to bottom as one section.\n"sections": [{ "of": "stop", "heading": "The move", "facts": ["pack_on", "deliver_on", "crew_size"] }, \u2026]\n```\n\n`"draw": "ledger"` reads the rows as a book of movements where the roles alone\nwould read a register. A book derives no head from its rows: the record\'s\nfigures are the header band\'s and the facts\', and a figure is drawn once per\nrecord \u2014 `facts` moves one of them over the book, out of the band.\nThe book closes on its own total, read AGAINST what the record OWES \u2014 the limit\nthe record\'s `rollup` over these rows is a `measure` against (`field_roles`:\n`{ "role": "measure", "against": "<owed>" }`) \u2014 where the record carries one;\nthe binder emits that column as the section\'s `against`, and the balance line\nunder the rows reads the total against it. A total read against its own sum\nwould balance at zero by construction, so a rollup no measure reads closes the\nbook on its total alone. A `summary` on this clause is refused naming `against`.\n`"draw": "worksheet"`: the child\'s rows\nare PRICED LINES the reader works down in place, each part of the job footed and\nthe sheet closing under them. `cost` and `sell` name the child\'s two figures,\nwhich is the one thing the roles cannot decide \u2014 an entity carries one `amount`,\nand a line that is costed and sold carries two. Everything else is read off the\nchild\'s own roles: what a line is FOR is its `identity`, how many it is for is\nthe `measure` the pair left, and the part it falls under is its `category`. The\nMARGIN is on none of them, because it is arithmetic over the pair.\n\n`"draw": "claim"`: the child\'s rows are the LINES OF ONE PERIOD\'S CLAIM against\na priced schedule \u2014 each read as what the contract holds, what the periods\nbefore claimed, what this one claims, what that comes to to date, the share done\nand what is left, with a retention held back under the total. Four figures, and\nthree of them are quantities no role tells apart, so the clause names each:\n`quantity` (THIS period\'s, the one cell the reader edits \u2014 a `number` nothing\ncomputes, written through the line\'s own update), `contract` and `price`, and\n`previous` \u2014 what the periods before claimed on the line, cumulative. Every\namount is arithmetic over those four, so no column states one; `unit` optionally\nnames what the line is counted in, the line\'s `reference` is its code, and\n`retention` names a percentage field of THIS record, the share held back.\n\n`previous` is DERIVED, and a `number` there is refused: a cumulative somebody\ntypes is right until the first earlier period is corrected, and wrong on every\nclaim after it with nothing saying so. It is a `rollup` SUMMING THIS PERIOD\'S\nQUANTITY OVER THE SAME ITEM\'S EARLIER LINES \u2014 a line per contract item per\nperiod, each linked (a many-row link of the line to its own entity) to that\nitem\'s line in every period before. Never a lookup of the prior line\'s own\nrunning total: that total is worked out from the lookup, so the column is\ncomputed from itself, has no order to compute in, and is refused as a cycle.\n\n```json\n{\n "entities": [\n { "alias": "item", "label": "Items", "fields": [\n { "alias": "name", "label": "Item", "type": "text", "required": true },\n { "alias": "unit", "label": "Unit", "type": "text" },\n { "alias": "quantity", "label": "Contract quantity", "type": "number" },\n { "alias": "price", "label": "Unit price", "type": "number", "format": "currency", "currency": "USD" } ] },\n { "alias": "claim", "label": "Claims", "singular": "Claim", "fields": [\n { "alias": "name", "label": "Claim", "type": "text", "required": true },\n { "alias": "stage", "label": "Stage", "type": "select", "options": [\n { "alias": "draft", "label": "Draft", "color": "slate" },\n { "alias": "certified", "label": "Certified", "color": "green" } ] },\n { "alias": "retention", "label": "Retention", "type": "number", "format": "percentage" },\n { "alias": "lines", "label": "Claim lines", "type": "select_record_link", "target_entity": "claim_line",\n "cardinality": "many", "sync_both_ways": true, "paired_field_alias": "claim" } ] },\n { "alias": "claim_line", "label": "Claim lines", "singular": "Claim line", "fields": [\n { "alias": "claim", "label": "Claim", "type": "select_record_link", "target_entity": "claim",\n "cardinality": "one", "required": true, "sync_both_ways": true, "paired_field_alias": "lines" },\n { "alias": "item", "label": "Item", "type": "select_record_link", "target_entity": "item",\n "cardinality": "one", "required": true, "display_field_aliases": ["name"] },\n { "alias": "earlier", "label": "Earlier lines", "type": "select_record_link", "target_entity": "claim_line" },\n { "alias": "quantity", "label": "This period", "type": "number" },\n { "alias": "claimed_before", "label": "Claimed before", "type": "rollup", "source_field_alias": "earlier",\n "aggregate_option": { "operation": "sum", "field_key": "quantity" } },\n { "alias": "contract_quantity", "label": "Contract quantity", "type": "lookup",\n "source_field_alias": "item", "lookup_field_alias": "quantity" },\n { "alias": "unit_price", "label": "Unit price", "type": "lookup", "source_field_alias": "item", "lookup_field_alias": "price" },\n { "alias": "unit", "label": "Unit", "type": "lookup", "source_field_alias": "item", "lookup_field_alias": "unit" } ] }\n ],\n "field_roles": {\n "item": { "name": "identity" },\n "claim": { "name": "identity",\n "stage": { "role": "lifecycle", "outcomes": ["certified"],\n "phases": [{ "label": "Claiming", "stages": ["draft"] }], "history": true } },\n "claim_line": { "item": "identity", "claim": "parent" }\n },\n "apps": [\n { "alias": "claims", "name": "Claims",\n "screen": { "alias": "claims", "label": "Claims", "shape": "lifecycle_desk", "entity": "claim",\n "sections": [{ "of": "claim_line", "draw": "claim", "quantity": "quantity", "contract": "contract_quantity",\n "price": "unit_price", "previous": "claimed_before", "unit": "unit", "retention": "retention" }] } }\n ]\n}\n```\n\nThe workflow that opens a period writes its lines and links each to the same\nitem\'s lines before it; from then on a correction to any period reaches every\nlater claim through the rollup by itself. The draw is over rows the record OWNS,\nlike a sheet\'s.\n\n### Trees, bars, curves, logs and sheets\n\n`"draw": "tree"`: the rows nested under each other through `nest`, a one-row\nlink of the CHILD to itself \u2014 a record\'s breakdown drawn as its outline, each\nrow folding what it holds and the money summed up the tree the way a register\'s\n`layout: "tree"` sums it. Unlike the other draws it is an arrangement and not a\nwrite, so it reads rows reaching the record through any link; a relation the\nrecord only counts has no rows here to nest and is refused.\n\n`"draw": "gantt"`: the rows as bars across the calendar \u2014 a record\'s own\nschedule, drawn by the rules a register\'s `layout: "gantt"` is. Each bar starts\non the child\'s `when` and runs the days its `measure` that `counts` days states,\nor to `until`; `nest`, `depends_on` and `baseline` fold, link and ghost the bars\nexactly as the register\'s clauses of the same names do, over the child\'s own\ncolumns, and are refused for the same reasons. Rows none of which a day or a\nplan places are listed as a register lists them.\n\n`"draw": "curve"`: two number fields of the rows, `planned` and `actual`, each\nadded up to date along the child\'s `when` and drawn as two lines \u2014 the S-curve of\na record\'s own work, read as `summary.curve` reads a register\'s. The actual line\nstops at the last day a row stated one, and a curve against itself, over rows\nwith no `when` or over anything but numbers is refused. Both are arrangements\nlike a tree: they read the rows through any link the record lists them by.\n\n```jsonc\n// A job\'s work breakdown as its schedule, and its value earned against plan.\n"sections": [{ "of": "task", "draw": "gantt", "nest": "part_of", "depends_on": "after",\n "baseline": { "start": "planned_start", "end": "planned_finish" } }, \u2026]\n"sections": [{ "of": "task", "draw": "curve", "planned": "planned_value", "actual": "earned_value" }, \u2026]\n```\n\n`"draw": "log"` and `"draw": "itinerary"` PLACE a child\'s fields on the entry\nits rows are read as, where the roles\' placement is not the reading. A log\'s\nentry and a run\'s stop are ONE anatomy \u2014 a `name` that leads, `words` under it,\nat most two supporting `lines` of at most four fields each, a `badge` at the\nright that is one select\'s option, a `figure` at the head, `evidence` shown\nunder the words and `detail` behind one fold \u2014 and the roles decide every rung\nby themselves. On a LOG the `identity` leads, the `body` is the words, the\n`recording` is the evidence, the `verbatim` the detail, the selects, the parties\nand the `contact` make the one line, the `amount` is the figure, and nothing\nwears the badge \u2014 an entry has no stage. On a RUN the `identity` leads, the\n`slot` and the first `party` make the first line, the `amount` is a second line\nof money words, and the `lifecycle` wears the badge. The `category` is on no\nrung: the name and the mark already say what the stop is and its own record has\nthe rest, so a clause places it where a business reads its stops by kind. The\nentry\'s day is its axis and never a run of a line.\n\nSo the clause is for the field a role cannot place: a photo that is the\nevidence rather than an attachment, a headcount worn as the `figure`, a line read\nby two words instead of five, a stop\'s hours or its kind on its line.\nEach rung named replaces that rung whole; a rung left out keeps the roles\'\nanswer minus whatever the clause placed elsewhere, so a field is drawn once on\nan entry \u2014 an `amount` the clause reads on a line is no longer the figure at the\nhead. A field the clause places is never also a fact under the words, and one\nthe roles placed on a rung the clause rewrote without it is a fact again \u2014 a\n`verbatim` left off the `detail` is read by its label, and folds nothing. A\nfield the child lacks, one placed on two rungs, the entry\'s own day on any rung\n(the surface that stacks the entries draws it already), a `badge` that is not a\nsingle select, a `figure` that is not a number, and on a run the stage on any\nrung but the badge or a line, or a `badge` naming another select while no line\nreads the stage, are each refused naming the places. `words` may make rows a log that the roles would not \u2014 a plain text with\nno `body` role \u2014 but nothing makes rows with no day one.\n\n```jsonc\n// A site diary: the photos are what the entry shows, the crew count sits at its head,\n// and the line says only what kind of visit it was and where.\n"sections": [{ "of": "visit", "draw": "log", "evidence": ["photos"], "figure": "crew", "lines": [["kind", "site"]] }, \u2026]\n```\n\n```jsonc\n// A cleaning round: the stop is read by its hours and the flat, and the stage stays on the badge.\n"sections": [{ "of": "visit", "draw": "itinerary", "lines": [["hours", "flat"]] }, \u2026]\n// A fitting run read by its stage on the line, with the kind of job as the badge.\n"sections": [{ "of": "fitting", "draw": "itinerary", "lines": [["window", "fitter", "status"], ["line_total"]], "badge": "handling" }, \u2026]\n```\n\nA sheet is the WRITE side of a price, so it is the one child section whose cells\nare editors: a figure typed into a cell is written as a diff through the child\'s\nown update, which is the same editor the line\'s own drawer saves through. Every\nother child section is read where it is drawn and edited in the row\'s drawer. A\nline is ADDED from the section\'s heading as every child\'s row is, with the record\nit hangs under as the act\'s own context.\n\n### Publish sections\n\n`"draw": "publish"`: the child\'s rows are ONE RECORD STANDING ON N\nDESTINATIONS, and the section is drawn as the strip of those destinations rather\nthan as a register of the rows. A register of them is the same record listed once\nper destination with a stage beside it; what a reader wants is every destination\nthey COULD reach, each wearing what has happened there, and the one press that\nsends the ones that are ready.\n\n```jsonc\n"sections": [{\n "of": "pinning",\n "draw": "publish",\n "states": { "queued": "waiting", "published": "up", "failed": "refused", "by_hand": "by_hand" },\n "text": "wording",\n "permalink": "address",\n "error": "fault",\n "link": "source",\n "destination": {\n "network": { "field": "surface", "options": { "page": "facebook", "feed": "instagram" } },\n "connected": "account",\n "bodies": { "field": "tongue", "options": { "near": "wording_near", "far": "wording_far" } },\n "offered": { "field": "standing", "in": ["open"] }\n },\n "publish": "send_pinnings",\n "when": { "field": "state", "in": ["ready"] }\n}, \u2026]\n```\n\nA destination is queued by opening a row, which is why `queued` is the stage the\nchild\'s flow opens at. A network that takes a card OR a picture takes neither\ntwice, so a plan whose records always carry both a `link` and a `mark` leaves the\ndesk nothing it can send there.\n\n`when` is an act\'s `when` (\xA7 Apps and screens, `acts`), read off THIS record\'s\nown lifecycle: a piece still being written is not one to send, and the desk\'s\nPublish is the one press on the page that cannot be taken back. The same refusals\nhold \u2014 a field that is not this entity\'s lifecycle, a stage it does not declare.\n\nEverything else is read off the roles: the child\'s `parent` (the record), its\n`party` (the destination entity), its `lifecycle` and its `when`; the\ndestination\'s `identity`, `mark`, `contact` (where a post put up by hand is\npasted) and `category` (what the strip folds the chips under); and this record\'s\nown `mark` (the pile the post carries). The destinations are read WHOLE, through\na query of their own, because the strip\'s point is the ones this record has not\ngone to yet \u2014 which the child\'s own read structurally cannot answer with. The\nREGISTER\'s row wears one mark per destination its record has reached, read one\npage of rows at a time; the face and the network each mark wears are read across\nthe `party` link by that read itself, so the model declares no lookup for them.\n\nA network the INSTANCE is not configured for is refused when the send\'s body is\nbound (`tool_capability_unavailable`): a check over a file cannot know which\nproviders this instance holds keys for, so the map is proven against the kit\'s\nnetworks alone and the rest is answered where the body is.\n\nA chip IS the draft, which is why the section mounts no Add: pressing an empty\ndestination opens a row, pressing a queued one takes it back, and both are\nworkflows the generator writes and binds. The create takes the RECORD and the\nDESTINATION and nothing else \u2014 every other column of one of these rows is what\nthe send writes back \u2014 and it reads the desk first, refusing a destination this\nrecord already stands on: a surface guards a second press for the life of a\nmount, and a second tab is a second mount. The withdrawal is the ONE generated\nbody that deletes, and it reads the stage first and refuses anything past the\nqueue. A delete names no column, so `app create` declares the child\'s TABLE in\n`package.json#lotics.deletes` beside the columns in `lotics.writes`, and `lotics\napp check` refuses a body that deletes from a table nothing there names. The SEND\nis the author\'s, like every other hand-off: `lotics app check`\nrefuses an alias nothing bound, and the press hands it the record and the rows it\nis sending.\n\nRefused: an alias naming no register of this record, rows the record does not\nOWN for any draw but a tree, a gantt or a curve (a line added to a history\nbelongs to the job it is about, not to what is reading it), a figure that is not a field of the child or whose role is neither\n`amount` nor `measure`, a sheet naming neither figure, one figure priced as both,\na book headed with a `summary`, and a placement over a section this record\ndraws as a register rather than as a run. A desk is refused\nfor rows that name no destination or carry no lifecycle, a state naming an option\nthat lifecycle has not got, a `queued` stage that is not the one the flow opens\nat, a destination that names no row of itself, a network the kit cannot preview,\nan option of the destination\'s own selects it has not got, and a body, an\noverride, a permalink, a refusal, a card address or a connected account that is\nnot a text field of the entity it is named on. A SECOND desk on one record is\nrefused too: the strip is every destination that record can reach, so two are two\nanswers to one question, and a row of the register wears the marks of one.\n\n### Section keys\n\n<!-- generated:start apps-sections -->\n\n#### Worksheet section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"worksheet"` | yes | Priced lines the reader works down in place, each part footed and the sheet closing under them |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `cost` | alias | no | The child\'s field holding what a line COSTS \u2014 the base the margin is taken against |\n| `sell` | alias | no | The child\'s field holding what it SELLS for \u2014 the figure the sheet is read for |\n\n#### Ledger section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"ledger"` | yes | A book of movements \u2014 closes on its total, read against the record\'s rollup that sums it |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `columns` | list of alias (1\u20134) | no | The child\'s fields this register draws as its columns, in this order \u2014 at most 4, each one its role or its type draws as a column; absent, the child\'s roles decide |\n\n#### Itinerary section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"itinerary"` | yes | A run of stops read a day at a time \u2014 stated to place the child\'s fields on the stop where the roles\' placement is not the reading |\n| `name` | alias | no | The child\'s field that NAMES the entry and leads it; absent, its `identity` |\n| `words` | alias | no | The child\'s text field the entry SAYS; absent, its `body`, else (on a log) its first markdown text |\n| `lines` | list of list of alias (1\u20134) (1\u20132) | no | The entry\'s supporting lines, at most 2, each the child\'s fields it reads in order, at most 4; absent, a log reads its selects, the parties it was with and the address it reached them at, and a stop reads its slot and its party, with its amount on a second line |\n| `badge` | alias | no | The child\'s select drawn as the entry\'s status at the right \u2014 a lifecycle or a category; absent, a stop\'s lifecycle, and nothing on a log |\n| `figure` | alias | no | The child\'s number worn at the trailing end of the entry\'s head; absent, a log\'s amount, and nothing on a stop |\n| `evidence` | list of alias (at least one) | no | The child\'s fields drawn under the words as what the entry shows, a playable file played; absent, its `recording` |\n| `detail` | list of alias (at least one) | no | The child\'s fields kept behind one fold under the words; absent, its `verbatim` |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n\n#### Log section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"log"` | yes | Dated entries read in the words each states \u2014 stated to place the child\'s fields on the entry where the roles\' placement is not the reading |\n| `name` | alias | no | The child\'s field that NAMES the entry and leads it; absent, its `identity` |\n| `words` | alias | no | The child\'s text field the entry SAYS; absent, its `body`, else (on a log) its first markdown text |\n| `lines` | list of list of alias (1\u20134) (1\u20132) | no | The entry\'s supporting lines, at most 2, each the child\'s fields it reads in order, at most 4; absent, a log reads its selects, the parties it was with and the address it reached them at, and a stop reads its slot and its party, with its amount on a second line |\n| `badge` | alias | no | The child\'s select drawn as the entry\'s status at the right \u2014 a lifecycle or a category; absent, a stop\'s lifecycle, and nothing on a log |\n| `figure` | alias | no | The child\'s number worn at the trailing end of the entry\'s head; absent, a log\'s amount, and nothing on a stop |\n| `evidence` | list of alias (at least one) | no | The child\'s fields drawn under the words as what the entry shows, a playable file played; absent, its `recording` |\n| `detail` | list of alias (at least one) | no | The child\'s fields kept behind one fold under the words; absent, its `verbatim` |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n\n#### Claim section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"claim"` | yes | Lines claimed against a priced schedule, one period at a time \u2014 the contract, before, now, to date and what is left |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `quantity` | alias | yes | The child\'s number field THIS period\'s quantity is stated in \u2014 the one cell the reader edits |\n| `contract` | alias | yes | The child\'s field holding the quantity the contract prices the line for |\n| `price` | alias | yes | The child\'s field holding the line\'s unit price |\n| `previous` | alias | yes | The child\'s DERIVED field holding what earlier periods claimed on the line, cumulative \u2014 a rollup summing `quantity` over the same item\'s earlier lines, never a number somebody types |\n| `unit` | alias | no | The child\'s field naming what the line is counted in |\n| `retention` | alias | no | A percentage field of THIS record \u2014 the share held back from what is claimed |\n\n#### Tree section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"tree"` | yes | The rows nested under each other, each money figure summed up the tree |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `columns` | list of alias (1\u20134) | no | The child\'s fields this register draws as its columns, in this order \u2014 at most 4, each one its role or its type draws as a column; absent, the child\'s roles decide |\n| `nest` | alias | yes | The child\'s one-row link to its own entity \u2014 the row each row sits under |\n\n#### Gantt section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"gantt"` | yes | The rows as bars across the calendar \u2014 from each row\'s `when`, for the days its measure counts |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `until` | alias | no | The child\'s date field each bar is drawn TO; absent, a bar runs for the days its measure counts |\n| `nest` | alias | no | The child\'s one-row link to its own entity \u2014 folds a row\'s bars under the row it sits under |\n| `depends_on` | alias | no | The child\'s link to its own entity naming the rows that must finish before a row starts |\n| `baseline` | [Baseline](#baseline) | no | The child\'s date fields each row was PLANNED to start and end on, drawn under its bar |\n\n#### Curve section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"curve"` | yes | Two figures of the rows, each added up to date along the rows\' `when` and drawn as two lines |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `planned` | alias | yes | The child\'s number each row was PLANNED to come to |\n| `actual` | alias | yes | The child\'s number each row DID come to \u2014 empty on a row that has not happened |\n\n#### Publish section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"publish"` | yes | One row per destination this record stands on \u2014 the strip, the preview, and the press that sends them; the record\'s facts read after it, unless a band is placed `first` or `last` |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `states` | [Publish states](#publish-states) | yes | Option aliases of the child\'s own lifecycle \u2014 all four, because each is a different thing the chip says |\n| `text` | alias | no | A plain text on the child \u2014 what this one destination goes out with instead of the body |\n| `permalink` | alias | no | A link-formatted text on the child \u2014 where the post landed |\n| `error` | alias | no | A text on the child holding the platform\'s own refusal |\n| `link` | alias | no | A link-formatted text of THIS entity \u2014 the address the post carries as its card |\n| `destination` | [Destination](#destination) | yes | The entity this child\'s party link names, read as the places a post can go \u2014 what reaches each, and what each receives |\n| `publish` | alias | yes | The workflow alias the APP binds (package.json#lotics.workflows) \u2014 the generator writes no body, and `lotics app check` refuses an alias nothing bound |\n| `when` | [Act condition](#act-condition) | no | Offer the desk\'s Publish only while THIS record stands at one of these stages |\n\n#### Publish states\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `queued` | alias | yes | Waiting to go out \u2014 the stage the child\'s flow OPENS at |\n| `published` | alias | yes | Sent through the API |\n| `failed` | alias | yes | The platform refused it |\n| `by_hand` | alias | yes | Posted by a person, and marked so here |\n\n#### Destination\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `network` | [Destination network](#destination-network) | yes | Which destinations the platform can reach through an API, and as what |\n| `connected` | alias | no | A text on the destination holding the account the platform posts as \u2014 empty means not connected, and the post is made by hand |\n| `bodies` | alias \\| [Bodies by destination](#bodies-by-destination) | yes | The text each destination goes out with \u2014 one field of this entity, or the destination\'s own select choosing between several |\n| `offered` | [Offered destinations](#offered-destinations) | no | Which destination rows the strip offers; absent, every row |\n\n#### Destination network\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select on the DESTINATION entity saying what kind of surface a row is |\n| `options` | map of alias \u2192 `"facebook"` \\| `"instagram"` \\| `"threads"` \\| `"x"` \\| `"linkedin"` | yes | Option alias \u2192 the network the kit previews and publishes it as; an option this map leaves out is posted by hand |\n\n#### Bodies by destination\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select on the DESTINATION entity deciding which body it receives |\n| `options` | map of alias \u2192 alias | yes | Option alias \u2192 a text field of THIS entity |\n\n#### Offered destinations\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `field` | alias | yes | A single select on the destination |\n| `in` | list of alias (at least one) | yes | Its option aliases the strip offers a row at |\n\n#### Acts section\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | `"acts"` | yes | Acts waiting for a person \u2014 the words, where to reach them, and the sheet that records what came of it |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `states` | [Acts states](#acts-states) | yes | Option aliases of the child\'s own lifecycle \u2014 all three, because each is a different way an act ends |\n| `verb` | alias | yes | A single select on the child saying which act this is \u2014 its glyph, its label, and how it is reached |\n| `words` | alias | yes | A text on the child \u2014 the words to send, copied and pasted where the act is done |\n| `why` | alias | no | A text on the child \u2014 why this is worth doing now |\n| `reason` | alias | no | A text or a single select on the child \u2014 why it was passed over |\n| `reach` | map of alias \u2192 [Reach](#reach) | no | Verb option \u2192 where that act is done; a verb this map leaves out is done wherever the reader already is |\n| `withhold` | alias | no | A yes/no of THIS entity \u2014 while it reads yes, no act reaches out, and the reach verb says why |\n| `log` | [Acts log](#acts-log) | yes | What Done writes \u2014 the touch an act becomes, the answer the sheet asks, and the next step |\n| `cta` | `true` | no | The record\'s NEXT waiting act leads its head and is worn by its register row as the verb \u2014 the one press that reaches the person; one queue of a record carries it |\n\n#### Acts states\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `queued` | alias | yes | Waiting to be done \u2014 the stage the child\'s flow OPENS at |\n| `done` | alias | yes | Done, and recorded as the touch it became |\n| `skipped` | alias | yes | Passed over on purpose |\n\n#### Reach\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `at` | alias \\| text | yes | A field of THIS entity whose value is the address \u2014 its own, or a lookup of the person\'s \u2014 or "<link of the act>.<field>", an address on the row the act names |\n| `by` | `"link"` \\| `"phone"` \\| `"sms"` \\| `"email"` \\| `"zalo"` \\| `"whatsapp"` | yes | How that address is opened |\n\n#### Acts log\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `became` | alias | yes | The child\'s link to the entity whose rows record what an act became \u2014 the touch this act turns into |\n| `outcome` | alias | yes | A single select on the touch \u2014 what came of it, drawn as the sheet\'s one-tap choices |\n| `outcomes` | map of alias \u2192 list of alias (at least one) | no | Verb option \u2192 the options of `outcome` the sheet offers on that verb; a verb this map leaves out offers every one |\n| `note` | alias | no | A text on the touch \u2014 what was said; the act\'s own words stand in where the reader writes none |\n| `next` | [Next step](#next-step) | no | The next step the sheet asks for, written on the touch and queued as the next act |\n| `set` | map of alias \u2192 (alias \\| map of alias \u2192 alias) | no | Single selects on the touch the verb decides \u2014 a constant option, or one per verb |\n| `ask` | map of alias \u2192 list of alias (at least one) | no | Verb option \u2192 selects on the touch the sheet asks as the act is closed, written with the touch; a verb this map leaves out asks none of them |\n| `copy` | map of alias \u2192 text | no | Links on the touch \u2192 the link they are copied from, read one hop across a one-row link of the act |\n| `silent` | list of alias (at least one) | no | Verb options whose doing leaves nothing anybody answers \u2014 marked done, and no touch is written |\n| `moves` | [Log moves](#log-moves) | no | A touch logged while THIS record stands at one of `from` moves it to `to`, in the same run that writes the touch |\n\n#### Next step\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `step` | alias | yes | A text on the touch \u2014 the next step, which is also queued as the next act |\n| `on` | alias | no | A date on the touch \u2014 when that next step is due |\n\n#### Log moves\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `from` | list of alias (at least one) | yes | Stages of THIS entity\'s lifecycle a logged touch moves the record out of |\n| `to` | alias | yes | The stage of THIS entity\'s lifecycle it lands at |\n\n#### Section heading\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `of` | alias | yes | The section this entry draws \u2014 the child entity alias its rows are derived from |\n| `draw` | absent | no | Never stated \u2014 the section keeps the draw its roles give it |\n| `heading` | text | no | What this section is headed on the record; absent, the child entity\'s own label |\n| `facts` | list of alias (at least one) | no | Fields of THIS record drawn at the head of the section, above its rows \u2014 filed here, and so in no band of the record\'s facts |\n| `columns` | list of alias (1\u20134) | no | The child\'s fields this register draws as its columns, in this order \u2014 at most 4, each one its role or its type draws as a column; absent, the child\'s roles decide |\n\n<!-- generated:end apps-sections -->\n\n### The acts queue\n\n`"draw": "acts"`: the child\'s rows are ACTS WAITING FOR A PERSON \u2014 a reply to\nwrite, a call to make, a message to send \u2014 and the section is drawn as a queue of\ncards rather than a register: the verb, the words already written, why now, how\nlong is left, where to reach them, and the ways an act ends. An act is an\nintention and what HAPPENED is a row of its own (the touch), so Done opens a\nsheet \u2014 what came of it, what was said, the next step \u2014 and one write files the\ntouch and closes the act.\n\n```jsonc\n"sections": [{\n "of": "task",\n "draw": "acts",\n "heading": "Waiting on us", // optional\n // The child\'s own lifecycle, all three \u2014 `queued` is the stage its flow OPENS\n // at (an act is queued by opening a row).\n "states": { "queued": "waiting", "done": "done", "skipped": "passed" },\n "verb": "verb", // a single select on the child \u2014 which act this is\n "words": "words", // a text on the child \u2014 what to send\n "why": "why_now", // optional \u2014 a text on the child, why this is worth doing now\n "reason": "pass_reason", // optional \u2014 a text or single select on the child, why it was passed over\n // Where each verb is done: a field of THIS record holding the address \u2014 its\n // own, or a lookup of the person\'s \u2014 or "<link of the act>.<field>", an address\n // on the row the act names; and how it is opened: "link" as it stands, "phone"\n // dialled, "sms" / "email" composed, "zalo" / "whatsapp" a chat opened on the\n // number. A verb left out is done where the reader already is.\n "reach": { "reply": { "at": "on_post.url", "by": "link" }, "call": { "at": "prospect_phone", "by": "phone" } },\n "withhold": "prospect_quiet", // optional \u2014 a yes/no of THIS record; while it reads yes, no act reaches out\n "log": {\n "became": "became", // the child\'s link to the touch it becomes\n "outcome": "outcome", // a single select on the touch \u2014 the sheet\'s one-tap answer\n // optional \u2014 per verb, the answers of `outcome` it can come to; the sheet offers only those and the body refuses the rest\n "outcomes": { "reply": ["answered", "no_reply"] },\n "note": "said", // optional \u2014 a text on the touch; the act\'s own words stand in where none is written\n "next": { "step": "next_step", "on": "next_on" }, // optional \u2014 texts/dates on the touch; a stated step is queued as the next act\n "set": { "direction": "out", "channel": { "reply": "in_public", "call": "phone" } }, // optional \u2014 touch selects the verb decides\n "silent": ["like"], // optional \u2014 verbs whose doing leaves nothing to answer: closed, and no touch written\n // optional \u2014 selects on the touch the sheet asks, per verb: one tap for a single select, a toggle per option for a multi-select\n "ask": { "reply": ["approach", "carried"] },\n // optional \u2014 links on the touch copied from a row the act names: "<link of the act>.<link on that row>"\n "copy": { "venue": "on_post.venue" },\n "moves": { "from": ["fresh"], "to": "working" } // optional \u2014 stages of THIS record\'s lifecycle: a touch logged at one of `from` lands it at `to`\n },\n "cta": true // optional \u2014 the register\'s row wears this record\'s NEXT waiting act as its verb\n}, \u2026]\n```\n\nEverything else is read off the roles: the child\'s `parent` (the record), its\n`party` (who the act is aimed at), its `lifecycle` and its `when` (the day it\nstops being worth doing, counted down on the card); the touch\'s link to this\nrecord, its link to the same party where it keeps one (the one carrying `party`\nwhere it names that entity twice), its `when` (stamped with the moment it is\nlogged), its member (who did it \u2014 the caller, never a value anybody states) and\nits text `identity` (the act\'s `why`, else its words). Where the act names\nnobody, it is done with this record\'s own `party` onto that entity: an act queued\nbefore the person was known is still done with them, and the next act is queued\nwith them.\n\n**AN ACT IS OFTEN ABOUT A ROW OF ITS OWN** \u2014 the post a reply is left under, the\nmessage it answers \u2014 and that row holds where the act is done and where the touch\nit becomes happened. A path `"<link of the act>.<field>"` reads one hop across a\nlink of the ACT holding ONE row (`cardinality: "one"`): in `reach.at` the field is\nthe address, read across the link by every read of the acts, so the card and the\nregister row wearing the next act both open it; in `log.copy` the field is a link\non that row, copied onto the touch\'s link to the same entity as the act is closed.\n`log.ask` names selects of the touch the sheet asks on the verbs listed \u2014 how it\nwas done, what it carried \u2014 each written with the touch, and only on those verbs.\n\nRefused beside the rest below: a path across a link holding several rows or onto a\nfield that row has not got, an address there that holds no words, an asked column\nthat is no select of the touch or one the log already writes, an asked verb that\nis `silent` or no option, an `outcomes` verb that is `silent` or no option or an\nanswer `outcome` does not offer, a copied column that is no link of the touch or is the\ntouch\'s own link to this record or to the person, a copy between links to\ndifferent entities, and a copy of a link naming several rows onto one that holds\none.\n\nThe generator writes and binds three bodies per queue. `create_<child>` opens an\nact from the section\'s heading \u2014 the verb (required), the words, why now and the\nday, never the person or the touch. `log_<child>` is Done: it opens the touch,\nsets the act done AND points it at that touch in ONE update \u2014 a row reading done\nwith nothing it became is exactly the gap it closes, and a table workflow waiting\non that state does not fire a second time \u2014 then queues the next act where the\nsheet states a step, by the verb the reader picked (this one\'s where they picked\nnone). With `moves`, the same run moves the record the act is under to `to`\nwhere it stood at one of `from` \u2014 read before anything is written, and moved\nonly where a touch is: reaching somebody for the first time is a stage of the\nrecord\'s own work, and a stage set by hand afterwards is one nobody sets. A\n`silent` verb\'s act is set done alone and reads done with nothing it became \u2014\nthe state such a table workflow fires on, and no move. `skip_<child>` passes it over,\nwriting the reason only where one is sent; passed-over acts are KEPT, because they\nare evidence about what the queue gets wrong. Both refuse an act that is no\nlonger waiting, read before anything is written. With `cta`, the\nregister reads the waiting acts of every row on the page in one read, soonest due\nfirst, and each row wears its next one as its verb \u2014 the one press that reaches\nthe person; that read and the touches\' own (whose live options colour the sheet)\nare declared beside the record\'s.\n\nA queue is worked IN PLACE on every door, the way a correspondence is from its\ncomposer: an act is added from the heading, its words corrected and it is closed\nfrom its own card, so a record that opens as a drawer carries the queue whole \u2014\nthe sheet that closes an act opens over the drawer.\n\nRefused: a queue on a screen stating `writes: false` (every way an act ends is a\nwrite of its row), rows with no lifecycle, a state naming an option it has not got,\na `queued` stage that is not the one the flow opens at, a verb that is not a\nsingle select beside the lifecycle, words, why or a reason that is not a text (or\nsingle select, for the reason) of the child, a `reach` key that is not a verb\noption or an address that is not a field of THIS record holding words, a\n`withhold` that is not a yes/no of this record, `cta` beside a row act already\nplaced there, and a log whose touch cannot be written: a `became` that is not a\nlink, a touch naming this record through no link or through two, an outcome that\nis not a single select of the touch, a note or step that is not a text, a day that\nis not a date, one column named for two things the log writes (the touch\'s name\nand day, what was said, the next step and its day), a `set` over a column the log\nalready writes or naming an option either select has not got, a `silent` verb\nthat is not an option, and a `moves` that names a stage this record\'s lifecycle\nhas not got (or a record with none), names its `to` among its `from`, lands at a\nstage that ENDS the flow (where the work ends is somebody\'s decision, not a\nreply\'s), lands at a stage only a workflow act\'s `moves` reaches, or moves a\nrecord the screen draws at rest (`writes: "children"`). A SECOND\nqueue on one record is refused: what a record is waiting on is one list read\nsoonest first.\n\n### Notes, printed clauses and custom screens\n\n**A CHILD NOBODY REGISTERS IS A NOTE, not a refusal.** Where a record lists a\nchild entity no app is a register of, `scaffold check` says `"<entity>" has no\nregister \u2014 its rows are read here and created nowhere` and the plan printout\nmarks that entity `(no app)`. Rows can arrive from an import or a workflow, so\nit is a reading rather than a rule; what it catches is the register somebody\nmeant to plan and did not. Rows only the automations write (\xA7 Entity) are\nexempt, since nothing should create them. A section the record ADDS to is\nexempt, because its heading is where those rows are typed: the rows a record\nowns on a PAGE, a log on any door \u2014 an entry is written where it is read \u2014 a\nqueue of acts on any door, and a sheet, which opens its own lines. A record\nopening in a DRAWER adds none of its other owned rows, so those are noted like\nanybody else\'s.\n\n`scaffold check` prints every clause a screen states, on its own line under the\nslots, spelling the screen\'s `writes` clause `operable` \u2014 the app manifest\'s\n`lotics.writes` keeps that word, and the two are different facts: `app create`\nSEEDS the manifest from the fields the screens\' own editors write, and from then\non the app owns the declaration `lotics app check` holds its bodies to.\n\nEach shape is a `@lotics/ui` component of the same name (`LifecycleDesk`,\n`PartyRegister`, \u2026) whose props are these slots, so once the tables exist\n`lotics app create <name> --from <this file>#<app alias>` scaffolds the app with\none screen per entry, each slot reading the field the plan bound.\n\n`"shape": "custom"` is the screen no registry row covers: it declares its own\n`roles` (slot name \u2192 role), and it is emitted on `ShapeFrame` \u2014 the one anatomy\nthe six shapes above are each a configuration of \u2014 so its strip, search,\nordinal, fit budget, empty and waiting states and record door are the same ones\nthey have. It opens the record its `record` says, drawer or page, like any other.\nA bound `lifecycle` slot IS its strip, the stages in the field\'s order as on a\ndesk, so such a screen names no `tabs`. Only a lifecycle earns a strip \u2014 each\ntab a queue of work at one stage \u2014 and a screen naming any other select as\n`tabs` is refused: a kind, a book, a channel is a `filters` entry beside the\nsearch. The three shapes whose strip means something of their own take any\nselect: a reconciliation\'s dated runs, a trend\'s series, a worksheet\'s versions.\n\n### An app is its files\n\n**An app IS its `app.json`.** `create --from` writes the bound plan \u2014 every\nscreen with its shape and slots, the record each row opens with its sections in\nthe archetype\'s order, the create panels, the acts \u2014 as one JSON document, plus\na five-line `src/main.tsx` that mounts `@lotics/app-runtime` over it and one\n`src/workflows/<alias>.ts` per write. There is no screen source: the runtime\ndraws the spec, so a kit correction reaches every app with its next install.\nWhere the plan has no word for what a screen or a section IS \u2014 or for what a\npaper act should ASK before it is made \u2014 the spec names one of the app\'s own\ncomponents and `src/components/index.ts` registers it, the one file under `src/`\na regeneration never rewrites. `lotics app eject <screen|<section key>|<act\nlabel>>` writes each of those, starting from what the runtime already drew. What\nthe APP does rather than what a screen draws \u2014 a bridge to the page it is framed\nin, a listener \u2014 is one component `app.json#root` names: mounted once around\nevery route, and carried over by every regeneration, since the plan has no word\nfor it.\n\n**How a spec reads a row.** Each query projects its columns under the alias the\nworkspace mints from the field\'s LABEL \u2014 never the alias this file keys it\nunder, which is the model\'s own namespace and which the workspace never saw. So\na field this file calls `partner` and labels `\u0110\u1ED1i t\xE1c` is referenced as\n`doi_tac` throughout the spec. Every id in it is live: the `tbl_` a screen is\nover, the `fld_` of each column, and the `opt_` of every option a role names.\n\n**Every query is described, in the model\'s own language.** The line an agent\nchooses between aliases by is written from this file\'s nouns \u2014 the entity\'s\nlabel, the screen\'s, the link\'s \u2014 and the sentence around them is the language\nthe file is mostly written in, decided the same way a generated write\'s refusals\nare. Hand-editing one is erased by the next regeneration; `lotics app check`\nrefuses an alias that carries none.\n\nA screen is that list and the RECORD it opens, and the record comes off the same\nroles \u2014 nothing to declare for it. It opens with a **header**: the entity\'s\n`identity` as the record\'s name \u2014 whether or not the list has a column for it, so\na ledger\'s and a trend\'s records are named too \u2014 the `mark` beside it on a PAGE,\nwhich is what the subject is recognised by (so the pile below is the papers that\nare left, and a `presentation.lead` spending that gutter on something else leaves\nthe picture in the pile), the key the row is filed under \u2014 else the `when`\nthis screen reads \u2014 under it, the day the platform stamped it made (a date\n`derive_from: "created_at"`) as its `stamp`, the `lifecycle` as ONE status, and\nONE headline figure (the `amount` the screen reads, else its `measure`). Nothing\nthe header states is a fact as well. BOTH DOORS state the name: which door a\nscreen uses is a layout answer, and what the record is ABOUT is not. The status\ncarries its outcomes on every record the app draws, a party\'s and a child\'s too,\nand a stage only an act reaches is chosen by running that act.\n\n### Record header and facts\n\n**A JOB\'S HEADER ALSO STATES WHO IT IS FOR, AND WHO IS WORKING IT.** Where the\nrecord is the work itself \u2014 a desk\'s rows that belong to nothing, opened on a\npage \u2014 its `party` link is under the name, beside the ways to reach the subject,\nand is then not a fact as well: a page headed by a code alone said nothing about\nwhose work it is. A record opening in a DRAWER has no row of links to hang it\non, so its party stays a fact; so does a LINE\'s, whose register files it under\nthe thing it belongs to. A `select_member` `party` on a job is the person in\ncharge: drawn as the person beside the status, on either door, and in no section.\nA one-link to an entity declaring a `mark` is drawn as that party \u2014 its logo,\nelse its initials \u2014 wherever a record page states it, the face read across the\nlink.\n\n**A record whose figures are a SENTENCE states them under the header, in a\nband**, and the header then states neither of them: a job reads what it comes to,\nhow far through it is (the `measure` against its limit), what is left of it (a\nformula that is exactly `{amount} - {measure}`) and how long there is (an\n`obligation`, else its `when`, counted down); a party reads its settlement, the\n`amount` and `measure` roles it carries in the order the model declares them; a\ncatalogue item reads its `amount` against the reference that amount names. Two\nfigures are the floor and four the ceiling \u2014 under two, the header keeps its one,\nsave a lone `measure` filling toward its limit (any `reading` but `"threshold"`): it\nstands in the band by itself, because its track, its pass mark and what is still\nowed are the record\'s goal, read whole.\nA band is a PAGE\'s strip, so a screen whose `record` is a drawer keeps every\nfigure where a drawer reads them. The band only READS: a figure nobody writes is\nstated there alone, and every one a person states \u2014 a limit, a pass mark, a\nwindow\'s days, a typed amount \u2014 stays a fact and is changed among the facts. The\nday a countdown counts to is not the provenance line above it. Then\nits sections, in the order its KIND reads them: the\n**facts** (every field neither the header, the band nor another section owns, the\nrow\'s own `parent` among them \u2014 led by the ones THIS screen\'s slots bound, then\nthe key it is filed under, then the order the model declares them in \u2014 a\n`markdown` field among them, drawn across the grid\'s whole row rather than\nwrapped to a third of it), the record\'s own **body** (that same markdown, taken\nas a section instead, ONLY where the kind leads with it: a catalogue item IS its\ndescription, and a job is not its notes), the **charge** (where the `amount` is a formula over exactly one\nquantity and one money field, the line states `quantity \xD7 unit price = amount`\nand neither figure is typed twice \u2014 the section is headed by the amount\'s own\nlabel and the closing row says what the figure IS, so the word is said once),\nthe **progress**, only where the screen states `ladder: true` \u2014 for a long flow\nwhose path the reader needs to see (the stages as a ladder of rungs, grouped into\nits `phases` and each dated where the lifecycle keeps a `history`; the header\nthen states no status), a\n**required set** (a multi-select `expected_set`, or a child entity whose rows\ncarry one entry of it each \u2014 that child\'s `files` field is what a paper attaches\nto), the record\'s **own rows** (any other child, its role-bound fields as\ncolumns), and its **files** (every `files` field the header does not lead with,\nthe `mark` first where it keeps one \u2014 a section like\nany other, read in Details straight after the facts it is the evidence for,\nexcept on an EVIDENCE record, which OPENS on the paper it exists for; the\nheading carries the Add verb, and stands it down while the pile is empty, where\nthe drop well is already the door; and a `verdict` whose FORMULA reads one of\nthose files fields is the pile\'s OWN state rather than a fact row \u2014 the file it\nis short, drawn as a ghost). A child is\nan entity that LINKS here, ONE SECTION PER LINK: the rows it owns (`parent`), the\nrows NAMED by it (their `party` or their `identity` \u2014 so this record\'s history),\nand the rows reaching it through a link carrying neither role, headed by that\nlink\'s own label. A child hanging off two records therefore declares one `parent` and\nleaves the second relationship a plain link, which still gets its section.\n\n**THE LADDER\'S DATES ARE WRITTEN BY THE TABLE, NOT BY THE APP.** A lifecycle that\nkeeps a `history` derives the table automations that append its rows\n(\xA7 Table automations), so the generated `update_<entity>` writes the stage like\nany other field and the generated `create_<entity>` writes nothing to the\nhistory: a rung is dated by the write that moved it, from whichever door made it,\nand an app that appended too would date one move twice.\n\n**THE ROWS A RECORD OWNS ARE ADDED AND OPENED FROM THEIR SECTION.** A `parent`\nsection\'s heading carries the Add for its rows, handing the record as the\nparent \u2014 filled, never asked. Every row opens its own record \u2014 a line of a book,\na paper on a desk, a stop on a schedule alike: where this app already routes a\npage for those rows (the register\'s own, under one of them) it is navigated to;\notherwise it is a DRAWER mounted on the record it belongs to, stepped \u25C0 \u25B6 over\nthe section\'s rows as they are drawn, with the row\'s framed editors, its stage\nand its files, saving through `update_<child>` \u2014 the one editor those rows have,\nwhich any control the section draws over a row writes through too. Never the\n`parent` link itself: it is the key the row is FILED under, stated by the record\nthe drawer was opened from, so it is neither a fact of the drawer nor an input\nof that editor. ONE LEVEL, and only from a PAGE: a child\'s own children draw as\nthe kit\'s panel of the row, with no drawer and no Add, and a register whose own\nrecord is a DRAWER is the same for the children it owns \u2014 a drawer mounts no\ndrawer. The rule is about RECORDS INSIDE RECORDS, not about rows of one kind\nnested by a link of their own: a tree (`layout: "tree"`, `"draw": "tree"`) is one\nlist of one entity, and each of its rows opens the one record surface that entity\nalready has, at whatever depth it is drawn. **A ROW IS ADDED WHERE IT CAN BE OPENED AGAIN**, so those sections lose\ntheir Add with their drawer; a row of the register\'s OWN kind keeps it, because\nthe register\'s list opens it. A correspondence\'s entries open nothing anywhere:\nthe composer under them is the section\'s Add whatever door the record has, and\nthe answer mark is one field of the reply\'s own editor. A queue\'s acts are the\nsame on a drawer: the heading\'s Add opens one, and its card saves its words\nthrough `update_<child>`. The rows a record is merely NAMED by, and those reaching it\nthrough a roleless link, are read only \u2014 a history, never a place to open or\nadd.\n\n### Time axis, logs and books\n\n**TWO CHILDREN ARE READ ON A TIME AXIS RATHER THAN AS A REGISTER**, and the\nmodel says which by the roles it declares on them. A child carrying an\n`obligation` whose `satisfied_by` is a DATE \u2014 and no `lifecycle`, because an\nentity with stages of its own is a JOB being worked and its rows under another\nrecord are that record\'s lines or its history \u2014 is a **schedule**: what is\nplanned against time, and what slipped. Its rows are the ordered stops, each\nstating the day it was promised for and the day it happened, with the delta\nnamed where the rung was late \u2014 drawn as a register those are two date columns\nand the reader subtracts them by eye, which is exactly what "three days late at\ndischarge" costs to learn. An obligation closed by a FILE says nothing about\nwhen, so those rows stay the register they were.\n\n**A body and a day is a LOG** \u2014 what happened, in order: the entry in the words\nsomebody wrote, filed under the day they wrote it. The body is the child\'s\n`body`, else its first `markdown` text; the format never decides, so a plain-text\nnote declared `body` is an entry and a markdown remarks field on a job is not\n(a `lifecycle` keeps it a register, as it keeps a schedule one). A register of\nentries is a table whose one useful column is the one it cannot draw, so the rows\nare read as one time axis instead: one head per entry, the words under it, and\neverything else the row states placed on the entry. WHO WROTE IT is the\n`select_member` the row names \u2014 a log is kept by the people who work the\nrecord \u2014 and the parties on it are who it was WITH, drawn on the entry\'s line\nbeside the `contact` it reached them at and the files that came with it. The\nother selects are words on that line; the `recording` plays under the words and\nthe `verbatim` waits behind one fold; everything else a person states on the\nentry \u2014 the day\'s headcount, the gear on site, what went wrong \u2014 is drawn under\nits words by its own label, where that entry states it: an entry is the whole\ntouch. Its Add is a composer asking exactly that \u2014 the entry, its name where the\nchild names one, the recording and the verbatim, the day (opening on today), the\nentry\'s own selects and those facts; the record it hangs under is not asked.\n\n**A `verdict` on those rows makes the log a CORRESPONDENCE** \u2014 a question, its\nreplies, and the one reply marked with that yes/no as the ANSWER. It is read\nfrom the question, oldest first, where a log is read from its latest entry under\nday heads. Where no member keeps it the first `party` is who wrote each entry,\nbecause there the other side writes too; a SECOND party is who the entry was\nleft WITH, and the latest one names the ball in court: while nothing answers,\nthe record says whose move it is and counts its own deadline (an `obligation`,\nelse its `when`) against them. Whose move and the clock are read only under the\nrecord that OWNS the messages \u2014 under the party who wrote them the same rows\nare several questions at once, and the last of them says nothing about any \u2014 but\nthe answer mark is a fact of the reply wherever it is read, and it is one field\nof the reply\'s own editor. A NAMED row is an entry that OPENS \u2014 a visit or an\ninspection is a thing in its own right, read in full on a record of its own,\nmounted on the page it is read from as a register\'s row is \u2014 where an entry\nnothing names is corrected where it stands. An entity only the automations\nwrite (`writes: false`) opens that record at rest and composes nothing.\n\n**A PROFILE\'S LADDER LEADS.** A profile whose screen states `ladder: true`\nreads `progress` first, right under the band, and its histories follow.\n\n**A RELATION THE RECORD NEVER ACTS ON IS A COUNTED TAB.** Every register that\nmerely NAMES the record is a tab of its own: its label, how many rows name this\nrecord (a COUNT, never a page of rows measured), and a press that opens the\nregister owning them narrowed to this record. An entity this app lists nowhere\nis a count with no door, and a tab counting none is not drawn. The one\nexception is a PROFILE, whose history IS its body and stands open in its tab.\n\n**Rows the record owns whose `amount` is `signed_by` a direction are a\nBOOK**, drawn as a statement \u2014 each line dated and signed, the balance under\nthem where there is more than one line to add up, labelled by the amount\'s own\nword \u2014 where the same rows on the party they\nwere transacted with stay that party\'s history, which is scanned for the line\nshort of its paperwork rather than struck to a balance. **Rows that carry a\n`when` read as RUNS**: a party\'s history under the year \u2014 and only where the rows\nin hand span more than one, since a subhead over every line of a book names the\nyear and separates nothing \u2014 and a job\'s own rows under the day they fall on,\nwhere some day holds more than one (a row per day keeps its date column), and\nwhich, where those rows also carry a `lifecycle`, is an ITINERARY rather than a\ngrouped table: the day heads the run, each row is one line (its `slot`, its name,\nits party, its stage, its `category` and `reference`, its amount), the figures end\non one edge and the run closes with their sum \u2014 unless the record\'s own BAND is a\nsum rollup over exactly these rows, which states it once already. A `category`\npaints the node the run is read down, in the option\'s own colour; a `reference`\ncloses the line with the code somebody quotes on the phone. **A `mark` LEADS the\nline with the stop\'s own picture** \u2014 a venue, a vehicle, a machine \u2014 and it is the one\nplace on a record where a child entity\'s mark is drawn at all, since no register\ncolumn is ever a picture; a line whose subject holds none is marked with its\n`category`\'s glyph instead, so the run keeps one left edge. All three are\nPROJECTED for the run whether or not the register\'s column budget would have\ndrawn them, and a mark is projected NOWHERE ELSE.\nA line\'s mark is most often the SUBJECT\'s rather than the line\'s own \u2014 the\nservice, the product, the asset it is a line of \u2014 which is a `lookup` through\nthat link onto the subject\'s own `files`, given the role on the line.\n**EACH DAY\'S HEAD CARRIES THE SECTION\'S OWN ADD** as well, handing the panel that\nday, so a line made from Tuesday opens with Tuesday answered; the heading keeps\nthe verb too, for the first line and for a day nothing is planned on yet. **THE\nKEY LEAVES THE COLUMNS where the head states the whole of it** \u2014 a day head does,\na year head states four digits of the date and the day is what the column is\nstill there to carry.\n**A run is READ FROM THE END THE READER WANTS**: a job\'s own rows and an itinerary\nascending, a book, a history and a log descending.\n**A REGISTER WHOSE `when` IS A DEADLINE IS READ FROM THE NEAR END** whatever its\nshape does \u2014 `due` (or an `until` that says where the countdown stops) makes the\ndate a promise, so the rows in breach of it lead and a row nobody dated falls\nlast. A plain `when` keeps the shape\'s own end. `scaffold check` prints the\npromise (`order: Theo d\xF5i ti\u1EBFp, overdue first`).\n\n**A DESK OVER AN ENTITY THAT CARRIES A `slot` IS ITSELF A DAY\'S RUN.** Its own\nregister is drawn under one subhead per day, sorted by that day and then by the\npart of it, and the date column goes \u2014 the subhead states it once for the whole\nrun, and a column repeating it down every line is the same value twice. Without\nit an itinerary was one flat list in whatever order the rows arrived.\nA document desk keeps its head either way \u2014 the entries it owes are its\ncontent, filed or not. The name and whatever figure is left to it are the header\'s\nalone \u2014 it states them in full, so a fact for either would be the same sentence\ntwice.\n\n### Where a field is drawn\n\n**NOTHING IS FOLDED: A FIELD IS DRAWN WHERE IT IS PLACED.** Everything a person\ntypes or picks is shown, empty or not, because absence is work somebody owes. An\n`autonumber`, a date the platform stamps (`derive_from`) and a date a formula\nworks out that no role names are what the SYSTEM wrote: the key under the name\nand the `created_at` stamp are the header\'s, and the rest are drawn only where a\n`facts` band or a section\'s `facts` names them \u2014 unplaced, they are not drawn.\n\n**A `contact` field whose kind the\nmodel decides** \u2014 an email, a number, a place, a `link`-formatted text, or a\nMESSAGING APP its label names (WhatsApp, Zalo, Telegram, Viber, WeChat, Line,\nMessenger) \u2014 leaves the facts too, for the row of links under the name on a\nrecord that opens as a page; one whose kind nothing decides stays a labelled\nfact, because a scheme nobody stated is a link that opens nothing. A handle is\ndrawn under its app\'s name \u2014 the app is what identifies the person there, and a\nbare number beside an envelope says the wrong thing \u2014 and it dials only where its\nvalue IS a number.\n`check` prints the record under each screen\'s slots:\n\n```\n Orders \u2014 lifecycle desk over Orders (12 rows) \xB7 page \xB7 tabs: Stage (New \u2192 Quoted \u2192 Confirmed \u2192 Shipped \u2192 Done)\n stage Stage \xB7 identity Order no. \xB7 mark Photo \xB7 party Customer \xB7 amount Total \xB7 level (none \u2014 no field declares "measure") \xB7 when Due\n record: header (Order no. \xB7 Customer) \xB7 band (Total \xB7 Shipped against Lines \xB7 Due counting down) \xB7 status: Stage (in the header) \xB7 facts (Ship to) \xB7 files: Photos \xB7 documents: Papers (Kind: 4 required) \xB7 itinerary: Order lines by Ship by at Window, read by Window \xB7 Handling / Line total, status Status (Product \xB7 Quantity \xB7 Line total) \xB7 related: Visits (count via Order)\n```\n\nThe ORDER of that line is the record\'s own, top to bottom, and it is the KIND\'s,\nand every entry of it is a part the anatomy places (its `anatomy:` line says\nwhere), `status:` excepted \u2014 the stage beside the name. `related:` comes last \u2014\na register the record never acts on, a counted tab of its own.\nA child register whose `amount` is `signed_by` a direction is a BOOK of movements\n\u2014 what the record came to rather than what it is made of \u2014 so it follows the\nrows the job is made of rather than standing among them.\n\n**A section over a child is named by the CHILD\'s own label**, because the reader\nalready knows which record they are on: "\u0110\u01A1n h\xE0ng", not "\u0110\u01A1n h\xE0ng \u2014 Kh\xE1ch h\xE0ng".\nWhere two links from one entity reach this record, that label names two sections\nand each takes its own link\'s label as a qualifier \u2014 `H\u1ED3 s\u01A1 (\u0110\u01A1n h\xE0ng)` and\n`H\u1ED3 s\u01A1 (\u0110\u01A1n g\u1ED1c)`.\n\nIn that line `documents:` is a `RecordExpectedSet`, and `lines:` a `RecordChildren` over the rows\nthis record owns \u2014 `history:` where they NAME it instead, as their `party` or their `identity`, capped at its latest 5\nwhere the record is a profile (`latest 5 of N by <day>`, the whole of them behind the foot); a required set\nwhose entries are a CHILD entity carrying files takes `kind="files"`, and the entity\'s own\nmulti-select takes `kind="items"`, since nothing attaches to an option.\n\n**A section that knows its size says so.** Where a `measure` on the record is a\n`count` rollup over the very link a rows section hangs on, the limit it is read\n`against` is how many rows that section is OWED: the printout adds `\u2014 expects\n<limit>` and the screen draws that many ghost rows until the first one lands,\ninstead of "nothing here" two bands under a count saying three are outstanding.\n\nA figure printed as `Total (USD)` is MONEY in the currency named \u2014 a `formula`\nstates it in `formula.currency`, and a `rollup` or a `lookup` inherits it from the\nfield it reads, so read those brackets: a rate that should be in dollars and is\nprinted bare will be drawn in the workspace\'s own money. Where the entity carries\na `currency` role, the ROW\'s code outranks the field\'s on every line that states\none, and a line stating none falls back to the field\'s.\n\n### Operating a record\n\n**A record surface can be OPERATED, and that is the default.** Every screen with\na record gets a workflow that writes its editable facts \u2014 every field a person\nstates \u2014 so the record\'s values are edited in place and its stage is advanced\nfrom its status, or its ladder. `"writes": false` makes one screen a reader: use it\nfor a screen over rows another desk owns, never as the default. A desk nobody\ncan act on is a viewer of state somebody must go and set somewhere else. A\nreader keeps the acts that only READ the row \u2014 its papers and its `export` \u2014\nand refuses every one that writes it: the quick column, the agent act, the\nhand-off on any surface, and the file an `import` would mint rows from.\n\n**`"writes": "children"` splits the record from the rows under it.** The record\nis drawn exactly as `false` draws it \u2014 the same refusals, and the register opens\nno row of its own \u2014 while every section listing the rows that record OWNS keeps\nits Add, its drawer and its own editor. Use it where one desk states the plan\nand another logs against it: the hours typed under a period the office set, the\nitems ticked under a job somebody else opened.\n\n**`"writes": "record"` is the mirror.** The record keeps every editor, its stage,\nits files, its quick column and its acts, and the register opens rows of its\nown; every section listing the rows that record OWNS is drawn at rest \u2014 no Add,\nno drawer that saves, no composer. Use it on the desk that decides the plan and\nreads what other desks log under it. `lotics app check` refuses a child create\nor a child editor under it, and the app\'s `package.json#lotics.writes` is seeded\nwithout the child tables.\n\n**Which facts those are is the FIELD\'s answer.** Text, number, date, yes/no and\nselect are typed or picked. A `"cardinality": "one"` link is RE-POINTED, through\na picker over the target entity named by its `display_field_aliases` \u2014 else the\ntarget\'s `identity` \u2014 narrowed by what the reader types and carrying that\nentity\'s own `read_scope` and the link\'s `options_where`, both refused again\nwhere the save lands, so a link the plan gives neither column is read instead\nof offering an empty list. The record\'s fact, a move\'s ask and a closing step\'s\nfield draw the one picker a create\'s draft asks with. A files field is ATTACHED\nTO and DETACHED FROM, as the delta rather than the pile, so two readers filing\nat once each keep their file. A link naming SEVERAL rows is picked through the\nsame picker and written the same way \u2014 the generated editor takes the rows added\nand the rows taken off, never the list \u2014 so two readers adding at once each keep\ntheirs. An `autonumber` and every computed field are read: they are the\nplatform\'s to write.\n\n**A one-link is that fact WHATEVER its sync.** `sync_both_ways` is one relation\nwith a field on each side, and the sides are not the same surface: the record\nthat names ONE row states it as a fact, and the MANY side is the register on the\nother entity\'s record. Declaring both directions does not give this record a\nregister of the single row its own fact already names.\n\n**The write is the record SURFACE\'s.** Its inputs are the fields that surface\noffers to save \u2014 the facts with an editor, the lifecycle its status or ladder\nadvances, the key under the name and the figure a book is read against where a\nperson states them, a files section over a stated field, and what the BAND\nstates: its countdown\'s date, and a meter\'s limit where a person types it \u2014\nnever every field a person could in principle type. What\nthe header states, a limit a level folds into a meter among the FACTS, a required\nset with a section of its own and the pile a page\'s mark draws are read there, so\nno input is declared for them. A second screen over the same entity draws no\nsurface of its own and adds nothing.\n\nWhere two operable screens of one plan reach the same entity, `scaffold check`\nnames them and the apps they belong to: two desks writing one record is a\ndecision, and the split is stated on the FIELD in each app\'s\n`package.json#lotics.writes` rather than left to whoever edits second.\n\nThat workflow lands in the app as source, like every other: its body in\n`src/workflows/update_<entity>.ts` and its declaration in\n`package.json#lotics.workflows`. The two together are the binding \u2014 `lotics app\ndeploy` pushes whatever differs from what it last saw live \u2014 so the write is\nversion-controlled and travels with the app rather than being bound by hand\nafterwards.\n\nA `custom` screen declares its slots under `roles` (slot \u2192 role) and they bind\nthe same way:\n\n```jsonc\n{ "alias": "readings", "label": "Readings", "shape": "custom", "entity": "reading",\n "roles": { "subject": "identity", "reading": "measure" } }\n```\n\n## Applying packages\n\n`apply` copies published packages into the workspace AFTER the model\'s own\ntables exist \u2014 apps over the tables you just described, and any tables of their\nown they still need. Ordered, and run by `lotics setup` and `lotics scaffold\napply` alike.\n\n```jsonc\n"apply": [\n {\n "package": "apg_k3nf82ldpq",\n "bind": { "company": { "label": "Customers", "fields": { "name": "Company name" } } },\n "no_sample_data": true\n }\n]\n```\n\n<!-- generated:start apply -->\n\n#### Package entry\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `package` | text | yes | The package id to copy in |\n| `bind` | map of alias \u2192 [Binding](#binding) | no | Entity alias \u2192 the labels this workspace calls that entity and its fields |\n| `no_sample_data` | boolean | no | Decline the package\'s sample records |\n\n#### Binding\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `label` | text | no | The workspace\'s own table name for this entity. |\n| `fields` | map of alias \u2192 text | no | Field alias \u2192 the workspace\'s own field name for it. |\n\n<!-- generated:end apply -->\n\n`bind` holds the LABELS this workspace uses: scaffold adopts by label, so binding points the package at the\ntables the model created instead of a second set beside them. Only naming\nmoves \u2014 a bound field must be the TYPE the package declares, or the copy is\nrefused. `lotics library list` is the shelf, and `lotics library show <apg_id>`\nlists the aliases to bind.\n\nEntries run in the order they are written, because a later one may bind onto a\ntable an earlier one created. **A refused entry stops the run and the entries\nbefore it stay** \u2014 they are separate copies, committed as they land, so the\nrefusal names them rather than leaving a caller to re-run the file and copy them\ntwice.\n\n### What a run remembers\n\nThe WORKSPACE remembers what each entity, field, select option, role and\ntemplate alias became here, plus which record each first row landed on and the\nfile each document path was uploaded as. The model file itself holds no live id\n\u2014 it is the portable half, and the same file applies to a demo workspace and to\na customer\'s \u2014 so the join lives where the things it names live, and one model\napplied to two workspaces holds two bindings that know nothing of each other.\n\nIt is the workspace\'s and not the file\'s because a model file is never\ncommitted: memory kept beside it is one author\'s disk, absent for a teammate, on\na second machine or after a delete \u2014 and every one of those goes quietly back to\nbinding by label, which is what grows the second table.\n\nEvery later verb binds through it: `apply`, `diff`, `app create --from`,\n`app regenerate --from` and `workspace build` take the remembered id first and\nfall back to a label only for an alias nothing has bound \u2014 a field you have just\nadded, or a workspace nothing has applied this model to. That is what makes a\nrelabel on EITHER side a rename rather than one thing the workspace lacks and\none the model lacks: `diff` prints one line under the alias\n(`order.state: label: model "Stage" \xB7 workspace "Giai \u0111o\u1EA1n"`), and `apply` binds\nthe thing it has always meant, reports the move and names the command that makes\nthe two agree \u2014 which side is right is yours to decide, not a reason to refuse\nthe run.\n\nA bound target the workspace no longer holds IS a refusal, by name: re-binding\nto whatever carries that label today is how the model comes to point at\nsomebody else\'s table. `lotics run restore_table` puts a deleted table back, and\n`lotics scaffold unbind <kind> <alias>` forgets the binding \u2014 after which the\nnext apply creates a new one and nothing joins it to what was there.\n\n## Presets\n\nA preset is a trade\'s model, published to be READ. An assistant reads it, asks\nat most two questions, picks a variant and writes a `model.json` from it \u2014\nnothing is copied, and a preset is a file rather than anything a workspace\ninstalls.\n\n```jsonc\n"preset": {\n "name": "Field service",\n "description": "Jobs, the crew that runs them, and what each one billed.",\n "questions": ["Do you dispatch crews, or one person per job?"],\n "variants": {\n "crews": {\n "when": "work is dispatched to crews rather than to one person",\n "entities": [ /* tables this branch ADDS */ ],\n "fields": { "job": [ /* fields this branch ADDS to `job` */ ] }\n }\n }\n}\n```\n\n<!-- generated:start presets -->\n\n#### Preset\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `name` | text | yes | The trade, as the shelf lists it |\n| `description` | text | yes | What the trade\'s workspace keeps, in a sentence |\n| `questions` | list of text (at most 2) | yes | What to ask before choosing a variant \u2014 at most two |\n| `variants` | map of alias \u2192 [Variant](#variant) | yes | Slug \u2192 a branch of the model, taken where its `when` describes the business |\n\n#### Variant\n\n| Key | Type | Required | Meaning |\n|---|---|---|---|\n| `when` | text | yes | The condition, in the words a person would use, that selects this variant |\n| `entities` | list of [Entity](#entity) | no | Tables this variant adds |\n| `fields` | map of alias \u2192 list of [Field](#field) | no | Entity alias \u2192 the fields this variant adds to that entity |\n\n<!-- generated:end presets -->\n\nVariants are **additive only**: a branch adds entities and fields and never\nremoves them, so the base is a model in its own right rather than a draft.\n`lotics scaffold check` proves the base AND every variant merged onto it, so a\npreset ships with every branch already proven \u2014 the branch nobody took is the\none that fails in the workspace of whoever takes it.\n\n`preset` is not scaffolded. `lotics setup` and `lotics scaffold apply` ignore\nit and create the base model\'s tables.\n\n`lotics scaffold export` prints a workspace that already works as one of these\nfiles \u2014 the starting point for a preset or for another business\'s model, never a\nsource of truth: it carries one business\'s words and stops describing that\nworkspace the moment either changes.\n\n## Starting from a preset\n\n`lotics library list` is the shelf of them and `lotics library show <slug>`\nprints one whole: its questions, every table as `alias \xB7 label` with each field\nas `alias:type`, and each variant as `slug \xB7 when` followed by the tables and\nfields that branch adds. When one of them is the trade in front of you, do not\ntranscribe it \u2014 name it:\n\n```jsonc\n{\n "from": "field_service",\n "variants": ["crews"],\n "rename": { "job": { "label": "\u0110\u01A1n h\xE0ng", "fields": { "code": "M\xE3 \u0111\u01A1n" } } },\n "entities": [ /* a table this business has that the preset does not */ ],\n "rows": { "job": [ { "ref": "j1", "fields": { "code": "J-1" } } ] },\n "field_roles": { "job": { "code": "identity" } },\n "write_rules": { "customer": { "natural_key": ["email"] } },\n "apps": [ /* the screens this business\'s apps will have */ ]\n}\n```\n\n- **`from`** is the preset\'s SLUG \u2014 its own file name, a lowercase slug. Naming\n it is what makes `entities` optional; every other rule on this page is\n unchanged, because the file is resolved into the full form and then checked and\n applied exactly as one. A slug nothing serves is refused with the ones there\n are, never resolved against something else.\n- **`variants`** names the branches to merge onto the base, in order. Pick the\n one whose `when` describes what the person said; a slug the preset does not\n declare is refused rather than ignored.\n- **`rename`** is keyed by the preset\'s entity alias and holds the labels this\n business uses \u2014 the same shape `apply[].bind` takes, and the same rule: only\n naming moves. An alias the preset does not declare, and a label that is\n already another table\'s, are both refused.\n- **`entities`** are added after the rename, already in this business\'s own\n words.\n- **`rows`**, **`field_roles`**, **`write_rules`**, **`apps`** and\n **`apply`** mean exactly what they mean in the full form \u2014 `"rows"` are this\n business\'s real first records,\n `"field_roles"` may name the preset\'s fields as well as its own (a role the\n preset declares itself is kept unless this file names the same field, or\n clears it with `null`), `"write_rules"` the create-time clauses on either\n (an entity this file names replaces the preset\'s whole entry for it),\n `"apps"` the screens it will have (a preset carries none), `"apply"` the\n packages copied in once its tables exist.\n\nThis is the ONE thing on this page that needs the network: `check` reads the\npreset it names, once. Everything after that read is the same offline check.\n\nWrite the full form when no preset is the trade.\n\n## A complete model\n\n```json\n{\n "entities": [\n {\n "alias": "customer",\n "label": "Customers",\n "singular": "Customer",\n "fields": [\n { "alias": "name", "label": "Name", "type": "text", "required": true },\n { "alias": "logo", "label": "Logo", "type": "files" },\n {\n "alias": "tier",\n "label": "Tier",\n "type": "select",\n "options": [\n { "alias": "standard", "label": "Standard", "color": "slate" },\n { "alias": "gold", "label": "Gold", "color": "amber" }\n ],\n "default": ["standard"]\n },\n {\n "alias": "orders",\n "label": "Orders",\n "type": "select_record_link",\n "target_entity": "order",\n "cardinality": "many",\n "sync_both_ways": true,\n "paired_field_alias": "customer",\n "display_field_aliases": ["code"]\n },\n {\n "alias": "total_ordered",\n "label": "Total ordered",\n "type": "rollup",\n "source_field_alias": "orders",\n "aggregate_option": { "operation": "sum", "field_key": "amount" }\n }\n ],\n "views": [\n {\n "alias": "gold",\n "label": "Gold customers",\n "filters": {\n "node_type": "condition",\n "type": "select",\n "field_key": "tier",\n "operator": "has_any_of",\n "value": ["gold"]\n },\n "sort": [{ "field_key": "name", "order": "asc" }]\n }\n ]\n },\n {\n "alias": "order",\n "label": "Orders",\n "singular": "Order",\n "fields": [\n { "alias": "code", "label": "Order no.", "type": "text", "unique": true },\n { "alias": "placed_on", "label": "Placed on", "type": "date", "format": "date" },\n {\n "alias": "amount",\n "label": "Amount",\n "type": "number",\n "format": "currency",\n "currency": "VND"\n },\n {\n "alias": "total",\n "label": "Total with VAT",\n "type": "formula",\n "formula": { "expression": "{amount} * 1.1", "format": "currency", "currency": "VND" }\n },\n {\n "alias": "customer",\n "label": "Customer",\n "type": "select_record_link",\n "target_entity": "customer",\n "cardinality": "one",\n "sync_both_ways": true,\n "paired_field_alias": "orders",\n "display_field_aliases": ["name"]\n }\n ]\n }\n ],\n "roles": [{ "alias": "sales", "label": "Sales" }],\n "field_roles": {\n "customer": { "name": "identity", "logo": "mark" },\n "order": { "code": "identity", "placed_on": "when", "amount": "amount", "customer": "party" }\n },\n "apps": [\n {\n "alias": "customers",\n "name": "Customers",\n "screen": { "alias": "customers", "label": "Customers", "shape": "party_register", "entity": "customer" }\n },\n {\n "alias": "orders",\n "name": "Orders",\n "screen": { "alias": "orders", "label": "Orders", "shape": "transaction_ledger", "entity": "order" }\n }\n ],\n "rows": {\n "customer": [\n { "ref": "acme", "fields": { "name": "Acme Trading", "tier": "gold" } },\n { "ref": "bluebird", "fields": { "name": "Bluebird Foods", "tier": "standard" } }\n ],\n "order": [\n {\n "ref": "so_1001",\n "fields": {\n "code": "SO-1001",\n "placed_on": "@month-start+2",\n "amount": 4200000,\n "customer": "customer:acme"\n }\n },\n {\n "ref": "so_1002",\n "fields": {\n "code": "SO-1002",\n "placed_on": "@today-3",\n "amount": 1150000,\n "customer": "customer:bluebird"\n }\n }\n ]\n }\n}\n```\n\n`lotics scaffold check` on this file reports\n`2 tables, 10 fields, 2 links, 1 view, 1 role, 4 rows, 2 apps`, then\nthe plan:\n\n```\nCustomers\n Customers \u2014 party register over Customers (2 rows) \xB7 page (default) \xB7 tabs: none (default)\n identity Name \xB7 mark Logo \xB7 contact (none \u2014 no field declares "contact") \xB7 worth (none \u2014 no field declares "measure") \xB7 risk (none \u2014 no field declares "verdict")\n order: Name, A to Z (default) \xB7 presentation: leads with the mark (default), roomy (default), as a table (default) (of table, list, cards, gallery) \xB7 read (default: 30 rows a page) \xB7 period: none, every row (default) \xB7 operable: the record and its rows (default)\n anatomy: head none \xB7 brief nothing (default) \xB7 tabs Details (facts) \xB7 Orders (Orders [order]) (default)\n record: header (Name \xB7 Logo) \xB7 presentation: leads with the mark (default), roomy (default) \xB7 sections: every one the record derives (default) \xB7 history: Orders (latest 5 (default) of N by Placed on) (Order no. \xB7 Placed on \xB7 Amount (VND)), by year (default) \xB7 facts (Tier \xB7 Total ordered (VND))\nOrders\n Orders \u2014 transaction ledger over Orders (2 rows) \xB7 drawer (default) \xB7 tabs: none (default)\n when Placed on \xB7 amount Amount (VND) \xB7 reference Order no. \xB7 party Customer \xB7 classification (none \u2014 no field declares "lifecycle") \xB7 document (none \u2014 no field declares "expected_set" or "verdict")\n order: Placed on, newest first (default) \xB7 presentation: leads with the figure (default), roomy (default), as a table (default) (of table, list, calendar, timeline) \xB7 read (default: 30 rows a page) \xB7 period: none, every row (default) \xB7 operable: the record and its rows (default)\n anatomy: head none \xB7 brief nothing (default) \xB7 tabs Details (facts) (default)\n record: header (Order no. \xB7 Placed on \xB7 Amount (VND)) \xB7 presentation: leads with the figure (default), roomy (default) \xB7 facts (Total with VAT (VND) \xB7 Customer)\n party: Customers \u2014 opens from Customer (profile: header (Name \xB7 Logo) \xB7 presentation: leads with the mark (default), roomy (default) \xB7 sections: every one the record derives (default) \xB7 history: Orders (latest 5 (default) of N by Placed on) (Order no. \xB7 Placed on \xB7 Amount (VND)), by year (default) \u2014 foot: the whole register, narrowed to this record \xB7 facts (Tier \xB7 Total ordered (VND)))\nWho writes what:\n Customers (customer, one: Customer): Customers \xB7 written from the rows of Orders: facts\n Name: required\n asks: every field a person states (default)\n Logo: attached at create \u2014 the row\'s own face, and the only pile a draft asks for\n Orders (order, one: Order): Orders\n asks: every field a person states (default)\nWhat the rows would show:\n rows.customer.logo: no row carries a picture \u2014 every register over it shows no mark\n```\n\nA `party:` line is the record a NAME on a row opens. A party is not a job, so it\ngets no app of its own \u2014 but a clerk reading the orders book presses the\ncustomer and corrects the phone number there, so the app whose rows name a party\ncarries that party\'s own record beside its register\'s. It is printed in the same\ngrammar the `record:` line is, because it is the same kind of surface: a profile,\non a page, its histories capped. Which cells are the door is what `opens from`\nsays \u2014 `the name` where the register\'s subject IS the party.\n\nThe block before the last is the WRITERS matrix \u2014 one line per record surface the plan\nleaves operable, the word one of its rows is called, and the app whose register\nopens it. Part of the verdict rather than a diagnostic beside it: which desk\nchanges a table is answerable from the plan, and two apps on one line is the\nsplit to state in each of their `package.json#lotics.writes`. A\n`written from the rows of \u2026` clause is the other reach: the party that register\nnames, and what the app can change on it \u2014 counted apart, because several\nregisters naming one account is the model working as designed rather than a\nsplit to declare. A publish desk\'s rows get a clause of their own, because the\npress that withdraws one DELETES it \u2014 the app declares that table in\n`package.json#lotics.deletes`, and `lotics app check` refuses a body that deletes\nfrom a table it does not name. Indented under each entity are the clauses only a\ncreate meets: the cells a row cannot stand without, what its draft asks for, and\nwhat `write_rules` state.\nThe last block is what the first `rows` would show: no customer carries a logo\nyet, so every register over them draws initials where the picture goes.\n\nEvery `(none)` is a slot no field fills, and it says WHY: no field declares the\nrole, or several could and the plan named none of them (`(none \u2014 a, b; name\none)`). Read it as the screen a person will see. The customer\'s mark is the\nparty\'s face, so the header takes it; the customer\'s `history:` is the orders\nthat name it as their party, and **a link is a section, not a fact**:\n`Customers.Orders` leaves the customer\'s facts because the section lists the same\nrelation. Both records state their name in the header and nowhere else, and\n`Total ordered (VND)` is the rollup inheriting the currency of the column it\nsums: money is read off the model, never off the field\'s own line.\n';
|
|
55379
55379
|
|
|
55380
55380
|
// src/docs_command.ts
|
|
55381
55381
|
var READING_ORDER = ["@lotics/app-runtime", "@lotics/ui", "@lotics/cli"];
|
|
@@ -58176,18 +58176,34 @@ function recordAnatomy(screen, entries2, roles) {
|
|
|
58176
58176
|
});
|
|
58177
58177
|
return [];
|
|
58178
58178
|
});
|
|
58179
|
-
const
|
|
58180
|
-
|
|
58181
|
-
const
|
|
58179
|
+
const stated3 = clause?.brief;
|
|
58180
|
+
const briefed = stated3 === void 0 ? derivedBrief(parts, primary) : named3(stated3, `${path40}.brief`);
|
|
58181
|
+
for (const alias of findDuplicates(stated3 ?? [])) refused.push({ severity: "error", path: `${path40}.brief`, message: `names "${alias}" twice` });
|
|
58182
|
+
const whole = new Set(briefed.filter((part) => !briefIsPartial(screen, part)));
|
|
58183
|
+
const said = (part) => anatomyAliases(part)[0] ?? (part.kind === "band" && part.group !== void 0 ? `the band "${part.group.caption}"` : "the unnamed facts");
|
|
58184
|
+
const once = (tabs2) => {
|
|
58185
|
+
const open = new Set(tabs2[0]?.parts ?? []);
|
|
58186
|
+
if (stated3 === void 0) return briefed.filter((part) => !open.has(part));
|
|
58187
|
+
for (const part of briefed) {
|
|
58188
|
+
if (!open.has(part) || whole.has(part)) continue;
|
|
58189
|
+
refused.push({
|
|
58190
|
+
severity: "error",
|
|
58191
|
+
path: `${path40}.brief`,
|
|
58192
|
+
message: `names ${said(part)}, which the first tab draws too \u2014 that tab is open at rest, so the record would draw it twice; drop it from the brief${clause?.tabs === void 0 ? "" : " or place it in a later tab"}`
|
|
58193
|
+
});
|
|
58194
|
+
}
|
|
58195
|
+
return briefed;
|
|
58196
|
+
};
|
|
58182
58197
|
if (clause?.tabs === void 0) {
|
|
58183
|
-
|
|
58198
|
+
const tabs2 = derivedTabs(screen, roles, parts.filter((part) => !whole.has(part)), primary);
|
|
58199
|
+
return { anatomy: { primary, brief: once(tabs2), tabs: tabs2 }, refused };
|
|
58184
58200
|
}
|
|
58185
58201
|
const tabs = clause.tabs.map((tab, index) => ({ label: { role: "stated", text: tab.label }, parts: named3(tab.of, `${path40}.tabs.${index}.of`) }));
|
|
58186
58202
|
for (const label of findDuplicates(clause.tabs.map((tab) => tab.label))) {
|
|
58187
58203
|
refused.push({ severity: "error", path: `${path40}.tabs`, message: `two tabs are both called "${label}" \u2014 a tab strip reads one label per tab` });
|
|
58188
58204
|
}
|
|
58189
58205
|
const tabbed = tabs.flatMap((tab) => tab.parts);
|
|
58190
|
-
const
|
|
58206
|
+
const brief = once(tabs);
|
|
58191
58207
|
for (const part of parts) {
|
|
58192
58208
|
const times = tabbed.filter((one) => one === part).length;
|
|
58193
58209
|
if (times > 1) refused.push({ severity: "error", path: `${path40}.tabs`, message: `places ${said(part)} in two tabs \u2014 a part of the record is read in one place` });
|
|
@@ -82700,7 +82716,12 @@ var PROBES = {
|
|
|
82700
82716
|
brief_repeats: {
|
|
82701
82717
|
law: "hierarchy.md",
|
|
82702
82718
|
section: "One fact, one place",
|
|
82703
|
-
fix: "Take
|
|
82719
|
+
fix: "Take it out of the brief: the head already states the value, or the tab open at rest already draws the part, so the brief says it a second time."
|
|
82720
|
+
},
|
|
82721
|
+
act_repeats: {
|
|
82722
|
+
law: "hierarchy.md",
|
|
82723
|
+
section: "One fact, one place",
|
|
82724
|
+
fix: "Draw the head's act in its queue's card by its words and outcomes alone (`ActCard` `carried`): its verb, due day and refusal are the head's."
|
|
82704
82725
|
},
|
|
82705
82726
|
empty_tab: {
|
|
82706
82727
|
law: "hierarchy.md",
|