@wix/himalaya-cli 0.1187.0 → 0.1189.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.
Binary file
@@ -25,7 +25,7 @@ camera — import from @himalaya/camera
25
25
  exports: capturePhoto, pickDocument, scanQR
26
26
  declare: config.ts `capabilities` AND each calling screen's `logicBundle.declaredCapabilities`
27
27
  binary: needs a NATIVE build — iOS NSCameraUsageDescription; Android android.permission.CAMERA …
28
- shells: served by owner-lite; NOT in the branded-lite, brandy, demo-app, player binary
28
+ shells: served by owner-lite, player; `himi dist` adds it to base44, branded-lite, brandy; NOT in the demo-app binary
29
29
  ```
30
30
 
31
31
  Agents: the same data is the `himi_capabilities` MCP tool. `himi sdk <name>` also prints the
@@ -89,8 +89,23 @@ Name the shell your app ships in once, in `himalaya.content.json`, and both `him
89
89
 
90
90
  `himi test` boots every screen's real worker and dispatches its actions. A declared capability
91
91
  answers with its "unavailable" result there, so the crawl checks that your worker handles a
92
- declined camera or a missing position without breaking. In the web preview, camera capture and
93
- geolocation use the browser's own file picker and location prompt.
92
+ declined camera or a missing position without breaking; a second pass then answers the modelled
93
+ ones (camera, location, photos, speech, …) with a SUCCESS result, so the code behind a granted
94
+ permission runs too. Two things in the `--json` report are about capabilities:
95
+
96
+ - a **`capability-undeclared`** finding for every call the screen's gate denied — even one your
97
+ worker caught, which on device looks like a dead button rather than an error;
98
+ - **`capabilityCoverage`**: each `capability.<id>.<verb>` your workers call, and what the crawl
99
+ answers it with — `success` (modelled), `store` (a real session store: `local_database`,
100
+ `secure_storage`), `unavailable-only` (no model yet, so only the declined path runs), or `denied`.
101
+ See [himi-test → Capabilities](himi-test.md#capabilities).
102
+
103
+ The bundle scan only ever **reports**: it never adds a capability to a screen's declaration. The
104
+ per-screen list is the authorization boundary, so a worker shared by two screens cannot widen the
105
+ one that never makes the call.
106
+
107
+ In the web preview, camera capture and geolocation use the browser's own file picker and location
108
+ prompt.
94
109
 
95
110
  ## 5. Ship it
96
111
 
@@ -103,6 +118,14 @@ geolocation use the browser's own file picker and location prompt.
103
118
  entitlements, Android permissions / features) and registers their handlers at boot.
104
119
  `himi capabilities --shell <s>` shows such an id as `yes → dist`, and `himi validate` warns that
105
120
  it "needs a new `himi dist`" — the installed binary cannot call it until you rebuild.
121
+ - **"needs a new binary".** A successful `himi dist` remembers what its binary was built with —
122
+ on the job (`buildCapabilities`) and in your package (`.himi/last-dist.json`). From then on
123
+ `himi push` prints `needs a new binary: <ids> — run himi dist` when the release declares an id
124
+ that build lacks, a following device's draft offer says the same, and `himi validate` compares
125
+ against that build rather than the committed shell.
126
+ - **Never a silent strip.** On iOS, a capability whose entitlement the signing profile cannot grant
127
+ fails the dist job, naming the capability — the binary would otherwise build green and fail on
128
+ the first call. The minted profile's Apple capabilities are derived from the build's own set.
106
129
  - **Preview before you build:** `himi dist --plan` prints what the release declares, what the shell
107
130
  already serves, what the build would add and the exact native delta. No job is started.
108
131
  - **Write the permission prompt.** The OS shows a sentence when the app asks for camera, location,
