@lotics/cli 0.315.17 → 0.316.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/AGENTS.md +37 -24
- package/README.md +42 -37
- package/dist/src/cli.js +27215 -48999
- package/dist/src/client.d.ts +12 -88
- package/dist/src/client.js +10 -74
- package/dist/src/invocation.d.ts +1 -1
- package/docs/agent_surface.md +121 -114
- package/docs/app_architecture.md +39 -0
- package/docs/app_bindings.md +24 -62
- package/docs/building_an_app.md +97 -81
- package/docs/cli_reference.md +18 -19
- package/docs/data_model.md +129 -183
- package/docs/design.md +156 -170
- package/docs/document_templates.md +125 -99
- package/docs/examples.md +1599 -1238
- package/docs/field_values.md +14 -11
- package/docs/filters.md +47 -9
- package/docs/first_build.md +180 -0
- package/docs/getting_started.md +15 -0
- package/docs/knowledge_docs.md +73 -209
- package/docs/migration.md +22 -26
- package/docs/workflow_steps.md +22 -579
- package/docs/workflows.md +407 -208
- package/docs/working_in_a_workspace.md +60 -0
- package/package.json +1 -1
package/docs/field_values.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Field values — what a write takes for each field type
|
|
2
2
|
|
|
3
|
-
The value `create_records` and `update_records` take for a field.
|
|
3
|
+
The value `create_records` and `update_records` take for a field. A field is named by its alias, as `get_table` shows it, and a select option the same way. `dry_run: true` on `create_records`, `update_records` and `delete_records` runs the write in a transaction that is rolled back: every check, rule and stage owner is judged, no number is consumed, and the answer says what would be written or why it is refused. Every write meets the table's rules (`model/write-rules`): a refusal lists every broken rule at once, and a locked record answers under `blocked` in `update_records`' result.
|
|
4
4
|
|
|
5
5
|
## Values by type
|
|
6
6
|
|
|
@@ -13,23 +13,26 @@ The value `create_records` and `update_records` take for a field.
|
|
|
13
13
|
| `datetime` | a `date` of this format: `"2025-03-14T09:00"`, never a month or a year |
|
|
14
14
|
| `date_range` | a `date` of this format: `"2025-03-01/2025-03-15"`, both halves full dates |
|
|
15
15
|
| `datetime_range` | a `date` of this format: `"2025-03-01T09:00/2025-03-15T17:00"`, both halves full |
|
|
16
|
-
| `select` | `["
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
16
|
+
| `select` | `["open"]`, the option's alias |
|
|
17
|
+
| `member` | `["mbr_…"]` |
|
|
18
|
+
| `link` | `["rec_…"]` |
|
|
19
19
|
| `files` | `["fil_…"]`, ids of uploaded files |
|
|
20
20
|
| `formula`, `rollup`, `lookup`, `autonumber`, a `date` with `derive_from` | none — the platform writes them, and a write naming one is refused |
|
|
21
21
|
|
|
22
22
|
## Lists
|
|
23
23
|
|
|
24
|
-
`select`, `
|
|
24
|
+
`select`, `member`, `link` and `files` store an ARRAY, even where the field holds
|
|
25
25
|
one value.
|
|
26
26
|
|
|
27
|
-
- A single-select is a ONE-element array; a bare `"
|
|
28
|
-
`
|
|
29
|
-
- `
|
|
27
|
+
- A single-select is a ONE-element array; a bare `"open"` is accepted and wrapped. A single
|
|
28
|
+
`member` takes a bare `"mbr_…"` the same way.
|
|
29
|
+
- A single `link` takes a bare `"rec_…"` the same way; a multi `link` and `files` take an array only.
|
|
30
30
|
- A `files` id the write adds must name a live file of this workspace, or the whole write is refused;
|
|
31
31
|
an id that cell already holds stays, though its file was archived since.
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
|
|
32
|
+
- A file previews by its kind: an image zooms; a PDF scrolls, zooms sharp and turns by page, its text selectable; a DXF
|
|
33
|
+
drawing shows read-only in 2D with a toggle per layer. A DWG stays a stored file with no preview; attach its plotted
|
|
34
|
+
PDF beside it.
|
|
35
|
+
- Two options on a single-select, two members on a single `member`, or two records on a single `link`, are refused.
|
|
36
|
+
- `update_records`' `add_to`, `remove_from` and `replace` take arrays of the same items: option aliases
|
|
37
|
+
for a select, member ids for a `member`, record ids for a `link`, file ids for
|
|
35
38
|
`files`.
|
package/docs/filters.md
CHANGED
|
@@ -9,7 +9,7 @@ to an app's query.
|
|
|
9
9
|
A filter is a single condition, or a group of conditions.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
{ "node_type": "condition", "field_key": "
|
|
12
|
+
{ "node_type": "condition", "field_key": "status", "operator": "has_any_of", "value": ["done"] }
|
|
13
13
|
{ "node_type": "group", "logic": "and" | "or", "children": [ …conditions or groups ] }
|
|
14
14
|
```
|
|
15
15
|
|
|
@@ -24,7 +24,32 @@ Groups nest up to 4 levels. AND(OR(status = A, status = B), age > 40):
|
|
|
24
24
|
|
|
25
25
|
Every `field_key` resolves against the table being filtered — read each table's fields (`get_table`)
|
|
26
26
|
and carry no name across tables: sibling tables holding the same concept routinely name it
|
|
27
|
-
differently. A field is named by its `
|
|
27
|
+
differently. A field is named by its alias (`"status"`), as `get_table` shows it, and a select option the same way
|
|
28
|
+
(`"done"`). A traversal's `path` and the field of its condition are named the same way, each on the table the
|
|
29
|
+
path has reached.
|
|
30
|
+
|
|
31
|
+
## One family of operators
|
|
32
|
+
|
|
33
|
+
A condition may state these operators; each reads as the type's own below. Which field kinds take each:
|
|
34
|
+
|
|
35
|
+
| Operators | Field kinds | Meaning |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `is`, `is_not` | a field holding one value: text, number, date, yes/no (`is` only, or `is_not` with a yes/no value), a select or member that is not multi, a link of cardinality one | equal to one value; on a select, member or link, to the one option, member or record |
|
|
38
|
+
| `is_any_of`, `is_none_of` | text, number, date, select, member, link | equal to any of a list; on a select, member or link, `has_any_of` and `has_none_of` |
|
|
39
|
+
| `lt`, `lte`, `gt`, `gte` | number, date | below, at most, above, at least |
|
|
40
|
+
| `is_empty`, `is_not_empty` | every kind but yes/no | no value, or one |
|
|
41
|
+
|
|
42
|
+
`is` and `is_not` on a field that holds several options, members or records (a multi select, a many-link) are refused:
|
|
43
|
+
state `has_any_of`, `has_none_of` or `has_all_of`, which say whether the row must hold exactly them or just some.
|
|
44
|
+
A date is `"2026-10-15"` for that whole day, whatever time a value holds. An `is_any_of` or `is_none_of` on a number
|
|
45
|
+
or a date, and an `is_not` on a date, stand for a group of conditions, which a traversal's single condition cannot hold;
|
|
46
|
+
a list that a workflow computes is expanded when the step runs, so a saved workflow states it as it states any list.
|
|
47
|
+
The depth a filter may nest is the depth you write: the groups an operator stands for do not count.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
{ "node_type": "condition", "field_key": "due", "operator": "gte", "value": "2026-10-01" }
|
|
51
|
+
{ "node_type": "condition", "field_key": "status", "operator": "is_any_of", "value": ["done", "shipped"] }
|
|
52
|
+
```
|
|
28
53
|
|
|
29
54
|
## Text
|
|
30
55
|
|
|
@@ -36,7 +61,7 @@ differently. A field is named by its `fld_…` key or by its name (`"Status"`).
|
|
|
36
61
|
| `is_empty`, `is_not_empty` | none |
|
|
37
62
|
|
|
38
63
|
```
|
|
39
|
-
{ "node_type": "condition", "field_key": "
|
|
64
|
+
{ "node_type": "condition", "field_key": "title", "operator": "is_any_of", "value": ["hello", "world"] }
|
|
40
65
|
```
|
|
41
66
|
|
|
42
67
|
A text value is a string, never an array; `is_any_of` matches several. Its array has no length cap —
|
|
@@ -55,7 +80,7 @@ value is in.
|
|
|
55
80
|
Rows holding another option are out; measured units of one dimension convert.
|
|
56
81
|
|
|
57
82
|
```
|
|
58
|
-
{ "node_type": "condition", "field_key": "
|
|
83
|
+
{ "node_type": "condition", "field_key": "amount", "operator": "greater_than", "value": 100, "unit_option": "USD" }
|
|
59
84
|
```
|
|
60
85
|
|
|
61
86
|
## Boolean
|
|
@@ -66,11 +91,11 @@ Rows holding another option are out; measured units of one dimension convert.
|
|
|
66
91
|
|
|
67
92
|
| Operators | Value |
|
|
68
93
|
|---|---|
|
|
69
|
-
| `has_any_of` (any of them — "A or B"), `has_none_of`, `has_all_of` (a multi-select only) | an ARRAY of option
|
|
94
|
+
| `has_any_of` (any of them — "A or B"), `has_none_of`, `has_all_of` (a multi-select only) | an ARRAY of option aliases, even for one |
|
|
70
95
|
| `is_empty`, `is_not_empty` | none |
|
|
71
96
|
|
|
72
97
|
```
|
|
73
|
-
{ "node_type": "condition", "field_key": "
|
|
98
|
+
{ "node_type": "condition", "field_key": "status", "operator": "has_any_of", "value": ["done", "in_progress"] }
|
|
74
99
|
```
|
|
75
100
|
|
|
76
101
|
## Member
|
|
@@ -90,7 +115,11 @@ Rows holding another option are out; measured units of one dimension convert.
|
|
|
90
115
|
| `duration_equals`, `duration_greater_than`, `duration_less_than` | `{ amount, unit: "minutes" \| "hours" \| "days" }` |
|
|
91
116
|
| `is_empty`, `is_not_empty` | none |
|
|
92
117
|
|
|
93
|
-
A point is
|
|
118
|
+
A date value is a point. A bare `"YYYY-MM-DD"` is the point of that whole day (`"YYYY-MM-DDTHH:mm"` of that minute),
|
|
119
|
+
and a stated date is compared as written, with no timezone shift: `on "2026-10-15"` matches every value from 00:00 to
|
|
120
|
+
23:59 of that day, on a date or a datetime field. `before` the day is earlier than its start, `after` later than its end,
|
|
121
|
+
`on_or_before` and `on_or_after` include it. A relative or period point counts today in the field's timezone. The
|
|
122
|
+
objects below say the same, and add a time, an offset or a period:
|
|
94
123
|
|
|
95
124
|
```
|
|
96
125
|
{ "type": "exact", "date": "2025-03-14", "time": "09:00" } time optional
|
|
@@ -107,9 +136,18 @@ A point is one of:
|
|
|
107
136
|
| `is_empty`, `is_not_empty` | none |
|
|
108
137
|
|
|
109
138
|
```
|
|
110
|
-
{ "node_type": "condition", "field_key": "
|
|
139
|
+
{ "node_type": "condition", "field_key": "customer", "operator": "has_any_of", "value": ["rec_abc123"] }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Through a link
|
|
143
|
+
|
|
144
|
+
A traversal chooses records by a field of the records they link to. `path` lists link fields, each of the table
|
|
145
|
+
before it (up to 3), and `condition` names a field of the last table reached:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
{ "node_type": "traversal", "path": ["customer"], "condition": { "field_key": "tier", "operator": "has_any_of", "value": ["gold"] } }
|
|
111
149
|
```
|
|
112
150
|
|
|
113
151
|
## Sort
|
|
114
152
|
|
|
115
|
-
A view's sort is a list, applied in order: `[{ "field_key": "
|
|
153
|
+
A view's sort is a list, applied in order: `[{ "field_key": "due", "order": "asc" | "desc" }]`.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# First build — a whole workspace, call by call
|
|
2
|
+
|
|
3
|
+
A bakery takes custom-cake orders at the counter. An order is a customer and the cakes on it, one line per cake from the menu; it moves taken, baking, ready, collected, and a slip of the cakes prints when it is ready. Six steps, in the order every workspace is built.
|
|
4
|
+
|
|
5
|
+
Step 0, before the first write: ask the owner once, in one message, only what you cannot settle yourself. Usually: their papers and lists — a photo, a spreadsheet or a file is fine; they answer most questions (what they send and receive, customers, items, prices) — and what no paper tells you that would change the tables or who does which step. The owner answers by hand, so every question costs them time: ask nothing they gave already or a paper settles. Each question is one sentence, no sub-questions or bullets under it; nothing else in that message. Wait for the answer, then build; never build first and ask after. Never ask about stages or design: take a default for everything else and list it in your report so the owner can correct it. Load the owner's own rows. `get_workspace` shows what already exists; what no one could guess from the data goes into a knowledge doc with `set_knowledge` (`knowledge_docs`).
|
|
6
|
+
|
|
7
|
+
## 1. Tables and their checks: `set_tables`
|
|
8
|
+
|
|
9
|
+
One call, linked tables together; a call names a table by its alias, a new table in the call by its `ref`, which becomes its alias; a link names the other table so. Three kinds appear: a book lines are chosen from (`menu_item`), the record the work moves (`cake_order`) and its lines (`cake_line`). One table per kind of thing, one column per fact, a link wherever one record points at another. A status is the stage of the work, six options or fewer; a touch ("called") is a log row, a money fact ("paid in full") a formula, a quantity a number with its unit in `notation`. Every table people work states `singular` (an app with an Add over a table lacking it is refused), `title` (who or what a row is; a code only where nothing else names it) and `records` (an app over a table without it is refused).
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"tables": [
|
|
14
|
+
{
|
|
15
|
+
"ref": "customer", "label": "Customers", "singular": "Customer", "title": "name",
|
|
16
|
+
"fields": {"name": {"type": "text", "label": "Name", "required": true}},
|
|
17
|
+
"records": {"party": "person"}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"ref": "menu_item", "label": "Menu", "singular": "Menu item", "title": "name",
|
|
21
|
+
"fields": {
|
|
22
|
+
"name": {"type": "text", "label": "Cake", "required": true},
|
|
23
|
+
"photo": {"type": "files", "label": "Photo"},
|
|
24
|
+
"price": {"type": "number", "label": "Price", "required": true, "notation": {"style": "currency", "currency": "USD"}}
|
|
25
|
+
},
|
|
26
|
+
"records": {"figure": "price", "image": "photo"}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"ref": "cake_order", "label": "Cake orders", "singular": "Cake order", "title": "reference",
|
|
30
|
+
"fields": {
|
|
31
|
+
"reference": {"type": "autonumber", "label": "Number", "prefix": "CK-", "padding": 3},
|
|
32
|
+
"customer": {"type": "link", "label": "Customer", "table": "customer", "multi": false, "required": true},
|
|
33
|
+
"due_on": {"type": "date", "label": "Pickup on"},
|
|
34
|
+
"pay_by": {"type": "date", "label": "Pay balance by"},
|
|
35
|
+
"lines": {"type": "link", "label": "Cakes", "table": "cake_line", "multi": true, "pair": "order"},
|
|
36
|
+
"total": {"type": "rollup", "label": "Total", "via": "lines", "op": "sum", "field": "amount"},
|
|
37
|
+
"prepaid": {"type": "number", "label": "Prepaid", "notation": {"style": "currency", "currency": "USD"}},
|
|
38
|
+
"balance": {"type": "formula", "label": "Balance", "expression": "{total} - {prepaid}", "notation": {"style": "currency", "currency": "USD"}},
|
|
39
|
+
"paid": {"type": "formula", "label": "Paid", "expression": "{total} > 0 && {balance} <= 0", "output_type": "boolean"},
|
|
40
|
+
"slip": {"type": "files", "label": "Slip"},
|
|
41
|
+
"stage": {"type": "select", "label": "Stage", "default": ["taken"], "options": [
|
|
42
|
+
{"alias": "taken", "label": "Taken", "color": "slate"}, {"alias": "baking", "label": "Baking", "color": "amber"},
|
|
43
|
+
{"alias": "ready", "label": "Ready", "color": "blue"}, {"alias": "collected", "label": "Collected", "color": "green"}
|
|
44
|
+
]}
|
|
45
|
+
},
|
|
46
|
+
"records": {"subtitle": ["customer"], "figure": "total", "due": ["pay_by"], "settled": "paid", "status": {"field": "stage", "closed": ["collected"]}},
|
|
47
|
+
"checks": {"pay_by_before_pickup": {"on": ["create", "update"], "field": "pay_by",
|
|
48
|
+
"when": "!isNull(record.pay_by) && !isNull(record.due_on)", "fail_when": "record.pay_by > record.due_on",
|
|
49
|
+
"message": "\"The balance is due on or before the pickup day\""}}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"ref": "cake_line", "label": "Cakes on orders", "singular": "Cake", "title": "item",
|
|
53
|
+
"fields": {
|
|
54
|
+
"item": {"type": "link", "label": "Cake", "table": "menu_item", "multi": false, "required": true},
|
|
55
|
+
"order": {"type": "link", "label": "Order", "table": "cake_order", "multi": false, "required": true, "pair": "lines"},
|
|
56
|
+
"quantity": {"type": "number", "label": "Quantity", "required": true, "min": 1},
|
|
57
|
+
"unit_price": {"type": "number", "label": "Unit price", "default_from": "item.price", "notation": {"style": "currency", "currency": "USD"}},
|
|
58
|
+
"amount": {"type": "formula", "label": "Amount", "expression": "{quantity} * {unit_price}", "notation": {"style": "currency", "currency": "USD"}}
|
|
59
|
+
},
|
|
60
|
+
"unique": [["order", "item"]],
|
|
61
|
+
"records": {"subtitle": ["quantity"], "figure": "amount"}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`stage` is the table's status. `party` (`"person"` or `"organization"`) draws a face; `image` is the files field that pictures a row, never the one an Act files its paper in; `settled` is a yes/no that ends the `due` deadlines, so it needs a `due`; `figure` is a number. `unique` is two fields or more no two rows share (one line per cake on an order). A line copies the menu's price with `default_from` when made, so a later price change leaves taken orders alone. `min`, `unique` and the check hold on every write, from any app or tool; a check's language is `workflows/table-checks`, an effect after any write a table workflow (`workflows/table-workflows`). Every key is in `model/tables`.
|
|
68
|
+
|
|
69
|
+
## 2. The paper: `set_template`
|
|
70
|
+
|
|
71
|
+
The app's Act names the template by its alias, made from its name (`order_slip`): the paper comes first.
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
set_template {"type": "html", "name": "Order slip",
|
|
75
|
+
"template": "<h1>Order {{reference}}</h1><p>{{customer}} — pickup {{date due_on}}</p>{{#each lines}}<p>{{item}} × {{quantity}} — {{money amount \"USD\"}}</p>{{/each}}<p>Total {{money total \"USD\"}}</p>",
|
|
76
|
+
"variables": {"reference": "string", "customer": "string", "due_on": "string", "lines": "array", "total": "string"}}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Every paper the owner sends is made, one template each. A slot is named after a field of the record; the order's cakes loop under the name of its link to them (`lines`). Fields, loops, layout and papers over many rows: `document_templates`.
|
|
80
|
+
|
|
81
|
+
## 3. The app and its acts: `set_app`
|
|
82
|
+
|
|
83
|
+
**Which apps.** An app is one person's job: the desk they open each day on a list of their own. Does a person open a list the owner names first to do their day's work? An app, that list its register. Part of one record (a vehicle's papers)? A Section or child List of that record's app. Used only while doing another job (the rates a quote is priced from)? A picker on that job's lines, price copied by `default_from`; a book with a keeper or Acts of its own is an app. Same person, another record or rhythm? Another app. Never an app per table; a record passing between people's steps is one app per person over the same table, each register narrowed by `where` to their steps.
|
|
84
|
+
|
|
85
|
+
**The view per kind of row**, chosen before writing the List (a plain register needs none):
|
|
86
|
+
|
|
87
|
+
| The rows are | The treatment | Keys |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| People or organisations | `records.party`, photo or logo as `image` | `model/records` |
|
|
90
|
+
| Photos people look at | `records.image`; List `layout: "cards"` | `model/cards` |
|
|
91
|
+
| Held on a day people schedule by | List `layout: "calendar"` | `model/calendar` |
|
|
92
|
+
| Booked on a resource over time | List `layout: {"lanes": {"by": …}}`, table `no_overlap` | `model/lanes` |
|
|
93
|
+
| Standing at places on a floor or yard | List `layout: {"plan": …}` | `model/plan` |
|
|
94
|
+
| People across days | List `layout: "roster"`, the cells' List inside | `model/roster` |
|
|
95
|
+
| Expected against counted, before against after | a List with `compare: {"before": …, "after": …}` | `model/list-node` |
|
|
96
|
+
| Ranked by a figure | List `sort: {"field": …, "desc": true}` | `model/list-node` |
|
|
97
|
+
| Moved through a status (stages) | an Act per move, `status.dates`, Readings | `model/act-node` |
|
|
98
|
+
| Each belonging to another row | a child List on its Record | `model/list-node` |
|
|
99
|
+
| Counted over a period | Tabs over one `period`; Readings | `model/tab-node` |
|
|
100
|
+
| Papers a case gathers | a List with `expect`; an Act's `template` | `model/list-node` |
|
|
101
|
+
|
|
102
|
+
Any other screen: `design/treatments`.
|
|
103
|
+
|
|
104
|
+
Here one app, the counter's job, naming the tables and the template by the aliases above. The register lists the orders; opening one shows a Section per stage, and each Act sets the next stage. The cakes are a child List of the open order, added in place with `create`. Starting to bake asks for the deposit, readying makes the slip, collecting waits for payment.
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"body": {
|
|
109
|
+
"type": "App", "key": "counter", "name": "Counter", "color": "amber",
|
|
110
|
+
"children": [
|
|
111
|
+
{"type": "List", "entity": "cake_order", "filters": ["customer"], "create": ["customer", "due_on", "pay_by"], "children": [
|
|
112
|
+
{"type": "Record", "door": "page", "children": [
|
|
113
|
+
{"type": "Section", "key": "order", "title": "Order", "at": ["taken", "baking"], "children": [
|
|
114
|
+
"due_on",
|
|
115
|
+
"pay_by",
|
|
116
|
+
{"type": "List", "entity": "cake_line", "create": ["item", "quantity"]},
|
|
117
|
+
{"type": "Act", "key": "start_baking", "label": "Start baking", "when": {"stage": ["taken"]}, "requires": ["due_on"], "asks": ["prepaid"], "set": {"stage": "baking"}},
|
|
118
|
+
{"type": "Act", "key": "ready", "label": "Ready", "when": {"stage": ["baking"]}, "set": {"stage": "ready"}, "template": "order_slip", "into": "slip"}
|
|
119
|
+
]},
|
|
120
|
+
{"type": "Section", "key": "pickup", "title": "Pickup", "at": ["ready"], "children": [
|
|
121
|
+
{"field": "balance", "edit": false},
|
|
122
|
+
{"field": "slip", "edit": false},
|
|
123
|
+
{"type": "Act", "key": "collect", "label": "Collected", "when": {"stage": ["ready"], "paid": true}, "set": {"stage": "collected"}}
|
|
124
|
+
]}
|
|
125
|
+
]}
|
|
126
|
+
]}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`set_app` answers with a note for what the rows call for that the tree leaves out; these fold with one: a read-only copy of a placed Field, `create`/`remove: false` on a read-only List, its `app` where one app is over its table, a lone text field's `unique`, a patch address unlike the node's `key`. **What the check refuses**, each with its id (`model/<id>` is its page):
|
|
133
|
+
|
|
134
|
+
- `record.placed-once`: a Field, child List, Reading, Statement or Act stands once in a Record, whose head draws the title, subtitle, image, status, figure.
|
|
135
|
+
- `register.filter-unshown`: a `filters` entry (at most 3) is a select, member, link, date, yes/no or number the rows show, or a formula of such; never text, and the status is the chips already.
|
|
136
|
+
- `register.columns-budget`, `block.columns-budget`: `columns` fit the room a desk leaves beside the name, status, deadline and figure.
|
|
137
|
+
- `act.requires-drawn`: an Act's `requires` field is drawn in its Section or asked by it.
|
|
138
|
+
- `app.logs`: a further List at the App's root is a log (at most 3) with its own `key`, over another table than the register's.
|
|
139
|
+
- `act.makes-or-runs`: an Act makes a `template` or runs a `workflow`.
|
|
140
|
+
- `reading.list-app`: a List's `app` has its register over that List's table.
|
|
141
|
+
- `block.list-opens`: a read-only child List opens its rows in the drawer, so its table is one a List of the app draws, or its `app` names the app that works it.
|
|
142
|
+
- A Field's `label` or `when` stands only on a gallery or prose Field (files, markdown text, or `block: true`).
|
|
143
|
+
- `entity.unique`: a `unique` set holds two fields or more of one value each (text, number, date, single select, one-row link).
|
|
144
|
+
- `model.names-declared`: each alias a key names exists (a table, field, option, Act `key`, template).
|
|
145
|
+
- A Query's `sort` is `{"field": …, "desc": true}` (`desc` optional) or a list of them, beside `fields`.
|
|
146
|
+
- A workflow writes a link as ids, `[x.id]`; a whole record or bare value is refused.
|
|
147
|
+
|
|
148
|
+
Then `set_access` gives the app to the people whose job it is: a `principal_type` of `member` (id from `get_workspace`, `kind: "member"`), `member_group` (an admin lists them: `kind: "group"`) or `organization`, a `role` of `user` or `manager`; none loses access.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
set_access {"resources": [{"resource_type": "app", "resource_id": "app_…"}], "principals": [{"principal_type": "member", "principal_id": "<member id>", "role": "user"}]}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 4. What the owner will ask an agent: Query and Workflow nodes
|
|
155
|
+
|
|
156
|
+
Read the brief again for what the business asks its assistant again and again: a figure or list whose definition is its own (orders waiting for pickup), an operation (chase overdue balances). Declare each as a Query or Workflow node of the app, with a `description` for an agent that has not seen the screen (`agent_surface` § Declaring more); a connected service (MISA, a carrier) is reached by a Workflow node's step (`workflows`); a question asked once stays on `aggregate_records`.
|
|
157
|
+
|
|
158
|
+
## 5. The owner's rows: `create_records`
|
|
159
|
+
|
|
160
|
+
The owner's own list goes in as rows, in the batches the tool allows: the customers and menu first, then the orders, linked by the ids those calls return, then each order's cakes, linked to the order and a menu item. A row created past the first stage (`records.status.stages`) is refused unless that owned stage's owner or a workspace admin makes it, so load the owner's rows as an admin. Example rows the papers ask for and do not list come from the owner's lists and sample, marked as examples, each created at the stage it should show; pressing rows proves the app and never spreads examples across steps; a placeholder ("Demo customer") is never made where the owner's own rows exist. The value each field type takes is in `field_values`.
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
create_records {"table_id": "menu_item", "field_keys": ["name", "price"], "rows": [["Lemon cake", 24], ["Chocolate cake", 28]]}
|
|
164
|
+
create_records {"table_id": "customer", "field_keys": ["name"], "rows": [["Ana Lopez"]]}
|
|
165
|
+
create_records {"table_id": "cake_order", "field_keys": ["customer", "due_on", "pay_by"], "rows": [["<Ana's record id>", "2031-03-14", "2031-03-12"]]}
|
|
166
|
+
create_records {"table_id": "cake_line", "field_keys": ["order", "item", "quantity"],
|
|
167
|
+
"rows": [["<that order's record id>", "<Lemon cake's record id>", 2]]}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 6. Verify: `run_app_workflow`, `screenshot_app`
|
|
171
|
+
|
|
172
|
+
Move the owner's own rows through the app as their person would, in stage order. An Act runs as `act_<key>` (or under the alias of the `workflow` it names), handed what it asks; dry-run first, then press. To prove a rule, send the write with `dry_run: true`; never make a test row or switch a check off. A row ends at the stage the owner says it is at, so press each only that far; where the owner gave no rows, ask for a few.
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
run_app_workflow {"app_id": "app_…", "alias": "act_start_baking", "inputs": {"record_id": "rec_…", "prepaid": 20}, "dry_run": true}
|
|
176
|
+
run_app_workflow {"app_id": "app_…", "alias": "act_start_baking", "inputs": {"record_id": "rec_…", "prepaid": 20}}
|
|
177
|
+
screenshot_app {"app_id": "app_…"}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Fix a failure in the table or app that causes it. Leave standing instructions with `set_workspace` (`instructions`, one entry per rule): who the business is, the words its people use, what any agent working here must keep to. As an admin, invite the people the brief names with `set_member` (`members` keyed by email, role `member` or `admin`); where the tool is missing, say whom to invite. Then show the owner what you built, job by job, each paper they sent and what makes it, with the choices you made for them to change.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
A workspace is where one business keeps what it knows and does its daily work. **Records** are what it tracks, one table per kind of thing and one row per entry. **Apps** are the screens a team works in, one per person's job, drawn over the tables. **Documents** are the quotes, invoices and slips it sends, each a template filled from a record, or from the many rows of a period or a statement. A paper is the workspace's once it is made from a template or uploaded and filed on its record; a file left on the host's own disk, the user's included, reaches no one in the workspace. A procedure or policy no table holds is a **knowledge doc**; a price list, a customer list or the open orders are rows.
|
|
4
|
+
|
|
5
|
+
Open the one page for the job. Pages are read with the `docs` tool (`docs path="design"`; `lotics docs design` on the CLI).
|
|
6
|
+
|
|
7
|
+
- **Build a workspace from a description:** `docs path="first_build"`. Ask the owner once, only what their papers and your defaults cannot settle, usually their papers and lists first, then a whole bakery built call by call: tables with a check, the paper, the app (which apps, a treatment per kind of row, what the check refuses, who gets it), the owner's rows, then pressing it.
|
|
8
|
+
- **Change an app:** `docs path="model/changing-an-app"`. `get_app`, then `set_app` with `nodes` and `base_version_id`. A table is changed with `get_table`, then `set_tables` with its `table_id` (`data_model`).
|
|
9
|
+
- **Enforce a rule on a table:** `docs path="workflows/table-checks"`. A condition no create, update or delete may meet, whoever makes it, and the language it is written in.
|
|
10
|
+
- **Make a document:** `docs path="document_templates"`. A template, its slots and loops, and the act that files it on a record.
|
|
11
|
+
- **Enter data, count, answer a question:** `docs path="working_in_a_workspace"`. The right tool for each job; the value each field takes is `field_values`, choosing rows is `filters`.
|
|
12
|
+
- **Reach a connected service, or do what a button cannot:** `docs path="workflows"`. Triggers, steps and expressions; each service step is in `workflow_steps`.
|
|
13
|
+
- **Share an app:** `docs path="first_build"`, § 3. `set_access`, and which app each person gets.
|
|
14
|
+
|
|
15
|
+
Which apps a business needs is `first_build`, § 3 (a worked split: `app_architecture`); how each record is drawn is `design`; every key is in `model`; each screen in a complete workspace is `examples`.
|