@wix/himalaya-cli 0.1431.0 → 0.1433.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.
@@ -108,7 +108,7 @@ read-only, but it writes the book folder, so it is not annotated read-only to th
108
108
  ],
109
109
  "functions": [
110
110
  { "name": "sendReport", "sourcePath": "base44/functions/sendReport/entry.ts", "kind": "http",
111
- "methods": ["POST"], "invoke": "base44.functions.invoke(\"sendReport\", payload)",
111
+ "methods": ["POST"], "invoke": "functions.invoke(\"sendReport\", payload)",
112
112
  "automations": [{ "name": "weekly", "type": "scheduled", "schedule": "0 9 * * 1" }] }
113
113
  ],
114
114
  "agents": [{ "name": "job_helper", "tools": [{ "entity": "Job", "operations": ["read"] }] }],
@@ -128,7 +128,7 @@ app's own project files) or `rest` (the owner-authenticated API). Treat an opera
128
128
  empty.
129
129
 
130
130
  `BOOK.md` is the same content, summarised for an agent. For each entity it lists the worker calls
131
- the Himi app would make through the `@himalaya/base44` capability: `base44.entities.<Name>.list()`,
131
+ the Himi app would make through the `@himalaya/base44` capability: `entities("<Name>").list()`,
132
132
  `.filter({…})`, `.get(id)`, and the writes (`create`, `update`, `delete`) that the entity's rules do
133
133
  not forbid outright.
134
134
 
@@ -61,8 +61,8 @@ whose `Task` entity anyone may read and write:
61
61
 
62
62
  | Tab | Screen | Worker | Pattern it shows |
63
63
  |---|---|---|---|
64
- | Tasks | `b44_tasks` | `tasks` | list, create, bulk-create, update, delete; loading / empty / error / ready; pull to refresh |
65
- | (pushed) | `b44_task` | `task` | read one row by its id (a nav param), edit, delete then go back |
64
+ | Tasks | `b44_tasks` | `b44-task-list` | **generated** by `himi base44 screens`: list, create, delete; loading / empty / error / ready; pull to refresh |
65
+ | (pushed) | `b44_task` | `b44-task` | **generated**: read one row by its id (a nav param), edit, delete then go back |
66
66
  | Functions | `b44_functions` | `functions` | `functions.invoke(name, payload)` with the reply and the error painted |
67
67
  | Account | `b44_account` | `account` | Native Google sign-in (requires tenant app-link associations), email + password and emailed-code sign-in, register, reset, profile, sign out |
68
68
 
@@ -89,27 +89,48 @@ Turn the book's entities and functions into an adopted declaration, then wire it
89
89
  himi base44 adopt --content . --book ../.himi/base44-book --write # the book step 1 wrote, one level up → base44-entities.ts (never overwrites)
90
90
  ```
91
91
 
92
+ The starter's `config.ts` declares the sample `Task` as a local `const base44Entities = [...]`.
93
+ Delete that declaration, then import yours in its place (keeping both fails with "Identifier
94
+ 'base44Entities' has already been declared"):
95
+
92
96
  ```ts
93
97
  import { base44Entities, base44Functions } from "./base44-entities.js";
94
98
  // …
