@wix/himalaya-cli 0.1610.0 → 0.1612.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
@@ -134,6 +134,10 @@ async function boot(msg) {
134
134
  let sweepError = null;
135
135
  try {
136
136
  actions = drain(ctx, runtime, msg.skipLoad ? "himi.quickjs.actions" : "himi.quickjs.loads");
137
+ if (msg.settleAuthoredActions) {
138
+ drainDetachedJobs(ctx, runtime);
139
+ actions = readbackJson(ctx, actionsReadback);
140
+ }
137
141
  } catch (e) {
138
142
  sweepError = String(e?.message ?? e);
139
143
  }
@@ -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",
@@ -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
 
@@ -166,7 +166,9 @@ worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to t
166
166
  lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
167
167
  `--write <id> --from <file>` puts an edited entry back (refused if it breaks the file), and
168
168
  `--add <id> --from <file>` appends a NEW screen and registers it as linkable — the authoring half
169
- of mining a template. Not sure
169
+ of mining a template. To add a screen, clone the closest one: `--show <closest>`, change the id
170
+ and content, then `--add <new> --from <file> --display-name "<New title>"` (view names must be unique). `--from` takes that entry, a ScreenDescriptor
171
+ JSON object, or `{ "<id>": <ScreenDescriptor> }`; never edit `dev/index.ts` by hand. Not sure
170
172
  which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
171
173
  dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
172
174
  list --remote` shows the full catalog (title/description/tags) — including templates published
@@ -40,6 +40,17 @@ keeps its existing fields and adds the code:
40
40
  }
41
41
  ```
42
42
 
43
+ ## `himi test` authored journey failures
44
+
45
+ | Code | Meaning |
46
+ | --- | --- |
47
+ | `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: []`. |
48
+ | `test_input_evidence_requires_json` | `--input-evidence` was requested without explicit `--json`. Exit 2 before build or worker execution. |
49
+ | `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
+ | `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. |
51
+
52
+ This is a scoped migration. Other legacy `himi test` failure paths retain their existing envelopes.
53
+
43
54
  ## `himi icon from-one-app` codes
44
55
 
45
56
  | Code | Meaning |
@@ -300,15 +311,17 @@ whole body gzip-encoded because it is over the plain 8 MB cap:
300
311
  |---|---|
301
312
  | `compressed_publish_unsupported` | The publish body went out gzipped and the publishing service did not accept it compressed. It either read the bytes as malformed JSON, or measured the decompressed body against the 8 MB uncompressed limit (HTTP 413). Either the service does not accept compressed bodies yet, or the credential is a self-serve one, which keeps the 8 MB uncompressed limit. Nothing was written. |
302
313
 
303
- ## `himi template screens --upsert` codes
314
+ ## `himi template screens --upsert | --add | --write` codes
304
315
 
305
- `--upsert` writes many generated screens at once. Its refusals carry a code; nothing is written
306
- on any of them (a failed batch restores every file). Other `template screens` paths stay legacy.
316
+ `--upsert` writes many generated screens at once; `--add <id>` and `--write <id>` write one. Their
317
+ `--from` refusals carry a code; nothing is written on any of them (a failed batch restores every
318
+ file). Other `template screens` paths, and `--add`/`--write` refusals about the package itself
319
+ (an existing id, a symlink, a result that would not parse), stay legacy.
307
320
 
308
321
  | Code | Meaning |
309
322
  |---|---|
310
- | `invalid_usage` | `--upsert` was given without `--from <file>`. Exit 2. |
311
- | `invalid_input` | The `--from` file cannot be read or is not JSON. Exit 2. |
323
+ | `invalid_usage` | `--upsert`, `--add` or `--write` was given without `--from <file>`. Exit 2. |
324
+ | `invalid_input` | The `--from` file is missing, unreadable or not JSON. For `--add`/`--write` also: it is not a ScreenDescriptor object, `{ "<id>": <ScreenDescriptor> }` or the entry `--show` prints, or its key or `screenId` is not the `<id>` given; a templated route (`{ pattern, display, template }`) takes back only the text `--show` prints for it. `error` names the file. Exit 2. |
312
325
  | `upsert_refused` | The JSON is not `{ "<screenId>": <ScreenDescriptor> }`, an id or `screenId` does not match, the result would not parse as plain SCREENS entries, or a target file is a symlink. `error` names which. Exit 2. |
313
326
 
314
327
  ## `himi site member-app` codes
@@ -189,7 +189,9 @@ worker layer — trim it or mine it (`himi docs trim-a-template`): **trim** to t
189
189
  lists its screens (size, workers, `onAppear`), `--show <id>` prints one entry, and
190
190
  `--write <id> --from <file>` puts an edited entry back (refused if it breaks the file), and
191
191
  `--add <id> --from <file>` appends a NEW screen and registers it as linkable — the authoring half
192
- of mining a template. Not sure
192
+ of mining a template. To add a screen, clone the closest one: `--show <closest>`, change the id
193
+ and content, then `--add <new> --from <file> --display-name "<New title>"` (view names must be unique). `--from` takes that entry, a ScreenDescriptor
194
+ JSON object, or `{ "<id>": <ScreenDescriptor> }`; never edit `dev/index.ts` by hand. Not sure
193
195
  which template fits? `himi template recommend --text "<the business>"` (or `--book <site analyze
194
196
  dir>` for a site's verticals) ranks the catalog with reasons. For a small **non-Wix** pattern, `himi init --template chat-bot --remote`. `himi template
195
197
  list --remote` shows the full catalog (title/description/tags) — including templates published
@@ -74,6 +74,80 @@ Each stage runs on hermetic data with zero network:
74
74
  Engine: `core/dev-server/src/screen-crawl.ts`.
75
75
  CLI orchestration: `tools/himi-cli/src/test.ts`.
76
76
 
77
+ ## Machine input evidence
78
+
79
+ `himi test --content <dir> --input-evidence --json` opts into additive
80
+ `inputEvidence.version: 1` output. The local evaluator records actual dispatches; it
81
+ does not interpret a Plan or decide whether a warning is benign. The flag requires
82
+ explicit `--json`; browser-loop and functions-package modes refuse it with typed
83
+ failure codes. An older CLI may omit the field: treat that as capability missing,
84
+ never as a passing scan.
85
+
86
+ The CLI runs this opt-in crawl in a separate process, enables the existing compiler
87
+ import confinement, and establishes QuickJS/WASM realms for generated dev modules,
88
+ config, worker boot and worker actions before loading generated modules. Explicit
89
+ host-realm overrides or unavailable isolation refuse with
90
+ `test_input_evidence_unavailable`; there is no host fallback. Packages containing
91
+ symlinks are refused. The compiler accepts package-owned files and CLI-owned
92
+ source/staged SDK roots; it does not expand arbitrary app or ancestor dependency
93
+ links into trusted roots. A separate editor SDK install cannot substitute for the
94
+ pinned CLI's runtime in this mode. The trusted caller must keep the generated
95
+ source and its environment quiescent during capture and bind the final subject. The local
96
+ receipt CLI projects only the caller-owned public `WIX_CLIENT_ID` and `WIX_SITE_URL`
97
+ into contained config/dev compilation, preserving their supplied values. Other
98
+ host environment keys (including secrets) are omitted; tokenless/default contained
99
+ callers still receive an empty environment. Capture those public bindings with
100
+ the run environment; the CLI does not choose an identity or provision a site.
101
+ Packages requiring host filesystem/network work during config or descriptor construction must report
102
+ that limitation instead of issuing contained evidence. The isolated crawl has a
103
+ three-minute process limit; interruption forwards SIGTERM/SIGINT to its child.
104
+
105
+ The receipt has these fields:
106
+
107
+ | Field | Evidence |
108
+ | --- | --- |
109
+ | `subject` | Actual app, tested release ID and payload hash, test run ID, source diff/untracked hashes, and SHA256 of the exact `himi-test.json` bytes parsed for the run (`absent` when missing). `sourceArchiveHash` is SHA256 of the base64 deterministic ZIP from the publish source collector, including authored `himi-test.json`. It is captured before loading generated modules and rechecked after the crawl; unreadable or changed source refuses with `test_input_evidence_unavailable`. Generated `.himi` outputs are excluded by the shared archive rules. Git hashes may explicitly be `unavailable`. |
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
+ | `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
+ | `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. |
114
+
115
+ State hashes fingerprint the exact serialized worker snapshot captured at the dispatch
116
+ boundary. `currentValuePresent` distinguishes an absent current value from null.
117
+ Input records include the implicit screen ID, then baked descriptor input and route
118
+ overrides with the same precedence the worker receives.
119
+ Requests and host calls are attempted hermetic effects, not live-service success.
120
+ Journey assertion kind `observation-substring` states the existing operator exactly:
121
+ substring inclusion in joined visible `nodeId.field=value` lines. It does not prove
122
+ exact text equality, complete result sets, absence of extra rows, or that an
123
+ agent-authored expected value is an independently approved oracle.
124
+
125
+ Journey parameters are synthetic overrides. A journey that replaces a descriptor's
126
+ literal service-reference ID with a real ID tests that synthetic dispatch, not the
127
+ actual UI tap. Inspect `testOverrides` alongside `descriptorPayload`; a passing
128
+ assertion cannot waive a failing descriptor-driven flow. Worker (`tier5.*`) actions
129
+ keep authored params/args literal by the existing action contract; reducers read
130
+ their selected record from state/input rather than expecting nested service binding
131
+ evaluation. This evidence does not change that contract.
132
+ The origin fields are always present: unavailable declared/effective payloads and
133
+ absent test parameters are explicit `null`; an explicitly empty override is `{}`.
134
+ An omitted origin field is unknown, not evidence that no override occurred.
135
+
136
+ This output leaves `ok`, finding severities and suppressions unchanged. In particular,
137
+ `typed-value-ignored` remains a warning and can coexist with `ok: true`. Use the
138
+ structured finding invariant/screen/trigger/field/axis to apply a separate trusted
139
+ acceptance policy; do not parse message prose or reject every warning. Finding IDs
140
+ are stable grouping identities, not unique case/dispatch IDs. Inspect suppressed,
141
+ skipped and absent coverage explicitly. A different successful input cannot excuse
142
+ an untested or failed required input.
143
+
144
+ Attestation v2 still binds only aggregate report/coverage values alongside its
145
+ existing source/release/descriptor/screenshot hashes; it does not acquire new
146
+ acceptance semantics. A trusted host must capture the actual pinned CLI process
147
+ exit/stdout, bind the full report to its own run and immutable final source/release,
148
+ and supply independent required-control/case policy. Neither a negative-clear scan
149
+ nor an unkeyed receipt is whole-product acceptance or trusted agent prose.
150
+
77
151
  ## Typed form journeys
78
152
 
79
153
  The crawl types into a control **only when it can read a legal value off the app itself** — an id
@@ -115,6 +189,19 @@ edge would hand it — so the journey boots the way a real tap into it would. Ke
115
189
  navigation in the ordinary edge pass, which boots the destination with real source
116
190
  parameters. Pair dynamic journeys with app-specific `dev/net-mocks.json` fixtures.
117
191
 
192
+ Journeys run only the initialization the bundle and descriptor actually declare: module-scope
193
+ calls and the root `onAppear`. Unlike the discovery crawl's convenience `load()` sweep, they do
194
+ not call an otherwise uncalled loader. A declaration of `actions.load` alone does not initialize
195
+ a cold screen on device.
196
+
197
+ Use `"steps": []` to assert the result of entering a detail screen, without adding a synthetic
198
+ load tap that could conceal missing initialization. Add separate journeys with different `input`
199
+ IDs and requirement-derived `expectText` (title, notes, metadata and numeric values). Include
200
+ missing/stale IDs and a not-found expectation. These are independent cold entries, not proof of
201
+ back/return, state carry-over or the source row's emitted navigation params; verify those with
202
+ real interactions as well. A green derived crawl without such expectations does not establish
203
+ that a detail shows the selected record.
204
+
118
205
  ## What it checks
119
206
 
120
207
  Only **unambiguous** defects, so a green run stays worth trusting:
@@ -483,6 +570,7 @@ behavior. This instruction does not add an automatic suppression or retry gate t
483
570
  | `--offline` | Strip every data layer — see the failure UI head-on. (Excludes `--net`.) |
484
571
  | `--no-hostile` | Skip the empty-list / all-500 passes. |
485
572
  | `--no-shots` | Skip the per-screen PNGs written to `.himi/shots/`. |
573
+ | `--input-evidence` | Requires `--json`; capture actual typed probes and authored-journey receipts in contained local worker mode (see the receipt contract above). |
486
574
  | `--net <file>` | Use an explicit net-mocks file instead of the app's own. |
487
575
  | `--budget <ms>` | Per-action settle budget (default 2000). |
488
576
  | `--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). |
@@ -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. |
@@ -636,6 +636,14 @@ gradle read the tree. The shell supplies the project; it no longer supplies the
636
636
  | `apps/<app>/ios/project.yml`, then its `build.gradle.kts` | the content app has source in this checkout — it already declared a version, so nothing new has to be declared |
637
637
  | the SHELL's own declared version | an off-repo app that declares nothing anywhere. Deliberate: `CFBundleShortVersionString` is **also** the OTA rollout floor (`HimalayaApp.swift` passes it as `binaryVersion`; serve-control's `resolveRelease` skips any ring whose `minBinaryVersion` it fails, and returns nothing when none qualifies), and the shell's version is the truth about what the binary can render |
