@wix/himalaya-cli 0.1611.0 → 0.1613.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -1047,6 +1047,47 @@
1047
1047
  }
1048
1048
  ]
1049
1049
  },
1050
+ "GET /loyalty-tiers/v1/tiers/program": {
1051
+ "programSettings": {
1052
+ "status": "ACTIVE",
1053
+ "baseTierDefinition": {
1054
+ "name": "Bronze"
1055
+ },
1056
+ "rollingWindow": {
1057
+ "durationInMonths": 12
1058
+ }
1059
+ },
1060
+ "tiers": [
1061
+ {
1062
+ "id": "tier-0001",
1063
+ "tierDefinition": {
1064
+ "name": "Silver"
1065
+ },
1066
+ "requiredPoints": 500
1067
+ },
1068
+ {
1069
+ "id": "tier-0002",
1070
+ "tierDefinition": {
1071
+ "name": "Gold"
1072
+ },
1073
+ "requiredPoints": 1000
1074
+ }
1075
+ ]
1076
+ },
1077
+ "GET /loyalty-coupons/v1/coupons/my-coupons": {
1078
+ "loyaltyCoupons": [
1079
+ {
1080
+ "id": "lc-0001",
1081
+ "rewardName": "Free filter coffee",
1082
+ "status": "ACTIVE",
1083
+ "couponReference": {
1084
+ "couponId": "cp-0001",
1085
+ "code": "BEANS-0001",
1086
+ "deleted": false
1087
+ }
1088
+ }
1089
+ ]
1090
+ },
1050
1091
  "POST /reviews/v1/reviews/query": {
1051
1092
  "reviews": [
1052
1093
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_comment": "GENERATED by core/web-embed/authoring/gen-reference.mjs from core/ts/src/schema.ts — do not hand-edit. The schema is the source of truth; regenerate on schema changes.",
3
3
  "schemaSource": "core/ts/src/schema.ts",
4
- "bindingGrammar": "$service:<service>.<path> (worker state) · $item.<field> (list row) · $flag:<key> (feature flag) · $env:<key> (client environment — #1056). Bindings resolve in the text/value props marked 'binding-aware' below — INCLUDING Modifiers token refs — but only `background` and `border.color`, which are the two whose token a binding actually fills: a worker can drive per-item theming (e.g. a game board's per-square color), not just text. `shadow` is NOT binding-aware on any renderer, and a gradient is driven by its own `backgroundGradientPath` prop resolving to a gradient token NAME, not by a binding inside a token ref — so `shadow: { token: \"$item.x\" }` and `backgroundGradient: { token: \"$item.x\" }` keep the sentinel and silently lose the per-item styling. Colour slots split by LEVEL, not by component: inside modifiers a colour is a TokenRef OBJECT ({ token: \"…\" }), inside props it is a bare TokenName STRING — there is no modifiers.color or modifiers.tint (see the 'Token slots' section in component-reference.md). A few input props render the LITERAL and are NOT binding-aware — notably SearchField.placeholder and Chip.label; SearchField.value is live and binding-aware like other content props. Bindings also CANNOT target strict-enum fields (style, size, alignment, severity, presentation, contentMode, …) — those fail at render, not validation. $env: is client-resolved (no worker) and valid in modifiers.hiddenPath and FragmentSlot.casePath ONLY — keys reduceMotion/colorScheme/sizeClass/isRTL, e.g. hiddenPath:\"$env:reduceMotion\" or hiddenPath:\"$env:colorScheme==light\", casePath:\"$env:colorScheme\"; unknown key fails open (shown / defaultCase).",
4
+ "bindingGrammar": "$service:<service>.<path> (worker state) · $item.<field> (list row) · $flag:<key> (feature flag) · $env:<key> (client environment — #1056). Bindings resolve in the text/value props marked 'binding-aware' below — INCLUDING Modifiers token refs — but only `background` and `border.color`, which are the two whose token a binding actually fills: a worker can drive per-item theming (e.g. a game board's per-square color), not just text. `shadow` is NOT binding-aware on any renderer, and a gradient is driven by its own `backgroundGradientPath` prop resolving to a gradient token NAME, not by a binding inside a token ref — so `shadow: { token: \"$item.x\" }` and `backgroundGradient: { token: \"$item.x\" }` keep the sentinel and silently lose the per-item styling. Colour slots split by LEVEL, not by component: inside modifiers a colour is a TokenRef OBJECT ({ token: \"…\" }), inside props it is a bare TokenName STRING — there is no modifiers.color or modifiers.tint (see the 'Token slots' section in component-reference.md). A few input props render the LITERAL and are NOT binding-aware — notably SearchField.placeholder and Chip.label; SearchField.value is live and binding-aware like other content props. Bindings also CANNOT target strict-enum fields (style, size, alignment, severity, presentation, contentMode, …) — those fail at render, not validation. $env: is client-resolved (no worker) and valid in modifiers.hiddenPath and FragmentSlot.casePath ONLY — keys reduceMotion/colorScheme/sizeClass/isRTL/keyboardVisible, e.g. hiddenPath:\"$env:reduceMotion\" or hiddenPath:\"$env:colorScheme==light\", casePath:\"$env:colorScheme\"; unknown key fails open (shown / defaultCase).",
5
5
  "componentCount": 88,
6
6
  "notImplementedComponents": [
7
7
  "Text",
@@ -515,14 +515,17 @@
515
515
  "currentIndexPath",
516
516
  "itemsPath"
517
517
  ],
518
- "propsSource": "export interface CarouselProps {\n /** Opt-in paging for itemsPath; absent preserves the horizontal card rail. */\n paged?: boolean;\n /** Worker-driven page index; clamped to the current page count. */\n currentIndexPath?: string;\n items: Component[];\n currentIndex?: number;\n onChange?: Action;\n autoplayIntervalMs?: number;\n /** `$service:` binding → a live array to render as slides (overrides `items`). */\n itemsPath?: string;\n /** 0 (default) = all slides mounted; N ⇒ mount current ± N, unmount the rest. */\n liveWindow?: number;\n}"
518
+ "propsSource": "export interface CarouselProps {\n /** Opt-in paging for itemsPath; absent preserves the horizontal card rail. */\n paged?: boolean;\n /** #4673 — `$service:` path to the page index, two-way with `onChange`: a CHANGE of the resolved\n * value animates the carousel there (instantly under reduced motion); a swipe still fires\n * `onChange` with `args.index`, which the worker writes back. Out of range ⇒ clamped; not an\n * integer ⇒ ignored; equal to the current page ⇒ nothing; a move the binding made fires no\n * `onChange`. Drives literal `items` and `paged` bound ones; inside a bound row a substituted\n * literal places the page like `currentIndex`. A rail (`itemsPath` without `paged`) ignores it. */\n currentIndexPath?: string;\n items: Component[];\n currentIndex?: number;\n onChange?: Action;\n autoplayIntervalMs?: number;\n /** `$service:` binding → a live array to render as slides (overrides `items`). */\n itemsPath?: string;\n /** 0 (default) = all slides mounted; N ⇒ mount current ± N, unmount the rest. */\n liveWindow?: number;\n}"
519
519
  },
520
520
  {
521
521
  "name": "Pager",
522
522
  "category": "Navigation primitives",
523
523
  "propsType": "PagerProps",
524
- "bindings": [],
525
- "propsSource": "export interface PagerProps {\n pages: Component[];\n currentIndex?: number;\n onChange?: Action;\n showIndicator?: boolean;\n /** As {@link CarouselProps.liveWindow}. */\n liveWindow?: number;\n}"
524
+ "bindings": [
525
+ "currentIndexPath",
526
+ "pagesPath"
527
+ ],
528
+ "propsSource": "export interface PagerProps {\n pages: Component[];\n currentIndex?: number;\n onChange?: Action;\n showIndicator?: boolean;\n /** As {@link CarouselProps.liveWindow}. */\n liveWindow?: number;\n /** #4673 — as {@link CarouselProps.currentIndexPath}: a worker-driven page, two-way with `onChange`. */\n currentIndexPath?: string;\n /** #4673 — `$service:` path to an array: `pages[0]` is the page TEMPLATE, instantiated once per\n * element with `$item.` substitution (as `Carousel.itemsPath`). Pages are keyed by `$item.id`, so\n * the page on screen stays on screen when items are inserted before it. */\n pagesPath?: string;\n}"
526
529
  },
527
530
  {
528
531
  "name": "StepIndicator",
@@ -6,7 +6,7 @@
6
6
  **88 core component types.** Every component body is `{ "package": "himalaya", "type": "<Type>", "props": { … } }`.
7
7
  Binding grammar: `$service:<service>.<path>` (worker state) · `$item.<field>` (list row template) · `$flag:<key>` (feature flag) · `$env:<key>` (client environment — #1056).
8
8
  Bindings resolve in the text/value props marked **binding-aware** below — **including two `Modifiers` token refs**, `background` and `border.color`, which are the only two whose token a binding actually fills: a worker can drive per-item **theming** (e.g. a game board's per-square color, `background: { token: "$item.bg" }`), not just text content. `shadow` and `backgroundGradient` are `TokenRef`s but are **not** binding-aware on any renderer — `shadow: { token: "$item.x" }` keeps the sentinel as a literal token name and silently loses the per-item styling, and a gradient is driven by its own `backgroundGradientPath` prop resolving to a gradient token NAME, not by a binding inside a token ref.
9
- `$env:` is **client-resolved** (no worker) and valid in `modifiers.hiddenPath` and `FragmentSlot.casePath` **only** — keys `reduceMotion` / `colorScheme` / `sizeClass` / `isRTL`, with an optional `==`/`!=` comparison (e.g. `hiddenPath: "$env:reduceMotion"`, `hiddenPath: "$env:colorScheme==light"`, `casePath: "$env:colorScheme"`). An unknown key fails open (shown / `defaultCase`).
9
+ `$env:` is **client-resolved** (no worker) and valid in `modifiers.hiddenPath` and `FragmentSlot.casePath` **only** — keys `reduceMotion` / `colorScheme` / `sizeClass` / `isRTL` / `keyboardVisible` (an on-screen keyboard is up — `hiddenPath: "$env:keyboardVisible"` hides a header while typing), with an optional `==`/`!=` comparison (e.g. `hiddenPath: "$env:reduceMotion"`, `hiddenPath: "$env:colorScheme==light"`, `casePath: "$env:colorScheme"`). An unknown key fails open (shown / `defaultCase`).
10
10
  A few input props render the **literal** and are **not** binding-aware — notably `SearchField.placeholder` and `Chip.label`. `SearchField.value` is live/binding-aware like other content props.
11
11
  They **cannot target strict-enum fields** (`style`, `size`, `alignment`, `severity`, `presentation`, `contentMode`, …) — those fail at render time, not validation.
12
12
 
@@ -63,7 +63,7 @@ See also **[Modifiers](#modifiers)** (the `modifiers` block) and **[Actions](#ac
63
63
  | `Tabs` | Navigation primitives | `optionsPath`, `activeColorPath`, `selectedId` |
64
64
  | `SegmentedControl` | Navigation primitives | `optionsPath`, `selectedId` |
65
65
  | `Carousel` | Navigation primitives | `currentIndexPath`, `itemsPath` |
66
- | `Pager` | Navigation primitives | — |
66
+ | `Pager` | Navigation primitives | `currentIndexPath`, `pagesPath` |
67
67
  | `StepIndicator` | Navigation primitives | — |
68
68
  | `CollapsingHeader` | Navigation primitives | — |
69
69
  | `AvatarStrip` | Navigation primitives | `avatarsPath` |
@@ -1075,7 +1075,12 @@ export interface SegmentedControlProps {
1075
1075
  export interface CarouselProps {
1076
1076
  /** Opt-in paging for itemsPath; absent preserves the horizontal card rail. */
1077
1077
  paged?: boolean;
1078
- /** Worker-driven page index; clamped to the current page count. */
1078
+ /** #4673 — `$service:` path to the page index, two-way with `onChange`: a CHANGE of the resolved
1079
+ * value animates the carousel there (instantly under reduced motion); a swipe still fires
1080
+ * `onChange` with `args.index`, which the worker writes back. Out of range ⇒ clamped; not an
1081
+ * integer ⇒ ignored; equal to the current page ⇒ nothing; a move the binding made fires no
1082
+ * `onChange`. Drives literal `items` and `paged` bound ones; inside a bound row a substituted
1083
+ * literal places the page like `currentIndex`. A rail (`itemsPath` without `paged`) ignores it. */
1079
1084
  currentIndexPath?: string;
1080
1085
  items: Component[];
1081
1086
  currentIndex?: number;
@@ -1098,6 +1103,12 @@ export interface PagerProps {
1098
1103
  showIndicator?: boolean;
1099
1104
  /** As {@link CarouselProps.liveWindow}. */
1100
1105
  liveWindow?: number;
1106
+ /** #4673 — as {@link CarouselProps.currentIndexPath}: a worker-driven page, two-way with `onChange`. */
1107
+ currentIndexPath?: string;
1108
+ /** #4673 — `$service:` path to an array: `pages[0]` is the page TEMPLATE, instantiated once per
1109
+ * element with `$item.` substitution (as `Carousel.itemsPath`). Pages are keyed by `$item.id`, so
1110
+ * the page on screen stays on screen when items are inserted before it. */
1111
+ pagesPath?: string;
1101
1112
  }
1102
1113
  ```
1103
1114
 
@@ -256,8 +256,9 @@ product. Right after `init`:
256
256
  typed input → submit → the persisted result with a typed journey in `himi-test.json`
257
257
  (`.himi/himi-test.stub.json` has a skeleton per form: fill the values and expected text)
258
258
  (`--deep` replays action chains but never types a field value), then prove the destination
259
- and cancel/back recovery separately — a typed journey is screen-local and does not follow
260
- navigation, so use a render of the destination or a device drive. A fresh-screen crawl alone
259
+ and cancel/back recovery separately. For device-local records, add explicit `coldVisits`
260
+ assertions from `himi docs himi-test` so another cold worker must read the same saved record.
261
+ Cold visits do not drive navigation or prove warm repaint/relaunch: drive those in the UI. A fresh-screen crawl alone
261
262
  does not prove a flow.
262
263
 
263
264
  Before authoring a multi-screen mobile app, ensure the customer-approved `SPEC.md` and
@@ -44,7 +44,10 @@ keeps its existing fields and adds the code:
44
44
 
45
45
  | Code | Meaning |
46
46
  | --- | --- |
47
+ | `test_journey_budget_exceeded` | The authored config exceeds a shared journey/visit/worker-segment/step/assertion work bound. Exit 2 with `ok: false` and the existing `error` on stderr before compilation or worker boot. Tokenless `POST /v1/test` uses the same code with HTTP422 before upload compilation/evaluation. Split the evaluation into separately identified bounded runs; no truncated pass is emitted. |
47
48
  | `test_journey_failed` | At least one unsuppressed authored journey failed: its destination is absent, initialization/action failed, or the expected rendered values were missing. The existing `failures`, `journeys`, counts and exit 1 remain. This includes cold-entry journeys with `steps: []`. |
49
+ | `test_browser_cold_visits_unsupported` | `--browser` was requested with an authored `coldVisits` contract. Exit 2 before compilation or opening a browser; run without `--browser` to exercise those visits. The browser loop cannot certify saved-record return behavior. |
50
+ | `test_browser_journeys_unsupported` | `--browser` was requested with authored journeys that have no `coldVisits`. Exit 2 before compilation or opening a browser. The single-screen browser loop does not execute authored steps or root `expectText`/`absentText` assertions; run without `--browser` to execute that contract. The existing cold-visit code remains in use when any journey has `coldVisits`. |
48
51
  | `test_input_evidence_requires_json` | `--input-evidence` was requested without explicit `--json`. Exit 2 before build or worker execution. |
49
52
  | `test_input_evidence_unsupported_mode` | `--input-evidence` was combined with `--browser`, or the content is a functions package rather than an app worker crawl. Exit 2 before execution. |
50
53
  | `test_input_evidence_unavailable` | The isolated input crawl cannot establish all contained realms, cannot read a stable source archive before/after the crawl, or fails to return one complete JSON report (including interruption, process timeout or output limit). Exit 1; no host-realm fallback or positive input acceptance. |
@@ -279,8 +279,9 @@ product. Right after `init`:
279
279
  typed input → submit → the persisted result with a typed journey in `himi-test.json`
280
280
  (`.himi/himi-test.stub.json` has a skeleton per form: fill the values and expected text)
281
281
  (`--deep` replays action chains but never types a field value), then prove the destination
282
- and cancel/back recovery separately — a typed journey is screen-local and does not follow
283
- navigation, so use a render of the destination or a device drive. A fresh-screen crawl alone
282
+ and cancel/back recovery separately. For device-local records, add explicit `coldVisits`
283
+ assertions from `himi docs himi-test` so another cold worker must read the same saved record.
284
+ Cold visits do not drive navigation or prove warm repaint/relaunch: drive those in the UI. A fresh-screen crawl alone
284
285
  does not prove a flow.
285
286
 
286
287
  Before authoring a multi-screen mobile app, ensure the customer-approved `SPEC.md` and
@@ -110,7 +110,7 @@ The receipt has these fields:
110
110
  | `execution` | CLI-established `devModule`, `appConfig`, `workerBoot` and `workerAction` modes, each `quickjs-wasm`. A v1 report missing these fields predates the contained CLI path; do not accept it as contained execution. This marker is meaningful only when captured from the trusted pinned producer. |
111
111
  | `probes` | Each synthesized typed dispatch's screen/component/action/hook, contract control and injected field/value, actual argument list, precondition input/current value/state hash, post-state hash, and observed state/navigation/request/host-call/throw results. Zero probes is not proof that required controls ran. |
112
112
  | `journeys` | Actual screen/input, each attempted step's scope, raw `declaredPayload`, effective row-resolved `descriptorPayload`, exact `testOverrides`, final merged `payload`, pre/post state hashes, executed/threw/not-dispatched status, final visible observations and evaluator-computed assertions. Unavailable actions remain not-dispatched, never successful steps. |
113
- | `unavailableScreens`, `unavailableJourneys` | Requested coverage for which the evaluator did not supply v1 evidence, including older engines, invalid/collapsed screens and unavailable journeys. With positional screen IDs, these lists contain only requested roots; requested unknown IDs remain unavailable. Unrelated configured journeys do not run or fail a scoped run. These gaps are not accepted behavior. |
113
+ | `unavailableScreens`, `unavailableJourneys` | Requested coverage for which the evaluator did not supply evaluated v1 evidence, including older engines, invalid/collapsed screens and explicitly unavailable source journeys. An unavailable source can still retain evaluated nested cold-visit receipts. With positional screen IDs, these lists contain only requested roots; requested unknown IDs remain unavailable. Unrelated configured journeys do not run or fail a scoped run. These gaps are not accepted behavior. |
114
114
 
115
115
  State hashes fingerprint the exact serialized worker snapshot captured at the dispatch
116
116
  boundary. `currentValuePresent` distinguishes an absent current value from null.
@@ -189,6 +189,69 @@ edge would hand it — so the journey boots the way a real tap into it would. Ke
189
189
  navigation in the ordinary edge pass, which boots the destination with real source
190
190
  parameters. Pair dynamic journeys with app-specific `dev/net-mocks.json` fixtures.
191
191
 
192
+ ### Prove a local mutation survives a cold visit
193
+
194
+ A success message proves only the current screen's outcome. It does not prove that another
195
+ screen can read the saved record. Add `coldVisits` to a typed journey when the approved flow
196
+ requires another screen worker to read a created, edited or removed authoritative local record:
197
+
198
+ ```json
199
+ {
200
+ "journeys": [{
201
+ "name": "create then read the same record",
202
+ "screen": "record_form",
203
+ "steps": [{
204
+ "action": "tier5.records.save",
205
+ "params": { "id": "probe-record", "title": "Field notebook", "notes": "Keep this note" }
206
+ }],
207
+ "expectText": ["Saved"],
208
+ "coldVisits": [
209
+ { "screen": "record_list", "expectText": ["Field notebook"] },
210
+ { "screen": "record_detail", "input": { "id": "probe-record" },
211
+ "expectText": ["Field notebook", "Keep this note"] },
212
+ { "screen": "record_list", "expectText": ["Field notebook"] }
213
+ ]
214
+ }]
215
+ }
216
+ ```
217
+
218
+ Use the app's actual screen/action names, field payloads and independently chosen expected
219
+ values. Every visit boots a **new worker realm** with only that destination's descriptor and
220
+ `input`; module variables and service state do not cross. Visits within one journey share a
221
+ private hermetic `local_database`, `secure_storage` and app-scoped `himalaya.storage` store. Each journey starts with empty
222
+ stores. Destination capability declarations and the app's local-entity schema still apply.
223
+ The ordinary crawl's other axes keep their fresh stores.
224
+
225
+ A visit can also have `steps`, using the same declared worker-action contract as the initial
226
+ journey. For a destructive flow, request removal then cancel, cold-visit the list and require
227
+ the selected row plus an unrelated row. In a separate sequence, confirm, then require the
228
+ unrelated row and use `"absentText": ["unique removed title"]`. `absentText` works on the
229
+ initial journey too. Both text checks use substrings of the resolved visible render tree;
230
+ choose distinct values that cannot be satisfied by another row or a static heading. Assert
231
+ all required fields and displayed totals, not just a generic success line. A missing screen,
232
+ failed expectation or unexecuted visit fails the journey; JSON reports include a receipt for
233
+ each cold visit.
234
+
235
+ Authored work is admitted as a whole before any worker executes: at most 32 journeys,
236
+ 32 cold visits per journey,128 total worker segments (sources plus visits),512 total
237
+ steps and2048 total positive/absent assertions. These shared limits apply to the CLI,
238
+ `POST /v1/test` and direct crawl callers. Over-budget work is rejected with
239
+ `test_journey_budget_exceeded`; visits or receipts are never silently truncated. Split a
240
+ larger evaluation into separately identified runs. A bundleless source retains an
241
+ explicit `worker-unavailable` receipt and every later visit's positional outcome.
242
+ Validation-blocked destination workers do not execute, but their baked descriptor
243
+ input and explicit visit overrides are retained in their unavailable receipt.
244
+
245
+ A same-screen favorite may legitimately use a retained worker snapshot; this no-snapshot check does not test that contract.
246
+
247
+ This is **headless local-store proof**, not a router drive or a real browser database
248
+ relaunch. It does not hydrate worker snapshots, run app services, synchronize with a backend,
249
+ or prove warm-screen refresh, native behavior, account switching or deployed-service
250
+ acceptance. Keep the normal navigation edge checks and drive the actual UI through return
251
+ and reload/relaunch on each required platform. Inspect storage separately when the row
252
+ exists but the screen is stale. The state ownership choices remain in
253
+ [tier-5-add-a-screen.md](tier-5-add-a-screen.md#state-does-not-cross-screens-automatically).
254
+
192
255
  Journeys run only the initialization the bundle and descriptor actually declare: module-scope
193
256
  calls and the root `onAppear`. Unlike the discovery crawl's convenience `load()` sweep, they do
194
257
  not call an otherwise uncalled loader. A declaration of `actions.load` alone does not initialize
@@ -575,7 +638,7 @@ behavior. This instruction does not add an automatic suppression or retry gate t
575
638
  | `--budget <ms>` | Per-action settle budget (default 2000). |
576
639
  | `--native` | Also decode every baked descriptor, and the release's `tokens.json`, with the real native decoders; bare `--native` runs whichever toolchains are present and names each one it could NOT check in a top-level `skippedPlatforms: [{ platform, reason }]` (never a pass), while `--native ios` or `--native android` selects one and exits 2 when it is unavailable. The Android SDK is found from `ANDROID_HOME`, then `ANDROID_SDK_ROOT`, then `sdk.dir` in the source checkout's Android test harness `local.properties`, then the default install path (`~/Library/Android/sdk` on macOS, `~/Android/Sdk` on Linux). **Source-checkout only** — the decoder oracles are that tree's own HimalayaCore Swift package and Gradle harness, which a published install does not ship, so it cannot run this at all (and its coverage ledger stays silent about native decoding rather than suggesting it). |
577
640
  | `--all-screens` | Crawl every templated instance instead of one representative per structure. |
578
- | `--browser` / `--no-open` / `--timeout <ms>` | Run the loop in your own browser tab (one screen per run; refuses record/net/offline/hostile/deep/budget/shots/native). |
641
+ | `--browser` / `--no-open` / `--timeout <ms>` | Run the loop in your own browser tab (one screen per run; refuses all authored journeys, including root assertions and `coldVisits`, and record/net/offline/hostile/deep/budget/shots/native). |
579
642
  | `himi test <screenId>…` | Focus one or more screens — the record and shots passes scope with them. |
580
643
 
581
644
  **Screenshots** are on by default when a headless browser is installed: one PNG per screen
@@ -25,8 +25,8 @@ Source snapshots: Catalyst `617c06d43d2a5608b6876d724553269b1e5346f0`; Himalaya
25
25
  | Module | 13 |
26
26
  | Primitive | 7 |
27
27
  | Composition | 9 |
28
- | Partial | 24 |
29
- | Empty | 24 |
28
+ | Partial | 25 |
29
+ | Empty | 23 |
30
30
  | Total | 77 |
31
31
 
32
32
  ## Mappings by server group
@@ -127,7 +127,7 @@ carousel placement remain separate gaps; the mappings above stay Partial.
127
127
 
128
128
  | Source sectionDefinitionId | Source viewer component | Himalaya implementation | Placement / reuse | Class | Settings, actions and gaps |
129
129
  | --- | --- | --- | --- | --- | --- |
130
- | `loyalty_status_section` | `loyalty.components.StatusSection` | | | Empty | The existing Loyalty worker lists the program and rewards but does not load this member's points balance/tier/status. A rewards catalog is not a loyalty-status widget. |
130
+ | `loyalty_status_section` | `loyalty.components.StatusSection` | [himi_loyalty member card](https://github.com/wix-private/himalaya/blob/adf4c8b47fd237307b278a5dbb8198a94b7feb69/apps/himi-branded-lite/dev/index.ts) (`buildLoyalty` points card) + [loyalty worker](https://github.com/wix-private/himalaya/blob/adf4c8b47fd237307b278a5dbb8198a94b7feb69/core/dev-server/tier5-bundles-src/himi-branded-lite-loyalty/index.ts "audience-lint-ok: optional pinned upstream evidence, not a local authoring dependency") | Existing base; adaptation needed | Partial | The member card on `himi_loyalty` reads the signed-in member's balance (`getCurrentMemberLoyaltyAccount`) and, beside it, the tiers program and the claimed coupons; it shows the balance, the tier name, the points-expiry line and `Rewards available (N)` (rewards the balance covers on the member's tier + ACTIVE claimed coupons), each blank when its read fails. Visitors get the sign-in gate. Not carried over: the referral-coupon share of the count (no referral spec in codegen) and the one-app `View` link (the catalog it opens is on the same screen). |
131
131
  | `loyalty_offering_section` | `loyalty.components.OfferingSection` | [himi_loyalty](https://github.com/wix-private/himalaya/blob/97f1542f2f7d6e8cb7a902b912c87dcaa37e5a67/apps/himi-branded-lite/dev/index.ts) + [rewards worker](https://github.com/wix-private/himalaya/blob/97f1542f2f7d6e8cb7a902b912c87dcaa37e5a67/core/dev-server/tier5-bundles-src/himi-branded-lite-loyalty/index.ts "audience-lint-ok: optional pinned upstream evidence, not a local authoring dependency") | Existing base; adaptation needed | Partial | Reuse program/reward reads and required-points presentation. Preserve offering selection. Member earning/redemption operations are not established by this read-only rewards list. |
132
132
 
133
133
  ### 8. referrals-section-definitions.json
@@ -355,7 +355,6 @@ These are the exact definitions whose target cells above remain empty. “Not fo
355
355
  | `events-section-definitions.json` | `events_discussion_section` | No event-scoped discussion implementation was found. Groups/social feed and Blog comments must not be selected automatically. |
356
356
  | `events-section-definitions.json` | `events_guests_section` | The Events detail has only a best-effort attendee summary/avatar strip using an owner-scoped guest query. It is not a full consumer guest-list section; leave the mapping unassigned. |
357
357
  | `groups-section-definitions.json` | `single_group_feed_section` | A feed exists, but it selects a relevant/home group internally and does not accept the configured fixed-group selector. No verified single-group feed adapter exists; keep this target empty until a policy/implementation is chosen. |
358
- | `loyalty-section-definitions.json` | `loyalty_status_section` | The existing Loyalty worker lists the program and rewards but does not load this member's points balance/tier/status. A rewards catalog is not a loyalty-status widget. |
359
358
  | `referrals-section-definitions.json` | `referrals_refer_section` | No consumer referral-code/link sharing and referral-status feature was found. Do not map to Loyalty or generic sharing without a user decision. A visitor-readable program read exists; see the row above. |
360
359
  | `referrals-section-definitions.json` | `referrals_description_section` | No referral-program description/data implementation was found. Text primitives alone do not resolve the business-bound referral content. A visitor-readable program read exists; see the row above. |
361
360
  | `photoalbum-section-definitions.json` | `photoAlbums_list_section` | Existing Photo Albums code is an empty-state placeholder with no album API wired. Pro Gallery uses a different data source and is not a substitute; choose an implementation explicitly. |
@@ -345,7 +345,7 @@ Apply these interpretation rules:
345
345
  typography tokens use the body family. Numeric metrics stay unchanged. Read each
346
346
  source heading/body family and check `himi fonts list`; use `himi fonts add <Family>`
347
347
  when it is available. A known source family that Himi does not ship (for example
348
- `spaceGrotesk` → Space Grotesk) is still compared with the actual target family.
348
+ `sora` → Sora) is still compared with the actual target family.
349
349
  Choose an available replacement, such as `$system`, only with the owner's explicit
350
350
  instruction; this is an adaptation, never a font match. Sync the coverage ledger
351
351
  against the current source evidence, then mark the corresponding `design` requirement
@@ -233,3 +233,12 @@ shape (heading swap, `savedScreenId`, back-navigation).
233
233
  - `himi docs recipe-dynamic-list` — the list the created entity lands in.
234
234
  - `himi components Form` / `FormField` / `TextField` / `Toggle` / `Picker`.
235
235
  - `.agents/skills/himalaya/examples/form-submit.*` (written into the app by `himi init` / `himi agent-init`) — this recipe, runnable.
236
+
237
+ ## Verify the saved record outside this worker
238
+
239
+ For a local create/edit flow, a saved banner alone is insufficient. Add a typed journey
240
+ with [cold visits](../himi-test.md#prove-a-local-mutation-survives-a-cold-visit) that reads
241
+ the same record from its list and detail, checks all entered fields and relevant totals,
242
+ and preserves an unrelated record. Then drive return and reload in the actual UI. Choose
243
+ the storage owner using [the cross-screen state contract](../tier-5-add-a-screen.md#state-does-not-cross-screens-automatically);
244
+ a worker snapshot is not a shared record store.
@@ -23425,19 +23425,43 @@
23425
23425
  "name": "WixLoyaltyRewardConfig",
23426
23426
  "kind": "interface",
23427
23427
  "jsdoc": "",
23428
- "decl": "export interface WixLoyaltyRewardConfig {\n tierId?: string;\n costInPoints?: number;\n amount?: number;\n percentage?: number;\n}"
23428
+ "decl": "export interface WixLoyaltyRewardConfig {\n tierId?: string;\n costInPoints?: number;\n amount?: string | number;\n percentage?: number;\n}"
23429
+ },
23430
+ {
23431
+ "name": "WixLoyaltyTierConfig",
23432
+ "kind": "type",
23433
+ "jsdoc": "/** One reward price row: what the reward costs a member on tier `tierId` (absent = the base tier). */",
23434
+ "decl": "export type WixLoyaltyTierConfig = WixLoyaltyRewardConfig;"
23429
23435
  },
23430
23436
  {
23431
23437
  "name": "WixLoyaltyProgram",
23432
23438
  "kind": "interface",
23433
23439
  "jsdoc": "",
23434
- "decl": "export interface WixLoyaltyProgram {\n id?: string;\n name?: string;\n /** ACTIVE | DRAFT | PAUSED */\n status?: string;\n pointDefinition?: { customName?: string; icon?: WixLoyaltyImage };\n}"
23440
+ "decl": "export interface WixLoyaltyProgram {\n id?: string;\n name?: string;\n /** ACTIVE | DRAFT | PAUSED */\n status?: string;\n pointDefinition?: { customName?: string; icon?: WixLoyaltyImage };\n /** Which premium loyalty features the site's plan unlocks. Absent on older responses. */\n premiumFeatures?: { loyaltyProgram?: boolean; tiers?: boolean; pointsExpiration?: boolean };\n /** The program-level expiry POLICY (not a member's date — that is `WixLoyaltyAccount.pointsExpiration`). */\n pointsExpiration?: { status?: string; monthsOfInactivity?: number; expiringPointsPercentage?: number };\n}"
23435
23441
  },
23436
23442
  {
23437
23443
  "name": "WixLoyaltyReward",
23438
23444
  "kind": "interface",
23439
23445
  "jsdoc": "",
23440
- "decl": "export interface WixLoyaltyReward {\n id: string;\n name?: string;\n type?: string;\n /** Legacy adapter field. REST costs live in the reward's tier configurations. */\n requiredPoints?: number;\n discountAmount?: { configsByTier?: { tierId?: string; costInPoints?: number; amount?: string }[] };\n couponReward?: {\n fixedAmount?: { configsByTier?: WixLoyaltyRewardConfig[] };\n percentage?: { configsByTier?: WixLoyaltyRewardConfig[] };\n freeShipping?: { configsByTier?: WixLoyaltyRewardConfig[] };\n };\n active?: boolean;\n}"
23446
+ "decl": "export interface WixLoyaltyReward {\n id: string;\n name?: string;\n /** DISCOUNT_AMOUNT | COUPON_REWARD | COUPON | SPI_DISCOUNT_AMOUNT | … */\n type?: string;\n /** Legacy adapter field. REST costs live in the reward's tier configurations. */\n requiredPoints?: number;\n discountAmount?: { configsByTier?: WixLoyaltyRewardConfig[] };\n couponReward?: {\n fixedAmount?: { configsByTier?: WixLoyaltyRewardConfig[] };\n percentage?: { configsByTier?: WixLoyaltyRewardConfig[] };\n freeShipping?: { configsByTier?: WixLoyaltyRewardConfig[] };\n };\n active?: boolean;\n}"
23447
+ },
23448
+ {
23449
+ "name": "WixLoyaltyAccount",
23450
+ "kind": "interface",
23451
+ "jsdoc": "/** The signed-in member's loyalty account (wix.loyalty.v1.account, `/my-account`). */",
23452
+ "decl": "export interface WixLoyaltyAccount {\n id?: string;\n points?: { balance?: number; earned?: number; adjusted?: number; redeemed?: number; expired?: number };\n /** The member's current tier; absent or without `id` while on the base tier. */\n tier?: { id?: string; points?: number };\n /** The member's own expiry — when and how many points lapse next. */\n pointsExpiration?: { expirationDate?: string; expiringPointsAmount?: number };\n rewardAvailable?: boolean;\n}"
23453
+ },
23454
+ {
23455
+ "name": "WixLoyaltyTiersProgram",
23456
+ "kind": "interface",
23457
+ "jsdoc": "/** The tiers program: settings (ACTIVE | PAUSED, the base tier's definition) plus the custom tiers. */",
23458
+ "decl": "export interface WixLoyaltyTiersProgram {\n tiers?: WixLoyaltyTier[];\n programSettings?: {\n status?: string;\n baseTierDefinition?: { name?: string; description?: string; icon?: unknown };\n rollingWindow?: { durationInMonths?: number };\n };\n}"
23459
+ },
23460
+ {
23461
+ "name": "WixLoyaltyCoupon",
23462
+ "kind": "interface",
23463
+ "jsdoc": "/** A reward the member already claimed as a coupon (wix.loyalty.v1.coupon). */",
23464
+ "decl": "export interface WixLoyaltyCoupon {\n id?: string;\n rewardName?: string;\n /** ACTIVE | APPLIED | DELETED */\n status?: string;\n couponReference?: { couponId?: string; code?: string; deleted?: boolean };\n}"
23441
23465
  },
23442
23466
  {
23443
23467
  "name": "WixLoyaltyTier",
@@ -23480,6 +23504,22 @@
23480
23504
  "http": "GET /_api/loyalty-earning-rules/v1/earning-rules/rules",
23481
23505
  "scope": "visitor"
23482
23506
  },
23507
+ {
23508
+ "name": "getLoyaltyTiersProgram",
23509
+ "kind": "function",
23510
+ "jsdoc": "/** GET /loyalty-tiers/v1/tiers/program → { tiers, programSettings }. The custom tiers plus the\n * settings that carry the BASE tier's name — the one a member with no `tier.id` is on.\n *\n * @scope visitor — readable without signing in. */",
23511
+ "decl": "export function getLoyaltyTiersProgram(readOptions?: WixCallOptions): Promise<WixLoyaltyTiersProgram>;",
23512
+ "http": "GET /loyalty-tiers/v1/tiers/program",
23513
+ "scope": "visitor"
23514
+ },
23515
+ {
23516
+ "name": "listMyLoyaltyCoupons",
23517
+ "kind": "function",
23518
+ "jsdoc": "/** GET /loyalty-coupons/v1/coupons/my-coupons → { loyaltyCoupons }. The member's claimed\n * rewards; an ACTIVE, undeleted one still counts as a reward available to use.\n *\n * @scope member — requires a signed-in site member. */",
23519
+ "decl": "export function listMyLoyaltyCoupons(readOptions?: WixCallOptions): Promise<{ loyaltyCoupons?: WixLoyaltyCoupon[] }>;",
23520
+ "http": "GET /loyalty-coupons/v1/coupons/my-coupons",
23521
+ "scope": "member"
23522
+ },
23483
23523
  {
23484
23524
  "name": "LoyaltyOverview",
23485
23525
  "kind": "interface",
@@ -23514,7 +23554,7 @@
23514
23554
  "name": "getCurrentMemberLoyaltyAccount",
23515
23555
  "kind": "function",
23516
23556
  "jsdoc": "/** Read the authenticated member's own Loyalty account; no customer ID is accepted.\n * Source contract: loyalty-v1.getCurrentMemberAccount. Anonymous visitors are refused.\n * A signed-in customer recording is still required for migration acceptance.\n * @scope member\n */",
23517
- "decl": "export function getCurrentMemberLoyaltyAccount(readOptions?: WixCallOptions): Promise<{ account?: { points?: { balance?: number } } }>;",
23557
+ "decl": "export function getCurrentMemberLoyaltyAccount(readOptions?: WixCallOptions): Promise<{ account?: WixLoyaltyAccount }>;",
23518
23558
  "http": "GET /loyalty-accounts/v1/accounts/my-account",
23519
23559
  "scope": "member"
23520
23560
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1611.0",
3
+ "version": "0.1613.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": [
@@ -72,5 +72,5 @@
72
72
  "artifactId": "himalaya-cli"
73
73
  }
74
74
  },
75
- "falconPackageHash": "5919de535cfa33dabfeb372e350f3934dc2b5ccd610a7c9acdea0c51"
75
+ "falconPackageHash": "8d1e7814c00d18146088e2680fe43650535ec12113548c216d4fdbe0"
76
76
  }
@@ -210,6 +210,32 @@
210
210
  }
211
211
  ]
212
212
  },
213
+ "DM Serif Display": {
214
+ "category": "serif",
215
+ "dir": "ofl/dmserifdisplay",
216
+ "webFallback": "serif",
217
+ "aliases": [
218
+ "dmserifdisplay",
219
+ "dm serif display",
220
+ "dm serif"
221
+ ],
222
+ "license": {
223
+ "id": "OFL-1.1",
224
+ "url": "https://raw.githubusercontent.com/google/fonts/8e44913e4ff26fc997e6856c1ec40ff4791c98c5/ofl/dmserifdisplay/OFL.txt",
225
+ "copyright": "Copyright 2014-2018 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries. Copyright 2019 Google LLC."
226
+ },
227
+ "faces": [
228
+ {
229
+ "weight": 400,
230
+ "style": "normal",
231
+ "postScriptName": "DMSerifDisplay-Regular",
232
+ "file": "DMSerifDisplay-Regular.ttf",
233
+ "sha256": "8cc3643535edf039aa5d95440a8542735e9197e4f4b8d9303e980fefbf5ab616",
234
+ "sri": "sha256-jMNkNTXt8DmqXZVECoVCc16Rl+T0uNkwPpgP779athY=",
235
+ "bytes": 76580
236
+ }
237
+ ]
238
+ },
213
239
  "Dancing Script": {
214
240
  "category": "handwriting",
215
241
  "dir": "ofl/dancingscript",
@@ -279,6 +305,54 @@
279
305
  }
280
306
  ]
281
307
  },
308
+ "Fraunces": {
309
+ "category": "serif",
310
+ "dir": "ofl/fraunces",
311
+ "webFallback": "serif",
312
+ "aliases": [
313
+ "fraunces"
314
+ ],
315
+ "license": {
316
+ "id": "OFL-1.1",
317
+ "url": "https://raw.githubusercontent.com/google/fonts/8e44913e4ff26fc997e6856c1ec40ff4791c98c5/ofl/fraunces/OFL.txt",
318
+ "copyright": "Copyright 2018 The Fraunces Project Authors (https://github.com/undercasetype/Fraunces)"
319
+ },
320
+ "variable": {
321
+ "axes": {
322
+ "opsz": {
323
+ "min": 9,
324
+ "default": 9,
325
+ "max": 144
326
+ },
327
+ "wght": {
328
+ "min": 100,
329
+ "default": 900,
330
+ "max": 900
331
+ },
332
+ "SOFT": {
333
+ "min": 0,
334
+ "default": 0,
335
+ "max": 100
336
+ },
337
+ "WONK": {
338
+ "min": 0,
339
+ "default": 1,
340
+ "max": 1
341
+ }
342
+ }
343
+ },
344
+ "faces": [
345
+ {
346
+ "weight": 900,
347
+ "style": "normal",
348
+ "postScriptName": "Fraunces-9ptBlack",
349
+ "file": "Fraunces[SOFT,WONK,opsz,wght].ttf",
350
+ "sha256": "177ff6c0f14e5550a3c624247cd1189611d4eb65d000b14944c63d967958abbb",
351
+ "sri": "sha256-F3/2wPFOVVCjxiQkfNEYlhHU62XQALFJRMY9lnlYq7s=",
352
+ "bytes": 360440
353
+ }
354
+ ]
355
+ },
282
356
  "IBM Plex Mono": {
283
357
  "category": "mono",
284
358
  "dir": "ofl/ibmplexmono",
@@ -863,6 +937,40 @@
863
937
  }
864
938
  ]
865
939
  },
940
+ "Space Grotesk": {
941
+ "category": "sans",
942
+ "dir": "ofl/spacegrotesk",
943
+ "webFallback": "sans-serif",
944
+ "aliases": [
945
+ "spacegrotesk",
946
+ "space grotesk"
947
+ ],
948
+ "license": {
949
+ "id": "OFL-1.1",
950
+ "url": "https://raw.githubusercontent.com/google/fonts/8e44913e4ff26fc997e6856c1ec40ff4791c98c5/ofl/spacegrotesk/OFL.txt",
951
+ "copyright": "Copyright 2020 The Space Grotesk Project Authors (https://github.com/floriankarsten/space-grotesk)"
952
+ },
953
+ "variable": {
954
+ "axes": {
955
+ "wght": {
956
+ "min": 300,
957
+ "default": 300,
958
+ "max": 700
959
+ }
960
+ }
961
+ },
962
+ "faces": [
963
+ {
964
+ "weight": 300,
965
+ "style": "normal",
966
+ "postScriptName": "SpaceGrotesk-Light",
967
+ "file": "SpaceGrotesk[wght].ttf",
968
+ "sha256": "acad6de1fc93436f5c0f1f4137751ef04f1aea3063e7036535970ffcfbd79f72",
969
+ "sri": "sha256-rK1t4fyTQ29cDx9BN3Ue8E8a6jBj5wNlNZcP/PvXn3I=",
970
+ "bytes": 136676
971
+ }
972
+ ]
973
+ },
866
974
  "Space Mono": {
867
975
  "category": "mono",
868
976
  "dir": "ofl/spacemono",
@@ -897,6 +1005,39 @@
897
1005
  }
898
1006
  ]
899
1007
  },
1008
+ "Vollkorn": {
1009
+ "category": "serif",
1010
+ "dir": "ofl/vollkorn",
1011
+ "webFallback": "serif",
1012
+ "aliases": [
1013
+ "vollkorn"
1014
+ ],
1015
+ "license": {
1016
+ "id": "OFL-1.1",
1017
+ "url": "https://raw.githubusercontent.com/google/fonts/8e44913e4ff26fc997e6856c1ec40ff4791c98c5/ofl/vollkorn/OFL.txt",
1018
+ "copyright": "Copyright 2017 The Vollkorn Project Authors (https://github.com/FAlthausen/Vollkorn-Typeface)"
1019
+ },
1020
+ "variable": {
1021
+ "axes": {
1022
+ "wght": {
1023
+ "min": 400,
1024
+ "default": 400,
1025
+ "max": 900
1026
+ }
1027
+ }
1028
+ },
1029
+ "faces": [
1030
+ {
1031
+ "weight": 400,
1032
+ "style": "normal",
1033
+ "postScriptName": "Vollkorn-Regular",
1034
+ "file": "Vollkorn[wght].ttf",
1035
+ "sha256": "4ebef434f76f80286204840354a84db79a0dc3c87deeeb791e015b70b2ade49d",
1036
+ "sri": "sha256-Tr70NPdvgChiBIQDVKhNt5oNw8h97ut5HgFbcLKt5J0=",
1037
+ "bytes": 577864
1038
+ }
1039
+ ]
1040
+ },
900
1041
  "Work Sans": {
901
1042
  "category": "sans",
902
1043
  "dir": "ofl/worksans",