95
- base44: { appId: "<id>", entities: base44Entities, functions: base44Functions },
99
+ base44: { appId: base44AppId, entities: base44Entities, functions: base44Functions },
96
100
  ```
97
101
 
98
102
  `himi validate` then lints the declaration (`pass: "base44"`), and `himi base44 entities query`
99
103
  reports row counts and schema drift against the live app. See
100
104
  [`base44-entities`](base44-entities.md) (`himi docs base44-entities`).
101
105
 
106
+ Then generate typed handles from the declaration, and import them in the workers instead of
107
+ spelling entity names:
108
+
109
+ ```bash
110
+ himi base44 types --content . # → tier5-src/_shared/base44-entities.ts (commit it)
111
+ ```
112
+
113
+ A field typo on a typed row (`row.titel`) is then a `himi validate` type error rather than a blank
114
+ cell on device. Re-run it after editing the block — `himi validate` reports `base44-types-stale`
115
+ until you do. See [`base44-sdk`](base44-sdk.md#typed-rows).
116
+
102
117
  ## 3. Plan the screens from the book
103
118
 
119
+ `himi base44 screens` (step 4) applies the rules below for you, so this step is about choosing
120
+ **what to declare**: which entities, in what order (with up to three entities each gets a tab; with
121
+ four or more, the first two get tabs and the rest go under More), which fields and which `rls`. The rules describe what the generator emits. Read them to predict the result, and to
122
+ know what to customise afterwards.
123
+
104
124
  Write `.himi/screen-plan.md` before editing code. For each entity in `book.entities`:
105
125
 
106
126
  1. **Decide whether it gets a tab.** An entity that a top-level route reads (`routes[].entities`)
107
127
  and that a user browses gets a tab. A child entity (one whose rows only make sense under a
108
128
  parent, such as `OrderItem` under `Order`) gets a section on the parent's detail screen
109
129
  instead. Keep to 3–5 tabs. Account always takes one when the app is not public (step 5).
110
- 2. **Pick the list row.** The title is the first `required` string field, preferring one named
111
- `title`, `name` or `label`. The subtitle is one or two short fields (a status `enum`, a date, a
112
- number). Never put a long text field in a row.
130
+ 2. **Pick the list row.** The title is the entity's `displayField` if it declares one, else the
131
+ first `required` string field, preferring one named `title`, `name` or `label`. The subtitle is
132
+ one or two short fields (a status `enum`, a date, a number). Never put a long text field in a
133
+ row.
113
134
  3. **Pick the detail and form fields** from each field's JSON Schema type:
114
135
 
115
136
  | Field (`type` / `format` / `enum`) | Detail shows | Form control |
@@ -118,20 +139,28 @@ Write `.himi/screen-plan.md` before editing code. For each entity in `book.entit
118
139
  | `string` + long text (`description`, `notes`, `body`, …) | `Text` with `maxLines` | `TextField` with `multilineMaxLines` |
119
140
  | `string` + `enum` | `Badge` / `Text` | `SegmentedControl` (≤ 4 values) or `Picker` |
120
141
  | `string` + `format: date` / `date-time` | formatted `Text` | `DatePicker` |
