@lotics/cli 0.300.0 → 0.301.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.
@@ -66,6 +66,7 @@ export type DownloadDestination = {
66
66
  } | {
67
67
  dir: string;
68
68
  };
69
+ export declare function findAvailableFilename(dir: string, filename: string, reserved?: Set<string>): string;
69
70
  export interface LoticsClientOptions {
70
71
  apiKey: string;
71
72
  /** The Lotics this key belongs to — `ResolvedContext.apiUrl`, which is the
@@ -730,6 +730,7 @@ export {
730
730
  LoticsClient,
731
731
  LoticsRequestError,
732
732
  WEB_APP_URL,
733
+ findAvailableFilename,
733
734
  pollCliLogin,
734
735
  startCliLogin
735
736
  };
@@ -80,7 +80,7 @@ lotics docs model # how to write model.json, with a worked
80
80
  lotics docs design # how to design each app: the method, a treatment per kind of row
81
81
  lotics model apply model.json # check it, apply the tables, mint a version of every app
82
82
  lotics model apply model.json --app orders # only the apps named; the tables are applied whole
83
- lotics model apply model.json --plan # what the apply would change, writing nothing
83
+ lotics model check model.json # what the apply would change, writing nothing
84
84
  lotics model pull -o model.json # the workspace's model, as the file apply reads
85
85
  ```
86
86
 
@@ -89,14 +89,14 @@ or written. It then adopts or creates each table (the table this workspace bound
89
89
  label, is adopted and given what it lacks; no stored value changes), writes the first rows only where every bound
90
90
  table is empty, and mints one version per app, printing each app's id, the version minted
91
91
  (`unchanged` when there was nothing to mint) and its address. Applying the same file again mints nothing.
92
- A table change is not undone by rolling an app back, so `--plan` first says what the apply would
92
+ A table change is not undone by rolling an app back, so `lotics model check` first says what the apply would
93
93
  create or change in the tables and which apps it would create, update or refuse — writing nothing.
94
94
 
95
95
  **An app leaving out a treatment its rows call for is refused** — readings over its rows, a record's
96
96
  sections, an act per status move, a picture where its rows hold photos (`lotics docs design`). Each
97
97
  refusal names the rule, and beside them comes each refused app's patch adopting its decisions: merge
98
98
  the patches into the file in the order given, or state why the app stays as it is under the
99
- `declines` key the refusal names (`lotics docs model/declines`), then apply again. `--plan` reads the
99
+ `declines` key the refusal names (`lotics docs model/declines`), then apply again. `lotics model check` reads the
100
100
  decisions beside the workspace's rows, and with `--json` gives each app as the patches leave it, its
101
101
  `draft`.
102
102
 
@@ -161,20 +161,22 @@ Look at every app you apply, at a desktop width and at a phone's — every check
161
161
  definition, none of them the pixels:
162
162
 
163
163
  ```
164
- lotics run screenshot_app '{"app_id":"app_…"}'
165
- # one PNG per width, saved as a workspace file; "path":"/rec_…" opens a screen inside the app
166
- lotics download <file_id>
164
+ lotics run screenshot_app '{"app_id":"app_…"}' -o ./shots
165
+ # one PNG per width saved into ./shots, each with its text snapshot beside it as <name>.txt;
166
+ # "path":"/rec_…" opens a screen inside the app
167
167
  ```
168
168
 
169
- Each shot is one screen, as a member sees it; `"full_page":true` grows it to the list scrolling
170
- inside the app. Beside each image comes its `snapshot`, the screen's accessibility tree as text —
171
- read it to check labels and values without opening the picture.
169
+ From the CLI each image is a link signed for an hour, and `-o` saves it. Each shot is one screen, as a member sees it; `"screen":2` draws the next screen of
170
+ the content scrolling inside the app, and so on to its end, and `"full_page":true` grows one image
171
+ to the first five screens — one or the other. Beside each image comes its `snapshot`, the screen's
172
+ accessibility tree as text — read it to check labels and values without opening the picture — and,
173
+ where a table left columns off at that width, `shed` names them.
172
174
 
173
175
  The app is drawn live, as you, read-only: a screen that writes when it opens shows that write
174
176
  refused, and `errors` lists what failed on the page. A version that looks wrong is one
175
177
  `lotics run rollback_app` away from the one before.
176
178
 
177
- For an app a model applied, `verdict` comes beside the images: what a `--plan` of the workspace's
179
+ For an app a model applied, `verdict` comes beside the images: what a check of the workspace's
178
180
  model finds of that app now, beside its rows — what the next apply would refuse it for and the notes
179
181
  on it, the `patch` adopting the decisions among them, and the app's body as the patch
180
182
  leaves it (`draft`). `verdict_unread` says why none was read.
@@ -27,8 +27,9 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
27
27
  | `lotics run <tool> '<json>'` | Execute a tool (text output via toModelOutput). Args may also come from a file (`lotics run <tool> @args.json`) or stdin behind the `-` sentinel (`cat args.json \| lotics run <tool> -`) — both bypass the OS `ARG_MAX` limit for large payloads (a knowledge-doc `content`, a bulk update). A leading `@` on the args is unambiguously a file path (JSON args start with `{`). **Stdin is asked for, never guessed.** `lotics run <tool>` with no payload runs the tool with no arguments and returns at once. `lotics report` takes the same sentinel. In PowerShell use `@file`: quotes inside an inline argument are consumed by the shell, and the CLI reports the JSON it received with its quotes gone — the error names both escapes. |
28
28
  | `lotics run <tool> --json '<json>'` | Execute a tool (full JSON output) |
29
29
  | `lotics run <tool>` — **file cells** | A file in a tool's result carries its `fil_…` id and metadata and **no `url`**, on every tool and in both output modes. That is not a broken file — this surface resolves no URL for a cell. Reach the bytes with `lotics file download <file_id>`, which takes the id straight from the cell; the text output says so whenever a result carries one. |
30
- | — | **Every tool is invoked here, including the ones that RUN something** (`run_app_workflow`, `run_app_agent`, `run_app_query`) and every one that changes an app (`set_app_queries`, `set_app_workflow`, `set_app_agent`, `update_app`, `rollback_app`). A command exists only for work that touches a local file: `model apply`, `model pull`, `app create --custom`, `app pull`, `app deploy`. |
30
+ | — | **Every tool is invoked here, including the ones that RUN something** (`run_app_workflow`, `run_app_agent`, `run_app_query`) and every one that changes an app (`set_app_queries`, `set_app_workflow`, `set_app_agent`, `update_app`, `rollback_app`). A command exists only for work that touches a local file: `model apply`, `model check`, `model pull`, `app create --custom`, `app pull`, `app deploy`. |
31
31
  | — | **The exit code reports the WORK, not just the call — for the two tools that RUN one.** `run_app_workflow` and `run_app_agent` whose envelope carries a failed `status` (`error`/`failed`/`cancelled`) exit non-zero and print `<tool> → <status>: <message>` to stderr, so `lotics run … && next-step` cannot walk past a refused run. The rule is an allowlist of FAILURE — an unrecognized status exits 0, so a status added later never turns a working script red. A parked run (`awaiting_input`) is not a failure: it is waiting for an answer and the work is still live. Only a TOP-LEVEL `status` counts; one inside the data belongs to the data. Any OTHER tool's `status` is data, and exits 0. |
32
+ | `lotics run <tool> '<json>' -o <dir>` | Also save every **signed link** the answer carries — an object with a `url`, a `filename` and the `expires_at` its signature ends at, at any depth, such as `screenshot_app`'s images. Each is fetched into `<dir>` (created if missing) under its `filename`, taking a free spelling beside a file of that name already there, and a link's `snapshot` text is written beside it as `<name>.txt`. The written paths follow the answer on **stdout**, one per line (on stderr under `--json`, whose stdout stays the one object). A link is signed for an hour; one the store no longer serves exits non-zero. A workspace file states no `expires_at` and is left to `lotics file download`. |
32
33
  | `lotics run <tool> --print-created` | Report the records the call created, grouped by table, with a paste-ready `delete_records` per table and the mandatory caveat naming what cannot be auto-undone (external integrations, notifications, possible sub-workflows). Works for any tool that returns a `side_effects` block, not workflows alone. |
33
34
  | `lotics run <tool> --cleanup` | Implies `--print-created`, then runs those deletes — harvested records **only**, never files / external calls / notifications. **Not a rollback**; a rollback is structurally impossible here. A partial cleanup exits non-zero so a script cannot read it as success. |
34
35
  | `lotics file upload <file\|dir...>` (alias `lotics upload`) · `--stdin` · `--base64` · `--url <url>` | Upload files/directories. **The transport is chosen by size and is not a flag**: under 8 MiB the file is POSTed to `/v1/files`, several such files to a request up to half that route's 100 MiB cap; at or above it the CLI takes presigned part URLs and PUTs the bytes straight to object storage, so they never pass through the API. That threshold matches the AWS CLI's own `multipart_threshold`, and the number matters less than there being nothing to choose — one verb, any size, up to the 2 GiB a workspace may store. A large upload reads one part at a time, so memory stays flat regardless of file size, and a failure part-way abandons the parts already sent rather than leaving them billable and invisible. A directory expands to its immediate files; `--as <name>` renames a single upload. **Three alternative byte sources, for a caller that never had the bytes on disk** — an attachment decoded in memory, a generated document, a signed download link — each mutually exclusive with the others and with a path argument: `--stdin` takes raw bytes on stdin, `--base64` takes base64 on stdin (the shape attachments arrive in), `--url <url>` fetches the URL first. `--stdin`/`--base64` REQUIRE `--as`, because stdin carries no filename and the mime type is derived from it; `--url` falls back to `Content-Disposition` then the URL's last path segment. `--base64` decodes STRICTLY — `Buffer.from(s, "base64")` silently skips invalid characters and truncates on bad padding, so a corrupted pipe would otherwise store a short file that only fails when a human opens it. The `--url` fetch happens in the CLI, not the server: the URL comes from the operator running the command, so routing it through the backend would add an SSRF surface to buy what `curl` already does. |
@@ -43,7 +44,8 @@ Per-command syntax, flags, contracts, and gotchas for the public `lotics` CLI. S
43
44
  | `lotics knowledge hide <id...>` / `lotics knowledge unhide <id...>` | `PATCH /v1/knowledge_docs` with `{ knowledge_doc_ids, hidden }`. Hiding takes docs out of every **listing** — the Library's list, `list_knowledge`, and the corpus `grep_knowledge` searches — while leaving IAM untouched and keeping them readable **by id** (`read_knowledge` with an id, a code run staging one, an app agent's declared set). So it can never silently break an app that depends on a doc, and unhiding costs nothing. Refuses the no-argument form rather than reading it as "everything". |
44
45
  | `lotics knowledge rm <id>` | Archive the doc via `delete_knowledge` (`{ knowledge_doc_id }`). The REST execute path does not gate `needsApproval`, so this runs unattended. |
45
46
  | `lotics setup <model.json> [--email <addr>] [--json]` | **The whole first run, in one command.** Creates an account when this machine has no credential (the same call `auth signup` makes — `--name` and `--timezone` apply), then applies the model to its workspace, then prints the one-time sign-in link. **When that email already has an account it hands over to the `lotics auth login` flow** — it prints the sign-in page to open and the code it must show, and **exits 1 having created nothing**; the person presses Confirm and runs the same command again, which collects the key and carries on into the model. (`--wait` holds the terminal through the Confirm instead, finishing in one command.) The re-run is not refused for naming an `--email` it is now signed in as — that address IS the account it holds, not a second one. The file is a workspace MODEL, and it is read and checked before an account is created — the design decisions an apply would refuse it for too (`lotics docs design`), since a new workspace holds no rows to change them — because a file with a typo in it must not leave an organization behind. Then it is `lotics model apply` run on the new workspace: its tables, rows and apps; the sign-in link lands on its app when it has one, else on the workspace's app list. It exists because the two-command form has a seam where the FIRST command exists only to produce a credential for the second, and a caller pasting a prompt has to get both right. **`--email` is only for creating an account**: with a credential already resolvable it is REFUSED rather than obeyed, because the two can name different organizations and preferring either one silently writes into an org the caller did not name — the message says how to do each thing on purpose. Without it, `setup` applies the model to the account you already have. A path positional after the file is accepted and IGNORED with a warning — `setup` writes nothing to disk — so a prompt that passes one still runs. **`--json` prints one object on stdout and nothing else** — what `lotics model apply --json` does (`tables`; `apps`, each with `alias`, `app_id`, `version_id`, `origin`, `address` and `findings`; and `findings`) plus `organization_id`, `workspace_id` and `signin_url`, and a `warnings` array carrying everything the prose form would have said out of band, such as a sign-in link that could not be minted. A warning is never merely silenced: when the command fails with an error before it can emit, the ones it had collected go to stderr alongside it. Reachable with no install: `npx -y @lotics/cli setup …`. |
46
- | `lotics model apply <model.json> [--app <alias> ...] [--plan] [--json]` | **The model, applied to this workspace, through the `apply_model` tool.** The file is read and checked against the model's own rules with the validator the server runs, every problem in one run, before anything is uploaded. **The apply refuses an app leaving out a treatment its rows call for** (`lotics docs design`) — judged by the server beside the workspace's rows, before it writes anything — until the app adopts it or states why not under the `declines` key the refusal names; the refusal carries each refused app's patch adopting its decisions, to merge in the order printed. Documents a row attaches by a path beside the file are uploaded first and the rows sent with their `fil_` ids; a path this workspace already recorded keeps its id, so a re-apply uploads nothing twice. **Where the file was pulled (`-o`) or applied in this workspace before, only what it changed since is sent**, as a `patch`: what changed in the workspace since and the file does not touch stays. An item named by its `alias` that the file puts in a new place — an act among an app's acts, a field between two others — is sent as a move beside the patch: inside an app it is built into the app, an option of a select takes its new place among that select's options on the table, and a table's fields and views, the roles, the templates and the apps themselves keep the order the workspace holds, said as a note. A list whose items carry no `alias` — an app's columns, a section's fields, the sections — is sent whole in its new order. What the file takes out is sent as a removal: it goes where an apply rebuilds it (an app's acts and columns, a write rule) and is refused, naming the tool that deletes it, where an apply never deletes it (a table, a field, an option, an app). A file stating a `null` a patch would read as a removal sends the whole model, said on stderr, and so does a reorder sent to a server that takes no move. A patch builds the apps it states or moves an item of and each other app whose build it changes, `--app` naming others; one whose every key and move lies inside one app's body leaves the tables as they stand — its `tables` is empty — and checks and builds that app alone, beside any other app whose build reads what it changed. The copy each change is read against is kept per workspace and file under `~/.lotics/model_bases`: an apply narrowed by `--app` leaves in it the other apps as they were, so the next apply sends their edits again, and an apply that fails — refused, or cut off — leaves none, so the next sends the whole model. Then the tool adopts or creates every table (the table this workspace bound the entity to, else an existing table of the same label, is ADOPTED and given the fields, options and views it lacks; no stored value changes), writes first rows only where every bound table is empty, and mints a new version of each app the model declares, or the patch builds — `--app` (repeatable, comma-separated) narrows which apps, while the tables are applied whole. Prints one line per app it built — alias, `app_id`, `created`/`updated` with the version minted or `unchanged`, and the address it is served at — then the model's notes once, as the server states them. **`--plan` writes nothing and uploads nothing**: it prints what the apply would do to the tables (what it would create, what it leaves as the workspace has it, what the two disagree about), which apps it would create or update, and what it would refuse an app for — the decisions among its findings, read beside the workspace's rows, with each refused app's patch — exiting 1 when it would refuse one; workflow bodies are checked only at apply, since they name fields a plan has not created. **A rollback restores an app's earlier version** (`lotics run rollback_app`); table changes and data writes stay. Resolves and ANNOUNCES its workspace first. `--json` prints `{workspace_id, tables, apps, findings}` on stdout (with `--plan`, each table and app is what the apply would do, an app the patches change carrying its body as they leave it as `draft`, and `designs` each refused app's patch in merge order), or `{ok: false, findings}` when the file does not check. |
47
+ | `lotics model apply <model.json> [--app <alias> ...] [--json]` | **The model, applied to this workspace, through the `apply_model` tool.** The file is read and checked against the model's own rules with the validator the server runs, every problem in one run, before anything is uploaded. **The apply refuses an app leaving out a treatment its rows call for** (`lotics docs design`) — judged by the server beside the workspace's rows, before it writes anything — until the app adopts it or states why not under the `declines` key the refusal names; the refusal carries each refused app's patch adopting its decisions, to merge in the order printed. Documents a row attaches by a path beside the file are uploaded first and the rows sent with their `fil_` ids; a path this workspace already recorded keeps its id, so a re-apply uploads nothing twice. **Where the file was pulled (`-o`) or applied in this workspace before, only what it changed since is sent**, as a `patch`: what changed in the workspace since and the file does not touch stays. An item named by its `alias` that the file puts in a new place — an act among an app's acts, a field between two others — is sent as a move beside the patch: inside an app it is built into the app, an option of a select takes its new place among that select's options on the table, and a table's fields and views, the roles, the templates and the apps themselves keep the order the workspace holds, said as a note. A list whose items carry no `alias` — an app's columns, a section's fields, the sections — is sent whole in its new order. What the file takes out is sent as a removal: it goes where an apply rebuilds it (an app's acts and columns, a write rule) and is refused, naming the tool that deletes it, where an apply never deletes it (a table, a field, an option, an app). A file stating a `null` a patch would read as a removal sends the whole model, said on stderr, and so does a reorder sent to a server that takes no move. A patch builds the apps it states or moves an item of and each other app whose build it changes, `--app` naming others; one whose every key and move lies inside one app's body leaves the tables as they stand — its `tables` is empty — and checks and builds that app alone, beside any other app whose build reads what it changed. The copy each change is read against is kept per workspace and file under `~/.lotics/model_bases`: an apply narrowed by `--app` leaves in it the other apps as they were, so the next apply sends their edits again, and an apply that fails — refused, or cut off — leaves none, so the next sends the whole model. Then the tool adopts or creates every table (the table this workspace bound the entity to, else an existing table of the same label, is ADOPTED and given the fields, options and views it lacks; no stored value changes), writes first rows only where every bound table is empty, and mints a new version of each app the model declares, or the patch builds — `--app` (repeatable, comma-separated) narrows which apps, while the tables are applied whole. Prints one line per app it built — alias, `app_id`, `created`/`updated` with the version minted or `unchanged`, and the address it is served at — then the model's notes once, as the server states them. **What it would change, writing nothing, is `lotics model check`.** **A rollback restores an app's earlier version** (`lotics run rollback_app`); table changes and data writes stay. Resolves and ANNOUNCES its workspace first. `--json` prints `{workspace_id, tables, apps, findings}` on stdout, or `{ok: false, findings}` when the file does not check. |
48
+ | `lotics model check <model.json> [--app <alias> ...] [--json]` | **What applying the model would change, through the `check_model` tool, writing nothing.** The file is read and checked as `model apply` checks it, then sent the same way — whole, or as what it changed since its base — and nothing is uploaded or kept. It prints what the apply would do to the schema (what it would add, each kind counted — tables, fields, options, constraints — what it leaves as the workspace has it, what the two disagree about), which apps it would create or update, and what it would refuse an app for — the decisions among its findings, read beside the workspace's rows, with each refused app's patch — exiting 1 when it would refuse one; workflow bodies are checked only at apply, since they name fields a check has not created. `check_model` only reads, so a key holding read access alone runs this where `model apply` is refused, and so does a private instance whose licence admits reads only: it needs `schema:read`, and `data:read` when the file states an app or is sent as what it changed. Resolves and ANNOUNCES its workspace first. `--json` prints `{workspace_id, tables, apps, findings}` — each table and app what the apply would do, an app the patches change carrying its body as they leave it as `draft` — and `designs`, each refused app's patch in merge order, or `{ok: false, findings}` when the file does not check. |
47
49
  | `lotics model pull [-o <model.json>]` | **This workspace's model, rebuilt from what owns each part** — the tables, fields, options, templates and roles the workspace holds, how rows are recognised, and each app's body from its current version — through the `get_model` tool, as the file `model apply` reads: to stdout, or to the file `-o` names. What the workspace holds that a model cannot state is printed on stderr, never written into the file. Applying what it wrote changes nothing. With `-o`, what it wrote is the copy the next `model apply` of that file reads its changes against. |
48
50
  | `lotics app create <name> --custom [path]` | **A custom-code app**: creates the app (`POST /v1/apps`), scaffolds a Vite + React + TypeScript project into `[path]` (default `./<name>`, refused when not empty — before the app row exists) that depends on `@lotics/app-sdk` alone and draws with plain React, and installs it (`npm install --ignore-scripts`), then writes the declarations of the app's live bindings (`get_app_types`) into `.lotics/`, which the project's `tsconfig.json` includes. `package.json#lotics` names the app and its workspace, which is how `app deploy` in that directory finds both. The app has no version until the first `lotics app deploy`. `--custom` is required: an app the runtime draws from a model is made by `lotics model apply`. The SDK's reference is `node_modules/@lotics/app-sdk/AGENTS.md` inside the project. |
49
51
  | `lotics app pull [app_id] [path]` | **A custom-code app's live source, as a project ready to deploy on it.** The target is `[path]`, else this directory when it is the app's project (or no app is named), else `./<name>`. **The app's own project is brought up to date in place** — but only when it holds no edit since the version `package.json#lotics.current_version_id` names: its source (packed as a deploy packs it) is compared with that version's archive, ignoring `.lotics/` and `package.json#lotics`, and any difference refuses the pull, naming the changed files; a pull never merges, so local work is never lost. Already at the live version is a no-op that says so. **An empty or new directory receives the source whole**; any other directory is refused. Downloads come from `GET /v1/apps/{id}/versions/{version_id}/source`. `package.json#lotics` is then set to exactly the app, its workspace and the pulled version, dropping every other key an older CLI wrote there, so the next `lotics app deploy` builds on the live version; then `.lotics/` is written (`get_app_types`) and dependencies installed (`npm ci` with a lockfile, else `npm install`, both `--ignore-scripts`). A JSON app has no source tree: the server's refusal names `get_model`, and `lotics model pull` is its pull. |
package/docs/design.md CHANGED
@@ -38,9 +38,9 @@ rows in time, a template's act — until the model states it or declines it.
38
38
  `blocks`, `write_rules`), and every write re-checks it; a check without `blocks` only warns.
39
39
  5. **A treatment for every list and every entity** — the next section. Choose it from what the rows ARE,
40
40
  before writing any register.
41
- 6. **Plan, apply, look.** The `apply_model` tool (`lotics model apply` on the CLI) with `plan` checks the
41
+ 6. **Plan, apply, look.** The `check_model` tool (`lotics model check` on the CLI) checks the
42
42
  whole model beside the workspace's rows and writes nothing — the patch adopting each app's decisions in
43
- `designs`, and the app's body as they leave it its `draft`; without it, it mints each app live. Then look at each app —
43
+ `designs`, and the app's body as they leave it its `draft`; `apply_model` (`lotics model apply`) mints each app live. Then look at each app —
44
44
  § Look at it — and judge it against § The visual bar. A correction is a change to the model, applied again.
45
45
 
46
46
  ## Treatments
@@ -63,6 +63,7 @@ then check again. A note refuses nothing.
63
63
  | Rows that each belong to another row | a `rows` block on that row's record | `model/blocks` |
64
64
  | Moving through a status | an act per move, `status.history`, and the register's `readings` | `model/acts`, `model/status`, `model/readings` |
65
65
  | Steps people drop out of between | a `funnel` block on the record whose child rows count them | `model/reading-blocks` |
66
+ | A record's own numbers nobody keys, read at a glance | a `figures` block: two to eight of them as tiles, one holding nothing read as not measured | `model/reading-blocks` |
66
67
  | Counted over a period | the register's `tabs` over one `period`; an `open` tab's `where` reads only what was true at the period's end, never a status the row moves on from | `model/tabs` |
67
68
  | Papers a case gathers | a rows block with `expect`; the papers made, an act's `templates` | `model/blocks`, `model/papers-and-recordings` |
68
69
  | Rows that are each a place a record is sent | a rows block with `expect` on the link to those places and `post`: each ticked, the post written there and read as its feed shows it | `model/publish-desk` |
@@ -90,6 +91,19 @@ a link to it previews the file. Never a field an act keeps the paper it makes in
90
91
  `layout: "cards"` where the picture is how a row is found. A files field the record keeps as a folder of
91
92
  papers is a `files` block, read whole. Worked: `examples/cards`.
92
93
 
94
+ #### A link opens its row
95
+
96
+ On a record, a link to one row opens that row's peek: a drawer on a desk, a sheet on a phone, reading the
97
+ row's face — its mark, its name, its status and the line under it — and nothing to edit. A link read in a
98
+ row's cell or on its line opens from its name; a link the record keeps as a field rests as its picker, and
99
+ its way out stands at the field's label where the row has somewhere to go. The peek's one act leaves for
100
+ the app whose register is that entity's rows — the first such app, in the model's order, that the member
101
+ can open — at the row's own address, or goes to the row inside this app where the entity is the app's own
102
+ or one of its books'. With no such app the peek has no act, and a link answered in what an act asks opens
103
+ no peek. The model states none of it: give the entity an app of its own and
104
+ its links lead there. The app opened decides what the member reads of the row; where it keeps the row
105
+ from them, they land on its not-found.
106
+
93
107
  ### Rows in time
94
108
 
95
109
  Rows that each stand on a day, or hold a run of days or hours, are read where they stand in time — never
@@ -101,7 +115,9 @@ turn — read by what they hold.
101
115
  #### Calendar
102
116
 
103
117
  `register.layout: "calendar"`: the rows by their day — a month, a week, a day or a list; an entry at its
104
- hour where its date holds one, over its span where its line holds a second date. Worked: `examples/calendar`.
118
+ hour where its date holds one, over its span where its line holds a second date. An entry pressed opens a card on
119
+ it: the row, Delete, its acts, Reschedule (the day alone moves; its hour and its length stay) and Open. Worked:
120
+ `examples/calendar`.
105
121
 
106
122
  #### Lanes
107
123
 
@@ -181,8 +197,9 @@ record's `metric`, `breakdown` and `trend` blocks read its own child rows. Worke
181
197
  A `funnel` block reads a record's child rows as steps, each a number column summed over them — a week's channels
182
198
  from impressions to bookings, an order's lines from ordered to shipped. It states how much of the first step
183
199
  each later one keeps, the share lost between two neighbours, and the last over the first. State two to eight
184
- `steps` in one unit; where the path turns — reach, then conversion — state two blocks, so the second's bars are
185
- read over its own first step. A step no row holds a number in reads as not measured, so leave a count empty
200
+ `steps` in one unit; where the path turns — reach, then conversion — state two blocks one straight after the
201
+ other, so the second's bars are read over its own first step: they draw on one grid, every bar starting at one
202
+ edge and every value ending at another, each under its own label. A step no row holds a number in reads as not measured, so leave a count empty
186
203
  rather than typing a zero nobody measured.
187
204
 
188
205
  ### The record
@@ -267,7 +284,8 @@ captured — and files it as a new row of the record's `timeline` child (`into`)
267
284
  ## Look at it
268
285
 
269
286
  After an apply, the `screenshot_app` tool draws each app as its people see it — the register, and a record
270
- by its `path` — at a desk's width and a phone's. Beside the shots, its `verdict` is what a plan of the workspace's
287
+ by its `path` — at a desk's width and a phone's, one screen a shot, naming the columns a table left off at
288
+ each width: `screen` names which screen of a long record. From the CLI, `-o <dir>` saves the shots. Beside the shots, its `verdict` is what a plan of the workspace's
271
289
  model finds of the app now, beside its rows: what the next apply would refuse it for and the notes on it, the
272
290
  `patch` adopting the decisions among them, and the app's body as the patch leaves it (`draft`). Judge each shot
273
291
  against § The visual bar, change the model, and apply again. The `rollback_app` tool returns an app to an earlier
package/docs/examples.md CHANGED
@@ -1306,6 +1306,7 @@ Every key: the `model` reference, at the pages each section names under **Keys**
1306
1306
  "buoc_tiep": {
1307
1307
  "title": "vi_sao_luc_nay",
1308
1308
  "subtitle": ["viec"],
1309
+ "mark": "viec",
1309
1310
  "status": {"field": "tinh_trang", "closed": ["xong", "bo_qua"]},
1310
1311
  "due": ["han"]
1311
1312
  },
@@ -1411,6 +1412,7 @@ Every key: the `model` reference, at the pages each section names under **Keys**
1411
1412
  "label": "Chuyển đổi",
1412
1413
  "when": {"trang_thai": ["da_lien_he"]},
1413
1414
  "workflow": "chuyen_doi_khach",
1415
+ "writes": ["lien_he", "cong_ty"],
1414
1416
  "confirm": true
1415
1417
  },
1416
1418
  {
package/docs/migration.md CHANGED
@@ -18,7 +18,7 @@ templates and roles are the workspace's own.
18
18
  | `lotics workspace build <model.json>` | `lotics model apply <model.json>` |
19
19
  | `lotics app create --from <model.json>#<app>` | `lotics model apply <model.json> --app <app>` |
20
20
  | `lotics app regenerate` | change the model file, then `lotics model apply` |
21
- | `lotics scaffold diff` | `lotics model apply <model.json> --plan` — what the apply would change, writing nothing |
21
+ | `lotics scaffold diff` | `lotics model check <model.json>` — what the apply would change, writing nothing |
22
22
  | `lotics scaffold export` | `lotics model pull [-o <model.json>]` — the workspace's model, rebuilt from what owns each part |
23
23
  | `lotics app codegen`, `app check`, `app dev`, `app preview` | nothing local: read an app with `lotics run get_app`, and change it through its tools |
24
24
  | `lotics app workflow set` / `app query set` / `app agent set` | `lotics run set_app_workflow` / `set_app_queries` / `set_app_agent` — each mints a version |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/cli",
3
- "version": "0.300.0",
3
+ "version": "0.301.0",
4
4
  "description": "Lotics SDK and CLI for AI agents",
5
5
  "type": "module",
6
6
  "bin": {