@lotics/cli 0.245.0 → 0.247.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/docs/migration.md CHANGED
@@ -4,63 +4,59 @@ What an app author has to DO when a release changes the shape of a project the C
4
4
  other contract is `docs/cli_reference.md`; this file is the one a reader opens once, because
5
5
  something already on disk no longer matches what the CLI writes.
6
6
 
7
- ## Apps built from a plan are JSON
8
-
9
- `lotics app create --from <model.json>#<app>` used to emit ~30 TSX files per app — `src/App.tsx`, a
10
- screen per plan entry, the record each row opened, a dialog per create, `src/screens/cells.ts` — and
11
- `lotics app regenerate` folded a later generation into them through a three-way merge over a base
12
- recorded in `.lotics/generated/files/`.
13
-
14
- It writes **`app.json`**: the bound plan, whole. Beside it, the `src/main.tsx` that
15
- mounts `@lotics/app-runtime` over the spec, `src/components/index.ts` seeded empty, and the
16
- `src/workflows/<alias>.ts` bodies, unchanged. `@lotics/app-runtime` draws the spec, so a kit
17
- correction reaches the app with its next `npm install` rather than with a regeneration — which is
18
- the whole point of the change.
19
-
20
- **A TSX app keeps working.** It never depended on the generator at run time, and nothing in this
21
- release touches a project that has no `app.json`. What it loses is the fold: `app regenerate` no
22
- longer writes screens, so an app built before this release has two ways forward.
23
-
24
- ### Moving a generated app to the JSON form
25
-
26
- 1. `lotics app regenerate` in the app directory. It writes `app.json`, `src/main.tsx`,
27
- `src/components/index.ts` and the workflow bodies, and leaves everything else where it is.
28
- 2. Delete what the generator no longer emits — `src/App.tsx`, `src/screens/`, and
29
- `.lotics/generated/files/` (the merge base; the alias record in
30
- `.lotics/generated/manifest.json` STAYS, and is what tells the next run which aliases were the
31
- generator's). Anything of your own that lived under `src/screens/` moves to `src/components/`
32
- and is registered there.
33
- 3. `npm install @lotics/app-runtime`, then `lotics app check` and `lotics app deploy`.
34
-
35
- Keeping the TSX instead is also a decision the CLI honours: delete `package.json#lotics.plan` and
36
- the app is a hand-written project like any other. `app regenerate` then refuses, which is the
37
- correct answer for a tree nothing generates.
38
-
39
- ### What changed in `app regenerate`
40
-
41
- - **No three-way merge, and no conflict markers.** The generator owns every file it emits.
42
- `app.json` is derived whole; a workflow body is rewritten and what it replaced is parked in
43
- `.lotics/regenerate-dropped.patch`, file by file.
44
- - **`src/components/` is the one exception** — seeded where it is absent, named back where you have
45
- changed it, never rewritten.
46
- - **`.lotics/generated/files/` is gone.** Only the alias record remains. A tree still holding those
47
- `.base` copies is harmless; delete it.
48
- - The summary counts `written / kept / deleted` rather than `written / merged / conflicted / kept /
49
- deleted`, and `lotics workspace build`'s per-app line counts files rewritten.
50
-
51
- ### What `lotics app check` runs on a JSON app
52
-
53
- Everything it ran before, plus the spec itself: a surface reading a column its own query never
54
- projects, a read no alias declares, an act running a workflow nothing declares, a create sending an
55
- input its body has no branch for, and a component the spec names that `src/components/` does not
56
- export. A JSON app has no TSX for a compiler to refuse, so every mistake the typecheck used to
57
- catch is caught there instead.
7
+ ## Apps built from a model: `records` and `apps`, `app.json` version 2
8
+
9
+ A model states an app in one vocabulary: `records` says how a row of each entity is recognised (its
10
+ title, the line under it, its picture, its status, its figure, its deadlines), and each entry of
11
+ `apps` is one register over one entity and the record its rows open — its columns and filters, its
12
+ fact groups and blocks, its acts and its checks (`lotics docs model/apps`). How each piece looks is
13
+ the runtime's; no key changes it. `lotics app create --from` compiles that into `app.json`
14
+ **version 2**, and `@lotics/app-runtime` reads version 2 alone: an older spec stops at its first
15
+ render with `app.json is version 1; this runtime reads version 2`.
16
+
17
+ What a model no longer holds, each refused by name by `lotics scaffold check`:
18
+
19
+ - **`field_roles`, screen shapes and their clauses** — `records` and `apps` state what they stated.
20
+ - **`table_workflows`** — a model keeps no table automation. A status `history` is appended by the
21
+ app's own generated write that moves the status.
22
+ - **`connections`** — nothing in a model pushes through an account any more.
23
+
24
+ ### Moving an app generated from an earlier model
25
+
26
+ 1. **Restate the model.** Replace `field_roles` with a `records` entry per entity the app lists,
27
+ opens or picks, and each app with its register, record, acts and checks. `lotics scaffold check`
28
+ prints every app as its reader will see it; `lotics app preview <model.json>#<app>` draws it.
29
+ 2. **`lotics app regenerate`** in the app directory. It rewrites `app.json`, `src/main.tsx` and the
30
+ generated workflow bodies. The generated aliases are new (`<entity>_list`, `create_<entity>`,
31
+ `act_<alias>`, …), so the old ones are retired: their bodies are deleted and the next
32
+ `lotics app deploy` unbinds them.
33
+ 3. **A component the app took over keeps its file, and loses what it wrapped.** The runtime exports
34
+ no screen, section or act renderer, and `lotics app eject` is gone. A part the vocabulary now
35
+ states goes back into the model; a record part it cannot state is a `component` block (the
36
+ component is handed `{ record, spec, refresh }` — `lotics docs components`); an act whose write
37
+ `set` cannot say names its own `workflow`, which runs after the generated guards.
38
+ 4. **Remove the history automations an earlier `scaffold apply` installed.** They are not deleted
39
+ by anything, and beside the app's own history append every move would write two rows.
40
+ `lotics run remove_table_workflow` takes each one off its table.
41
+ 5. `lotics app check`, then `lotics app deploy`.
42
+
43
+ ### What `app regenerate` owns
44
+
45
+ - **No three-way merge, and no conflict markers.** The generator owns every file it emits:
46
+ `app.json` is compiled whole; a generated workflow body is rewritten and what it replaced is parked
47
+ in `.lotics/regenerate-dropped.patch`, file by file.
48
+ - **Two things are yours**: `src/components/` (seeded where it is absent, named back where you have
49
+ changed it, never rewritten), and an act's own `workflow`, of which only the guard region between
50
+ its `<lotics:guards>` markers is rewritten.
51
+ - A TSX app with no `app.json` is untouched by all of this. Delete `package.json#lotics.plan` and it
52
+ is a hand-written project like any other; `app regenerate` then refuses, which is the correct
53
+ answer for a tree nothing generates.
58
54
 
59
55
  ## The SDK is part of the runtime
60
56
 
61
57
  `@lotics/app-sdk` moved into `@lotics/app-runtime`. One package carries the hooks, the router and
62
58
  the renderer, so an app lists one range and a kit correction cannot reach the renderer without
63
- reaching the hooks it reads through. An app changes three things:
59
+ reaching the hooks it reads through. An app changes two things:
64
60
 
65
61
  - **The dependency.** `@lotics/app-runtime` replaces `@lotics/app-sdk`. `@lotics/ui` stays beside it
66
62
  only where the app's own code imports the kit, at the range the runtime depends on: two ranges
@@ -68,13 +64,8 @@ reaching the hooks it reads through. An app changes three things:
68
64
  - **The import specifiers.** `@lotics/app-sdk` becomes `@lotics/app-runtime/sdk`, and
69
65
  `@lotics/app-sdk/router` becomes `@lotics/app-runtime/router` — in `src/`, and in `vite.config.ts`,
70
66
  whose test optimizer names the SDK in a scaffolded app.
71
- - **`useOptimistic` is gone.** Every settled write re-reads what the screen holds, and a call that
72
- knows its outcome passes it to `useWorkflow` as a `RowChange` (`lotics docs mutations`), which is
73
- drawn from the press and put back on a refusal. A list that patched its own items through
74
- `useOptimistic` passes that change instead.
75
67
 
76
68
  `lotics app kit --published` makes the first two: it rewrites every quoted specifier, drops the
77
69
  dependency, installs the runtime and names each file it rewrote. `lotics app regenerate` does the
78
70
  same, and so does `lotics workspace build` for every app it regenerates; `lotics app check` names
79
- every app still listing `@lotics/app-sdk`. The third is the app's own edit — the typecheck names each call
80
- site. A deployed app keeps running on the bundle it shipped until it is rebuilt.
71
+ every app still listing `@lotics/app-sdk`. A deployed app keeps running on the bundle it shipped until it is rebuilt.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.245.0",
3
+ "version": "0.247.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {
package/docs/clauses.md DELETED
@@ -1,135 +0,0 @@
1
- # Plan clauses
2
-
3
- Every clause an app in a model's `apps` can state, what it draws, and an example
4
- value. `[]` is every element of a list or a map; `as` is one form a key takes.
5
- Each clause's rules are `lotics docs model/apps-and-screens`.
6
-
7
- `lotics app preview <model.json>#<app> --shots <dir>` renders an app stating any
8
- of them, over the rows of your own model, before a table exists.
9
-
10
- ## On the app
11
-
12
- | Clause | Draws | Example |
13
- |---|---|---|
14
- | `alias` | Stable app alias, unique within the contract | `"vans"` |
15
- | `name` | What the app is called wherever its members open it | `"Vans"` |
16
- | `description` | What the app is for, in a sentence shown beside its name | `"What is on the shelf, against what has to be."` |
17
- | `icon` | A Lucide icon name, kebab-case | `"users"` |
18
- | `theme` | The app's colour | `{"color":"sky"}` |
19
- | `theme.color` | Theme color for the app | `"sky"` |
20
- | `scope` | Narrow every read of this app to one row of an entity, the pick shared with every app that names it | `{"entity":"order","param":"order_id"}` |
21
- | `reads` | "shared": this app's queries and writes carry no entity's read_scope — whoever the app is shared with reads and writes every row it draws. Absent, each read_scope binds the viewer. | `"shared"` |
22
-
23
- ## On its screen
24
-
25
- | Clause | Draws | Example |
26
- |---|---|---|
27
- | `alias` | Stable screen alias, unique within the app | `"vans"` |
28
- | `label` | What the screen is called in the app | `"Vans"` |
29
- | `shape` | A shape, or "custom" with its own `roles` | `"custom"` |
30
- | `entity` | The entity whose rows this screen is over | `"van"` |
31
- | `record` | How one record opens from the list — "drawer", "page", or "expand" to reveal it in the row — or `{"door", "brief", "tabs"}` placing its sections too; absent, the shape decides the door and the roles the rest | `"expand"` |
32
- | `record as Record clause` | The door, and which parts of the record stand at rest under its head and which are one press away | `{"door":"page","brief":["posting"],"tabs":[{"label":"Filing","of":["filed"]}]}` |
33
- | `record.door` | "drawer", "page", or "expand"; absent, the shape decides | `"page"` |
34
- | `record.brief` | What stands under the head at rest, in this order — a log by its newest entry, a queue by its next act's words; absent, derived from the roles | `["posting"]` |
35
- | `record.tabs` | The tabs, in this order — every section and band not wholly in the brief is in one; absent, derived from the roles | `[{"label":"Filing","of":["filed"]}]` |
36
- | `tabs` | 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 — 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 | `"run"` |
37
- | `slots` | Slot → the field that fills it, where roles alone cannot decide; null leaves an optional slot unbound | `{"contact":"phone"}` |
38
- | `slots[] as list of alias (at least 2)` | The tiers this slot folds by, outermost first — or the measures one level column stacks, each against its own limit | `["load","cube"]` |
39
- | `slots[] as Quick slot` | The field AND the reader's own control for it, drawn as the column | `{"field":"hours","quick":true}` |
40
- | `slots[].order` | A lifecycle whose stages are a WALK: the cell advances to the next one rather than offering them all | `"sequence"` |
41
- | `roles` | For "custom" only: slot → the role that fills it | `{"identity":"identity","scan":"mark","filed":"when"}` |
42
- | `columns` | Extra fields drawn after the slots, in this order — 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 | `["headroom"]` |
43
- | `columns[] as Chip column` | A related row's category drawn as ONE chip, with how long ago it was and how many others stand behind it | `{"field":"latest_kind","age":"latest_seen","more":"enquiry_count"}` |
44
- | `columns[].age` | A date of this entity dating the row the chip's ordered lookup picks — a `latest`/`earliest` rollup of the order's date, or a lookup ordered the same way — drawn after the chip as how long ago it was | `"latest_seen"` |
45
- | `columns[].more` | A count rollup of this entity over the SAME link — drawn beside the chip as how many OTHER rows there are | `"enquiry_count"` |
46
- | `sources` | Other entities whose rows this register reads beside its own, in ONE read sorted by its order — 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 | `["consigned_item"]` |
47
- | `create` | The fields a new row is asked for, in this order — 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 | `["name","company","page","phone"]` |
48
- | `writes` | false makes this screen's record read-only — 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 | `false` |
49
- | `acts` | The papers this register makes — from one row, from a ticked set, and from the whole view | `{"row":[{"kind":"agent","label":"Find the contact","agent":"contact_finder","fills":["phone","email"]}],"expo…` |
50
- | `acts.row` | The acts in every row's ⋯ menu, in this order | `[{"kind":"sibling","label":"Open the order","app":"orders","record":"order"}]` |
51
- | `acts.row[] as Agent act` | A run of an app agent over the row, whose proposed values the reader reviews before one write Also `acts.record[] as Agent act`, `acts.selection[] as Agent act`, `section_acts[][] as Agent act`. | `{"kind":"agent","label":"Find the contact","agent":"contact_finder","fills":["phone","email"]}` |
52
- | `acts.row[].place` | Draw this act ON the row rather than in its ⋯ menu — at most one per screen | `"cta"` |
53
- | `acts.row[].when` | Offer this act only while the row stands at one of these stages | `{"field":"state","in":["ready"]}` |
54
- | `acts.row[].needs` | Fields of this entity — the act is offered only while at least one of them holds a value | `["name"]` |
55
- | `acts.row[].confirm` | Ask before this act runs, with its own label as the commit word — for a press that cannot be taken back | `true` |
56
- | `acts.row[] as Workflow act` | The row handed to a workflow the app binds, run on the press Also `acts.record[] as Workflow act`, `acts.selection[] as Workflow act`, `section_acts[][] as Workflow act`. | `{"kind":"workflow","label":"Hand to despatch","workflow":"hand_to_despatch","inputs":{"order_id":"record"},"w…` |
57
- | `acts.row[].asks` | What the reader states in the act's panel before it runs: the workflow's own input name → the field whose editor asks it | `{"owner":"owner"}` |
58
- | `acts.row[].moves` | The stage of this entity's lifecycle the body lands the row at — the act's alone: no stage picker, ladder or board of this app moves a row there | `"converted"` |
59
- | `acts.row[].sets` | The fields of this entity the body writes — read-only on the record where no draft, closing step, outcome, agent or act panel of this app writes them | `["customer","converted_at"]` |
60
- | `acts.row[] as Sibling act` | A record of this row opened in a sibling app of this plan, in place Also `acts.record[] as Sibling act`, `acts.selection[] as Sibling act`, `section_acts[][] as Sibling act`. | `{"kind":"sibling","label":"Open the order","app":"orders","record":"order"}` |
61
- | `acts.row[] as Recording act` | A call, a visit, a walk-through recorded onto the row and filed through a workflow once transcribed Also `acts.record[] as Recording act`, `acts.selection[] as Recording act`, `section_acts[][] as Recording act`. | `{"kind":"recording","label":"Record a call","workflow":"log_call","inputs":{"prospect_id":"record"}}` |
62
- | `acts.row[] as Paper act` | A paper made from a template this model declares — from the row, or from every ticked row at once Also `acts.record[] as Paper act`, `acts.selection[] as Paper act`, `section_acts[][] as Paper act`. | `{"label":"Chase the papers","template":"chaser"}` |
63
- | `acts.record` | The acts in the RECORD's own header menu, in this order — the same reach as a row's, with the work open | `[{"label":"Print the order file","template":"order_file"}]` |
64
- | `acts.selection` | The acts over the TICKED rows — each a `template`, generating ONE document over the set | `[{"label":"Remittance advice","template":"remittance"}]` |
65
- | `acts.export` | Save the rows in view — true for a workbook of the drawn columns, or a template this model declares | `true` |
66
- | `acts.export as Export template` | The rows in view as a paper laid out by the trade's own template | `{"template":"customer_book"}` |
67
- | `acts.import` | Turn a file into rows — mapped, validated per row, previewed, then upserted by the entity's natural key | `{"kind":"import","label":"Import the statement","entity":"payment","key":"reference","columns":["date","amoun…` |
68
- | `acts.import.columns` | The fields the file may fill, in this order — absent, every field a person states | `["date","amount","direction","state"]` |
69
- | `section_acts` | Acts on the RECORD's own sections, keyed by the alias each section is derived from — 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) | `{"document":[{"label":"Chase the papers","template":"chaser"}]}` |
70
- | `sections` | The RECORD's sections, drawn in exactly this order — 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>, …}` 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 | `[]` |
71
- | `sections[] as alias` | A section drawn as the roles derive it — the alias it is derived from | `"stage"` |
72
- | `sections[] as Worksheet section` | Priced lines the reader works down in place, each part footed and the sheet closing under them | `{"of":"order_line","draw":"worksheet","cost":"line_cost","sell":"line_total"}` |
73
- | `sections[].heading` | What this section is headed on the record; absent, the child entity's own label | `"Touches"` |
74
- | `sections[].facts` | Fields of THIS record drawn at the head of the section, above its rows — filed here, and so in no band of the record's facts | `["outstanding"]` |
75
- | `sections[].cost` | The child's field holding what a line COSTS — the base the margin is taken against | `"line_cost"` |
76
- | `sections[].sell` | The child's field holding what it SELLS for — the figure the sheet is read for | `"line_total"` |
77
- | `sections[] as Ledger section` | A book of movements — closes on its total, read against the record's rollup that sums it | `{"of":"payment","draw":"ledger","facts":["outstanding"],"columns":["date","amount","reference"]}` |
78
- | `sections[].columns` | The child's fields this register draws as its columns, in this order — at most 4, each one its role or its type draws as a column; absent, the child's roles decide | `["date","amount","reference"]` |
79
- | `sections[] as Itinerary section` | A run of stops read a day at a time — stated to place the child's fields on the stop where the roles' placement is not the reading | `{"of":"order_line","draw":"itinerary","lines":[["window","fitting_hours","buyer"],["line_total"]],"badge":"st…` |
80
- | `sections[].name` | The child's field that NAMES the entry and leads it; absent, its `identity` | `"subject"` |
81
- | `sections[].words` | The child's text field the entry SAYS; absent, its `body`, else (on a log) its first markdown text | `"words"` |
82
- | `sections[].lines` | 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 | `[["with_party"]]` |
83
- | `sections[].badge` | The child's select drawn as the entry's status at the right — a lifecycle or a category; absent, a stop's lifecycle, and nothing on a log | `"status"` |
84
- | `sections[].figure` | The child's number worn at the trailing end of the entry's head; absent, a log's amount, and nothing on a stop | `"hours_open"` |
85
- | `sections[].evidence` | The child's fields drawn under the words as what the entry shows, a playable file played; absent, its `recording` | `["photos"]` |
86
- | `sections[].detail` | The child's fields kept behind one fold under the words; absent, its `verbatim` | `["desk_note"]` |
87
- | `sections[] as Claim section` | Lines claimed against a priced schedule, one period at a time — the contract, before, now, to date and what is left | `{"of":"claim_line","draw":"claim","quantity":"now","contract":"contract_qty","price":"price","previous":"befo…` |
88
- | `sections[].unit` | The child's field naming what the line is counted in | `"unit"` |
89
- | `sections[].retention` | A percentage field of THIS record — the share held back from what is claimed | `"retention"` |
90
- | `sections[] as Tree section` | The rows nested under each other, each money figure summed up the tree | `{"of":"claim_line","draw":"tree","nest":"part_of"}` |
91
- | `sections[] as Gantt section` | The rows as bars across the calendar — from each row's `when`, for the days its measure counts | `{"of":"works_task","draw":"gantt","until":"finishes","nest":"part_of","depends_on":"after","baseline":{"start…` |
92
- | `sections[].until` | The child's date field each bar is drawn TO; absent, a bar runs for the days its measure counts | `"finishes"` |
93
- | `sections[].nest` | The child's one-row link to its own entity — folds a row's bars under the row it sits under | `"part_of"` |
94
- | `sections[].depends_on` | The child's link to its own entity naming the rows that must finish before a row starts | `"after"` |
95
- | `sections[].baseline` | The child's date fields each row was PLANNED to start and end on, drawn under its bar | `{"start":"planned_start","end":"planned_end"}` |
96
- | `sections[] as Curve section` | Two figures of the rows, each added up to date along the rows' `when` and drawn as two lines | `{"of":"works_task","draw":"curve","planned":"planned_value","actual":"earned_value"}` |
97
- | `sections[] as Log section` | Dated entries read in the words each states — stated to place the child's fields on the entry where the roles' placement is not the reading | `{"of":"order_message","draw":"log","name":"subject","words":"message","evidence":["photos"],"detail":["desk_n…` |
98
- | `sections[] as Publish section` | One row per destination this record stands on — 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` | `{"of":"posting","draw":"publish","states":{"queued":"queued","published":"out","failed":"refused","by_hand":"…` |
99
- | `sections[].text` | A plain text on the child — what this one destination goes out with instead of the body | `"wording"` |
100
- | `sections[].permalink` | A link-formatted text on the child — where the post landed | `"address"` |
101
- | `sections[].error` | A text on the child holding the platform's own refusal | `"fault"` |
102
- | `sections[].link` | A link-formatted text of THIS entity — the address the post carries as its card | `"source"` |
103
- | `sections[].when` | Offer the desk's Publish only while THIS record stands at one of these stages | `{"field":"state","in":["ready"]}` |
104
- | `sections[] as Acts section` | Acts waiting for a person — the words, where to reach them, and the sheet that records what came of it | `{"of":"follow_up","draw":"acts","heading":"Next steps","states":{"queued":"waiting","done":"done","skipped":"…` |
105
- | `sections[].why` | A text on the child — why this is worth doing now | `"why_now"` |
106
- | `sections[].reason` | A text or a single select on the child — why it was passed over | `"pass_reason"` |
107
- | `sections[].reach` | Verb option → where that act is done; a verb this map leaves out is done wherever the reader already is | `{"reply":{"at":"enquiry.source","by":"link"},"message":{"at":"page","by":"link"},"call":{"at":"phone","by":"p…` |
108
- | `sections[].withhold` | A yes/no of THIS entity — while it reads yes, no act reaches out, and the reach verb says why | `"quiet"` |
109
- | `sections[].cta` | The record's NEXT waiting act leads its head and is worn by its register row as the verb — the one press that reaches the person; one queue of a record carries it | `true` |
110
- | `sections[] as Section heading` | Rename a section of the rows this record owns, head it with the record's facts that frame them, or name its columns, however the roles draw it | `{"of":"conversation","heading":"Touches"}` |
111
- | `filters` | The chips beside the search — a single-select or select_member field's alias, `{"field": …, "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 | `["kind"]` |
112
- | `filters[] as Lens` | A lens the MODEL states as predicates over the entity's fields, where no select holds the answer | `{"label":"Shelf","predicates":[{"label":"Empty","tone":"rose","where":{"node_type":"group","logic":"and","chi…` |
113
- | `filters[].sort` | The order the rows are read in while the lens is on | `{"field":"next_due","order":"asc"}` |
114
- | `filters[].sort.order` | "asc" (the default) puts the soonest or smallest first; a row holding none goes last | `"asc"` |
115
- | `filters[] as Member lens` | A member field's lens, opened on the reader's own rows | `{"field":"handled_by","default":"mine"}` |
116
- | `facts` | How the record's facts are BANDED — where the aside's fields group under captions | `{"groups":[{"caption":"Where it came from","alias":"filed","fields":["source","sent_on"]}]}` |
117
- | `ladder` | 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 | `true` |
118
- | `summary` | What the rows in view come to, stated before or after their names | `{"above":"counts"}` |
119
- | `summary.totals` | Number fields the whole view adds up — under the column that draws one, else in a band beneath the register | `["lines"]` |
120
- | `summary.above` | A band of figures over the register: "counts" for how many rows and how many need attention, or number fields — each added up over the window, or `{"field": …, "at": "end"}` for a stock STANDING at its end | `"counts"` |
121
- | `summary.above[] as Stock` | A stock standing at the end of the window, beside the sums over it | `{"field":"in_yard","at":"end"}` |
122
- | `summary.ageing` | How old the money over this register is — the amount split by how many days past its date each row is | `{"amount":"outstanding","due":"due_date","buckets":[14,30,60]}` |
123
- | `summary.ageing.buckets` | The bucket edges, in days past due, ascending — absent, 30, 60, 90 | `[14,30,60]` |
124
- | `summary.trend` | Which way this figure moved across the window the reader is reading — needs a `period` to be a window of | `{"field":"amount","direction":"up"}` |
125
- | `summary.curve` | Planned against actual, each added up to date over the rows' dates and drawn as two lines — read along the `period`, else the `when` | `{"planned":"planned_value","actual":"earned_value"}` |
126
- | `period` | A date field on the entity the reader narrows the view by; absent, the view is every row | `"day"` |
127
- | `presentation` | How this screen and the record it opens are DRAWN, where the shape's own answer is not the one wanted | `{"lead":"none"}` |
128
- | `presentation.lead` | What each row leads with; absent, what the shape's rows are decides | `"none"` |
129
- | `presentation.density` | How many lines a row's subject may take; absent, what the shape's rows are decides | `"dense"` |
130
- | `presentation.layout` | How the rows are arranged; absent, a table | `"gantt"` |
131
- | `presentation.line` | The register row's supporting line, in this order — at most 2 fields of this entity, each a plain text or a single select; absent, the key the row is filed under | `["company","role"]` |
132
- | `presentation.until` | For a gantt: the date field each bar is drawn TO; absent, the bar runs for the days its measure counts | `"due_back"` |
133
- | `presentation.nest` | A one-row link from this entity to itself — the row each row sits under; affords `layout: "tree"` and nests a gantt's bars | `"part_of"` |
134
- | `presentation.depends_on` | For a gantt: a link from this entity to itself naming the rows that must finish before a row starts | `"after"` |
135
- | `presentation.baseline` | For a gantt: the date fields the row was PLANNED to start and end on, drawn under its bar | `{"start":"planned_start","end":"planned_end"}` |