@@ -6,7 +6,6 @@
6
6
  "notImplementedComponents": [
7
7
  "FieldError",
8
8
  "Picker",
9
- "DatePicker",
10
9
  "Tabs",
11
10
  "Snackbar",
12
11
  "Slider",
@@ -348,22 +347,16 @@
348
347
  "name": "DatePicker",
349
348
  "category": "Form primitives",
350
349
  "propsType": "DatePickerProps",
351
- "bindings": [],
350
+ "bindings": [
351
+ "valuePath"
352
+ ],
352
353
  "dispatches": [
353
354
  {
354
355
  "callback": "onChange",
355
356
  "key": "value",
356
357
  "encoding": "unix-seconds-string",
357
- "note": "Unix SECONDS, string-encoded so the params mirror stays legal. iOS ships `valueUnix` as an int instead; RN ships nothing. The normative key is `value` because that is what three of four arms already use for every other scalar control.",
358
+ "note": "Unix SECONDS, string-encoded so the params mirror stays legal, clamped into [minUnix, maxUnix]. iOS, Android and web conform (iOS shipped `valueUnix` as an int until the SwiftUI-parity DatePicker fix); RN ships nothing. The normative key is `value` because that is what the arms use for every other scalar control.",
358
359
  "divergences": [
359
- {
360
- "arms": [
361
- "ios"
362
- ],
363
- "kind": "wrong-key",
364
- "actual": "sends `valueUnix` as an int instead of the contract's `value`.",
365
- "reason": "Only web conforms. This is the widest split in the vocabulary and the clearest argument for the file."
366
- },
367
360
  {
368
361
  "arms": [
369
362
  "rn"
@@ -382,19 +375,6 @@
382
375
  ]
383
376
  }
384
377
  ],
385
- "notImplemented": {
386
- "props": [
387
- "mode",
388
- "minUnix",
389
- "maxUnix",
390
- "valuePath"
391
- ],
392
- "platforms": [
393
- "ios"
394
- ],
395
- "severity": "styling",
396
- "note": "The picker itself renders as a native date wheel — these four props are inert: `mode: \"time\"` still renders a date wheel; `minUnix`/`maxUnix` do not clamp; `valuePath` does not bind."
397
- },
398
378
  "propsSource": "export interface DatePickerProps {\n valueUnix?: number;\n minUnix?: number;\n maxUnix?: number;\n mode?: \"date\" | \"time\" | \"datetime\";\n onChange?: Action;\n /** \"$service:\" sentinel → ISO-8601 string (or unix seconds); prefills + tracks. */\n valuePath?: string;\n}"
399
379
  },
400
380
  {
@@ -52,7 +52,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
52
52
  | `FormField` | Form primitives | — |
53
53
  | `FieldError` | Form primitives | `textPath` |
54
54
  | `Picker` | Form primitives | — |
55
- | `DatePicker` | Form primitives | — |
55
+ | `DatePicker` | Form primitives | `valuePath` |
56
56
  | `StatefulSection` | State envelopes | — |
57
57
  | `Skeleton` | State envelopes | — |
58
58
  | `FragmentSlot` | Descriptor-shipped dynamic structure | `casePath` |
@@ -116,16 +116,15 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
116
116
 
117
117
  These props are accepted by the schema and pass validation, but the named platform's renderer never reads them — they silently do nothing (no warning, no log). Prefer a prop NOT listed here, or drive the behaviour from a worker.
118
118
 
119
- **Scope: 14 of 84 components, all of them ios-only** — every other component/platform pair reads every prop it decodes. The list splits in two, and the halves mean different things:
119
+ **Scope: 13 of 84 components, all of them ios-only** — every other component/platform pair reads every prop it decodes. The list splits in two, and the halves mean different things:
120
120
 
121
121
  - **`category` (4: `BottomSheet`, `Menu`, `Tooltip`, `Popover`)** — the prop changes the component's *kind*. These genuinely are **not implemented**: an overlay renders always-visible inline, so the component does not do the job its name implies. Check the row's note for a working alternative — for a dismissible sheet it is `presentation: "sheet"` on the navigating `Action`, not `BottomSheet`.
122
- - **`styling` (10: `FieldError`, `Picker`, `DatePicker`, `Tabs`, `Snackbar`, `Slider`, `ActivityIndicator`, `ChipsInput`, `OtpInput`, `Video`)** — the component itself **works**; only the listed prop is inert. `Slider` on iOS is a working slider whose `debounceMs` does not throttle — not a broken `Slider`. Use the component; just do not depend on that one prop.
122
+ - **`styling` (9: `FieldError`, `Picker`, `Tabs`, `Snackbar`, `Slider`, `ActivityIndicator`, `ChipsInput`, `OtpInput`, `Video`)** — the component itself **works**; only the listed prop is inert. `Slider` on iOS is a working slider whose `debounceMs` does not throttle — not a broken `Slider`. Use the component; just do not depend on that one prop.
123
123
 
124
124
  | Component | Unread props | Platform | Kind | Note |
125
125
  |---|---|---|---|---|
126
126
  | `FieldError` | `severity` | ios | styling | `severity: "warning"` still paints `danger` red. |
127
127
  | `Picker` | `optionsPath`, `placeholder` | ios | styling | `optionsPath` does not bind options; `placeholder` is not shown. |
128
- | `DatePicker` | `mode`, `minUnix`, `maxUnix`, `valuePath` | ios | styling | The picker itself renders as a native date wheel — these four props are inert: `mode: "time"` still renders a date wheel; `minUnix`/`maxUnix` do not clamp; `valuePath` does not bind. |
129
128
  | `Tabs` | `style` | ios | styling | The tab bar itself renders — only `style` is inert: `swipe` still renders as top tabs. |
130
129
  | `Snackbar` | `durationMs` | ios | styling | `durationMs` does not control the auto-dismiss timeout. |
131
130
  | `Slider` | `debounceMs` | ios | styling | `debounceMs` does not throttle the onChange stream. |
@@ -821,17 +820,13 @@ export interface DatePickerProps {
821
820
  }
822
821
  ```
823
822
 
824
- > ⚠️ **Implemented, but these props are inert on ios:** `mode`, `minUnix`, `maxUnix`, `valuePath` — The picker itself renders as a native date wheel — these four props are inert: `mode: "time"` still renders a date wheel; `minUnix`/`maxUnix` do not clamp; `valuePath` does not bind.
825
-
826
823
  **Dispatches** — see [What a control sends](#what-a-control-sends-action-payloads):
827
824
 
828
825
  | callback | key | type |
829
826
  |---|---|---|
830
827
  | `onChange` | `value` | unix-seconds-string |
831
828
 
832
- > `onChange` — Unix SECONDS, string-encoded so the params mirror stays legal. iOS ships `valueUnix` as an int instead; RN ships nothing. The normative key is `value` because that is what three of four arms already use for every other scalar control.
833
-
834
- > ⚠️ **`onChange` on ios — DIVERGES:** sends `valueUnix` as an int instead of the contract's `value`. Only web conforms. This is the widest split in the vocabulary and the clearest argument for the file.
829
+ > `onChange` — Unix SECONDS, string-encoded so the params mirror stays legal, clamped into [minUnix, maxUnix]. iOS, Android and web conform (iOS shipped `valueUnix` as an int until the SwiftUI-parity DatePicker fix); RN ships nothing. The normative key is `value` because that is what the arms use for every other scalar control.
835
830
 
836
831
  > ⚠️ **`onChange` on rn — DIVERGES:** dispatches the bare action with no date at all.
837
832
 
@@ -125,6 +125,7 @@ Only **unambiguous** defects, so a green run stays worth trusting:
125
125
  | `poll-action-missing` | error | A `_poll` directive every host will silently ignore — an action the bundle never declares, a malformed `action`, a missing `id`, or a non-positive `intervalMs`. Each ends the same way: a screen that never refreshes, with nothing saying why. Nothing in the descriptor names the polled action, so no static check can see it. (A merely too-small interval is legal — the host clamps it.) |
126
126
  | `no-request-issued` | **warning** | A worker that uses the Wix transport but never fetches — "the screen loaded with sample data." A warning: a conditional or tap-triggered fetch is legitimate. |
127
127
  | `native-decode-failed` | error | Under `--native`, the real Swift/Kotlin decoder rejected the baked descriptor bytes (one bad enum blanks the whole screen on device). |
128
+ | `capability-undeclared` | error | A `capability.<id>.<verb>` call the screen's gate denied — reported from the gate itself, so it is raised **even when the worker catches the rejection** (which on device leaves a control that silently does nothing). Fix: declare the id on that screen's `logicBundle.declaredCapabilities` and in `config.ts` `capabilities`. One per capability per screen. See [Capabilities](#capabilities). |
128
129
  | `journey-failed` | error | A `himi-test.json` journey named an action the screen lacks, threw, or never rendered its `expectText`. |
129
130
  | `inverse-pair-diverges` | **warning** | An action and the action that undoes it do not return to the state the boot settled on — `add` then `remove` leaving a different list, or a counter it forgot to put back. |
130
131
  | `refresh-not-idempotent` | **warning** | `refresh` from a fresh boot settles somewhere other than the boot did, on identical data — usually a refresh that appends instead of replacing. |
@@ -267,6 +268,36 @@ went unproven, it names the gap and pairs it with a himi command or a source edi
267
268
  derived from what actually ran, never homework. Assembly:
268
269
  `tools/himi-cli/src/coverage.ts`.
269
270
 
271
+ ## Capabilities
272
+
273
+ A `capability.*` call is answered twice. The main pass answers every granted one with `null` — the
274
+ device's "unavailable" arm — so a screen's declined-camera / no-position path is checked. The
275
+ capability-success pass then re-crawls each screen whose bundle calls a **modelled** capability
276
+ (audio-record, speech, on-device-ai, calls, media-playback, calendar, files, photos, pdf, share,
277
+ open-url, camera, location) with a success answer shaped exactly like the one its SDK binding
278
+ destructures, so the code behind a granted permission runs too. `local_database` and
279
+ `secure_storage` are served from a real session store on both passes.
280
+
281
+ `--json` carries what each call got as `capabilityCoverage`:
282
+
283
+ ```jsonc
284
+ "capabilityCoverage": {
285
+ "counts": { "success": 2, "store": 1, "unavailable-only": 1, "denied": 0 },
286
+ "calls": {
287
+ "capability.camera.capture": { "arm": "success", "screens": ["notes"] },
288
+ "capability.local_database.getAll": { "arm": "store", "screens": ["notes"] },
289
+ "capability.location.getCurrent": { "arm": "success", "screens": ["notes"] },
290
+ "capability.haptic.play": { "arm": "unavailable-only", "screens": ["notes"] }
291
+ }
292
+ }
293
+ ```
294
+
295
+ `unavailable-only` means no model exists yet, so only the declined path of that call has run;
296
+ `denied` means no screen running the call grants its id (each denial is also a
297
+ `capability-undeclared` finding), and `deniedOn` lists screens that deny a call others allow. The
298
+ map is static — it says what a call would be answered with, not that a tap reached it; the gap
299
+ ledger says that.
300
+
270
301
  ## Writes to Wix
271
302
 
272
303
  Every run lists what the app **writes** to the Wix site, per screen and action — the inventory an
@@ -326,7 +326,7 @@ one never blocks a rollout — the dist routes just `501` with the reason.
326
326
  | `HIMI_DIST_ALLOWED_XCODE_VERSIONS` | comma-separated Xcode versions a caller may pin (`xcodeVersion` in the request body); unset ⇒ the built-in list. Nothing upstream constrains this field — the Job Service client validates it as `z.string().max(50)` — so without the allow-list it is caller-supplied text choosing a toolchain on a shared pool. A blank value reads as unset (the built-in list), never as "allow anything". |
327
327
  | `HIMI_DIST_APPLE_SIGNING` | **Minted iOS signing is ON by default** — set `0`/`false`/`off` to disable it and go back to scavenging a profile off the pool. Rarely what you want: minting ships the profile and the certificate with the job, so nothing has to pre-exist on the machine. (Earlier text here cited `0 profile(s) available` on four machines as a reason to keep minting on — that reading came from a TRUNCATED survey and is retracted below; build 992367 logged 352 profiles. The recommendation is unchanged, the evidence for it was wrong.) |
328
328
  | `HIMI_DIST_APPLE_API_KEY_NAME` | Apple account the identity is minted under; unset ⇒ `wix-enterprise` |
329
- | `HIMI_DIST_APPLE_CAPABILITIES` | COMPLETE capability set for a minted id (the sync call replaces rather than merges); unset ⇒ push + associated domains |
329
+ | `HIMI_DIST_APPLE_CAPABILITIES` | Apple capability types to ADD to every minted id. The set itself is derived per job: push + associated domains (the template baseline) plus the Apple capability of every entitlement in the iOS plan of the job's `buildCapabilities` (`appleCapabilitiesForBuild`). This variable only **widens** that derived set (e.g. `APP_GROUPS` for a widget) — it can never remove a derived capability, because the sync call replaces rather than merges and a narrower list would switch off what the content asked for. Unset ⇒ the derived set alone |
330
330
  | `QA_DIST_SLACK_CHANNEL` | Post QA build results to a channel instead of DMing the requester. Worth setting: a DM is unreadable by anything but that person, so a channel is what makes build results scriptable (and visible to the team). |
331
331
 
332
332
  Both per-credential caps are **clamped into the range that can be enforced**, and the two ranges
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1187.0",
3
+ "version": "0.1189.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": [
@@ -67,5 +67,5 @@
67
67
  "artifactId": "himalaya-cli"
68
68
  }
69
69
  },
70
- "falconPackageHash": "ffb3ffbafe40c1d9eed8d08db97af4d46cc1506f60fc49e11cf7df80"
70
+ "falconPackageHash": "6cdd1d60a3c8d77a450fac977716f1d7ded301af4e074c978148513e"
71
71
  }