121
- | `string` + `format: email` / `uri` | `Text`, tap opens mail / URL | `TextField` with `keyboard: "email"` / `"url"` |
142
+ | `string` + `format: email` / `uri` | `Text` (the generator does not make it tappable yet) | `TextField` with `keyboard: "email"` / `"url"` |
122
143
  | `string` holding an image URL (`image`, `photo`, `avatar`, `*_url`) | `Image` | read-only, see [file upload](#what-the-sdk-cannot-do) |
123
144
  | `number` / `integer` | `Text` (with unit) | `Stepper` (small ranges) or numeric `TextField` |
124
145
  | `boolean` | `Badge` or `Text` | `Toggle` |
125
- | `array` of strings | `FilterChips` / `ChipsInput` (read-only) | `ChipsInput` |
126
- | `object` / nested `array` of objects | a sub-section of rows | none at first. Edit it on the web app |
146
+ | `array` of strings | comma-joined `Text` | `ChipsInput` |
147
+ | `object` / nested `array` of objects | its JSON as `Text` (a sub-section of rows is yours to build) | none. Edit it on the web app |
127
148
  | built-ins `id`, `created_date`, `updated_date`, `created_by` | "Created …" metadata row | never editable |
128
149
 
129
150
  4. **Respect the entity's rules.** `rls` (and field-level `rls`) decide what the signed-in user
130
- may do. Offer only the writes `BOOK.md` lists for the entity. An operation whose rule is
131
- `false` gets no button. A rule that is a condition (`{ "created_by": "{{user.email}}" }`)
132
- means the write only works for a signed-in user, on their own rows, so it belongs behind
133
- sign-in (step 5). A denied call still fails with Base44's message, and every screen must paint
134
- that error rather than swallow it.
151
+ may do. Declare the `rls` `BOOK.md` lists for the entity. The generator gates each write
152
+ control on its operation's rule (falling back to the entity-level `write` rule):
153
+
154
+ | Rule | Control |
155
+ |---|---|
156
+ | `false`, or absent | not emitted at all |
157
+ | `true` | always shown |
158
+ | exactly `{ "created_by": "{{user.email}}" }` or `{ "created_by_id": "{{user.id}}" }` | shown only on the signed-in user's own rows |
159
+ | any other condition | shown only when signed in |
160
+
161
+ A field whose field-level `rls.write` is not `true` is read-only in the form. The gate is
162
+ advisory: a denied call still fails with Base44's message, and every screen paints that error
163
+ rather than swallowing it.
135
164
  5. **Use the sample rows** (`sampleRows`, already redacted) to size rows and choose the subtitle,
136
165
  never as data the app ships.
137
166
 
@@ -140,22 +169,48 @@ Compose each screen with [`designing-himi-screens`](designing-himi-screens.md)
140
169
 
141
170
  ## 4. Build one list + detail per entity
142
171
 
143
- Copy the Task pair and rename it. For an entity `Project`:
172
+ Declare the entity, then generate its screens:
173
+
174
+ ```bash
175
+ himi base44 adopt --content . --book ../.himi/base44-book --write # or declare by hand (step 2)
176
+ himi base44 screens --content . --json
177
+ ```
178
+
179
+ For each entity in `config.ts` `base44.entities`, `himi base44 screens` writes a list screen
180
+ (`b44_<plural>`, e.g. `b44_projects`) and a detail screen (`b44_<name>`, e.g. `b44_project`):
181
+
182
+ - `tier5-src/_shared/b44-<entity>-plan.ts`: the plan (row title and subtitle, facts, form
183
+ controls, write gates) the rules in step 3 produce. The list, the detail and the descriptors all
184
+ read it, so this is the file to customise.
185
+ - `tier5-src/b44-<entity>-list/index.ts` and `tier5-src/b44-<entity>/index.ts`: one-line worker
186
+ entries over the template's shared engine (`_shared/entity-list.ts`, `_shared/entity-detail.ts`).
187
+ Each entity gets its own bundle and service, so two entity tabs never share state.
188
+ - `dev/entities.generated.ts` and `config.entities.generated.ts`: the screen registrations and the
189
+ `linkableScreens` / `tabBar` items. `dev/index.ts` and `config.ts` already import them.
190
+
191
+ The first three entities in config order get a tab, in front of Functions and Account. With more,
192
+ the last entity slot becomes a generated `b44_more` screen listing the rest, so the bar never has
193
+ more than five items. Ids pluralise naively (`Category` → `b44_categories`, `Person` →
194
+ `b44_persons`); give the entity a `title` for the visible name. Two entities that would share an id
195
+ are refused with `code: "entity_id_collision"`.
196
+
197
+ Re-running is safe. Each generated file starts with
198
+ `// @generated by himi base44 screens — sha256:<hash>`:
199
+
200
+ - an unchanged config reports every file `unchanged` and writes nothing;
201
+ - a generated file you edited by hand (its body no longer matches the hash) is reported
202
+ `skipped-edited` and left alone, and the command still exits 0. Pass `--force` to overwrite it;
203
+ - files for an entity you removed from `config.ts` are reported in `orphaned[]` and never deleted.
204
+
205
+ `--dry-run` reports the same outcomes without writing. Failures carry a top-level `code`
206
+ (`no_entities_block`, `entity_id_collision`, `content_dir_not_base44_starter`), per
207
+ [`himi-json-output`](himi-json-output.md). The generator never calls Base44.
144
208
 
145
- - `tier5-src/projects/index.ts` from `tier5-src/tasks/index.ts`. Set `ENTITY = "Project"`, the
146
- service name (`b44Projects`), and `toRow()` for the fields you chose. Keep the structure:
147
- module-eval `load()`, the `latestList` sequence that drops stale replies, the `pendingWrites`
148
- guard, and `derive()` producing the `hide*` flags.
149
- - `tier5-src/project/index.ts` from `tier5-src/task/index.ts`. `inputId()` reads the row id from
150
- the nav param, `get(id)` loads it, and `remove` returns `navigate(…, { presentation: "back" })`
151
- from the action the host dispatched.
152
- - In `dev/index.ts`, a `projects()` and a `project()` builder (copy `tasks()` and `task()`), each
153
- bound with `bundle: "<worker dir>"` and `declaredCapabilities: ["base44"]`. Register both in
154
- `screens.exact` and the tab root in `seed.screens`.
155
- - In `config.ts`, a `linkableScreens` entry for each (the detail with `paramKeys: ["id"]` and its
156
- list as `parentScreenId`) and a `tabBar` item for the list.
209
+ To change a screen beyond what the plan expresses (a filtered list, a child section, a custom
210
+ action), edit its generated entry or plan. The edit makes the file `skipped-edited`, so the
211
+ generator leaves it alone from then on.
157
212
 
158
- The calls a worker makes, all on `entities("<Name>")`:
213
+ The calls the engine makes (and a customised worker can make), all on `entities("<Name>")`:
159
214
 
160
215
  | Need | Call |
161
216
  |---|---|
@@ -176,7 +231,9 @@ Rules that keep it correct:
176
231
  `$item.openLabel`. `himi validate` flags this as an error.
177
232
  - **Every write re-lists**, and a failed write keeps the list on screen with the error above it.
178
233
  Only a failed *first* list is the error state.
179
- - Remove `tasks` / `task` once your own entities replace them, and change `homeScreenId`.
234
+ - Replace the sample `Task` declaration with your own entities and re-run `himi base44 screens`.
235
+ The home screen is the first entity's list. The generator reports the sample's files as
236
+ `orphaned`; delete them by hand.
180
237
 
181
238
  ## 5. Auth: the Account tab
182
239
 
@@ -285,8 +342,14 @@ himi run --content . # web preview against the live Base
285
342
  ```
286
343
 
287
344
  `himi test` crawls every action against the real workers with hermetic transport, so it proves
288
- the wiring, not your data. Its gap ledger names what the hermetic crawl could not reach (rows that
289
- only exist on the live app, for instance). Close a gap with the remedy it names, or accept it.
345
+ the wiring, not your data. Base44 reads are served from rows you already have: an owned entity's
346
+ `seed:` rows in the `base44.entities` block, then an adopted entity's redacted `sampleRows` from
347
+ `.himi/base44-book/book.json` in the content directory (copy the book in, or re-run analyze with
348
+ `--out .himi/base44-book`). So a list screen crawls populated, its row actions get a row to act on,
349
+ and a typo'd entity name fails loudly instead of rendering empty. Writes and `functions.invoke` are
350
+ refused in the crawl by design, so their error paths are what it exercises. With no seed and no
351
+ book, a declared Base44 read answers nothing and the list crawls empty, as before. Its gap ledger
352
+ names what the hermetic crawl could not reach (rows that only exist on the live app, for instance). Close a gap with the remedy it names, or accept it.
290
353
  Then drive the real flow in `himi run`: list, create, edit, delete a row of each entity, and
291
354
  sign in when the app is not public.
292
355
 
@@ -147,4 +147,3 @@ Failure codes are listed in [`himi-json-output`](himi-json-output.md#himi-base44
147
147
  full-set sync, which is why it is a separate, deliberate step.
148
148
  - **Typed clients and hermetic test data**: `entities<"X">()` typed from the declaration, with
149
149
  `seed` rows as mocks.
150
- - **Generated screens**: list and detail screens per declared entity.
@@ -249,6 +249,24 @@ branch on them without parsing prose:
249
249
  - `source_unavailable`: the project download (the eject read) failed, so no drift was
250
250
  computed. Row counts still ran; `sourceRead` is `"unavailable"` and `sourceError` says why.
251
251
 
252
+ ## `himi base44 screens` codes
253
+
254
+ `himi base44 screens` (#4560, [base44-app-to-himi-app](base44-app-to-himi-app.md)) generates one
255
+ list + detail screen per declared `base44.entities` entity. Failures print `ok: false` and `code`
256
+ on stderr (exit 1):
257
+
258
+ - `content_dir_not_base44_starter`: the directory lacks the shared engine the generated files
259
+ import (`missing[]` names the files). Scaffold with `himi init --template base44-starter`.
260
+ - `no_entities_block`: `config.ts` declares no `base44.entities`, so there is nothing to generate.
261
+ - `entity_id_collision`: two entities would generate the same screen id (or one would shadow
262
+ `b44_functions` / `b44_account` / `b44_more`); `entity`, `screenId` and `with` name the clash.
263
+ - `config_unloadable` / `invalid_config`: as for `himi base44 entities` above.
264
+
265
+ On success (`ok: true`, stdout, exit 0) `files[]` gives every generated path with its `outcome`:
266
+ `written`, `unchanged`, or `skipped-edited` (the file was edited by hand and was left alone; pass
267
+ `--force` to overwrite it). A skipped file is not a failure. `orphaned[]`, when present, lists
268
+ generated files for entities no longer declared. They are never deleted.
269
+
252
270
  ## Follow-ups
253
271
 
254
272
  - Migrate failure envelopes in subcommands other than `push`, keeping this contract.
@@ -201,6 +201,16 @@ assembles it from four layers in priority order, **none of which touches the wir
201
201
  4. **offline reject** — the floor that always exists; see the failure UI head-on
202
202
  (`--offline`).
203
203
 
204
+ **Base44 apps have a fifth, beside these.** Base44 reads are `capability.base44.*` calls on the
205
+ host bus, not HTTP, so the layers above never see them. The crawl serves them from the
206
+ `base44.entities` block's `seed:` rows (owned entities, keyed by the physical `Himi<App><Name>`),
207
+ then from `.himi/base44-book/book.json`'s redacted `sampleRows` (adopted entities, keyed by the
208
+ Base44 name). `list` honours `sort` / `skip` / `limit` / `fields`, `filter` matches top-level
209
+ equality only, `get` finds by `id`. An entity neither knows is an error (`http_404`) naming it,
210
+ never `[]`; writes and `functions.invoke` are refused by design. The transport line says
211
+ `+ base44 (N entities …)` when the layer is on. With neither seed nor book it is off, and a
212
+ declared Base44 read answers `null` (the SDK's empty list) as it always did. `--offline` strips it.
213
+
204
214
  **Need your site's real shapes?** `himi test --record` refreshes layer 1 from live reads —
205
215
  `load()` and `onAppear` **only**, read-path-only by construction (GET and read-shaped POST;
206
216
  every write refused at the transport) — then crawls hermetically on what it froze. Explicit
@@ -468,14 +468,14 @@
468
468
  {
469
469
  "name": "EntityHandle",
470
470
  "kind": "interface",
471
- "jsdoc": "",
472
- "decl": "export interface EntityHandle {\n list(opts?: ListOptions): Promise<Base44Row[]>;\n filter(query: JsonObject, opts?: ListOptions): Promise<Base44Row[]>;\n get(id: string): Promise<Base44Row>;\n create(data: JsonObject): Promise<Base44Row>;\n update(id: string, patch: JsonObject): Promise<Base44Row>;\n delete(id: string): Promise<void>;\n bulkCreate(rows: JsonObject[]): Promise<Base44Row[]>;\n}"
471
+ "jsdoc": "/**\n * One entity's verbs. `T` is the row shape: `Base44Row` (untyped) unless the caller names one,\n * e.g. a row type generated by `himi base44 types`. A row type must be a type alias, not an\n * `interface` — only an alias satisfies `Base44Row`'s `JsonObject` index signature.\n */",
472
+ "decl": "export interface EntityHandle<T extends Base44Row = Base44Row> {\n list(opts?: ListOptions): Promise<T[]>;\n filter(query: JsonObject, opts?: ListOptions): Promise<T[]>;\n get(id: string): Promise<T>;\n create(data: Partial<T>): Promise<T>;\n update(id: string, patch: Partial<T>): Promise<T>;\n delete(id: string): Promise<void>;\n bulkCreate(rows: Partial<T>[]): Promise<T[]>;\n}"
473
473
  },
474
474
  {
475
475
  "name": "entities",
476
476
  "kind": "function",
477
- "jsdoc": "/** Typed access to one entity of the configured Base44 app, e.g. `entities(\"Task\").list()`. */",
478
- "decl": "export function entities(name: string): EntityHandle;"
477
+ "jsdoc": "/**\n * Typed access to one entity of the configured Base44 app, e.g. `entities(\"Task\").list()`.\n * Pass a row type to type the rows: `entities<TaskRow>(\"Task\")`. Types only — runtime is identical.\n */",
478
+ "decl": "export function entities<T extends Base44Row = Base44Row>(name: string): EntityHandle<T>;"
479
479
  },
480
480
  {
481
481
  "name": "functions",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1431.0",
3
+ "version": "0.1433.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -71,5 +71,5 @@
71
71
  "artifactId": "himalaya-cli"
72
72
  }
73
73
  },
74
- "falconPackageHash": "cb6ae1657ee30165de2fe500a05d3f531ee3a61071976c6db531172e"
74
+ "falconPackageHash": "f0352e9dd51208b098564413b02976840565ff1539e321ae4ea327f5"
75
75
  }