638
638
 
639
+ Branded Lite keeps the two apart, so the store version never moves its rollout floor. Its Android
640
+ app reports `BuildConfig.SHELL_VERSION` as the OTA `binaryVersion`, and its iOS app the Info.plist
641
+ key `HimalayaShellVersion`. Each is pinned equal to the shell's committed version. So a
642
+ Containers app's App Store build, which stamps the owner's version into `CFBundleShortVersionString`
643
+ through `APP_VERSION`, still reports the shell's version to update checks, and the build log says so.
644
+ A shell commit older than the key reports `CFBundleShortVersionString`, as before. Force update
645
+ (`minVersion`) compares the store version on both.
646
+
639
647
  The **build number** (`CFBundleVersion` / `versionCode`) is seconds since 2020-01-01Z, so it is
640
648
  monotonic and unique per app per second — App Store Connect rejects a duplicate version/build pair,
641
649
  and Play requires `versionCode` to increase.
@@ -14,6 +14,18 @@ areas, always in the same order, so you can skim for the one you care about.
14
14
 
15
15
  ---
16
16
 
17
+ ## 2026-11-08
18
+
19
+ ### Getting started & the CLI
20
+
21
+ - **`himi template screens --add` and `--write` now take the screen as JSON.** `--from` accepts a
22
+ plain ScreenDescriptor (the id comes from `--add <id>` / `--write <id>`), `{ "<id>": <ScreenDescriptor> }`,
23
+ or the entry `--show` prints. Before, only that `--show` entry worked, and a JSON file failed with an
24
+ error pointing at a line of `dev/index.ts`. A file that is not one of these shapes, has a different
25
+ id or cannot be read is refused with `code: "invalid_input"`, and the error names your file. To add
26
+ a screen, clone the closest one: `--show <closest>`, edit it, then
27
+ `--add <new> --from <file> --display-name "<title>"`. (#5422)
28
+
17
29
  ## 2026-11-04
18
30
 
19
31
  ### Branded Lite & Owner Lite
@@ -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
  }
