@wix/himalaya-cli 0.830.0 → 0.832.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.
@@ -140,7 +140,16 @@ export interface Modifiers {
140
140
  * Absent (or <= 0) keeps the node at its intrinsic size. Use this — not
141
141
  * `fillMaxWidth` — when a node should claim the remaining space *beside* its
142
142
  * siblings; a full-bleed fill grabs the whole axis and starves them. Ignored
143
- * outside an HStack/VStack. */
143
+ * outside an HStack/VStack.
144
+ * CAVEAT — row-sharing controls have NO intrinsic width to measure first: in an
145
+ * HStack a `Button` and a `Toggle` are implicitly weighted, so they DIVIDE the
146
+ * row with your weighted child instead of taking a width ahead of it —
147
+ * `[VStack(weight:1), Button, Button]` is a three-way split, not "the VStack gets
148
+ * the remainder". The RELIABLE opt-outs are `iconOnly` and `tertiary`/`link` with
149
+ * `fullWidth` unset. `fullWidth: false` is NOT reliable here: only Android honors
150
+ * it in a weighted row (`wantsRowStretch`); iOS (`WeightedStack`) and web still
151
+ * hand the button a share. Otherwise give the actions their own row.
152
+ * See `himi docs designing-himi-screens` §4. */
144
153
  weight?: number;
145
154
  /** RTL — pin the subtree's writing direction: `"ltr"` | `"rtl"` | `"locale"`
146
155
  * (inherit). The common RTL pattern is the inverse of `Text.textDirection`: pin a
package/dist/cli.mjs CHANGED
@@ -40331,6 +40331,205 @@ var init_net_scenario = __esm({
40331
40331
  }
40332
40332
  });
40333
40333
 
40334
+ // ../../core/dev-server/src/capability-fixtures.ts
40335
+ function capabilityIdsIn(bundleJs) {
40336
+ const out = /* @__PURE__ */ new Set();
40337
+ for (const m of bundleJs.matchAll(/["'`]capability\.([a-z_]+)\.([A-Za-z]+)["'`]/g)) {
40338
+ if (m[1] === "local_database") continue;
40339
+ out.add(`capability.${m[1]}.${m[2]}`);
40340
+ }
40341
+ return [...out].sort();
40342
+ }
40343
+ var decodeItem, MEDIA_EXT, MEDIA_MIME, FILE, CALL, MEDIA_STATUS, CAPABILITY_SUCCESS;
40344
+ var init_capability_fixtures = __esm({
40345
+ "../../core/dev-server/src/capability-fixtures.ts"() {
40346
+ decodeItem = (raw) => {
40347
+ if (typeof raw !== "string") return {};
40348
+ try {
40349
+ const v = JSON.parse(raw);
40350
+ return v && typeof v === "object" ? v : {};
40351
+ } catch {
40352
+ return {};
40353
+ }
40354
+ };
40355
+ MEDIA_EXT = (url) => {
40356
+ if (typeof url !== "string") return null;
40357
+ let path;
40358
+ try {
40359
+ path = new URL(url).pathname;
40360
+ } catch {
40361
+ path = url.split("#")[0].split("?")[0];
40362
+ }
40363
+ const segment = path.split("/").pop() ?? "";
40364
+ const m = /\.([a-z0-9]{2,5})$/i.exec(segment);
40365
+ return m ? m[1].toLowerCase() : null;
40366
+ };
40367
+ MEDIA_MIME = {
40368
+ mp4: "video/mp4",
40369
+ m4v: "video/mp4",
40370
+ mov: "video/quicktime",
40371
+ webm: "video/webm",
40372
+ mp3: "audio/mpeg",
40373
+ m4a: "audio/mp4",
40374
+ aac: "audio/aac",
40375
+ wav: "audio/wav",
40376
+ ogg: "audio/ogg"
40377
+ };
40378
+ FILE = (name) => ({ uri: `file:///crawl/${name}`, name, mimeType: "audio/m4a", sizeBytes: 4096 });
40379
+ CALL = (peerId, state, clock) => ({
40380
+ callId: "crawl-call-1",
40381
+ kind: "audio",
40382
+ state,
40383
+ peerId,
40384
+ muted: false,
40385
+ route: "earpiece",
40386
+ startedAtMs: clock
40387
+ });
40388
+ MEDIA_STATUS = (clock, autoplay = true) => ({
40389
+ // A real host reports `paused` when a queue is set with `autoplay: false` — which is exactly what
40390
+ // video-courses does. Always answering "playing" exercises the wrong branch and hides the
40391
+ // paused-state behaviour the screen actually has.
40392
+ state: autoplay ? "playing" : "paused",
40393
+ currentIndex: 0,
40394
+ positionMs: 1e3,
40395
+ durationMs: 6e5,
40396
+ rate: 1,
40397
+ bufferedMs: 6e4,
40398
+ queue: [{ id: "lesson-1", title: "Lesson one", url: "https://crawl.invalid/lesson-1.mp4", durationMs: 6e5 }],
40399
+ currentItem: { id: "lesson-1", title: "Lesson one", url: "https://crawl.invalid/lesson-1.mp4", durationMs: 6e5 },
40400
+ startedAtMs: clock
40401
+ });
40402
+ CAPABILITY_SUCCESS = {
40403
+ // --- audio-record ---------------------------------------------------------------------------
40404
+ "capability.audio_record.startSession": (p, ctx) => ({
40405
+ session: {
40406
+ id: "crawl-session-1",
40407
+ mode: p.mode === "background" ? "background" : "foreground",
40408
+ state: "recording",
40409
+ startedAtMs: ctx.clock,
40410
+ segmentCount: 1
40411
+ }
40412
+ }),
40413
+ "capability.audio_record.getSession": (_p, ctx) => ({
40414
+ session: { id: "crawl-session-1", mode: "foreground", state: "recording", startedAtMs: ctx.clock, segmentCount: 1 }
40415
+ }),
40416
+ "capability.audio_record.stopSession": (_p, ctx) => ({
40417
+ session: { id: "crawl-session-1", mode: "foreground", state: "stopped", startedAtMs: ctx.clock, segmentCount: 1 }
40418
+ }),
40419
+ "capability.audio_record.listSegments": (_p, ctx) => ({
40420
+ segments: [
40421
+ { id: "seg-1", sequence: 0, file: FILE("segment-1.m4a"), startedAtMs: ctx.clock, endedAtMs: ctx.clock + 3e4, durationMs: 3e4, transcriptionState: "complete" }
40422
+ ]
40423
+ }),
40424
+ "capability.audio_record.listSessions": (_p, ctx) => ({
40425
+ sessions: [{ id: "crawl-session-1", mode: "foreground", state: "stopped", startedAtMs: ctx.clock, segmentCount: 1 }]
40426
+ }),
40427
+ "capability.audio_record.getPermissionStatus": () => ({ status: "granted" }),
40428
+ "capability.audio_record.requestPermission": () => ({ status: "granted" }),
40429
+ // --- speech ---------------------------------------------------------------------------------
40430
+ "capability.speech.availability": () => ({ recognition: "full", synthesis: "full" }),
40431
+ "capability.speech.transcribeFile": () => ({
40432
+ complete: true,
40433
+ language: "en-US",
40434
+ segments: [{ id: "t-1", text: "crawl transcript", startMs: 0, endMs: 3e4 }]
40435
+ }),
40436
+ "capability.speech.recognizeOnce": () => ({ transcript: "crawl transcript", cancelled: false }),
40437
+ // --- speaker-diarization --------------------------------------------------------------------
40438
+ "capability.speaker_diarization.availability": () => ({ diarization: "full" }),
40439
+ "capability.speaker_diarization.analyzeFile": () => ({
40440
+ speakers: ["speaker-1", "speaker-2"],
40441
+ segments: [
40442
+ { id: "d-1", speakerId: "speaker-1", startMs: 0, endMs: 15e3 },
40443
+ { id: "d-2", speakerId: "speaker-2", startMs: 15e3, endMs: 3e4 }
40444
+ ],
40445
+ complete: true,
40446
+ deferred: false
40447
+ }),
40448
+ // --- on-device-ai ---------------------------------------------------------------------------
40449
+ "capability.on_device_ai.availability": () => ({ textGeneration: "full" }),
40450
+ "capability.on_device_ai.generateText": () => ({ success: true, text: "crawl summary" }),
40451
+ // --- pdf / open-url / share (coach-lab, himi-social) ------------------------------------------
40452
+ "capability.pdf.isAvailable": () => ({ availability: "full" }),
40453
+ "capability.pdf.open": () => ({ opened: true }),
40454
+ "capability.open_url.canOpen": () => ({ canOpen: true }),
40455
+ "capability.open_url.open": () => ({ opened: true }),
40456
+ "capability.share.availability": () => ({ availability: "full" }),
40457
+ "capability.share.share": () => ({ completed: true }),
40458
+ // --- photos (himi-social) ---------------------------------------------------------------------
40459
+ "capability.photos.getSavePermissionStatus": () => ({ status: "granted" }),
40460
+ "capability.photos.saveToLibrary": () => ({ saved: true }),
40461
+ "capability.photos.pick": () => ({
40462
+ items: [{ uri: "file:///crawl/photo-1.jpg", name: "photo-1.jpg", mimeType: "image/jpeg", sizeBytes: 20480 }]
40463
+ }),
40464
+ // --- calls (msgr) -----------------------------------------------------------------------------
40465
+ //
40466
+ // `callStateFrom` drops anything without a non-empty string `callId`, and coerces an unrecognised
40467
+ // `state`/`route` to "failed"/"earpiece" rather than rejecting — so a wrong enum here would not
40468
+ // throw, it would quietly put every call in a failed phase and look like an app bug.
40469
+ "capability.calls.availability": () => ({ availability: "full" }),
40470
+ "capability.calls.getPermissionStatus": () => ({ status: "granted" }),
40471
+ "capability.calls.requestPermission": () => ({ status: "granted" }),
40472
+ "capability.calls.listCalls": () => ({ calls: [] }),
40473
+ "capability.calls.start": (p, ctx) => ({ call: CALL(String(p.peerId ?? "peer-1"), "dialing", ctx.clock) }),
40474
+ "capability.calls.offer": (p, ctx) => ({ call: CALL(String(p.peerId ?? "peer-1"), "ringing", ctx.clock) }),
40475
+ "capability.calls.answer": (_p, ctx) => ({ call: CALL("peer-1", "connected", ctx.clock) }),
40476
+ "capability.calls.getState": (_p, ctx) => ({ call: CALL("peer-1", "connected", ctx.clock) }),
40477
+ "capability.calls.reject": (_p, ctx) => ({ call: CALL("peer-1", "ended", ctx.clock) }),
40478
+ "capability.calls.hangup": (_p, ctx) => ({ call: CALL("peer-1", "ended", ctx.clock) }),
40479
+ "capability.calls.setMuted": (_p, ctx) => ({ call: { ...CALL("peer-1", "connected", ctx.clock), muted: true } }),
40480
+ "capability.calls.setSpeaker": (_p, ctx) => ({ call: { ...CALL("peer-1", "connected", ctx.clock), route: "speaker" } }),
40481
+ // --- media-playback / calendar (video-courses-preview) ----------------------------------------
40482
+ "capability.media_playback.getCapabilities": () => ({ download: true, pictureInPicture: true }),
40483
+ // `autoplay` arrives stringified — the binding sends `String(options.autoplay === true)`.
40484
+ "capability.media_playback.setQueue": (p, ctx) => MEDIA_STATUS(ctx.clock, p.autoplay === true || p.autoplay === "true"),
40485
+ "capability.media_playback.status": (_p, ctx) => MEDIA_STATUS(ctx.clock),
40486
+ "capability.media_playback.getState": (_p, ctx) => MEDIA_STATUS(ctx.clock),
40487
+ // `status` defaults to "failed" when absent and must be one of the MediaDownloadResult literals;
40488
+ // `file` is the handle. Returning `state`/`localUri` modelled a FAILED download while intending
40489
+ // completion, so video-courses took its failure-label branch under a fixture meant to succeed.
40490
+ //
40491
+ // ECHOES THE REQUESTED ITEM. Hard-coding `id: "lesson-1"` overrode the binding's `r?.id ?? item.id`
40492
+ // fallback, so ANY other caller — the demo bundle downloads `demo-tone` — got a lesson-1 handle
40493
+ // back and the crawl labelled the wrong download. A fixture that ignores its request and answers
40494
+ // a constant is the same defect as a screen that renders one.
40495
+ "capability.media_playback.download": (p, ctx) => {
40496
+ const item = decodeItem(p.item);
40497
+ const id = item.id ?? "crawl-item";
40498
+ const ext = MEDIA_EXT(item.url);
40499
+ const name = ext ? `${id}.${ext}` : id;
40500
+ return {
40501
+ id,
40502
+ status: "downloaded",
40503
+ progress: 1,
40504
+ file: { uri: `file:///crawl/${name}`, name, mimeType: ext && MEDIA_MIME[ext] || "application/octet-stream", sizeBytes: 1048576, modifiedAt: ctx.clock }
40505
+ };
40506
+ },
40507
+ "capability.calendar.getPermissionStatus": () => ({ status: "granted" }),
40508
+ "capability.calendar.requestPermission": () => ({ status: "granted" }),
40509
+ "capability.calendar.listEvents": () => ({ events: [] }),
40510
+ "capability.calendar.createEvent": () => ({ id: "crawl-event-1" }),
40511
+ // --- files ----------------------------------------------------------------------------------
40512
+ // `list` reads `r.items` as FileStat[] — `{ handle, modifiedAt }`, not a bare handle — and `stat`
40513
+ // reads `r.stat`. Returning `files`/`file` gave the binding an empty list and a null stat, so the
40514
+ // file flow could not reach its read/remove paths at all. `write` returns the handle itself,
40515
+ // which `toHandle` normalises, NOT an envelope.
40516
+ "capability.files.list": (_p, ctx) => ({ items: [{ handle: FILE("segment-1.m4a"), modifiedAt: ctx.clock }] }),
40517
+ "capability.files.stat": (_p, ctx) => ({ stat: { handle: FILE("segment-1.m4a"), modifiedAt: ctx.clock } }),
40518
+ "capability.files.readText": () => ({ text: "crawl file contents" }),
40519
+ // ECHOES THE REQUESTED PATH. `files.write` returns this object DIRECTLY to `toHandle`, whose
40520
+ // fallback to the requested path only applies when a field is absent — so a hard-coded handle
40521
+ // made every write resolve to the same file, and a consumer passing that handle onward (an export
40522
+ // writing `exports/<id>.gpx`) referenced something that does not exist.
40523
+ "capability.files.write": (p, ctx) => {
40524
+ const path = typeof p.path === "string" && p.path ? p.path : "crawl/written.txt";
40525
+ const name = path.split("/").pop() || path;
40526
+ return { uri: `file:///${path.replace(/^\/+/, "")}`, name, mimeType: typeof p.mimeType === "string" ? p.mimeType : "application/octet-stream", sizeBytes: 1024, modifiedAt: ctx.clock };
40527
+ },
40528
+ "capability.files.remove": () => ({ removed: true })
40529
+ };
40530
+ }
40531
+ });
40532
+
40334
40533
  // ../../core/dev-server/src/crawl-relations.ts
40335
40534
  function inversePartner(action, declared) {
40336
40535
  if (/^toggle/i.test(action)) return null;
@@ -40467,6 +40666,7 @@ __export(screen_crawl_exports, {
40467
40666
  bootAndSnapshotState: () => bootAndSnapshotState,
40468
40667
  bootScreenWorker: () => bootScreenWorker,
40469
40668
  crawlScreen: () => crawlScreen,
40669
+ crawlScreenCapabilitySuccess: () => crawlScreenCapabilitySuccess,
40470
40670
  crawlScreenDeep: () => crawlScreenDeep,
40471
40671
  crawlScreenHostile: () => crawlScreenHostile,
40472
40672
  crawlScreenJourney: () => crawlScreenJourney,
@@ -40746,7 +40946,7 @@ function declaredCapabilitiesOf(descriptor) {
40746
40946
  const list = Array.isArray(lb?.declaredCapabilities) ? lb.declaredCapabilities : [];
40747
40947
  return new Set(list.filter((c) => typeof c === "string"));
40748
40948
  }
40749
- function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new Set()) {
40949
+ function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new Set(), capabilitySuccess = false, clock = 0) {
40750
40950
  return async (action, params) => {
40751
40951
  if (/memberIdentity|member\.identity|currentMember/i.test(action)) {
40752
40952
  return { id: "crawl-member-0001", loggedIn: true, memberId: "crawl-member-0001" };
@@ -40759,6 +40959,10 @@ function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new
40759
40959
  );
40760
40960
  }
40761
40961
  if (cap[1] === "local_database") return localDatabaseAnswer(localDbSchema, cap[2], params ?? {});
40962
+ if (capabilitySuccess) {
40963
+ const answer = CAPABILITY_SUCCESS[action];
40964
+ if (answer) return answer(params ?? {}, { clock });
40965
+ }
40762
40966
  }
40763
40967
  return null;
40764
40968
  };
@@ -40865,9 +41069,13 @@ async function bootScreenWorker(opts) {
40865
41069
  wixCall: opts.wixCall,
40866
41070
  input,
40867
41071
  budgetMs: opts.budgetMs ?? 2e3,
40868
- dispatch: hostDispatch(opts.localDbSchema, declaredCapabilitiesOf(opts.descriptor)),
41072
+ // THE SAME CLOCK THE WORKER IS PINNED TO, one line below. `?? 0` gave the fixtures epoch 1970
41073
+ // while the worker believed it was 2026, so a modelled session carried a timestamp fifty-six
41074
+ // years before the "now" the app computes against — inconsistent rendered dates, and any
41075
+ // time-dependent branch taking the wrong arm.
41076
+ dispatch: hostDispatch(opts.localDbSchema, declaredCapabilitiesOf(opts.descriptor), opts.capabilitySuccess === true, opts.clock ?? CRAWL_CLOCK),
40869
41077
  deviceParity: true,
40870
- clock: opts.clock ?? Date.parse("2026-01-01T00:00:00.000Z"),
41078
+ clock: opts.clock ?? CRAWL_CLOCK,
40871
41079
  seed: opts.seed ?? 1,
40872
41080
  skipLoad: !!(appear && parseWorkerAction(appear.actionId)),
40873
41081
  ...opts.evaluator ? { evaluator: opts.evaluator } : {}
@@ -40941,6 +41149,18 @@ async function withSwallowedRejections(fn) {
40941
41149
  else globalThis.removeEventListener?.("unhandledrejection", swallow);
40942
41150
  }
40943
41151
  }
41152
+ async function crawlScreenCapabilitySuccess(opts) {
41153
+ return withSwallowedRejections(async () => {
41154
+ const declared = declaredCapabilitiesOf(opts.descriptor);
41155
+ const modelled = opts.bundleJs ? capabilityIdsIn(opts.bundleJs).filter((id) => CAPABILITY_SUCCESS[id] && grants(declared, id.split(".")[1])) : [];
41156
+ if (!modelled.length) return { findings: [], modelled: 0 };
41157
+ const r = await crawlScreenInner({ ...opts, capabilitySuccess: true });
41158
+ return {
41159
+ findings: r.findings.map((f) => ({ ...f, axis: f.axis ? `capability:success+${f.axis}` : "capability:success" })),
41160
+ modelled: modelled.length
41161
+ };
41162
+ });
41163
+ }
40944
41164
  async function crawlScreen(opts) {
40945
41165
  return withSwallowedRejections(() => crawlScreenInner(opts));
40946
41166
  }
@@ -40972,6 +41192,7 @@ async function crawlScreenJourney(opts) {
40972
41192
  clock: opts.clock,
40973
41193
  seed: opts.seed,
40974
41194
  localDbSchema: opts.localDbSchema,
41195
+ capabilitySuccess: opts.capabilitySuccess === true,
40975
41196
  ...opts.evaluator ? { evaluator: opts.evaluator } : {}
40976
41197
  });
40977
41198
  const sites = actionSitesForDescriptor(opts.descriptor);
@@ -41145,7 +41366,7 @@ async function crawlScreenInner(opts) {
41145
41366
  actionsSkipped.push({ action: s.actionId, why: "embedded-preview" });
41146
41367
  return false;
41147
41368
  });
41148
- const boot = (skipAppear = false) => bundleJs ? bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, inputOverride: opts.inputOverride, budgetMs: opts.budgetMs, clock: opts.clock, seed: opts.seed, skipAppear, localDbSchema: opts.localDbSchema, ...opts.evaluator ? { evaluator: opts.evaluator } : {} }) : null;
41369
+ const boot = (skipAppear = false) => bundleJs ? bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, inputOverride: opts.inputOverride, budgetMs: opts.budgetMs, clock: opts.clock, seed: opts.seed, skipAppear, localDbSchema: opts.localDbSchema, capabilitySuccess: opts.capabilitySuccess === true, ...opts.evaluator ? { evaluator: opts.evaluator } : {} }) : null;
41149
41370
  const check = (trigger, state, errors, defects = {}) => {
41150
41371
  for (const [svc, paths] of Object.entries(defects)) {
41151
41372
  for (const p of paths) {
@@ -41422,7 +41643,7 @@ async function crawlScreenHostileInner(opts, variant5) {
41422
41643
  };
41423
41644
  let w;
41424
41645
  try {
41425
- w = await bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, budgetMs: opts.budgetMs, clock: opts.clock, seed: opts.seed, localDbSchema: opts.localDbSchema, ...opts.evaluator ? { evaluator: opts.evaluator } : {} });
41646
+ w = await bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, budgetMs: opts.budgetMs, clock: opts.clock, seed: opts.seed, localDbSchema: opts.localDbSchema, capabilitySuccess: opts.capabilitySuccess === true, ...opts.evaluator ? { evaluator: opts.evaluator } : {} });
41426
41647
  } catch (e) {
41427
41648
  return [{
41428
41649
  screen: screenId,
@@ -41567,7 +41788,7 @@ async function bootAndSnapshotState(opts) {
41567
41788
  const { call: wixCall } = layeredTransport({ rules: opts.rules, cmsSeed: opts.cmsSeed, canned: opts.canned });
41568
41789
  let w;
41569
41790
  try {
41570
- w = await bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, budgetMs: opts.budgetMs, localDbSchema: opts.localDbSchema, ...opts.evaluator ? { evaluator: opts.evaluator } : {} });
41791
+ w = await bootScreenWorker({ bundleJs, descriptor, wixCall, screenId, budgetMs: opts.budgetMs, localDbSchema: opts.localDbSchema, capabilitySuccess: opts.capabilitySuccess === true, ...opts.evaluator ? { evaluator: opts.evaluator } : {} });
41571
41792
  } catch {
41572
41793
  return null;
41573
41794
  }
@@ -41618,7 +41839,8 @@ async function crawlScreenDeep(opts, deep) {
41618
41839
  budgetMs: opts.budgetMs,
41619
41840
  clock: opts.clock,
41620
41841
  seed: opts.seed,
41621
- localDbSchema: opts.localDbSchema
41842
+ localDbSchema: opts.localDbSchema,
41843
+ capabilitySuccess: opts.capabilitySuccess === true
41622
41844
  });
41623
41845
  const pathLabel = (path) => path.map((i) => sites[i].actionId.replace(/^tier5\./, "")).join(" \u2192 ");
41624
41846
  while (queue.length) {
@@ -41780,6 +42002,7 @@ async function crawlScreenRelations(opts) {
41780
42002
  clock: opts.clock,
41781
42003
  seed: opts.seed,
41782
42004
  localDbSchema: opts.localDbSchema,
42005
+ capabilitySuccess: opts.capabilitySuccess === true,
41783
42006
  ...opts.evaluator ? { evaluator: opts.evaluator } : {}
41784
42007
  });
41785
42008
  const replay = async (path, rowFor = () => null) => {
@@ -41920,7 +42143,7 @@ async function crawlScreenRelations(opts) {
41920
42143
  return { findings: dedupe(findings), checked, byKind };
41921
42144
  });
41922
42145
  }
