@wix/himalaya-cli 0.833.0 → 0.835.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.
@@ -194,7 +194,9 @@ closed. The loop to teach an agent: **build → test → close the top ledger ga
194
194
  Before calling a multi-screen app ready, run `himi ux audit --content . --strict` and fix the
195
195
  acceptance/evidence gaps it reports. After driving a native shell, record what actually happened
196
196
  with `himi ux record --content . --platform ios|android --status passed|failed|unavailable
197
- --checks back,keyboard,…`; an unavailable platform needs its reason recorded, not guessed.
197
+ [--checks back,keyboard,…]`. `--checks` is required for `passed`/`failed` and names what you
198
+ actually drove; an unavailable platform drove nothing, so it takes no checks and needs its reason
199
+ recorded, not guessed.
198
200
 
199
201
  Report v2 sharpens all of it: a worker throw carries `in tier5-src/<screen>/index.ts:<line>`
200
202
  (mapped through the bundle's sourcemap — fix it without grepping), the ledger lists worker
@@ -66,9 +66,15 @@ CLI orchestration: `tools/himi-cli/src/test.ts`.
66
66
 
67
67
  ## Typed form journeys
68
68
 
69
- The automatic form axis intentionally dispatches the descriptor's default (often empty)
70
- value. For a flow where the real proof is **typed value → submit → visible completion**,
71
- add a short screen-local journey to `himi-test.json`:
69
+ The crawl types into a control **only when it can read a legal value off the app itself** — an id
70
+ from the control's own `options`/`optionsPath` that is not the one selected, or a word from the
71
+ booted worker's own seeded state that is not what the field already shows — and only for a control
72
+ the payload contract names. A `Slider`, `Stepper`, `Toggle` or `DatePicker` has no such source, so
73
+ those still dispatch with the descriptor's default (often empty) value, and the gap ledger says so
74
+ per screen.
75
+
76
+ For a flow where the real proof is **typed value → submit → visible completion** — several fields,
77
+ in order, ending in a rendered result — add a short screen-local journey to `himi-test.json`:
72
78
 
73
79
  ```json