@@ -117,6 +117,8 @@ himi template screens # list the screens and their worker
117
117
  himi template screens --show himi_saved > saved.txt # ONE screen's entry
118
118
  himi template screens --write himi_saved --from saved.txt # put it back — re-parsed, refused if it breaks the file
119
119
  himi template screens --add himi_sellers --from sellers.txt --parent himi_branded_home # a NEW screen, registered as linkable
120
+ # --add/--write --from takes a ScreenDescriptor JSON object, { "<id>": <ScreenDescriptor> }, or the --show entry.
121
+ # To add a screen, clone the closest one: --show it, change the id and content, then --add it.
120
122
  himi template sweep --set "Sample content=>Our latest" --dry-run # copy inside string literals only
121
123
  himi template trim --from-spec --links drop --dry-run # keep SPEC.md's Screen catalogue ids — read fromSpec.droppedPrimary first
122
124
  ```
@@ -779,6 +779,20 @@ This works identically on web, iOS, and Android. (Regression-guarded by
779
779
  detail bundles for BOTH the primary `input.id` param and the back-compat
780
780
  `input.screenId` recovery.)
781
781
 
782
+ **Prove the selected record, not just a successful build.** Each screen has its own worker,
783
+ even when several screens name the same bundle and service. Setting a selected id in the list's
784
+ service does not set the detail's state. Trace the tapped row id → navigation params →
785
+ `himalaya.input` → the detail's initialized state. Route input is not automatically a loader
786
+ argument, and `$input.id` is not a descriptor binding; read input in the worker as above.
787
+
788
+ Add cold-entry journeys (`steps: []`) in `himi-test.json` for at least two distinct records per
789
+ detail, asserting their requirement-derived title, metadata, notes and numeric values. Also
790
+ assert missing/stale ids. See [cold-entry assertions](himi-test.md#typed-form-journeys). Do not
791
+ manually dispatch `load` in these checks: that can make an uninitialized generated screen look
792
+ correct. Then drive the actual row taps and back/return on each required surface, confirming
793
+ that the second selection and a missing id show none of the first record's fields. Keep the
794
+ source-nav and return evidence separate from the screen-local cold-entry assertions.
795
+
782
796
  **Alternative — a templated route that bakes a static `input`.** When the id space is
783
797
  enumerable, a `screens.templated` route can bake the id straight into the descriptor's
784
798
  **static `input`** instead of relying on a nav param — the `property_<id>` pattern. The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1610.0",
3
+ "version": "0.1612.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": "fd34bb941dc2b1092069d5876a747cc0048164fc1b1ce148a32257b2"
75
+ "falconPackageHash": "ce8fd7a735a426892fa25365dcc0599b1c137be31ce634d59245ef55"
76
76
  }
@@ -45241,6 +45241,90 @@ const SCREENS = runtimeSetting({
45241
45241
  }
45242
45242
  }
45243
45243
  },
45244
+ {
45245
+ "id": "himi_loyalty.member_tier",
45246
+ "body": {
45247
+ "package": "himalaya",
45248
+ "type": "Text",
45249
+ "props": {
45250
+ "text": "$service:himiBrandedLoyalty.memberTier",
45251
+ "style": "footnote",
45252
+ "color": "textSecondary",
45253
+ "maxLines": 1,
45254
+ "collapseWhenEmpty": true
45255
+ }
45256
+ }
45257
+ },
45258
+ {
45259
+ "id": "himi_loyalty.member_expiry",
45260
+ "body": {
45261
+ "package": "himalaya",
45262
+ "type": "Text",
45263
+ "props": {
45264
+ "text": "$service:himiBrandedLoyalty.memberExpiry",
45265
+ "style": "caption",
45266
+ "color": "textSecondary",
45267
+ "collapseWhenEmpty": true
45268
+ }
45269
+ }
45270
+ },
45271
+ {
45272
+ "id": "himi_loyalty.member_expiry_urgent",
45273
+ "body": {
45274
+ "package": "himalaya",
45275
+ "type": "Text",
45276
+ "props": {
45277
+ "text": "$service:himiBrandedLoyalty.memberExpiryUrgent",
45278
+ "style": "caption",
45279
+ "color": "danger",
45280
+ "collapseWhenEmpty": true
45281
+ }
45282
+ }
45283
+ },
45284
+ {
45285
+ "id": "himi_loyalty.member_rewards",
45286
+ "body": {
45287
+ "package": "himalaya",
45288
+ "type": "HStack",
45289
+ "props": {
45290
+ "spacing": 4,
45291
+ "alignment": "center",
45292
+ "children": [
45293
+ {
45294
+ "id": "",
45295
+ "body": {
45296
+ "package": "himalaya",
45297
+ "type": "Icon",
45298
+ "props": {
45299
+ "name": "gift",
45300
+ "size": 14,
45301
+ "tint": "success"
45302
+ }
45303
+ },
45304
+ "modifiers": {
45305
+ "accessibilityRole": "decorative"
45306
+ }
45307
+ },
45308
+ {
45309
+ "id": "",
45310
+ "body": {
45311
+ "package": "himalaya",
45312
+ "type": "Text",
45313
+ "props": {
45314
+ "text": "$service:himiBrandedLoyalty.memberRewards",
45315
+ "style": "footnote",
45316
+ "color": "success",
45317
+ "maxLines": 1
45318
+ }
45319
+ }
45320
+ }
45321
+ ]
45322
+ }
45323
+ },
45324
+ "modifiers": {
45325
+ "hiddenPath": "$service:himiBrandedLoyalty.hideMemberRewards"
45326
+ }
45327
+ },
45244
45328
  {
45245
45329
  "id": "",
45246
45330
  "body": {