41923
- var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, BLANK_WHEN_BOUND, UNBINDABLE_ROW;
42146
+ var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, CRAWL_CLOCK, BLANK_WHEN_BOUND, UNBINDABLE_ROW;
41924
42147
  var init_screen_crawl = __esm({
41925
42148
  "../../core/dev-server/src/screen-crawl.ts"() {
41926
42149
  init_validate_descriptor_core();
@@ -41930,6 +42153,7 @@ var init_screen_crawl = __esm({
41930
42153
  init_FakeServiceProvider();
41931
42154
  init_actionParams();
41932
42155
  init_implications();
42156
+ init_capability_fixtures();
41933
42157
  init_crawl_relations();
41934
42158
  ACTION_KEYS = [
41935
42159
  "action",
@@ -41950,6 +42174,7 @@ var init_screen_crawl = __esm({
41950
42174
  WIX_DATA_QUERY = /\/wix-data\/v2\/items\/query$/;
41951
42175
  WIX_DATA_ANY = /\/wix-data\//;
41952
42176
  CAPABILITY_ACTION = /^capability\.([a-z0-9_]+)\.([A-Za-z0-9_]+)$/;
42177
+ CRAWL_CLOCK = Date.parse("2026-01-01T00:00:00.000Z");
41953
42178
  BLANK_WHEN_BOUND = {
41954
42179
  Marquee: ["items", "itemsPath", "text"],
41955
42180
  MediaPlayer: ["items", "itemsPath"]
@@ -43530,6 +43755,7 @@ async function runCrawl(opts) {
43530
43755
  const hostileFindings = [];
43531
43756
  const relationFindings = [];
43532
43757
  let relationsChecked = 0;
43758
+ let capabilityModelled = 0;
43533
43759
  const relationsByKind = { inverse: 0, refresh: 0, loadMore: 0, filter: 0 };
43534
43760
  const deepFindings = [];
43535
43761
  const journeyFindings = [];
@@ -43608,6 +43834,20 @@ async function runCrawl(opts) {
43608
43834
  const already = new Set(r.findings.map(keyOf));
43609
43835
  for (const f of rr.findings) if (!already.has(keyOf(f))) relationFindings.push(f);
43610
43836
  }
43837
+ if (deps.crawlScreenCapabilitySuccess && baked.bundleJs) {
43838
+ const cr = await deps.crawlScreenCapabilitySuccess(args);
43839
+ capabilityModelled += cr.modelled;
43840
+ const keyOfC = deps.findingKey ?? ((f) => `${f.invariant}|${f.at ?? ""}|${f.screen}|${f.message}`);
43841
+ const seen = new Set([...r.findings, ...relationFindings].map(keyOfC));
43842
+ for (const f of cr.findings) if (!seen.has(keyOfC(f))) relationFindings.push(f);
43843
+ if (cr.modelled > 0 && deps.crawlScreenRelations) {
43844
+ const rs = await deps.crawlScreenRelations({ ...args, capabilitySuccess: true });
43845
+ const seen2 = new Set([...r.findings, ...relationFindings].map(keyOfC));
43846
+ for (const f of rs.findings) {
43847
+ if (!seen2.has(keyOfC(f))) relationFindings.push({ ...f, axis: f.axis ? `capability:success+${f.axis}` : "capability:success" });
43848
+ }
43849
+ }
43850
+ }
43611
43851
  await finishScreenCoverage();
43612
43852
  }
43613
43853
  const keep = (f) => {
@@ -43748,6 +43988,7 @@ async function runCrawl(opts) {
43748
43988
  ...retired.length ? { retired } : {},
43749
43989
  ...opts.deep && opts.deep >= 2 ? { deep: { depth: opts.deep, ...deepTotals } } : {},
43750
43990
  ...deps.crawlScreenRelations ? { relations: { checked: relationsChecked, byKind: relationsByKind } } : {},
43991
+ ...deps.crawlScreenCapabilitySuccess ? { capabilities: { modelled: capabilityModelled } } : {},
43751
43992
  ...journeyResults.length ? { journeys: journeyResults } : {},
43752
43993
  ...results.some((r) => r.observations) ? { observations: Object.fromEntries(results.filter((r) => r.observations).map((r) => [r.screen, r.observations])) } : {}
43753
43994
  };
@@ -45175,15 +45416,15 @@ function createFunctionDataCapability(options) {
45175
45416
  const decode = (result) => {
45176
45417
  if (!result || typeof result !== "object") return result;
45177
45418
  const output = structuredClone(result);
45178
- const decodeItem = (item) => {
45419
+ const decodeItem2 = (item) => {
45179
45420
  if (!item || typeof item !== "object") return item;
45180
45421
  const record2 = item;
45181
45422
  if (!record2.data || typeof record2.data !== "object" || Array.isArray(record2.data)) return item;
45182
45423
  return { ...record2, data: Object.fromEntries(Object.entries(record2.data).map(([key2, value]) => [physicalFields.get(key2) ?? key2, value])) };
45183
45424
  };
45184
- if (Array.isArray(output.dataItems)) output.dataItems = output.dataItems.map(decodeItem);
45185
- if (Array.isArray(output.items)) output.items = output.items.map(decodeItem);
45186
- if (output.dataItem !== void 0) output.dataItem = decodeItem(output.dataItem);
45425
+ if (Array.isArray(output.dataItems)) output.dataItems = output.dataItems.map(decodeItem2);
45426
+ if (Array.isArray(output.items)) output.items = output.items.map(decodeItem2);
45427
+ if (output.dataItem !== void 0) output.dataItem = decodeItem2(output.dataItem);
45187
45428
  return output;
45188
45429
  };
45189
45430
  return Object.freeze({
@@ -59976,6 +60217,7 @@ async function loadAuthoring() {
59976
60217
  crawlScreenJourney: crawl.crawlScreenJourney,
59977
60218
  crawlScreenHostile: crawl.crawlScreenHostile,
59978
60219
  crawlScreenRelations: crawl.crawlScreenRelations,
60220
+ crawlScreenCapabilitySuccess: crawl.crawlScreenCapabilitySuccess,
59979
60221
  findingKey: crawl.findingKey,
59980
60222
  startWorkerCoverage: cov.startWorkerCoverage,
59981
60223
  uncoveredFunctions: cov.uncoveredFunctions,
@@ -60562,7 +60804,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
60562
60804
  return results.every((result2) => result2.ok) ? 0 : 1;
60563
60805
  }
60564
60806
  const app = await appId(dir);
60565
- const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2, crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2, recordScreenTraffic: recordScreenTraffic2, mintVisitor: mintVisitor2, webWixCall: webWixCall2, freezeTrafficToMocks: freezeTrafficToMocks2, bootAndSnapshotState: bootAndSnapshotState2, renderDescriptorWeb: renderDescriptorWeb2, renderPngBatch: renderPngBatch2, pngAvailable: pngAvailable2, crawlBlockingPasses } = await loadAuthoring();
60807
+ const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2, crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, crawlScreenCapabilitySuccess: crawlScreenCapabilitySuccess2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2, recordScreenTraffic: recordScreenTraffic2, mintVisitor: mintVisitor2, webWixCall: webWixCall2, freezeTrafficToMocks: freezeTrafficToMocks2, bootAndSnapshotState: bootAndSnapshotState2, renderDescriptorWeb: renderDescriptorWeb2, renderPngBatch: renderPngBatch2, pngAvailable: pngAvailable2, crawlBlockingPasses } = await loadAuthoring();
60566
60808
  const { readTestConfig: readTestConfig2, runCrawl: runCrawl2, formatReport: formatReport2, readCannedResponses: readCannedResponses2, makeRecordingWixCall: makeRecordingWixCall2, runRecordPass: runRecordPass2, runShotsPass: runShotsPass2, defaultNetRules: defaultNetRules2, readNetRules: readNetRules2, scopeDescriptors: scopeDescriptors2, writeRecordedMocks: writeRecordedMocks2 } = await Promise.resolve().then(() => (init_test(), test_exports));
60567
60809
  let config;
60568
60810
  try {
@@ -60800,7 +61042,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
60800
61042
  ...deep ? { deep } : {},
60801
61043
  ...offline ? { offline: true } : {},
60802
61044
  ...offline || flags["no-hostile"] === true ? { hostile: false } : {},
60803
- deps: { crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2 }
61045
+ deps: { crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, crawlScreenCapabilitySuccess: crawlScreenCapabilitySuccess2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2 }
60804
61046
  });
60805
61047
  } catch (e) {
60806
61048
  io.error(JSON.stringify({ error: e.message }));
@@ -881,7 +881,7 @@
881
881
  "ScreenDescriptor": "export interface ScreenDescriptor {\n screenId: string;\n renderer: RendererKind;\n title?: string;\n /** Present when `renderer` is `sdui_intent` or `sdui_layout`. */\n payload?: Component;\n /** A component pinned ABOVE the scrolling payload (sticky tab-header / toolbar).\n * Rendered by the host outside the screen scroll, so it stays put as content\n * scrolls. Optional; absent = no sticky header. */\n stickyHeader?: Component;\n /** A component pinned BELOW the scrolling payload (sticky footer — price/confirm\n * bar, peeking bottom-sheet). Rendered outside the screen scroll by the host. */\n stickyFooter?: Component;\n /** When true, the host wraps the screen in a scroll that starts pinned to the\n * bottom and shows a floating \"jump to latest\" button whenever the user scrolls\n * up (a chat transcript). Tapping it animates back to the bottom. Absent/false =\n * a normal top-anchored scroll. Honored by the app host (Router). */\n scrollToBottom?: boolean;\n /** Present when `renderer` is `webview_native_bridge` or `webview_ds`. */\n url?: string;\n capabilitiesRequired?: string[];\n schemaVersion: number;\n togglesApplied?: string[];\n /** Per-session flag/experiment assignments baked in for this request. */\n assignments?: import(\"./flags.js\").Assignment[];\n /** Per-screen top bar (trailing actions +\n * leading/style override). Fully replaces `NavigationConfig.topBarDefault`. */\n topBar?: TopBarConfig;\n etag?: string;\n cacheControl?: CacheControl;\n telemetryScreenName?: string;\n bundleSri?: string;\n manifestVersion?: number;\n /**\n * Present on Tier-5 screens. The presence of this field is the tier-5\n * signal — there is no separate `tier: 5` flag.\n */\n logicBundle?: LogicBundleRef;\n /**\n * Static per-screen input injected as `globalThis.himalaya.input` before the\n * Tier-5 worker bundle evaluates — the worker reads it synchronously in `load()`\n * (e.g. `himalaya.input.id` for a detail screen, or a baked catalog like\n * `himalaya.input.cmsCollections`).\n *\n * A push merges its navigation parameters into this object on web, iOS, and Android.\n * For example, `himi.open({ screenId: \"event\", id: \"$item.id\" })` makes the id\n * available as `himalaya.input.id` in the pushed worker; static `input` remains the\n * fallback for values baked into a descriptor or a templated route. The host also\n * supplies `screenId`. See docs/tier-5-add-a-screen.md → \"Navigate to a detail screen\".\n *\n * Arbitrary JSON object; consumed by the web host (ScreenController) and the\n * native JSWorker.\n */\n input?: Record<string, unknown>;\n /**\n * Tier 3 / Tier 4 — behavior of the hosted WebView. Ignored by SDUI tiers.\n * Authored in the descriptor builder (git); served OTA verbatim.\n */\n webViewConfig?: WebViewConfig;\n}",
882
882
  "Component": "export interface Component {\n id: string;\n body: ComponentBody;\n modifiers?: Modifiers;\n minVersion?: number;\n}",
883
883
  "LogicBundleRef": "export interface LogicBundleRef {\n url: string;\n /** \"sha256-...\" SRI string. */\n integrity: string;\n contentType?: string;\n /** SDK capability ids the bundle is permitted to import / invoke. */\n declaredCapabilities: string[];\n /** Bundle refuses to load on a binary older than this (semver). */\n minBinaryVersion?: string;\n /** Bundle's own semver. */\n bundleVersion?: string;\n /** Base64 detached publisher signature over the bundle bytes\n * (authenticity beyond SRI). Verified by a host-injected verifier when present;\n * unsigned bundles load as today. */\n signature?: string;\n /** The publisher key id a host pins, paired with `signature`. */\n keyId?: string;\n /** Ambient refresh (gap G2): the host invokes `action` (a `tier5.<svc>.<action>`\n * path) every `intervalMs` while the screen is visible — workers have no timer\n * of their own, so the host owns the cadence. */\n refresh?: { intervalMs: number; action: string };\n}",
884
- "Modifiers": "export interface Modifiers {\n padding?: EdgeInsets;\n background?: TokenRef;\n cornerRadius?: number;\n /** Fixed intrinsic width (pt/dp/px). Honored by iOS (`FrameIfPresent`), Android\n * (frame) and web (`modifiers.ts`). With no `ScrollView` in the vocabulary and\n * regions clipped at `maxHeight`, `width`/`height`/`fillMaxWidth` are the main\n * sizing tools for embed/host content. */\n width?: number;\n /** Fixed intrinsic height (pt/dp/px). See `width`. */\n height?: number;\n /** Fill the available cross-axis width (keeps any fixed `height`). Mirrors\n * Compose `fillMaxWidth` / SwiftUI `.frame(maxWidth: .infinity)`. See `width`. */\n fillMaxWidth?: boolean;\n /** CAP the node's laid-out width at this many points. It CONSTRAINS, it\n * does not SET: a node narrower than the cap keeps its intrinsic width, so a\n * short chat bubble stays short while a long one stops at the cap — the\n * asymmetry `width` cannot express and `weight` cannot either (weights divide\n * the *available* space, so every child lands on the same fraction).\n * The cap covers the node's TOTAL width, padding + border included (CSS\n * `max-width` under `box-sizing: border-box`). `fillMaxWidth` grabs the axis but\n * THE CAP WINS — fill + cap is exactly `maxWidth` wide; a literal `width` larger\n * than the cap is clamped to it too. POINTS, not a fraction: a modifier is\n * resolved with no portable notion of \"the row\" on any renderer — compose a\n * percentage from a weighted sibling gap instead. `0`/absent = no cap. */\n maxWidth?: number;\n /** Flex weight along the parent stack's MAIN axis (HStack = width, VStack =\n * height). The stack measures its intrinsic (unweighted) children first, then\n * divides what is LEFT among the weighted ones in proportion to their weights:\n * two children at `1` split the leftover equally, `2` and `1` split it 2:1.\n * Absent (or <= 0) keeps the node at its intrinsic size. Use this — not\n * `fillMaxWidth` — when a node should claim the remaining space *beside* its\n * siblings; a full-bleed fill grabs the whole axis and starves them. Ignored\n * outside an HStack/VStack. */\n weight?: number;\n /** RTL — pin the subtree's writing direction: `\"ltr\"` | `\"rtl\"` | `\"locale\"`\n * (inherit). The common RTL pattern is the inverse of `Text.textDirection`: pin a\n * container `ltr` (a logotype, an attachment-tile row, a composer button row)\n * inside an otherwise-RTL screen. Absent = inherit the ambient direction. #1060. */\n layoutDirection?: \"ltr\" | \"rtl\" | \"locale\";\n /** Absolute visual translation (pt/dp/px). Applied as the OUTERMOST transform so\n * it shifts the fully-styled node WITHOUT changing its layout footprint — lets a\n * ZStack place polar/absolute children (e.g. AgentOrbit). Negative allowed. */\n offsetX?: number;\n offsetY?: number;\n onTap?: Action;\n /** Double-tap gesture (e.g. Instagram double-tap-to-like). Distinct from onTap. */\n onDoubleTap?: Action;\n accessibilityLabel?: string;\n hidden?: boolean;\n /** Data-bound conditional visibility: a `\"$service:<svc>.<field>\"`\n * or `\"$item.<field>\"` sentinel resolving to a bool at render; when true the\n * subtree fully COLLAPSES (zero layout, like `hidden`), overriding the\n * literal `hidden`. */\n hiddenPath?: string;\n /** Long-press gesture modifier. */\n onLongPress?: Action;\n /** Fires once when the component first appears on screen. */\n onAppear?: Action;\n /** Scoped token overrides for the subtree. */\n theme?: ThemeOverrides;\n /** Names a token from the app's `shadows` registry. */\n shadow?: TokenRef;\n /** Names a token from the app's `gradients` registry (background fill). */\n backgroundGradient?: TokenRef;\n /** Value-driven gradient: a `$service:`/`$item.` path resolving to a gradient\n * TOKEN NAME used as the live background (e.g. weather sky day→night). */\n backgroundGradientPath?: string;\n /** Value-driven background COLOR: a `$service:`/`$item.` path resolving to a\n * color TOKEN NAME used as the live background. Honored on iOS + Android\n * (`BoundBackgroundIfPresent`); web render support is tracked in #1057. */\n backgroundColorPath?: string;\n /** Value-driven background IMAGE: a `$service:`/`$item.` path resolving to an\n * image URL used as the live background. Honored on iOS + Android; web render\n * support is tracked in #1057. */\n backgroundImagePath?: string;\n /** #1051 — MEASURE (publish): report this node's laid-out size into the renderer's\n * self-contained (worker-free) measure store under the named keys, so a SIBLING can\n * feed the number back into its own layout via a `$measure:<key>` binding. The SDUI\n * equivalent of SwiftUI `.onGeometryChange` / Compose `onGloballyPositioned`; the\n * canonical use is a pinned bottom bar whose measured height becomes a scroll view's\n * bottom inset (sticky-footer + scroll-inset, unrepresentable before — #1051). */\n onMeasure?: Measure;\n /** #1051 — bound height: a `\"$measure:<key>\"` (measure store) or\n * `\"$service:<svc>.<field>\"` / `\"$item.<field>\"` (worker/row state) sentinel resolving\n * to a NUMBER at render. Wins over the literal `height`. String path only, so a\n * binding can never blank the node (never overload the numeric `height`). */\n heightPath?: string;\n /** #1051 — bound width. See `heightPath`. Wins over the literal `width`. */\n widthPath?: string;\n /** #1051 — bound TOP inset (the header-height → top-inset idiom). A `$measure:`/\n * `$service:`/`$item.` sentinel resolving to a number; added as top padding. */\n paddingTopPath?: string;\n /** #1051 — bound BOTTOM inset (the sticky-footer-height → bottom-inset idiom). A\n * `$measure:`/`$service:`/`$item.` sentinel resolving to a number; added as bottom\n * padding so the last scroll row clears a pinned bottom bar. */\n paddingBottomPath?: string;\n /** #1053 — INLINE gradient background paint. Distinct from `backgroundGradient`\n * (which names a `gradients` registry token an embed host can't register): this\n * carries its stops/colours in the descriptor, so it works anywhere. Painted as\n * a surface (behind children), below `material`/`backgroundGradient` if those are\n * also set. */\n gradient?: Gradient;\n /** #1053 — gradient ALPHA MASK: the node shows through where the gradient is\n * opaque and fades where it's transparent (a transparent-token stop = fully\n * clear). The material-under-gradient-scrim / scroll-fade-out idiom. Mirrors iOS\n * `.mask()`, Android `BlendMode.DstIn`, web `mask-image`. */\n mask?: Gradient;\n /** Inline border/stroke. */\n border?: Border;\n /** Subtree alpha 0.0–1.0; absent = opaque. */\n opacity?: number;\n /** #433 — constrain the subtree to a width:height ratio (e.g. `1` = square,\n * `1.777` = 16:9). Width-driven: the height derives from the resolved width.\n * Lets a data-bound Grid cell / photo tile hold its shape at any width. */\n aspectRatio?: number;\n /** Names a token from the app's `motions` registry; the subtree\n * fades + slides up on first appearance. */\n appear?: TokenRef;\n /** Surface material token (flat/glass/frosted). */\n material?: TokenRef;\n /** Per-node construction-language override. */\n variant?: string;\n /** Per-node density / motion override. */\n density?: string;\n motion?: string;\n /** Motion grammar (Batch 1) — press/tap feedback style\n * (auto|none|scale|highlight|scaleHighlight|lift); timing comes from `motion`.\n * Applied only when the node is interactive. Unknown degrades to `auto`. */\n pressFeedback?: string;\n /** Motion grammar (Batch 1) — press-coupled haptic\n * (none|selection|light|medium|heavy|success|warning|error); fires with onTap.\n * Default none (opt-in). Web is a no-op. */\n haptic?: string;\n /** Motion grammar (Batch 1) — animate this container's size changes instead of\n * snapping (iOS `.animation(value:)`, Android `animateContentSize`, web height\n * transition). Curve from the active `motion` personality. */\n animateContentSize?: boolean;\n /** Motion grammar (Batch 1) — enter/exit transition; supersedes `appear` when\n * both are present. */\n transition?: Transition;\n /** Motion grammar (Batch 2, scroll-driven) — defer the `transition` enter until\n * the node first crosses into the scroll viewport (reveal-on-scroll). Gated by\n * reduced motion (shown immediately when reduced). */\n revealOnScroll?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — parallax factor: the node drifts\n * vertically at this fraction of the scroll delta as it moves through the\n * viewport. Gated by reduced motion. Generalizes CollapsingHeader parallaxFactor. */\n parallax?: number;\n /** Motion grammar (Batch 4, rich visual) — when a backgroundGradient is present,\n * continuously drift/rotate it (animated gradient). Gated by reduced motion. */\n animatedGradient?: boolean;\n /** Motion grammar (Batch 3, hero/nav) — shared-element key: a matching\n * transitionId on the source + destination screen morphs that element across\n * navigation (web View Transitions / iOS matchedTransitionSource / Android\n * SharedTransitionLayout). Gated by reduced motion. */\n transitionId?: string;\n /** Motion grammar (Batch 4, rich visual) — render the backgroundGradient as a\n * mesh (overlapping multi-point blend) rather than a linear sweep; combine with\n * animatedGradient to drift it. */\n meshGradient?: boolean;\n /** Motion grammar (Batch 4, celebration) — burst a one-shot confetti overlay\n * within the node on appear. Gated by reduced motion. */\n confetti?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — pin this node to the top of its\n * scroll container (sticky section header). Functional positioning; not gated\n * by reduced motion. */\n sticky?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — turn this thin node into a\n * reading-progress bar that fills left→right with scroll (scaleX). web reads\n * window scroll; native reads the host scroll fraction. Functional. */\n scrollProgress?: boolean;\n /** Motion grammar (Batch 4, rich vector) — a named built-in vector animation\n * (\"loader\" | \"success\" | \"pulse\"), dependency-free. Gated by reduced motion.\n * Arbitrary Lottie/Rive JSON is the lib-backed superset follow-up. */\n vectorAnim?: string;\n /** Motion grammar (Batch 1, choreography) — delay this node's ENTER transition\n * by N ms so a screen can SEQUENCE its entrance beyond list stagger (\"title\n * fades, subtitle slides in 100ms later\"). Adds to any list `staggerMs`. Gated\n * by reduced motion. 0/absent = no extra delay. */\n enterDelayMs?: number;\n /** Motion grammar (Phase-3, per-node MotionSpec) — overrides the PRESS-feedback\n * curve with a `motions`-registry token, mirroring `Transition.motion` for the\n * enter curve. Absent → press uses the global `motion` personality. */\n pressMotion?: TokenRef;\n /** Motion grammar (Phase-3, gesture-driven) — finger-tracked drag-to-dismiss; past\n * threshold along `direction` it animates off and fires `action`, else springs back. */\n swipeDismiss?: SwipeDismiss;\n /** Motion grammar (Phase-4, reduced-motion) — per-node escape from the reduced-motion\n * gate. Default true (respect). false = \"must move anyway\" (transition/press animate\n * even when reduced motion is on). */\n respectReducedMotion?: boolean;\n /** #1052 — uniform scale (1 = no change). */\n scale?: number;\n /** #1052 — rotation in DEGREES. */\n rotation?: number;\n /** #1052 — stacking order within a ZStack/overlay. */\n zIndex?: number;\n /** #1052 — gaussian blur radius (pt/dp/px). */\n blur?: number;\n /** #1052 — `false` = decorative (ignores taps, unlike `hidden` which stays hit-testable);\n * `true` = force hit-testable (a clear tap target). */\n hitTestable?: boolean;\n /** #1052 — explicit drop shadow (colour + radius + offset), for upward / multi-layer\n * shadows the `shadow` token can't express. */\n boxShadow?: Shadow;\n}",
884
+ "Modifiers": "export interface Modifiers {\n padding?: EdgeInsets;\n background?: TokenRef;\n cornerRadius?: number;\n /** Fixed intrinsic width (pt/dp/px). Honored by iOS (`FrameIfPresent`), Android\n * (frame) and web (`modifiers.ts`). With no `ScrollView` in the vocabulary and\n * regions clipped at `maxHeight`, `width`/`height`/`fillMaxWidth` are the main\n * sizing tools for embed/host content. */\n width?: number;\n /** Fixed intrinsic height (pt/dp/px). See `width`. */\n height?: number;\n /** Fill the available cross-axis width (keeps any fixed `height`). Mirrors\n * Compose `fillMaxWidth` / SwiftUI `.frame(maxWidth: .infinity)`. See `width`. */\n fillMaxWidth?: boolean;\n /** CAP the node's laid-out width at this many points. It CONSTRAINS, it\n * does not SET: a node narrower than the cap keeps its intrinsic width, so a\n * short chat bubble stays short while a long one stops at the cap — the\n * asymmetry `width` cannot express and `weight` cannot either (weights divide\n * the *available* space, so every child lands on the same fraction).\n * The cap covers the node's TOTAL width, padding + border included (CSS\n * `max-width` under `box-sizing: border-box`). `fillMaxWidth` grabs the axis but\n * THE CAP WINS — fill + cap is exactly `maxWidth` wide; a literal `width` larger\n * than the cap is clamped to it too. POINTS, not a fraction: a modifier is\n * resolved with no portable notion of \"the row\" on any renderer — compose a\n * percentage from a weighted sibling gap instead. `0`/absent = no cap. */\n maxWidth?: number;\n /** Flex weight along the parent stack's MAIN axis (HStack = width, VStack =\n * height). The stack measures its intrinsic (unweighted) children first, then\n * divides what is LEFT among the weighted ones in proportion to their weights:\n * two children at `1` split the leftover equally, `2` and `1` split it 2:1.\n * Absent (or <= 0) keeps the node at its intrinsic size. Use this — not\n * `fillMaxWidth` — when a node should claim the remaining space *beside* its\n * siblings; a full-bleed fill grabs the whole axis and starves them. Ignored\n * outside an HStack/VStack.\n * CAVEAT — row-sharing controls have NO intrinsic width to measure first: in an\n * HStack a `Button` and a `Toggle` are implicitly weighted, so they DIVIDE the\n * row with your weighted child instead of taking a width ahead of it —\n * `[VStack(weight:1), Button, Button]` is a three-way split, not \"the VStack gets\n * the remainder\". The RELIABLE opt-outs are `iconOnly` and `tertiary`/`link` with\n * `fullWidth` unset. `fullWidth: false` is NOT reliable here: only Android honors\n * it in a weighted row (`wantsRowStretch`); iOS (`WeightedStack`) and web still\n * hand the button a share. Otherwise give the actions their own row.\n * See `himi docs designing-himi-screens` §4. */\n weight?: number;\n /** RTL — pin the subtree's writing direction: `\"ltr\"` | `\"rtl\"` | `\"locale\"`\n * (inherit). The common RTL pattern is the inverse of `Text.textDirection`: pin a\n * container `ltr` (a logotype, an attachment-tile row, a composer button row)\n * inside an otherwise-RTL screen. Absent = inherit the ambient direction. #1060. */\n layoutDirection?: \"ltr\" | \"rtl\" | \"locale\";\n /** Absolute visual translation (pt/dp/px). Applied as the OUTERMOST transform so\n * it shifts the fully-styled node WITHOUT changing its layout footprint — lets a\n * ZStack place polar/absolute children (e.g. AgentOrbit). Negative allowed. */\n offsetX?: number;\n offsetY?: number;\n onTap?: Action;\n /** Double-tap gesture (e.g. Instagram double-tap-to-like). Distinct from onTap. */\n onDoubleTap?: Action;\n accessibilityLabel?: string;\n hidden?: boolean;\n /** Data-bound conditional visibility: a `\"$service:<svc>.<field>\"`\n * or `\"$item.<field>\"` sentinel resolving to a bool at render; when true the\n * subtree fully COLLAPSES (zero layout, like `hidden`), overriding the\n * literal `hidden`. */\n hiddenPath?: string;\n /** Long-press gesture modifier. */\n onLongPress?: Action;\n /** Fires once when the component first appears on screen. */\n onAppear?: Action;\n /** Scoped token overrides for the subtree. */\n theme?: ThemeOverrides;\n /** Names a token from the app's `shadows` registry. */\n shadow?: TokenRef;\n /** Names a token from the app's `gradients` registry (background fill). */\n backgroundGradient?: TokenRef;\n /** Value-driven gradient: a `$service:`/`$item.` path resolving to a gradient\n * TOKEN NAME used as the live background (e.g. weather sky day→night). */\n backgroundGradientPath?: string;\n /** Value-driven background COLOR: a `$service:`/`$item.` path resolving to a\n * color TOKEN NAME used as the live background. Honored on iOS + Android\n * (`BoundBackgroundIfPresent`); web render support is tracked in #1057. */\n backgroundColorPath?: string;\n /** Value-driven background IMAGE: a `$service:`/`$item.` path resolving to an\n * image URL used as the live background. Honored on iOS + Android; web render\n * support is tracked in #1057. */\n backgroundImagePath?: string;\n /** #1051 — MEASURE (publish): report this node's laid-out size into the renderer's\n * self-contained (worker-free) measure store under the named keys, so a SIBLING can\n * feed the number back into its own layout via a `$measure:<key>` binding. The SDUI\n * equivalent of SwiftUI `.onGeometryChange` / Compose `onGloballyPositioned`; the\n * canonical use is a pinned bottom bar whose measured height becomes a scroll view's\n * bottom inset (sticky-footer + scroll-inset, unrepresentable before — #1051). */\n onMeasure?: Measure;\n /** #1051 — bound height: a `\"$measure:<key>\"` (measure store) or\n * `\"$service:<svc>.<field>\"` / `\"$item.<field>\"` (worker/row state) sentinel resolving\n * to a NUMBER at render. Wins over the literal `height`. String path only, so a\n * binding can never blank the node (never overload the numeric `height`). */\n heightPath?: string;\n /** #1051 — bound width. See `heightPath`. Wins over the literal `width`. */\n widthPath?: string;\n /** #1051 — bound TOP inset (the header-height → top-inset idiom). A `$measure:`/\n * `$service:`/`$item.` sentinel resolving to a number; added as top padding. */\n paddingTopPath?: string;\n /** #1051 — bound BOTTOM inset (the sticky-footer-height → bottom-inset idiom). A\n * `$measure:`/`$service:`/`$item.` sentinel resolving to a number; added as bottom\n * padding so the last scroll row clears a pinned bottom bar. */\n paddingBottomPath?: string;\n /** #1053 — INLINE gradient background paint. Distinct from `backgroundGradient`\n * (which names a `gradients` registry token an embed host can't register): this\n * carries its stops/colours in the descriptor, so it works anywhere. Painted as\n * a surface (behind children), below `material`/`backgroundGradient` if those are\n * also set. */\n gradient?: Gradient;\n /** #1053 — gradient ALPHA MASK: the node shows through where the gradient is\n * opaque and fades where it's transparent (a transparent-token stop = fully\n * clear). The material-under-gradient-scrim / scroll-fade-out idiom. Mirrors iOS\n * `.mask()`, Android `BlendMode.DstIn`, web `mask-image`. */\n mask?: Gradient;\n /** Inline border/stroke. */\n border?: Border;\n /** Subtree alpha 0.0–1.0; absent = opaque. */\n opacity?: number;\n /** #433 — constrain the subtree to a width:height ratio (e.g. `1` = square,\n * `1.777` = 16:9). Width-driven: the height derives from the resolved width.\n * Lets a data-bound Grid cell / photo tile hold its shape at any width. */\n aspectRatio?: number;\n /** Names a token from the app's `motions` registry; the subtree\n * fades + slides up on first appearance. */\n appear?: TokenRef;\n /** Surface material token (flat/glass/frosted). */\n material?: TokenRef;\n /** Per-node construction-language override. */\n variant?: string;\n /** Per-node density / motion override. */\n density?: string;\n motion?: string;\n /** Motion grammar (Batch 1) — press/tap feedback style\n * (auto|none|scale|highlight|scaleHighlight|lift); timing comes from `motion`.\n * Applied only when the node is interactive. Unknown degrades to `auto`. */\n pressFeedback?: string;\n /** Motion grammar (Batch 1) — press-coupled haptic\n * (none|selection|light|medium|heavy|success|warning|error); fires with onTap.\n * Default none (opt-in). Web is a no-op. */\n haptic?: string;\n /** Motion grammar (Batch 1) — animate this container's size changes instead of\n * snapping (iOS `.animation(value:)`, Android `animateContentSize`, web height\n * transition). Curve from the active `motion` personality. */\n animateContentSize?: boolean;\n /** Motion grammar (Batch 1) — enter/exit transition; supersedes `appear` when\n * both are present. */\n transition?: Transition;\n /** Motion grammar (Batch 2, scroll-driven) — defer the `transition` enter until\n * the node first crosses into the scroll viewport (reveal-on-scroll). Gated by\n * reduced motion (shown immediately when reduced). */\n revealOnScroll?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — parallax factor: the node drifts\n * vertically at this fraction of the scroll delta as it moves through the\n * viewport. Gated by reduced motion. Generalizes CollapsingHeader parallaxFactor. */\n parallax?: number;\n /** Motion grammar (Batch 4, rich visual) — when a backgroundGradient is present,\n * continuously drift/rotate it (animated gradient). Gated by reduced motion. */\n animatedGradient?: boolean;\n /** Motion grammar (Batch 3, hero/nav) — shared-element key: a matching\n * transitionId on the source + destination screen morphs that element across\n * navigation (web View Transitions / iOS matchedTransitionSource / Android\n * SharedTransitionLayout). Gated by reduced motion. */\n transitionId?: string;\n /** Motion grammar (Batch 4, rich visual) — render the backgroundGradient as a\n * mesh (overlapping multi-point blend) rather than a linear sweep; combine with\n * animatedGradient to drift it. */\n meshGradient?: boolean;\n /** Motion grammar (Batch 4, celebration) — burst a one-shot confetti overlay\n * within the node on appear. Gated by reduced motion. */\n confetti?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — pin this node to the top of its\n * scroll container (sticky section header). Functional positioning; not gated\n * by reduced motion. */\n sticky?: boolean;\n /** Motion grammar (Batch 2, scroll-driven) — turn this thin node into a\n * reading-progress bar that fills left→right with scroll (scaleX). web reads\n * window scroll; native reads the host scroll fraction. Functional. */\n scrollProgress?: boolean;\n /** Motion grammar (Batch 4, rich vector) — a named built-in vector animation\n * (\"loader\" | \"success\" | \"pulse\"), dependency-free. Gated by reduced motion.\n * Arbitrary Lottie/Rive JSON is the lib-backed superset follow-up. */\n vectorAnim?: string;\n /** Motion grammar (Batch 1, choreography) — delay this node's ENTER transition\n * by N ms so a screen can SEQUENCE its entrance beyond list stagger (\"title\n * fades, subtitle slides in 100ms later\"). Adds to any list `staggerMs`. Gated\n * by reduced motion. 0/absent = no extra delay. */\n enterDelayMs?: number;\n /** Motion grammar (Phase-3, per-node MotionSpec) — overrides the PRESS-feedback\n * curve with a `motions`-registry token, mirroring `Transition.motion` for the\n * enter curve. Absent → press uses the global `motion` personality. */\n pressMotion?: TokenRef;\n /** Motion grammar (Phase-3, gesture-driven) — finger-tracked drag-to-dismiss; past\n * threshold along `direction` it animates off and fires `action`, else springs back. */\n swipeDismiss?: SwipeDismiss;\n /** Motion grammar (Phase-4, reduced-motion) — per-node escape from the reduced-motion\n * gate. Default true (respect). false = \"must move anyway\" (transition/press animate\n * even when reduced motion is on). */\n respectReducedMotion?: boolean;\n /** #1052 — uniform scale (1 = no change). */\n scale?: number;\n /** #1052 — rotation in DEGREES. */\n rotation?: number;\n /** #1052 — stacking order within a ZStack/overlay. */\n zIndex?: number;\n /** #1052 — gaussian blur radius (pt/dp/px). */\n blur?: number;\n /** #1052 — `false` = decorative (ignores taps, unlike `hidden` which stays hit-testable);\n * `true` = force hit-testable (a clear tap target). */\n hitTestable?: boolean;\n /** #1052 — explicit drop shadow (colour + radius + offset), for upward / multi-layer\n * shadows the `shadow` token can't express. */\n boxShadow?: Shadow;\n}",
885
885
  "TokenRef": "export interface TokenRef {\n token: TokenName;\n}",
886
886
  "Action": "export interface Action {\n actionId: string;\n /** Backwards-compatible string-only params. Prefer `args` for typed payloads. */\n params?: Record<string, string>;\n /** Typed action args; nested objects/arrays preserved end-to-end. */\n args?: Record<string, Value>;\n /** Navigation hint for the router; ignored for non-nav actions. */\n presentation?: Presentation;\n}",
887
887
  "Presentation": "export type Presentation =\n | \"push\"\n | \"sheet\"\n | \"replace\"\n | \"dismiss\"\n | \"back\"\n | \"full_screen\"\n | \"alert\"\n | \"snackbar\";",
@@ -1976,7 +1976,16 @@ export interface Modifiers {
1976
1976
  * Absent (or <= 0) keeps the node at its intrinsic size. Use this — not
1977
1977
  * `fillMaxWidth` — when a node should claim the remaining space *beside* its
1978
1978
  * siblings; a full-bleed fill grabs the whole axis and starves them. Ignored
1979
- * outside an HStack/VStack. */
1979
+ * outside an HStack/VStack.
1980
+ * CAVEAT — row-sharing controls have NO intrinsic width to measure first: in an
1981
+ * HStack a `Button` and a `Toggle` are implicitly weighted, so they DIVIDE the
1982
+ * row with your weighted child instead of taking a width ahead of it —
1983
+ * `[VStack(weight:1), Button, Button]` is a three-way split, not "the VStack gets
1984
+ * the remainder". The RELIABLE opt-outs are `iconOnly` and `tertiary`/`link` with
1985
+ * `fullWidth` unset. `fullWidth: false` is NOT reliable here: only Android honors
1986
+ * it in a weighted row (`wantsRowStretch`); iOS (`WeightedStack`) and web still
1987
+ * hand the button a share. Otherwise give the actions their own row.
1988
+ * See `himi docs designing-himi-screens` §4. */
1980
1989
  weight?: number;
1981
1990
  /** RTL — pin the subtree's writing direction: `"ltr"` | `"rtl"` | `"locale"`
1982
1991
  * (inherit). The common RTL pattern is the inverse of `Text.textDirection`: pin a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.830.0",
3
+ "version": "0.832.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": [
@@ -63,5 +63,5 @@
63
63
  "artifactId": "himalaya-cli"
64
64
  }
65
65
  },
66
- "falconPackageHash": "bdb0d9a1e6584ff150b26a219383712dd907441944ed2cc044ddb3f6"
66
+ "falconPackageHash": "e11f7e039210de169e2608faa2fcb6cdbd90f8df91336bcba6398dbe"
67
67
  }