74
80
  {
@@ -111,6 +117,7 @@ Only **unambiguous** defects, so a green run stays worth trusting:
111
117
  | `refresh-not-idempotent` | **warning** | `refresh` from a fresh boot settles somewhere other than the boot did, on identical data — usually a refresh that appends instead of replacing. |
112
118
  | `list-shrank-on-load-more` | **warning** | A "load more" returned FEWER rows than before it ran: the new page replaced the list instead of extending it. |
113
119
  | `list-grew-on-filter` | **warning** | A filter, search or sort made a list longer. Narrowing can reorder or remove, never add. |
120
+ | `typed-value-ignored` | **warning** | The crawl typed the value a control really carries — the payload key every renderer injects for that control — and the dispatch changed **nothing**: no state, no navigation, no request. The value is read off the app itself (an id from the control's own options, or a word from the worker's seeded state) and is always one the control is *not* already showing, so a reducer that ignores it cannot work on device. A warning because the input is the crawl's: a reducer that legitimately no-ops on a value is possible, and `himi-test.json` `skip[]` takes the invariant. |
114
121
 
115
122
  **Deliberately never reported:** an empty collection · an `error` state (often the correct
116
123
  answer to empty input) · a `null` optional · whether a nav target *exists* (approximating
@@ -131,8 +131,9 @@ Use `himi ux audit --content <app>` as the repeatable evidence check. It is advi
131
131
  existing templates migrate; `--strict` promotes an incomplete contract, remaining template
132
132
  copy, missing app fixtures, or unrecorded platform audit to a release blocker. After actually
133
133
  driving a native shell, record the result—not an assumption—with `himi ux record --content <app>
134
- --platform ios|android --status passed|failed|unavailable --checks back,keyboard,...`; an
135
- unavailable platform needs a reason.
134
+ --platform ios|android --status passed|failed|unavailable [--checks back,keyboard,...]`. `--checks`
135
+ is required for `passed`/`failed` and names what you actually drove; an unavailable platform drove
136
+ nothing, so it takes no checks and needs a `--reason` instead.
136
137
 
137
138
  ### Strict audit contract
138
139
 
@@ -140,15 +141,53 @@ unavailable platform needs a reason.
140
141
  rewrite that drops a required string is caught in the loop you are already running rather than at
141
142
  the end of it. `himi validate --strict` and `himi ux audit --strict` both promote that warning to a
142
143
  **release blocker**. Both read the same exact markers, and each gate **substring-matches** these
143
- phrases — so use them verbatim: a semantically equivalent rewrite ("each state exposes exactly one
144
- primary call to action") fails the same as omitting them:
144
+ three phrases — so use them verbatim: a semantically equivalent rewrite ("each state exposes exactly
145
+ one primary call to action") fails the same as omitting them:
145
146
 
146
147
  - The heading `## Implementation acceptance`.
147
148
  - The heading `## Evidence`.
148
149
  - The phrase `one primary CTA per state`.
149
- - A width-evidence sentence containing `compact (320/360)` followed later by `expanded`. The two
150
- **may wrap across lines** — the check is not line-based — but `expanded` must follow within about
151
- 400 characters, so keep them in the same sentence or bullet rather than in separate sections.
150
+
151
+ The fourth marker, **width evidence**, is graded by **content, not by phrase** — it is a
152
+ fact about what you rendered rather than a string, so there is nothing to paste. Satisfy it either
153
+ way:
154
+
155
+ - Name two pixel widths spanning compact → wider, within about 400 characters of each other so
156
+ they read as one statement: one **below 600px** (compact) and one **at 600px or wider** (medium
157
+ or expanded). `Rendered 320, 390 and 834 (home-320.png, home-390.png, home-834.png)` satisfies it
158
+ — a single width does not. What this proves is that you rendered more than one size class, not
159
+ which wider one: 600px is the `medium` breakpoint and `expanded` begins at 840px, so 834 counts
160
+ as wider without being expanded. The two bands are contiguous, so every width you actually render
161
+ counts as one or the other: `pixel` (412) and `iphone-max` (430) from `himi render --device` are
162
+ compact.
163
+ Each number must read as a **width**: give it a unit (`834px`), put it in a screenshot filename
164
+ (`home-834.png`, `home-834-dark.png`, `home-834x1112.png`), or name it with the vocabulary of
165
+ widths — `width`, `wide`, `pixel`, `viewport`, `breakpoint`, `compact`, `expanded`, `column`,
166
+ `portrait`, `landscape`. One such word covers a whole list of widths from either end (`Rendered
167
+ 320, 390 and 834 widths`, `Widths: 320, 390, 834` and `Rendered compact (320) and expanded (834)`
168
+ all work), and the list may wrap across lines — but it does not reach across another phrase or
169
+ into the next line unless that line continues the list: in `Rendered home at 834px; p95
170
+ 250 ms` only `834` is a width, so that line names a single width and is still reported. In a
171
+ `390x844` pair the first number is the width and the second is the height, so one portrait
172
+ screenshot is one width — `home-390x844.png` on its own is not two. Bare
173
+ digits count for nothing, which keeps a latency budget, a price or an issue number from standing
174
+ in for the half you did not render. A number in the 1990-2099 range needs its **unit** to count
175
+ as a width: `2048px` is taken at its word, while a bare `2026` and a year-shaped filename
176
+ (`shot-2026.png`, `evidence-2026-09-18.png`) are not widths — `home-2048.png` cannot be told
177
+ apart from `shot-2026.png`, so spell the unit when your render really is that wide.
178
+ `render` is deliberately **not** one of the cues: an acceptance row says "renders in under
179
+ 250 ms" far more often than it names a viewport. Neither is a pixel measurement of something
180
+ that is not a viewport — `Row height 320px`, `Font size: 512px` and `844 tall` are ignored,
181
+ because a unit says pixels, not width. Assets are judged by the SENTENCE they sit in rather than
182
+ by word order: a sentence naming an image, logo, banner, hero, icon, media or artwork is a record
183
+ of assets, so none of its numbers count — `Use 320px-wide mobile and 1024px-wide desktop hero
184
+ images` records no render. A screenshot **filename** is the exception and outranks the sentence
185
+ it appears in, because a file that was written is the strongest evidence this marker takes:
186
+ `Screenshots: hero-320.png and hero-834.png` counts.
187
+ - Or keep the scaffold's own sentence, `compact (320/360)` followed later by `expanded`, which is
188
+ still accepted as-is. The two **may wrap across lines** — the check is not line-based — but
189
+ `expanded` must follow within about 400 characters, so keep them in the same sentence or bullet
190
+ rather than in separate sections.
152
191
 
153
192
  A `MOBILE-UX.md` that is **absent or empty** is not four missing markers. `himi ux audit` reports it
154
193
  once, as a missing contract; `himi validate` stays silent about it entirely. Asking for the document
@@ -7105,39 +7105,12 @@
7105
7105
  "decl": "export function updateService(serviceId: string, service: WixServiceWrite & { revision?: string }): Promise<WixUpdateServiceResponse>;",
7106
7106
  "http": "PATCH /bookings/v2/services/{…}"
7107
7107
  },
7108
- {
7109
- "name": "deleteService",
7110
- "kind": "function",
7111
- "jsdoc": "/** Delete a service — DELETE /bookings/v1/services/{id}. */",
7112
- "decl": "export function deleteService(serviceId: string): Promise<WixDeleteServiceResponse>;",
7113
- "http": "DELETE /bookings/v1/services/{…}"
7114
- },
7115
- {
7116
- "name": "WixListBookingsResponse",
7117
- "kind": "interface",
7118
- "jsdoc": "",
7119
- "decl": "export interface WixListBookingsResponse {\n bookings: WixBooking[];\n}"
7120
- },
7121
- {
7122
- "name": "listBookings",
7123
- "kind": "function",
7124
- "jsdoc": "/** List bookings — GET /bookings/v1/bookings. */",
7125
- "decl": "export function listBookings(): Promise<WixListBookingsResponse>;",
7126
- "http": "GET /bookings/v1/bookings"
7127
- },
7128
7108
  {
7129
7109
  "name": "WixGetBookingResponse",
7130
7110
  "kind": "interface",
7131
7111
  "jsdoc": "",
7132
7112
  "decl": "export interface WixGetBookingResponse {\n booking: WixBooking;\n}"
7133
7113
  },
7134
- {
7135
- "name": "getBooking",
7136
- "kind": "function",
7137
- "jsdoc": "/** Fetch a single booking by id — GET /bookings/v1/bookings/{id}. */",
7138
- "decl": "export function getBooking(bookingId: string): Promise<WixGetBookingResponse>;",
7139
- "http": "GET /bookings/v1/bookings/{…}"
7140
- },
7141
7114
  {
7142
7115
  "name": "WixBookingWrite",
7143
7116
  "kind": "interface",
@@ -7150,13 +7123,6 @@
7150
7123
  "jsdoc": "",
7151
7124
  "decl": "export interface WixCreateBookingResponse {\n booking: WixBooking;\n}"
7152
7125
  },
7153
- {
7154
- "name": "createBooking",
7155
- "kind": "function",
7156
- "jsdoc": "/** Create a booking on behalf of a client — POST /bookings/v1/bookings. */",
7157
- "decl": "export function createBooking(booking: WixBookingWrite): Promise<WixCreateBookingResponse>;",
7158
- "http": "POST /bookings/v1/bookings"
7159
- },
7160
7126
  {
7161
7127
  "name": "WixUpdateBookingResponse",
7162
7128
  "kind": "interface",
@@ -7234,11 +7200,17 @@
7234
7200
  "jsdoc": "",
7235
7201
  "decl": "export interface WixQueryAvailabilityResponse { availabilityEntries?: WixAvailabilityEntry[] }"
7236
7202
  },
7203
+ {
7204
+ "name": "WixServiceAvailabilityOptions",
7205
+ "kind": "interface",
7206
+ "jsdoc": "/** How to bound an availability query. `bookable` is a `query.filter` member — the\n * request's `query` has only `filter` and `sort`, so there is NO paging member and\n * a `paging: { limit }` you add is silently dropped rather than honoured. */",
7207
+ "decl": "export interface WixServiceAvailabilityOptions {\n /** Bookable slots only. Defaults to `true`; pass `false` to get the unbookable\n * ones too (a greyed-out calendar), which is what the endpoint returns unfiltered. */\n bookable?: boolean;\n}"
7208
+ },
7237
7209
  {
7238
7210
  "name": "queryServiceAvailability",
7239
7211
  "kind": "function",
7240
- "jsdoc": "/** Query bookable slots for a service in a date window —\n * POST /bookings/v1/availability/query. Verified live (571 entries). */",
7241
- "decl": "export function queryServiceAvailability(serviceId: string, startDate: string, endDate: string): Promise<WixQueryAvailabilityResponse>;",
7212
+ "jsdoc": "/**\n * Query bookable slots for a service in a date window —\n * POST /bookings/v1/availability/query. Verified live (571 entries).\n *\n * BOUND IT. Unfiltered, this returns every slot in the window, bookable AND\n * unbookable — a two-week window on a busy calendar is hundreds of entries and a\n * response big enough to stall a worker. Two things bound it, and only one of them\n * is a parameter here:\n *\n * - `bookable`, which this now defaults to `true`. Slots you cannot book are not\n * what a booking screen is paging through.\n * - THE WINDOW ITSELF. Ask for the days you are about to paint, not the month.\n *\n * What is NOT available is paging: the request's `query` carries only `filter` and\n * `sort`, so a `paging: { limit }` is an unknown field the server drops on the\n * floor — which is why \"it ignores its own paging.limit\" is a misreading. There was\n * never a paging member to ignore.\n *\n * A per-day cap (`slotsPerDay`) is deliberately NOT offered. This v1 Availability\n * Calendar endpoint is absent from the current published Wix reference (superseded\n * by Time Slots V2), and the two plausible spellings put it in different places — a\n * `query.filter` member, or a top-level sibling of `query`. An unknown field in\n * either position is dropped silently rather than rejected, so shipping the wrong\n * guess would hand callers a knob that does nothing while this comment promised it\n * worked. Narrow the window instead, until the level can be confirmed against a\n * schema or a live call.\n */",
7213
+ "decl": "export function queryServiceAvailability(\n serviceId: string,\n startDate: string,\n endDate: string,\n options: WixServiceAvailabilityOptions = {},\n): Promise<WixQueryAvailabilityResponse>;",
7242
7214
  "http": "POST /bookings/v1/availability/query"
7243
7215
  },
7244
7216
  {
@@ -7342,7 +7314,7 @@
7342
7314
  {
7343
7315
  "name": "getBookingFull",
7344
7316
  "kind": "function",
7345
- "jsdoc": "/** Rich single-booking read — GET /bookings/v1/bookings/{id}. Returns the full\n * booking (payment/intake/location/staff/attendance). The booking-detail data source. */",
7317
+ "jsdoc": "/** Rich single-booking read — GET /bookings/v1/bookings/{id}. Returns the full\n * booking (payment/intake/location/staff/attendance). The booking-detail data source.\n * Owner-scoped: needs an owner/BO session; a visitor or member token gets 403. A\n * member-facing screen reads its own bookings via `queryBookings` instead. */",
7346
7318
  "decl": "export function getBookingFull(bookingId: string): Promise<WixGetBookingFullResponse>;",
7347
7319
  "http": "GET /bookings/v1/bookings/{…}"
7348
7320
  },
@@ -20549,16 +20521,16 @@
20549
20521
  {
20550
20522
  "name": "queryStoreProducts",
20551
20523
  "kind": "function",
20552
- "jsdoc": "",
20553
- "decl": "export async function queryStoreProducts(limit = 50, cursor?: string): Promise<StoreProductsResponse>;",
20524
+ "jsdoc": "/** List products from whichever catalog this site runs. `fields` is additive — it\n * WIDENS the default v3 display fieldset rather than replacing it. */",
20525
+ "decl": "export async function queryStoreProducts(\n limit = 50,\n cursor?: string,\n fields?: readonly string[],\n): Promise<StoreProductsResponse>;",
20554
20526
  "http": "POST /stores/v3/products/query"
20555
20527
  },
20556
20528
  {
20557
20529
  "name": "getStoreProduct",
20558
20530
  "kind": "function",
20559
- "jsdoc": "",
20560
- "decl": "export async function getStoreProduct(productId: string): Promise<StoreGetProductResponse>;",
20561
- "http": "GET /stores/v3/products/{…}?fields={…}"
20531
+ "jsdoc": "/** Read one product from whichever catalog this site runs. `fields` is additive —\n * it WIDENS the default v3 display fieldset rather than replacing it. */",
20532
+ "decl": "export async function getStoreProduct(\n productId: string,\n fields?: readonly string[],\n): Promise<StoreGetProductResponse>;",
20533
+ "http": "GET /stores/v3/products/{…}?{…}"
20562
20534
  },
20563
20535
  {
20564
20536
  "name": "productPrice",
@@ -20806,7 +20778,7 @@
20806
20778
  "name": "StoreProduct",
20807
20779
  "kind": "interface",
20808
20780
  "jsdoc": "",
20809
- "decl": "export interface StoreProduct {\n id?: string;\n name?: string;\n slug?: string;\n description?: string;\n plainDescription?: string;\n media?: {\n mainMedia?: WixMediaSlot;\n main?: WixMediaSlot;\n items?: WixMediaSlot[];\n };\n price?: { formatted?: { price?: string }; price?: number; currency?: string };\n priceData?: { formatted?: { price?: string }; price?: number; currency?: string };\n currency?: string;\n actualPriceRange?: {\n minValue?: { amount?: string; formattedAmount?: string; currency?: string };\n maxValue?: { amount?: string; formattedAmount?: string; currency?: string };\n };\n}"
20781
+ "decl": "export interface StoreProduct {\n id?: string;\n name?: string;\n slug?: string;\n description?: string;\n plainDescription?: string;\n /** Both gallery layouts: V1 `mainMedia`/`items`, V3 `main`/`itemsInfo.items`. */\n media?: {\n mainMedia?: WixMediaSlot;\n main?: WixMediaSlot;\n items?: WixMediaSlot[];\n itemsInfo?: { items?: WixMediaSlot[] };\n };\n price?: { formatted?: { price?: string }; price?: number; currency?: string };\n priceData?: { formatted?: { price?: string }; price?: number; currency?: string };\n currency?: string;\n actualPriceRange?: {\n minValue?: { amount?: string; formattedAmount?: string; currency?: string };\n maxValue?: { amount?: string; formattedAmount?: string; currency?: string };\n };\n}"
20810
20782
  },
20811
20783
  {
20812
20784
  "name": "StoreProductsResponse",
@@ -20824,7 +20796,7 @@
20824
20796
  "name": "StoreProductSearch",
20825
20797
  "kind": "interface",
20826
20798
  "jsdoc": "/** User-facing product discovery. V3 name search/sorting belong to Search\n * Products; Query Products deliberately does not support the name field.\n * V1 uses its documented JSON-string filter/sort dialect. */",
20827
- "decl": "export interface StoreProductSearch {\n query?: string;\n sort?: 'default' | 'az' | 'za';\n ids?: string[];\n limit?: number;\n cursor?: string;\n}"
20799
+ "decl": "export interface StoreProductSearch {\n query?: string;\n sort?: 'default' | 'az' | 'za';\n ids?: string[];\n limit?: number;\n cursor?: string;\n /** Extra v3 fieldsets. Additive — widens the default, never replaces it. */\n fields?: readonly string[];\n}"
20828
20800
  },
20829
20801
  {
20830
20802
  "name": "searchStoreProducts",
@@ -20839,6 +20811,12 @@
20839
20811
  "jsdoc": "",
20840
20812
  "decl": "export {\n wixMediaUrl,\n isUnsizedWixMedia,\n WIX_LIST_IMAGE,\n WIX_DETAIL_IMAGE,\n type WixImageSize,\n} from \"@himalaya/wix-media\";"
20841
20813
  },
20814
+ {
20815
+ "name": "productGallery",
20816
+ "kind": "function",
20817
+ "jsdoc": "/**\n * EVERY image on the product, sized, in wire order — the gallery a detail screen\n * pages through, not just the one tile `productImage` returns.\n *\n * It reads both catalog layouts (V1 `media.items`, V3 `media.itemsInfo.items`) and\n * falls back to the primary image when the product carries no gallery, so a caller\n * always gets a non-empty list for a product that has any image at all. Empty slots\n * and duplicates are dropped.\n *\n * Exported because the gallery union was being re-derived by hand in ~10 template\n * workers, each free to miss a layout — which is exactly how a V3 store silently\n * lost its thumbnails. Defaults to the LIST budget like `productImage`; pass\n * `WIX_DETAIL_IMAGE` for a full-bleed pager, or `null` for the stored originals.\n */",
20818
+ "decl": "export function productGallery(p: StoreProduct, size: WixImageSize | null = WIX_LIST_IMAGE): string[];"
20819
+ },
20842
20820
  {
20843
20821
  "name": "plainText",
20844
20822
  "kind": "function",
@@ -38,13 +38,14 @@ answers three questions:
38
38
  > himi test --content . --json
39
39
  > himi render home --content . --png home-light.png # one scheme per invocation
40
40
  > himi render home --content . --scheme dark --png home-dark.png
41
- > himi ux record --content . --platform ios --status unavailable --checks back,keyboard --reason "no simulator off-repo"
42
- > himi ux record --content . --platform android --status unavailable --checks back,keyboard --reason "no emulator off-repo"
41
+ > himi ux record --content . --platform ios --status unavailable --reason "no simulator off-repo"
42
+ > himi ux record --content . --platform android --status unavailable --reason "no emulator off-repo"
43
43
  > himi ux audit --content . --strict
44
44
  > ```
45
45
  >
46
- > Use `--status passed` and the checks you actually drove when you did drive a device;
47
- > `unavailable` requires a reason, which is the point — it records the gap rather than hiding it.
46
+ > Use `--status passed` and `--checks` naming what you actually drove when you did drive a device.
47
+ > An `unavailable` platform takes **no `--checks`** — nothing was driven, so there is nothing to
48
+ > list — and requires a `--reason`, which is the point: it records the gap rather than hiding it.
48
49
 
49
50
  ---
50
51
 
@@ -334,7 +334,7 @@ happened — or, honestly, that it could not.
334
334
  ```
335
335
  himi ux audit --strict # the gate that fails on a missing per-platform record
336
336
  himi ux record --content . --platform ios --status passed --checks back,keyboard,scroll
337
- himi ux record --content . --platform ios --status unavailable --checks back --reason "no simulator on this machine"
337
+ himi ux record --content . --platform ios --status unavailable --reason "no simulator on this machine"
338
338
  ```
339
339
 
340
340
  For the full tier ladder — which test tier observes which kind of change, and what a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.833.0",
3
+ "version": "0.835.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": "a2c1ef13eceb008115d02b8e16b9dd82c4a1040e822600be9b93647c"
66
+ "falconPackageHash": "28f3f50cd02d76203d4daf19d83865ed5be46f5158dc74ca6e1764b1"
67
67
  }
@@ -3,7 +3,7 @@ import { defineService } from "@himalaya/state";
3
3
  import { share as shareSheet } from "@himalaya/share";
4
4
  import { addToCurrentCart, getCurrentCart } from "@himalaya/wix-ecom";
5
5
  import { get, upsert, enqueue, type DbEntity } from "@himalaya/local-db";
6
- import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, mediaSlotUrl, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
6
+ import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, productGallery as sdkProductGallery, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
7
7
  import { cleanTitle } from "../_shared/himi-text.js";
8
8
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
9
9
  import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
@@ -96,11 +96,12 @@ export function productGallery(p: StoreProduct): GalleryItem[] {
96
96
  // entry goes through the same CDN transform — the primary AND the extra media
97
97
  // items, which would otherwise stay untouched 4000px originals.
98
98
  push(productImage(p, WIX_DETAIL_IMAGE));
99
- for (const item of p.media?.items ?? []) {
100
- // Same reader productImage uses — a slot may be the reference STRING rather than
101
- // an object, and two private copies of that knowledge is how they drift.
102
- push(mediaSlotUrl(item, WIX_DETAIL_IMAGE));
103
- }
99
+ // The SDK helper reads BOTH gallery layouts — V1 `media.items` and Catalog V3
100
+ // `media.itemsInfo.items`. This loop used to walk `media.items` itself, which is
101
+ // the V1 spelling only, so a V3 product with a ten-image gallery rendered as one
102
+ // image no matter what the read asked for. One reader, not a private copy per
103
+ // worker — that is how the two layouts drifted apart in the first place.
104
+ for (const url of sdkProductGallery(p, WIX_DETAIL_IMAGE)) push(url);
104
105
  return urls.map((url) => ({ url }));
105
106
  }
106
107
 
@@ -3,7 +3,7 @@ import { defineService } from "@himalaya/state";
3
3
  import { share as shareSheet } from "@himalaya/share";
4
4
  import { addToCurrentCart, getCurrentCart } from "@himalaya/wix-ecom";
5
5
  import { get, upsert, enqueue, type DbEntity } from "@himalaya/local-db";
6
- import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, mediaSlotUrl, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
6
+ import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, productGallery as sdkProductGallery, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
7
7
  import { cleanTitle } from "../_shared/himi-text.js";
8
8
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
9
9
  import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
@@ -96,11 +96,12 @@ export function productGallery(p: StoreProduct): GalleryItem[] {
96
96
  // entry goes through the same CDN transform — the primary AND the extra media
97
97
  // items, which would otherwise stay untouched 4000px originals.
98
98
  push(productImage(p, WIX_DETAIL_IMAGE));
99
- for (const item of p.media?.items ?? []) {
100
- // Same reader productImage uses — a slot may be the reference STRING rather than
101
- // an object, and two private copies of that knowledge is how they drift.
102
- push(mediaSlotUrl(item, WIX_DETAIL_IMAGE));
103
- }
99
+ // The SDK helper reads BOTH gallery layouts — V1 `media.items` and Catalog V3
100
+ // `media.itemsInfo.items`. This loop used to walk `media.items` itself, which is
101
+ // the V1 spelling only, so a V3 product with a ten-image gallery rendered as one
102
+ // image no matter what the read asked for. One reader, not a private copy per
103
+ // worker — that is how the two layouts drifted apart in the first place.
104
+ for (const url of sdkProductGallery(p, WIX_DETAIL_IMAGE)) push(url);
104
105
  return urls.map((url) => ({ url }));
105
106
  }
106
107
 
@@ -3,7 +3,7 @@ import { defineService } from "@himalaya/state";
3
3
  import { share as shareSheet } from "@himalaya/share";
4
4
  import { addToCurrentCart, getCurrentCart } from "@himalaya/wix-ecom";
5
5
  import { get, upsert, enqueue, type DbEntity } from "@himalaya/local-db";
6
- import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, mediaSlotUrl, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
6
+ import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, productGallery as sdkProductGallery, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
7
7
  import { cleanTitle } from "../_shared/himi-text.js";
8
8
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
9
9
  import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
@@ -96,11 +96,12 @@ export function productGallery(p: StoreProduct): GalleryItem[] {
96
96
  // entry goes through the same CDN transform — the primary AND the extra media
97
97
  // items, which would otherwise stay untouched 4000px originals.
98
98
  push(productImage(p, WIX_DETAIL_IMAGE));
99
- for (const item of p.media?.items ?? []) {
100
- // Same reader productImage uses — a slot may be the reference STRING rather than
101
- // an object, and two private copies of that knowledge is how they drift.
102
- push(mediaSlotUrl(item, WIX_DETAIL_IMAGE));
103
- }
99
+ // The SDK helper reads BOTH gallery layouts — V1 `media.items` and Catalog V3
100
+ // `media.itemsInfo.items`. This loop used to walk `media.items` itself, which is
101
+ // the V1 spelling only, so a V3 product with a ten-image gallery rendered as one
102
+ // image no matter what the read asked for. One reader, not a private copy per
103
+ // worker — that is how the two layouts drifted apart in the first place.
104
+ for (const url of sdkProductGallery(p, WIX_DETAIL_IMAGE)) push(url);
104
105
  return urls.map((url) => ({ url }));
105
106
  }
106
107
 
@@ -1269,13 +1269,13 @@ const SCREENS = runtimeSetting({
1269
1269
  "style": "primary",
1270
1270
  "size": "sm",
1271
1271
  "fullWidth": false,
1272
- "accessibilityLabel": "Send",
1273
1272
  "action": {
1274
1273
  "actionId": "tier5.chat.send"
1275
1274
  }
1276
1275
  }
1277
1276
  },
1278
1277
  "modifiers": {
1278
+ "accessibilityLabel": "Send",
1279
1279
  "hiddenPath": "$service:chat.busy"
1280
1280
  }
1281
1281
  },
@@ -1291,13 +1291,13 @@ const SCREENS = runtimeSetting({
1291
1291
  "style": "primary",
1292
1292
  "size": "sm",
1293
1293
  "fullWidth": false,
1294
- "accessibilityLabel": "Stop",
1295
1294
  "action": {
1296
1295
  "actionId": "tier5.chat.stop"
1297
1296
  }
1298
1297
  }
1299
1298
  },
1300
1299
  "modifiers": {
1300
+ "accessibilityLabel": "Stop",
1301
1301
  "hiddenPath": "$service:chat.idle"
1302
1302
  }
1303
1303
  },
@@ -3,7 +3,7 @@ import { defineService } from "@himalaya/state";
3
3
  import { share as shareSheet } from "@himalaya/share";
4
4
  import { addToCurrentCart, getCurrentCart } from "@himalaya/wix-ecom";
5
5
  import { get, upsert, enqueue, type DbEntity } from "@himalaya/local-db";
6
- import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, mediaSlotUrl, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
6
+ import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, productGallery as sdkProductGallery, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
7
7
  import { cleanTitle } from "../_shared/himi-text.js";
8
8
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
9
9
  import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
@@ -96,11 +96,12 @@ export function productGallery(p: StoreProduct): GalleryItem[] {
96
96
  // entry goes through the same CDN transform — the primary AND the extra media
97
97
  // items, which would otherwise stay untouched 4000px originals.
98
98
  push(productImage(p, WIX_DETAIL_IMAGE));
99
- for (const item of p.media?.items ?? []) {
100
- // Same reader productImage uses — a slot may be the reference STRING rather than
101
- // an object, and two private copies of that knowledge is how they drift.
102
- push(mediaSlotUrl(item, WIX_DETAIL_IMAGE));
103
- }
99
+ // The SDK helper reads BOTH gallery layouts — V1 `media.items` and Catalog V3
100
+ // `media.itemsInfo.items`. This loop used to walk `media.items` itself, which is
101
+ // the V1 spelling only, so a V3 product with a ten-image gallery rendered as one
102
+ // image no matter what the read asked for. One reader, not a private copy per
103
+ // worker — that is how the two layouts drifted apart in the first place.
104
+ for (const url of sdkProductGallery(p, WIX_DETAIL_IMAGE)) push(url);
104
105
  return urls.map((url) => ({ url }));
105
106
  }
106
107
 
@@ -3,7 +3,7 @@ import { defineService } from "@himalaya/state";
3
3
  import { share as shareSheet } from "@himalaya/share";
4
4
  import { addToCurrentCart, getCurrentCart } from "@himalaya/wix-ecom";
5
5
  import { get, upsert, enqueue, type DbEntity } from "@himalaya/local-db";
6
- import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, mediaSlotUrl, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
6
+ import { getStoreProduct, plainText, productImage, productPrice, wixMediaUrl, productGallery as sdkProductGallery, WIX_DETAIL_IMAGE, type StoreProduct } from "../_shared/himi-store.js";
7
7
  import { cleanTitle } from "../_shared/himi-text.js";
8
8
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
9
9
  import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
@@ -96,11 +96,12 @@ export function productGallery(p: StoreProduct): GalleryItem[] {
96
96
  // entry goes through the same CDN transform — the primary AND the extra media
97
97
  // items, which would otherwise stay untouched 4000px originals.
98
98
  push(productImage(p, WIX_DETAIL_IMAGE));
99
- for (const item of p.media?.items ?? []) {
100
- // Same reader productImage uses — a slot may be the reference STRING rather than
101
- // an object, and two private copies of that knowledge is how they drift.
102
- push(mediaSlotUrl(item, WIX_DETAIL_IMAGE));
103
- }
99
+ // The SDK helper reads BOTH gallery layouts — V1 `media.items` and Catalog V3
100
+ // `media.itemsInfo.items`. This loop used to walk `media.items` itself, which is
101
+ // the V1 spelling only, so a V3 product with a ten-image gallery rendered as one
102
+ // image no matter what the read asked for. One reader, not a private copy per
103
+ // worker — that is how the two layouts drifted apart in the first place.
104
+ for (const url of sdkProductGallery(p, WIX_DETAIL_IMAGE)) push(url);
104
105
  return urls.map((url) => ({ url }));
105
106
  }
106
107