@wix/himalaya-cli 0.1359.0 → 0.1361.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.
@@ -1955,6 +1955,12 @@ export interface PdfViewProps {
1955
1955
  zoomable?: boolean;
1956
1956
  allowShare?: boolean;
1957
1957
  }
1958
+ /**
1959
+ * Inline video player. Needs the `media_playback` capability: the player lives in the native
1960
+ * media module, which a binary links only when the app declares `media_playback` (in
1961
+ * `capabilities` or `capabilityEnvelope`). Without it the component draws a sized placeholder,
1962
+ * and `himi validate` rejects a screen that uses it.
1963
+ */
1958
1964
  export interface VideoProps {
1959
1965
  url?: string;
1960
1966
  poster?: string;
@@ -2023,7 +2029,12 @@ export interface MediaPlayerAppearance {
2023
2029
  controlSize?: "compact" | "regular" | "large";
2024
2030
  cornerStyle?: "none" | "small" | "medium" | "large" | "pill";
2025
2031
  }
2026
- /** RFC 0044 — the server-driven audio/video/podcast/provider player. */
2032
+ /**
2033
+ * RFC 0044 — the server-driven audio/video/podcast/provider player. Needs the `media_playback`
2034
+ * capability: the player lives in the native media module, which a binary links only when the
2035
+ * app declares `media_playback` (in `capabilities` or `capabilityEnvelope`). Without it the
2036
+ * component draws a sized placeholder, and `himi validate` rejects a screen that uses it.
2037
+ */
2027
2038
  export interface MediaPlayerProps {
2028
2039
  sessionId?: string;
2029
2040
  items?: MediaItem[];
@@ -2075,7 +2086,10 @@ export interface MediaPlayerProps {
2075
2086
  * Lottie/Rive vector animation (motion grammar Batch 4). `source` is an https
2076
2087
  * URL to the animation JSON or the inline JSON string; it ships OTA. Native:
2077
2088
  * lottie-ios / lottie-compose; web: lottie-web. Reduced motion → static last
2078
- * frame. Needs an explicit `height` to compose in scroll containers.
2089
+ * frame. Needs an explicit `height` to compose in scroll containers. Needs the `animation`
2090
+ * capability: Lottie lives in the native animation module, which a binary links only when the
2091
+ * app declares `animation` (in `capabilities` or `capabilityEnvelope`). Without it the component
2092
+ * draws a sized placeholder, and `himi validate` rejects a screen that uses it.
2079
2093
  */
2080
2094
  export interface LottieProps {
2081
2095
  source?: string;
@@ -2086,7 +2100,10 @@ export interface LottieProps {
2086
2100
  }
2087
2101
  /** A Rive animation (motion grammar, Phase-2). `source` = https .riv URL shipped OTA;
2088
2102
  * `artboard`/`stateMachine` select within it. Native: rive-ios/rive-android; web:
2089
- * @rive-app/canvas (dynamic import). Reduced motion → static first frame. */
2103
+ * @rive-app/canvas (dynamic import). Reduced motion → static first frame. Needs the `animation`
2104
+ * capability: Rive lives in the native animation module, which a binary links only when the app
2105
+ * declares `animation` (in `capabilities` or `capabilityEnvelope`). Without it the component
2106
+ * draws a sized placeholder, and `himi validate` rejects a screen that uses it. */
2090
2107
  export interface RiveProps {
2091
2108
  source?: string;
2092
2109
  artboard?: string;
package/dist/cli.mjs CHANGED
@@ -28057,6 +28057,7 @@ __export(capabilities_exports, {
28057
28057
  capabilityConfigFindings: () => capabilityConfigFindings,
28058
28058
  capabilityInfo: () => capabilityInfo,
28059
28059
  capabilityIssues: () => capabilityIssues,
28060
+ capabilityRemedy: () => capabilityRemedy,
28060
28061
  contentShell: () => contentShell,
28061
28062
  envelopeSubsetIssues: () => envelopeSubsetIssues,
28062
28063
  formatCapability: () => formatCapability,
@@ -28065,6 +28066,7 @@ __export(capabilities_exports, {
28065
28066
  isShell: () => isShell,
28066
28067
  lastDistFromJob: () => lastDistFromJob,
28067
28068
  needsBinaryNotice: () => needsBinaryNotice,
28069
+ notInBinaryReason: () => notInBinaryReason,
28068
28070
  readLastDist: () => readLastDist,
28069
28071
  shellDistAdds: () => shellDistAdds,
28070
28072
  shellServes: () => shellServes,
@@ -28109,6 +28111,8 @@ function capabilityInfo(rawId, sdkRef = []) {
28109
28111
  const pkgs = PACKAGES[id] ?? [...new Set((CAPABILITY_IMPLIES[id] ?? []).flatMap((i2) => PACKAGES[i2] ?? []))];
28110
28112
  const ios = iosPlanFor([id]);
28111
28113
  const android = androidPlanFor([id]);
28114
+ const modules = modulesOf(id);
28115
+ const refusal = distAddRefusal(id);
28112
28116
  return {
28113
28117
  id,
28114
28118
  import: [...pkgs],
@@ -28118,9 +28122,30 @@ function capabilityInfo(rawId, sdkRef = []) {
28118
28122
  ios: { info: ios.info, entitlements: ios.entitlements },
28119
28123
  android: { permissions: android.permissions, features: android.features.map((f2) => f2.name) },
28120
28124
  shells: Object.fromEntries(SHELLS.map((s2) => [s2, shellSupport(s2, id)])),
28121
- declare: DECLARE_HINT
28125
+ declare: DECLARE_HINT,
28126
+ description: summaryOf2(pkgs, sdkRef),
28127
+ implies: [...CAPABILITY_IMPLIES[id] ?? []],
28128
+ modules,
28129
+ components: Object.entries(COMPONENT_MODULES).filter(([, m2]) => modules.some((x2) => x2.id === m2)).map(([component]) => component).sort(),
28130
+ distAddable: refusal === null,
28131
+ distAddRefusal: refusal
28122
28132
  };
28123
28133
  }
28134
+ function modulesOf(id) {
28135
+ return NATIVE_MODULES.filter((m2) => {
28136
+ const caps = capabilitiesOfModule(m2);
28137
+ return caps.ios.includes(id) || caps.android.includes(id);
28138
+ }).map((m2) => ({
28139
+ id: m2.id,
28140
+ ...m2.ios ? { ios: m2.ios.product } : {},
28141
+ ...m2.android ? { android: m2.android.project } : {}
28142
+ }));
28143
+ }
28144
+ function summaryOf2(pkgs, sdkRef) {
28145
+ const d2 = pkgs.map((p2) => sdkRef.find((s2) => s2.name === p2)?.description ?? "").find((x2) => x2.trim()) ?? "";
28146
+ const first = /^[\s\S]*?[.!?](?=\s|$)/.exec(d2.trim())?.[0] ?? d2.trim();
28147
+ return first.replace(/\s+/g, " ").slice(0, 200);
28148
+ }
28124
28149
  function allCapabilities(sdkRef = []) {
28125
28150
  return KNOWN_CAPABILITY_IDS.map((id) => capabilityInfo(id, sdkRef));
28126
28151
  }
@@ -28179,22 +28204,53 @@ function needsBinaryNotice(json6) {
28179
28204
  function noImportLabel(id) {
28180
28205
  return BRIDGE_CAPABILITY_IDS.includes(id) || id === "wix_platform" ? "a bridge capability (no @himalaya import of its own)" : "declare-only (no @himalaya import of its own)";
28181
28206
  }
28182
- function formatCapability(c2) {
28207
+ function notInBinaryReason(c2, shell) {
28208
+ const s2 = SHELL_CAPABILITIES[shell];
28209
+ if (s2 && !s2.extensible) {
28210
+ return `${s2.shared ? "one shared binary" : "a fixed binary"} \u2014 only a new ${shell} build adds it; a content package cannot`;
28211
+ }
28212
+ return c2.distAddRefusal ? `a dist build cannot add it: ${c2.id} ${c2.distAddRefusal}` : "a dist build would add it";
28213
+ }
28214
+ function capabilityRemedy(c2, shell) {
28215
+ const sel = shell ? SHELL_CAPABILITIES[shell] : void 0;
28216
+ if (shell && sel && !sel.extensible && c2.shells[shell] !== "serves") {
28217
+ return `${shell} is ${sel.shared ? "one shared binary" : "a fixed binary"} \u2014 only a new ${shell} build adds ${c2.id}; \`himi dist\` and an OTA push cannot. Or pick a shell whose binary already serves it (${SHELLS.filter((x2) => c2.shells[x2] === "serves").join(", ") || "none today"}).`;
28218
+ }
28219
+ if (!c2.distAddable) {
28220
+ const serves = SHELLS.filter((s2) => c2.shells[s2] === "serves");
28221
+ return `pick a shell whose binary already serves it (${serves.join(", ") || "none today"}) \u2014 a dist build cannot add it (${c2.id} ${c2.distAddRefusal}).`;
28222
+ }
28223
+ if (!c2.needsNativeBuild) return "declare it; `himi dist` registers it in an extensible shell that does not yet.";
28224
+ return "a NEW binary (`himi dist`) \u2014 declare it in config.ts `capabilities`, or list it in `capabilityEnvelope` to build it in now and turn it on later over the air. An OTA push alone cannot add it.";
28225
+ }
28226
+ function formatCapability(c2, shell) {
28183
28227
  const lines = [`${c2.id} \u2014 ${c2.import.length ? `import from ${c2.import.join(" or ")}` : noImportLabel(c2.id)}`];
28228
+ if (c2.description) lines.push(` about: ${c2.description}`);
28184
28229
  if (c2.exports.length) lines.push(` exports: ${c2.exports.join(", ")}`);
28185
28230
  if (c2.verbs.length) lines.push(` verbs: ${c2.verbs.map((v2) => `capability.${c2.id}.${v2}`).join(", ")}`);
28231
+ if (c2.implies.length) lines.push(` implies: ${c2.implies.join(", ")} (declaring ${c2.id} grants it)`);
28186
28232
  lines.push(` declare: ${c2.declare}`);
28187
28233
  const iosKeys = [...Object.keys(c2.ios.info), ...Object.keys(c2.ios.entitlements)];
28188
28234
  const androidKeys = [...c2.android.permissions, ...c2.android.features];
28189
28235
  lines.push(
28190
28236
  c2.needsNativeBuild ? ` binary: needs a NATIVE build \u2014 iOS ${iosKeys.join(", ") || "(nothing)"}; Android ${androidKeys.join(", ") || "(nothing)"}. An OTA push cannot add it.` : " binary: no native footprint \u2014 works in any shell that registers its handler."
28191
28237
  );
28238
+ lines.push(
28239
+ c2.modules.length ? ` module: ${c2.modules.map((m2) => `${m2.id} (${[m2.ios && `iOS ${m2.ios}`, m2.android && `Android ${m2.android}`].filter(Boolean).join(", ")})`).join("; ")}` + (c2.components.length ? ` \u2014 also needed by the ${c2.components.join(", ")} component${c2.components.length === 1 ? "" : "s"}` : "") : " module: none \u2014 its native code is in the core runtime"
28240
+ );
28241
+ lines.push(` dist: ${c2.distAddable ? "a `himi dist` build can add it to an extensible shell" : `cannot be added at dist \u2014 ${c2.id} ${c2.distAddRefusal}`}`);
28192
28242
  const serves = SHELLS.filter((s2) => c2.shells[s2] === "serves");
28193
28243
  const adds = SHELLS.filter((s2) => c2.shells[s2] === "dist-adds");
28194
28244
  const not = SHELLS.filter((s2) => c2.shells[s2] === "not-in-binary");
28245
+ const byReason = /* @__PURE__ */ new Map();
28246
+ for (const s2 of not) {
28247
+ const r2 = notInBinaryReason(c2, s2);
28248
+ byReason.set(r2, [...byReason.get(r2) ?? [], s2]);
28249
+ }
28195
28250
  lines.push(
28196
- ` shells: served by ${serves.join(", ") || "none"}` + (adds.length ? `; \`himi dist\` adds it to ${adds.join(", ")}` : "") + (not.length ? `; NOT in the ${not.join(", ")} binary` : "")
28251
+ ` shells: served by ${serves.join(", ") || "none"}` + (adds.length ? `; \`himi dist\` adds it to ${adds.join(", ")}` : "") + [...byReason].map(([r2, shells]) => `; NOT in the ${shells.join(", ")} binary (${r2})`).join("")
28197
28252
  );
28253
+ if (adds.length || not.length) lines.push(` remedy: ${capabilityRemedy(c2, shell)}`);
28198
28254
  return lines;
28199
28255
  }
28200
28256
  function formatCapabilityList(caps, shell) {
@@ -153532,7 +153588,7 @@ function rawTools() {
153532
153588
  {
153533
153589
  name: "himi_capabilities",
153534
153590
  title: "Native capabilities (camera, location, push, \u2026)",
153535
- description: "The native capabilities a worker can use: the @himalaya import, where to declare it (config.ts `capabilities` + the screen's logicBundle.declaredCapabilities), what the binary needs (usage strings, permissions), and which `himi dist --shell` binaries serve it. A capability with a native footprint only arrives with a new binary \u2014 an OTA push cannot add it. Offline.",
153591
+ description: "The native capabilities a worker can use: the @himalaya import, where to declare it (config.ts `capabilities` + the screen's logicBundle.declaredCapabilities), what the binary needs (usage strings, permissions), the native module it links (`modules`) and the components that need that module (`components`), and which `himi dist --shell` binaries serve it. A capability with a native footprint only arrives with a new binary \u2014 an OTA push cannot add it; list it in config.ts `capabilityEnvelope` to build it in now and turn it on later. `distAddable` / `distAddRefusal` say whether a `himi dist` build can add it to an extensible shell, and why not. Offline.",
153536
153592
  annotations: LEARN,
153537
153593
  inputSchema: z3.object({
153538
153594
  id: z3.string().optional().describe("One capability, e.g. `camera`. Omit for all."),
@@ -178517,7 +178573,7 @@ Guides in your browser: ${idx.portalUrl}`);
178517
178573
  return 0;
178518
178574
  }
178519
178575
  case "capabilities": {
178520
- const { allCapabilities: allCapabilities2, capabilityInfo: capabilityInfo2, contentShell: contentShell2, formatCapability: formatCapability2, formatCapabilityList: formatCapabilityList2, isShell: isShell2, SHELLS: SHELLS2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
178576
+ const { allCapabilities: allCapabilities2, capabilityInfo: capabilityInfo2, contentShell: contentShell2, formatCapability: formatCapability2, formatCapabilityList: formatCapabilityList2, isShell: isShell2, notInBinaryReason: notInBinaryReason2, SHELLS: SHELLS2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
178521
178577
  const sdkRef = loadSdkReference();
178522
178578
  const shell = contentShell2(contentDir(flags), str9(flags.shell));
178523
178579
  if (shell && !isShell2(shell)) {
@@ -178535,8 +178591,11 @@ Guides in your browser: ${idx.portalUrl}`);
178535
178591
  print({ ok: true, ...c2, ...shell ? { shell, servedByShell: c2.shells[shell] === "serves" } : {} });
178536
178592
  return 0;
178537
178593
  }
178538
- for (const line of formatCapability2(c2)) io2.log(line);
178539
- if (shell) io2.log(` ${shell}: ${c2.shells[shell] === "serves" ? "serves it" : "does NOT serve it \u2014 its binary is not built with it"}`);
178594
+ for (const line of formatCapability2(c2, shell ?? void 0)) io2.log(line);
178595
+ if (shell) {
178596
+ const v2 = c2.shells[shell];
178597
+ io2.log(` ${shell}: ${v2 === "serves" ? "serves it" : v2 === "dist-adds" ? "does not serve it yet \u2014 `himi dist` adds it" : `does NOT serve it \u2014 ${notInBinaryReason2(c2, shell)}`}`);
178598
+ }
178540
178599
  return 0;
178541
178600
  }
178542
178601
  const caps = allCapabilities2(sdkRef);
@@ -17,6 +17,12 @@ A content package configures itself in three layers:
17
17
  prefix of `id`), `ports: { devServer, web }`, `schemaPackageRange: { min, max }`.
18
18
  - **Theming:** `designTokensPath` → tokens.json (the kit is chosen *inside* tokens.json's `kit` block).
19
19
  - `capabilities: Capability[]` — the app's declared native capabilities.
20
+ - `capabilityEnvelope?: Capability[]` — the native BUILD envelope, a superset of `capabilities`. The
21
+ binary is built for `capabilities ∪ capabilityEnvelope` (usage strings, entitlements, permissions and
22
+ the native modules those capabilities link), so a later release can turn an envelope capability on
23
+ over the air by widening `capabilities` — within the envelope only; beyond it needs a new binary.
24
+ `capabilities ⊆ capabilityEnvelope` is enforced (`himi validate` and `defineApp` error on an id
25
+ outside it). Omitted ⇒ defaults to `capabilities`. `himi docs capabilities` has the details.
20
26
  - `navigation: NavigationConfig`.
21
27
  - `entities: Record<string, EntityConfig>` — **required**, and `entities: {}` is the normal value.
22
28
  Each entry is a build-time DOMAIN entity and its cache materialization shape (`{ file }` or
@@ -115,6 +121,12 @@ with a native footprint only arrives with a new binary. `himi docs capabilities`
115
121
  loop — discover, declare, validate, ship — and `himi capabilities <id>` shows the import and which
116
122
  shells' binaries serve it.
117
123
 
124
+ Declared capabilities also decide which native modules link into the binary: a capability the build
125
+ does not include answers `module_not_linked` on device. Some components need one — `Video`,
126
+ `MediaPlayer` and `CarBrowse` need `media_playback`, `Lottie` and `Rive` need `animation` —
127
+ and `himi validate` errors on a screen that uses one the app does not declare.
128
+ `presigned_upload.putFile` also requires `files` on the calling screen.
129
+
118
130
  App-level capability vocabulary: `camera`, `barcode_scanner`, `biometrics`, `secure_storage`, `share`, `push`, `file_picker`, `files`, `open_url`, `pdf`, `clipboard`, `theme`, `photos`, `presigned_upload`, `ui`, `calendar`, `contacts`, `tasks`, `audio_record`, `audio_record_background`, `calls`, `location`, `location_background`, `motion`, `health`, `media_playback`, `webview`, `browser_auth`, `haptic`, `himi_apps`, `iap`, `keyboard`, `wix_cashier`, `live_activity`, `local_database`, `speech`, `speaker_diarization`, `system_intents`, `on_device_ai`, `app_info`, `release_control`, `godot`, `base44`, `animation`.
119
131
 
120
132
  ## What `himi validate` checks (7 passes)
@@ -59,6 +59,40 @@ The screen list is enforced at runtime: a call the screen did not declare is **d
59
59
  so it must name every capability the app uses. A background variant (`location_background`,
60
60
  `audio_record_background`) is declared instead of — and grants — its foreground capability.
61
61
 
62
+ ### Headroom for later: `capabilityEnvelope`
63
+
64
+ `capabilities` is what the app serves today, and by default it is also what the binary is built
65
+ for. To be able to turn a capability on later **without a new binary**, list it in
66
+ `capabilityEnvelope` as well — the native build envelope, a superset of `capabilities`:
67
+
68
+ ```ts
69
+ // config.ts — the binary carries camera's usage string and permission; the app serves only push today
70
+ capabilities: ["push"],
71
+ capabilityEnvelope: ["push", "camera"],
72
+ ```
73
+
74
+ - The binary is built for `capabilities ∪ capabilityEnvelope`: every id in the envelope gets its
75
+ usage strings, entitlements, permissions and native module, **where applicable** (ids such as
76
+ `secure_storage` or `share` add none of these), even before `capabilities` lists it.
77
+ - `capabilities ⊆ capabilityEnvelope` is enforced when `capabilityEnvelope` is **present**:
78
+ `himi validate` (and `defineApp`) reports an error for an id in `capabilities` that the envelope
79
+ lacks, because no install would carry its
80
+ native config. A background variant in the envelope covers its foreground capability.
81
+ - Turning a capability on over the air means widening `capabilities` **within** the envelope and
82
+ pushing. Going beyond the envelope still needs a new binary and a store resubmission.
83
+ - Omit `capabilityEnvelope` and it defaults to `capabilities` — the smallest binary.
84
+
85
+ ### Capabilities that need another capability
86
+
87
+ - **`presigned_upload.putFile` requires `files`.** `putFile` uploads a file from the app's document
88
+ store, so the screen that calls it must declare `files` too (and so must `config.ts`). Without
89
+ it the worker gate refuses `capability.presigned_upload.putFile` on iOS, macOS, Android and the
90
+ web. Declaring `presigned_upload` grants none of the files surface, and `put` (bytes the worker
91
+ already holds) needs only `presigned_upload`.
92
+ - **Some components need a capability** — `Video`, `MediaPlayer`, `CarBrowse`, `Lottie` and
93
+ `Rive` render through a native module, so the app must declare the capability that links
94
+ it. See [Components that need a capability](#components-that-need-a-capability).
95
+
62
96
  ### Local database and tenant switches
63
97
 
64
98
  On native hosts `local_database` is also scoped by the host's active tenant. On a multi-tenant host such as Himi Player, a worker is stamped with the tenant that was active when its boot started. The
@@ -87,10 +121,14 @@ The `capabilities` pass reports:
87
121
  - an **unknown id** (a typo such as `camara`) — an error, with a suggestion;
88
122
  - a bundle that **calls a capability its screen does not declare**;
89
123
  - a capability a screen declares that **`config.ts` `capabilities` is missing**;
90
- - when the app names its shell, a capability **that shell's binary does not serve**.
91
-
92
- An undeclared call is a warning, and an error under `--strict`. The last two describe the binary
93
- rather than your content, so they stay warnings — except a mismatch with the shared Himi Player
124
+ - when the app names its shell, a capability **that shell's binary does not serve**;
125
+ - an id in `capabilities` that a **present** `capabilityEnvelope` is missing — an error (omitting
126
+ the field defaults it to `capabilities`, so it can never trip);
127
+ - a screen using a **component whose native module the app does not link** (`Video` without
128
+ `media_playback`, `Lottie` without `animation`, …) — an error.
129
+
130
+ An undeclared call is a warning, and an error under `--strict`. The missing app-level id and the
131
+ shell mismatch describe the binary rather than your content, so they stay warnings — except a mismatch with the shared Himi Player
94
132
  binary, which is always an error: one binary hosts every app, and no content change can add to it.
95
133
 
96
134
  Name the shell your app ships in once, in `himalaya.content.json`, and both `himi validate` and
@@ -137,9 +175,18 @@ prompt.
137
175
  it "needs a new `himi dist`" — the installed binary cannot call it until you rebuild.
138
176
  - **"needs a new binary".** A successful `himi dist` remembers what its binary was built with —
139
177
  on the job (`buildCapabilities`) and in your package (`.himi/last-dist.json`). From then on
140
- `himi push` prints `needs a new binary: <ids> — run himi dist` when the release declares an id
141
- that build lacks, a following device's draft offer says the same, and `himi validate` compares
142
- against that build rather than the committed shell.
178
+ `himi validate` compares against that build rather than the committed shell, and `himi push`
179
+ checks every release against it:
180
+ - a release that declares a **permission-bearing** capability (an iOS usage string or
181
+ entitlement, an Android permission — camera, location, push, …) the last dist lacks is
182
+ **refused**: the push fails with 409 `binary_capability_gap`, naming the missing ids and each
183
+ platform's last dist that lacks them. An over-the-air push cannot add an OS permission, and on
184
+ iOS asking for one whose purpose string is missing kills the app. Run `himi dist` first, or
185
+ pass `--allow-binary-gap` when every installed binary already carries it (the release records
186
+ `allowedBinaryGap`). `himi push --only` and `himi patch` refuse the same way;
187
+ - a gap in a **permission-free** capability (share, clipboard, …) does not block: the push
188
+ prints `needs a new binary: <ids> — run himi dist`, and a following device's draft offer says
189
+ the same.
143
190
  - **Never a silent strip.** On iOS, a capability whose entitlement the signing profile cannot grant
144
191
  fails the dist job, naming the capability — the binary would otherwise build green and fail on
145
192
  the first call. The minted profile's Apple capabilities are derived from the build's own set.
@@ -150,11 +197,88 @@ prompt.
150
197
  attach it to an expense." }` — or the build uses a generic one, which App Review often rejects.
151
198
  - **What a build cannot add.** `player` (Himi Player — one binary for every app) and `demo-app` are
152
199
  fixed: a release declaring an id they do not serve is refused with `shell_capability_mismatch`,
153
- naming shells that can carry it. On an extensible shell, an id with no core handler, or one that
154
- needs wiring a build cannot do (`local_database` needs the app's data schema installed first,
155
- `live_activity` a widget extension target, `base44` / `godot` an opt-in SDK, `wix_platform` boot
156
- configuration), is refused with `unsupported_capability` and the reason. `health` is addable:
157
- the build links HealthKit and Health Connect, and `permissionCopy.health` is the iOS purpose
158
- string and the Health Connect rationale. A shell that does not carry a capability's usage
159
- string can crash the app at the permission prompt, which is why both the build and
160
- `himi validate` check before you ship.
200
+ naming shells that can carry it. On an extensible shell, a build links any capability whose
201
+ native module the platform ships (see [Native modules](#6-native-modules-what-a-capability-links))
202
+ and installs its handlers at boot. What it cannot do is the per-app **boot wiring** a few
203
+ capabilities need beyond that registration, so these are refused with `unsupported_capability`
204
+ and the reason:
205
+
206
+ | Id | Why a build cannot add it |
207
+ |---|---|
208
+ | `local_database` | the shell has to install the app's data schema first — registering it without one would store declared-encrypted entities in plaintext |
209
+ | `live_activity` | needs a WidgetKit extension target in the shell's project |
210
+ | `wix_platform` | the shell's boot has to configure it with a Wix API client and launch services |
211
+ | `himi_apps` | a multi-app host surface (Himi Player), not something one app's binary serves |
212
+ | `app_info` | the shell's boot has to configure it with the app id it reports |
213
+ | `system_intents` | the shell has to wire the system-intents host (app identity, deep-link routing, refresh) |
214
+ | `release_control` | the shell's boot has to configure it with the app id whose releases it switches |
215
+ | `base44` | the shell's boot has to bind the Base44 app it talks to; the module links, the binding is the shell's |
216
+ | `godot` | needs the shell's game surface view and, on Android, the shell's pick of the engine's C++ runtime |
217
+
218
+ An id with no handler on both iOS and Android is refused the same way. Everything else with a
219
+ native module is addable — `health` (the build links HealthKit and Health Connect, and
220
+ `permissionCopy.health` is the iOS purpose string and the Health Connect rationale), `iap`,
221
+ `calls` (the WebRTC engine), `animation`, `media_playback` and the rest. A shell that does not
222
+ carry a capability's usage string can crash the app at the permission prompt, which is why both
223
+ the build and `himi validate` check before you ship.
224
+
225
+ ## 6. Native modules: what a capability links
226
+
227
+ A capability whose native code pulls in a permission-gated OS framework or a third-party library
228
+ (HealthKit, StoreKit, WebRTC, Lottie and Rive, the media player, …) lives in its own native module.
229
+ **The capabilities your app declares decide which native code is linked into its binary**: a
230
+ build links a module only when its build set — `capabilities ∪ capabilityEnvelope`, plus what
231
+ `himi dist` bakes in — contains a capability the module serves. Everything else stays out, so the
232
+ binary carries neither the code nor the store-review footprint of a framework it does not use.
233
+
234
+ | Module | Linked by |
235
+ |---|---|
236
+ | animation | `animation` |
237
+ | audio (iOS) | `audio_record`, `audio_record_background` |
238
+ | base44 | `base44` |
239
+ | biometrics | `biometrics` |
240
+ | calendar | `calendar` |
241
+ | calls | `calls` |
242
+ | contacts | `contacts` |
243
+ | godot | `godot` |
244
+ | health | `health` |
245
+ | iap | `iap` (and `wix_cashier` on iOS) |
246
+ | live_activity | `live_activity` |
247
+ | location | `location`, `location_background` |
248
+ | location_background (iOS) | `location_background` |
249
+ | media | `media_playback` |
250
+ | motion | `motion` |
251
+ | on_device_ai (iOS) | `on_device_ai` |
252
+ | push | `push` |
253
+ | speech (iOS) | `speech` |
254
+ | tasks (iOS) | `tasks` |
255
+
256
+ What that means for a worker:
257
+
258
+ - **Undeclared means not linked.** The `capability.<id>.*` verbs always exist in the core, so the
259
+ declaration gate and the call shape are the same in every binary. In a binary that does not link
260
+ the module, the verbs answer `module_not_linked`: a permission check reports `denied` with
261
+ `reason: "module_not_linked"`, and other verbs reject with `unavailable: module_not_linked`.
262
+ The fix is to declare the capability and ship a new binary — not to retry.
263
+ - **Treat it like any other declined capability.** `himi test` already exercises the
264
+ unavailable path; a worker that handles a denied camera handles an unlinked module too.
265
+
266
+ ### Components that need a capability
267
+
268
+ A few components draw through a native module. A screen that uses one in an app whose build set
269
+ does not link that module would render a component whose library is not in the binary, so
270
+ `himi validate` reports an **error** naming the component and the capabilities that would link it.
271
+
272
+ | Component | Needs | Without it |
273
+ |---|---|---|
274
+ | `Video` | `media_playback` | sized placeholder |
275
+ | `MediaPlayer` | `media_playback` | sized placeholder |
276
+ | `CarBrowse` — the Android Auto media browse tree | `media_playback` | no car media surface |
277
+ | `Lottie` | `animation` | sized placeholder |
278
+ | `Rive` | `animation` | sized placeholder |
279
+
280
+ `animation` has no verbs and no permission: it exists only so the build links Lottie and Rive.
281
+ Add it to `capabilityEnvelope` instead of `capabilities` if a later release may start using
282
+ those components. A release whose screens use one of these components also requires the
283
+ capability from its host, so a Player binary that does not link the module refuses the release
284
+ instead of drawing placeholders.
@@ -1212,6 +1212,7 @@
1212
1212
  "name": "Video",
1213
1213
  "category": "Cross-cutting & media",
1214
1214
  "propsType": "VideoProps",
1215
+ "requiresCapability": "media_playback",
1215
1216
  "bindings": [],
1216
1217
  "notImplemented": {
1217
1218
  "props": [
@@ -1261,6 +1262,7 @@
1261
1262
  "name": "MediaPlayer",
1262
1263
  "category": "Cross-cutting & media",
1263
1264
  "propsType": "MediaPlayerProps",
1265
+ "requiresCapability": "media_playback",
1264
1266
  "bindings": [
1265
1267
  "itemsPath"
1266
1268
  ],
@@ -1320,6 +1322,7 @@
1320
1322
  "name": "Lottie",
1321
1323
  "category": "Rich vector & celebration (motion grammar Batch 4)",
1322
1324
  "propsType": "LottieProps",
1325
+ "requiresCapability": "animation",
1323
1326
  "bindings": [],
1324
1327
  "propsSource": "export interface LottieProps {\n source?: string;\n loop?: boolean;\n autoplay?: boolean;\n speed?: number;\n height?: number;\n}"
1325
1328
  },
@@ -1327,6 +1330,7 @@
1327
1330
  "name": "Rive",
1328
1331
  "category": "Rich vector & celebration (motion grammar Batch 4)",
1329
1332
  "propsType": "RiveProps",
1333
+ "requiresCapability": "animation",
1330
1334
  "bindings": [],
1331
1335
  "propsSource": "export interface RiveProps {\n source?: string;\n artboard?: string;\n stateMachine?: string;\n autoplay?: boolean;\n height?: number;\n}"
1332
1336
  },
@@ -2012,6 +2012,8 @@ export interface VideoProps {
2012
2012
  }
2013
2013
  ```
2014
2014
 
2015
+ > **Needs the `media_playback` capability.** It renders through a native module the binary links only when `config.ts` declares `media_playback` (in `capabilities` or `capabilityEnvelope`). Without it the component draws a sized placeholder, and `himi validate` rejects a screen that uses it.
2016
+
2015
2017
  > ⚠️ **Implemented, but these props are inert on ios:** `poster` — `poster` is not shown.
2016
2018
 
2017
2019
  #### PdfView
@@ -2084,6 +2086,8 @@ export interface MediaPlayerProps {
2084
2086
  }
2085
2087
  ```
2086
2088
 
2089
+ > **Needs the `media_playback` capability.** It renders through a native module the binary links only when `config.ts` declares `media_playback` (in `capabilities` or `capabilityEnvelope`). Without it the component draws a sized placeholder, and `himi validate` rejects a screen that uses it.
2090
+
2087
2091
  > ⚠️ **Not implemented on ios:** `autoplayVisibilityThreshold`, `preload`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` — `autoplayVisibilityThreshold` is ignored (whenVisible = once on appear). `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render.
2088
2092
 
2089
2093
  > ⚠️ **Not implemented on android:** `externalPlayback`, `appearance`, `allowCellular`, `preload`, `playbackRates`, `showQueue`, `showChapters`, `showTranscript` — `externalPlayback` is forced off. `appearance` is ignored. `allowCellular` is ignored. `preload` is ignored. `playbackRates` is ignored, and the queue/chapters/transcript panels never render.
@@ -2104,6 +2108,8 @@ export interface LottieProps {
2104
2108
  }
2105
2109
  ```
2106
2110
 
2111
+ > **Needs the `animation` capability.** It renders through a native module the binary links only when `config.ts` declares `animation` (in `capabilities` or `capabilityEnvelope`). Without it the component draws a sized placeholder, and `himi validate` rejects a screen that uses it.
2112
+
2107
2113
  #### Rive
2108
2114
 
2109
2115
  ```ts
@@ -2116,6 +2122,8 @@ export interface RiveProps {
2116
2122
  }
2117
2123
  ```
2118
2124
 
2125
+ > **Needs the `animation` capability.** It renders through a native module the binary links only when `config.ts` declares `animation` (in `capabilities` or `capabilityEnvelope`). Without it the component draws a sized placeholder, and `himi validate` rejects a screen that uses it.
2126
+
2119
2127
  #### MonthCalendar
2120
2128
 
2121
2129
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1359.0",
3
+ "version": "0.1361.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": "8b9cac99029b9f7bced8bd984c585ef4ce4cd8b5fe5290a8bfb7a34d"
74
+ "falconPackageHash": "b5d19122c31f70fd18ec1b8a2e6d66e67ad3c2ffb5a4545a8bc432a1"
75
75
  }