@wix/himalaya-cli 0.1579.0 → 0.1580.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.
@@ -0,0 +1,313 @@
1
+ // ../../core/dev-server/src/quickjs-worker-entry.ts
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ import { pathToFileURL } from "node:url";
4
+ var CTL_READY = 0;
5
+ var CTL_LEN = 1;
6
+ var CTL_SEQ = 2;
7
+ var CTL_WORDS = 4;
8
+ var DATA_OFFSET = CTL_WORDS * 4;
9
+ var sessions = /* @__PURE__ */ new Map();
10
+ var { sab, intlPath, webGlobalsPath } = workerData;
11
+ var webGlobalsInstaller = null;
12
+ async function installWebGlobalsInto(ctx) {
13
+ webGlobalsInstaller ??= (await import(pathToFileURL(webGlobalsPath).href)).installWebGlobals;
14
+ webGlobalsInstaller(ctx);
15
+ }
16
+ var intlInstaller = null;
17
+ async function installIntlInto(ctx) {
18
+ intlInstaller ??= (await import(pathToFileURL(intlPath).href)).installIntl;
19
+ intlInstaller(ctx);
20
+ }
21
+ var ctl = new Int32Array(sab, 0, CTL_WORDS);
22
+ var data = new Uint8Array(sab, DATA_OFFSET);
23
+ var decoder = new TextDecoder();
24
+ var crossingSeq = 0;
25
+ function crossToHost(kind, method, path, body, waitMs, headers = "") {
26
+ const mine = ++crossingSeq;
27
+ const deadline = Date.now() + waitMs;
28
+ Atomics.store(ctl, CTL_SEQ, 0);
29
+ Atomics.store(ctl, CTL_READY, 0);
30
+ parentPort.postMessage({ want: { kind, method, path, body, headers, seq: mine } });
31
+ for (; ; ) {
32
+ const left = deadline - Date.now();
33
+ if (left <= 0) {
34
+ return JSON.stringify({ ok: false, e: `the host transport did not answer within ${waitMs}ms` });
35
+ }
36
+ const res = Atomics.wait(ctl, CTL_READY, 0, left);
37
+ if (res === "timed-out") {
38
+ return JSON.stringify({ ok: false, e: `the host transport did not answer within ${waitMs}ms` });
39
+ }
40
+ if (Atomics.load(ctl, CTL_SEQ) !== mine) {
41
+ Atomics.store(ctl, CTL_READY, 0);
42
+ continue;
43
+ }
44
+ const len = Atomics.load(ctl, CTL_LEN);
45
+ if (len < 0 || len > data.length) {
46
+ return JSON.stringify({ ok: false, e: `the host transport returned an out-of-range length (${len})` });
47
+ }
48
+ return decoder.decode(data.subarray(0, len));
49
+ }
50
+ }
51
+ var modulePromise = null;
52
+ async function realmModule() {
53
+ modulePromise ??= (async () => {
54
+ const { newQuickJSWASMModuleFromVariant } = await import("quickjs-emscripten");
55
+ const variant = await import("@jitl/quickjs-wasmfile-release-sync");
56
+ return newQuickJSWASMModuleFromVariant(variant.default ?? variant);
57
+ })();
58
+ return modulePromise;
59
+ }
60
+ async function boot(msg) {
61
+ const mod = await realmModule();
62
+ const runtime = mod.newRuntime();
63
+ runtime.setMemoryLimit(msg.memoryLimitBytes);
64
+ let deadline = Date.now() + (msg.timeoutMs > 0 ? msg.timeoutMs : 2e3);
65
+ let waiting = false;
66
+ runtime.setInterruptHandler(() => !waiting && Date.now() > deadline);
67
+ const ctx = runtime.newContext();
68
+ let keepAlive = false;
69
+ try {
70
+ const host = ctx.newFunction("__himiWixCall", (mH, pH, bH, hH) => {
71
+ waiting = true;
72
+ try {
73
+ const answer = crossToHost(
74
+ "wix",
75
+ ctx.getString(mH),
76
+ ctx.getString(pH),
77
+ ctx.getString(bH),
78
+ // An explicit per-crossing budget when the caller set one; otherwise the remaining boot
79
+ // time, floored, so a stalled transport still surfaces as a rejected call rather than as
80
+ // the whole boot timing out with no attribution.
81
+ msg.answerTimeoutMs !== void 0 ? msg.answerTimeoutMs : Math.max(1e3, deadline - Date.now()),
82
+ // `undefined` when the guest called with three arguments, which every caller did
83
+ // before per-call site scope existed.
84
+ hH === void 0 ? "" : ctx.getString(hH)
85
+ );
86
+ return ctx.newString(answer);
87
+ } finally {
88
+ waiting = false;
89
+ }
90
+ });
91
+ ctx.setProp(ctx.global, "__himiWixCall", host);
92
+ host.dispose();
93
+ const dispatchHost = ctx.newFunction("__himiDispatch", (aH, pH) => {
94
+ waiting = true;
95
+ try {
96
+ const answer = crossToHost(
97
+ "dispatch",
98
+ "",
99
+ ctx.getString(aH),
100
+ ctx.getString(pH),
101
+ msg.answerTimeoutMs !== void 0 ? msg.answerTimeoutMs : Math.max(1e3, deadline - Date.now())
102
+ );
103
+ return ctx.newString(answer);
104
+ } finally {
105
+ waiting = false;
106
+ }
107
+ });
108
+ ctx.setProp(ctx.global, "__himiDispatch", dispatchHost);
109
+ dispatchHost.dispose();
110
+ const pre = ctx.evalCode(msg.prelude, "himi-sandbox.js");
111
+ if (pre.error) {
112
+ const detail = ctx.dump(pre.error);
113
+ pre.error.dispose();
114
+ throw new Error(`sandbox prelude failed: ${String(detail?.message ?? detail)}`);
115
+ }
116
+ const bridge = pre.value;
117
+ const readback = ctx.getProp(bridge, "readback");
118
+ const dispatch2 = ctx.getProp(bridge, "dispatch");
119
+ const actionsReadback = ctx.getProp(bridge, "actions");
120
+ bridge.dispose();
121
+ if (msg.webGlobals && !msg.hide?.length) await installWebGlobalsInto(ctx);
122
+ if (msg.intl) await installIntlInto(ctx);
123
+ let bundleThrow = null;
124
+ try {
125
+ const res = ctx.evalCode(msg.bundleJs, msg.filename);
126
+ if (res.error) {
127
+ const detail = ctx.dump(res.error);
128
+ res.error.dispose();
129
+ bundleThrow = String(detail?.message ?? detail);
130
+ } else {
131
+ res.value.dispose();
132
+ }
133
+ let actions = "[]";
134
+ let sweepError = null;
135
+ try {
136
+ actions = drain(ctx, runtime, msg.skipLoad ? "himi.quickjs.actions" : "himi.quickjs.loads");
137
+ } catch (e) {
138
+ sweepError = String(e?.message ?? e);
139
+ }
140
+ const services = readbackJson(ctx, readback);
141
+ sessions.set(msg.id, {
142
+ runtime,
143
+ ctx,
144
+ dispatch: dispatch2,
145
+ actions: actionsReadback,
146
+ setDeadline(timeoutMs) {
147
+ deadline = Date.now() + (timeoutMs > 0 ? timeoutMs : 2e3);
148
+ }
149
+ });
150
+ keepAlive = true;
151
+ return { actions, error: bundleThrow ?? sweepError ?? void 0, console: services, sessionId: msg.id };
152
+ } finally {
153
+ readback.dispose();
154
+ if (!keepAlive) {
155
+ dispatch2.dispose();
156
+ actionsReadback.dispose();
157
+ }
158
+ }
159
+ } finally {
160
+ if (!keepAlive) {
161
+ try {
162
+ ctx.dispose();
163
+ } catch {
164
+ }
165
+ try {
166
+ runtime.dispose();
167
+ } catch {
168
+ }
169
+ }
170
+ }
171
+ }
172
+ function dispatch(msg) {
173
+ const session = sessions.get(msg.sessionId);
174
+ if (!session) throw new Error("the contained guest session is no longer available");
175
+ session.setDeadline(msg.timeoutMs);
176
+ const service = session.ctx.newString(msg.service);
177
+ const action = session.ctx.newString(msg.action);
178
+ const args = session.ctx.newString(msg.argsJson);
179
+ try {
180
+ const called = session.ctx.callFunction(
181
+ session.dispatch,
182
+ session.ctx.undefined,
183
+ service,
184
+ action,
185
+ args,
186
+ typeof msg.online === "boolean" ? msg.online ? session.ctx.true : session.ctx.false : session.ctx.undefined
187
+ );
188
+ if (called.error) {
189
+ const detail = session.ctx.dump(called.error);
190
+ called.error.dispose();
191
+ throw new Error(`the guest dispatch could not be started: ${String(detail?.message ?? detail)}`);
192
+ }
193
+ settlePromise(session.ctx, session.runtime, called.value, "guest dispatch");
194
+ drainDetachedJobs(session.ctx, session.runtime);
195
+ return { actions: readbackJson(session.ctx, session.actions) };
196
+ } finally {
197
+ service.dispose();
198
+ action.dispose();
199
+ args.dispose();
200
+ }
201
+ }
202
+ function release(sessionId) {
203
+ const session = sessions.get(sessionId);
204
+ if (!session) return;
205
+ sessions.delete(sessionId);
206
+ try {
207
+ session.dispatch.dispose();
208
+ } catch {
209
+ }
210
+ try {
211
+ session.actions.dispose();
212
+ } catch {
213
+ }
214
+ try {
215
+ session.ctx.dispose();
216
+ } catch {
217
+ }
218
+ try {
219
+ session.runtime.dispose();
220
+ } catch {
221
+ }
222
+ }
223
+ function drain(ctx, runtime, trigger) {
224
+ const trig = ctx.evalCode(`globalThis[${JSON.stringify(trigger)}]`, "himi-actions.js");
225
+ if (trig.error) {
226
+ const detail = ctx.dump(trig.error);
227
+ trig.error.dispose();
228
+ throw new Error(`the guest load sweep could not be started: ${String(detail?.message ?? detail)}`);
229
+ }
230
+ return settlePromise(ctx, runtime, trig.value, "guest load sweep");
231
+ }
232
+ function settlePromise(ctx, runtime, promise, label) {
233
+ try {
234
+ for (let i = 0; i < MAX_PUMP_ROUNDS; i++) {
235
+ const state = ctx.getPromiseState(promise);
236
+ if (state.type === "fulfilled") {
237
+ const out = String(ctx.dump(state.value));
238
+ state.value.dispose();
239
+ return out;
240
+ }
241
+ if (state.type === "rejected") {
242
+ const detail = ctx.dump(state.error);
243
+ state.error.dispose();
244
+ throw new Error(`the ${label} failed: ${String(detail?.message ?? detail)}`);
245
+ }
246
+ state.value?.dispose?.();
247
+ const pending = runtime.executePendingJobs();
248
+ if (pending.error) {
249
+ const detail = ctx.dump(pending.error);
250
+ pending.error.dispose();
251
+ throw new Error(`a guest job threw during the load sweep: ${String(detail?.message ?? detail)}`);
252
+ }
253
+ if (pending.value === 0) {
254
+ const ticked = ctx.evalCode(`globalThis["himi.quickjs.tick"]`, "himi-tick.js");
255
+ if (ticked.error) {
256
+ const detail = ctx.dump(ticked.error);
257
+ ticked.error.dispose();
258
+ throw new Error(`the guest timer pump failed: ${String(detail?.message ?? detail)}`);
259
+ }
260
+ const fired = ctx.dump(ticked.value) === true;
261
+ ticked.value.dispose();
262
+ if (fired) continue;
263
+ throw new Error(
264
+ `the ${label} is pending with no runnable jobs \u2014 a worker awaited something the sandbox never resolves (a capability this realm does not provide)`
265
+ );
266
+ }
267
+ }
268
+ throw new Error(`the ${label} did not settle within ${MAX_PUMP_ROUNDS} job rounds`);
269
+ } finally {
270
+ promise.dispose();
271
+ }
272
+ }
273
+ function drainDetachedJobs(ctx, runtime) {
274
+ for (let i = 0; i < MAX_PUMP_ROUNDS; i++) {
275
+ const pending = runtime.executePendingJobs();
276
+ if (pending.error) {
277
+ const detail = ctx.dump(pending.error);
278
+ pending.error.dispose();
279
+ throw new Error(`a nested guest job threw after dispatch: ${String(detail?.message ?? detail)}`);
280
+ }
281
+ if (pending.value === 0) {
282
+ const ticked = ctx.evalCode(`globalThis["himi.quickjs.tick"]`, "himi-tick.js");
283
+ if (ticked.error) {
284
+ const detail = ctx.dump(ticked.error);
285
+ ticked.error.dispose();
286
+ throw new Error(`the nested guest timer pump failed: ${String(detail?.message ?? detail)}`);
287
+ }
288
+ const fired = ctx.dump(ticked.value) === true;
289
+ ticked.value.dispose();
290
+ if (!fired) return;
291
+ }
292
+ }
293
+ throw new Error(`nested guest actions did not quiesce within ${MAX_PUMP_ROUNDS} job rounds`);
294
+ }
295
+ var MAX_PUMP_ROUNDS = 1e4;
296
+ parentPort.on("message", (msg) => {
297
+ if ("end" in msg) {
298
+ process.exit(0);
299
+ }
300
+ const operation = msg.kind === "warm" ? realmModule().then(() => ({})) : msg.kind === "boot" ? boot(msg) : msg.kind === "dispatch" ? Promise.resolve(dispatch(msg)) : Promise.resolve((release(msg.sessionId), {}));
301
+ operation.then((r) => parentPort.postMessage({ id: msg.id, ...r })).catch((e) => parentPort.postMessage({ id: msg.id, error: String(e?.message ?? e) }));
302
+ });
303
+ function readbackJson(ctx, readback) {
304
+ const called = ctx.callFunction(readback, ctx.undefined);
305
+ if (called.error) {
306
+ const detail = ctx.dump(called.error);
307
+ called.error.dispose();
308
+ throw new Error(String(detail?.message ?? detail));
309
+ }
310
+ const dumped = ctx.dump(called.value);
311
+ called.value.dispose();
312
+ return String(dumped);
313
+ }
@@ -6,7 +6,7 @@ repurposed when human wording changes. New codes may be added; consumers must
6
6
  handle an unknown code as a failure, without parsing prose.
7
7
 
8
8
  **Rollout:** this contract is enforced for **`himi push`**, including `--only`,
9
- first, for **`himi store iap connect`**, for **`himi site member-app`**, for **`himi fidelity`**, for the **`himi base44`** entity and function verbs, and for **`himi l10n import`**. Other subcommands still have legacy failures without codes and are a
9
+ first, for **`himi publish-size`**, for **`himi store iap connect`**, for **`himi site member-app`**, for **`himi fidelity`**, for the **`himi base44`** entity and function verbs, and for **`himi l10n import`**. Other subcommands still have legacy failures without codes and are a
10
10
  follow-up migration. This is an additive change: existing `error`, `errors`,
11
11
  `message`, `hint`, status, body, and gate-specific fields stay as they were.
12
12
  Some legacy envelopes have no singular `error` or `hint`; consumers must not
@@ -47,6 +47,22 @@ keeps its existing fields and adds the code:
47
47
  | `source_icon_unavailable` | Verified source evidence or its logo is missing, unreadable, unavailable, over the download/decoding budget, or cannot be normalized. |
48
48
  | `source_icon_unusable` | The prepared source mark fails mechanical launcher/store checks. Existing icon files and metadata are left unchanged. |
49
49
 
50
+ ## `himi publish-size` codes
51
+
52
+ This local planning command never claims or publishes. Every failure includes
53
+ `pushed: false` and `claimed: false`.
54
+
55
+ | Code | Meaning |
56
+ | --- | --- |
57
+ | `invalid_usage` | Unsupported, repeated, missing or invalid measurement options (exit 2). Publishing flags are refused. |
58
+ | `build_failed` | The contained local release could not be built completely, required host/network access, or the isolated build failed; measuring missing screens or bundles would undercount (exit 1). |
59
+ | `app_source_contains_secrets` | The shared source scanner found possible credentials (exit 1). |
60
+ | `app_source_too_large` | The base64 source archive exceeds its independent limit (exit 1). |
61
+ | `publish_body_too_large` | Measured draft JSON plus the explicit metadata planning reserve exceeds the publish budget (exit 1). `bodyBytes` and `overageBytes` describe the measured envelope; `planningBytes` and `planningOverageBytes` also include the reserve. |
62
+
63
+ `ok: true` means the measured draft plus its reserve fits. It does not certify
64
+ publication readiness or bound metadata not yet supplied to the final push.
65
+
50
66
  ## `himi one-app validate-payments` codes
51
67
 
52
68
  | Code | Meaning |
@@ -58,6 +74,19 @@ keeps its existing fields and adds the code:
58
74
  The checker validates recorded evidence and decisions; it does not independently
59
75
  verify merchant readiness or create transactions. General publication remains unchanged.
60
76
 
77
+ ## `himi one-app validate-identity` codes
78
+
79
+ | Code | Meaning |
80
+ | --- | --- |
81
+ | `invalid_usage` | Missing, repeated or unsupported identity-check arguments. |
82
+ | `identity_source_unverified` | Collected source hashes or the business display name cannot be verified. Recollect older evidence that did not save business identity. |
83
+ | `identity_target_unverified` | The technical app slug, target compilation or complete built descriptors are unavailable. |
84
+ | `literal_slug_visible` | Built display text or the known business-chat identity seed contains the technical slug instead of the collected business identity, without matching exact authored source copy. |
85
+
86
+ The check reads built descriptors, not every possible runtime string. A passing
87
+ result does not verify dynamic live data, translations, branding images or device
88
+ appearance. It does not change publication or coverage policy.
89
+
61
90
  ## Push codes
62
91
 
63
92
  | Code | Meaning |
@@ -16,7 +16,7 @@ Use this companion to the [Structure inference guide](https://apps.wix.com/_api/
16
16
  - Existing screen builders often return a complete screen. Preserve the source inline placement using supported composition; do not replace a mixed source screen with an unrelated full vertical screen.
17
17
  - Map data selectors, labels/media, design, permissions, CTA parameters and target destinations. The source definition's `settingsData.componentId` identifies a source editor component, not a Himalaya implementation or a complete settings schema.
18
18
 
19
- Source snapshots: Catalyst `617c06d43d2a5608b6876d724553269b1e5346f0`; Himalaya `97f1542f2f7d6e8cb7a902b912c87dcaa37e5a67`. GitHub references are pinned to these inspected versions. Recheck implementation contracts if using another version. No customer data was fetched, no app was generated, and no parity tests were run for this documentation inventory.
19
+ Source snapshots: Catalyst `617c06d43d2a5608b6876d724553269b1e5346f0`; Himalaya `97f1542f2f7d6e8cb7a902b912c87dcaa37e5a67`. GitHub references are pinned to these inspected versions. Recheck implementation contracts if using another version. No customer data was fetched, no app was generated, and no parity tests were run for the original documentation inventory. The Pages row now links the current source-selected editorial reconstruction recipe; its capture/decoder tests do not certify an individual app’s parity.
20
20
 
21
21
  ## Coverage summary
22
22
 
@@ -24,9 +24,9 @@ Source snapshots: Catalyst `617c06d43d2a5608b6876d724553269b1e5346f0`; Himalaya
24
24
  | --- | ---: |
25
25
  | Module | 13 |
26
26
  | Primitive | 7 |
27
- | Composition | 8 |
27
+ | Composition | 9 |
28
28
  | Partial | 24 |
29
- | Empty | 25 |
29
+ | Empty | 24 |
30
30
  | Total | 77 |
31
31
 
32
32
  ## Mappings by server group
@@ -169,7 +169,7 @@ carousel placement remain separate gaps; the mappings above stay Partial.
169
169
 
170
170
  | Source sectionDefinitionId | Source viewer component | Himalaya implementation | Placement / reuse | Class | Settings, actions and gaps |
171
171
  | --- | --- | --- | --- | --- | --- |
172
- | `pages_section` | `Pages.SectionPageClientScreen` | | | Empty | No matching Wix Pages content fetch/render adapter was found. Generic CMS or RichContent renderers do not identify the original page entity or its data contract. |
172
+ | `pages_section` | `Pages.SectionPageClientScreen` | [Pages capture recipe](https://apps.wix.com/_api/himi-serve/docs/one-app-to-himi-app.md#referenced-pages-content) / `oneAppPageSection` | Editable inline native RichContent | Composition | Select `settings.value.pageId`; use the collector’s Visitor-only referenced read and `pages-content.json`. Preserve source placement order, text, formatting and links. Ready/empty captures can be reconstructed; missing, forbidden, malformed and unsupported captures remain explicit gaps. This is editorial reconstruction, not a live runtime Pages adapter. |
173
173
 
174
174
  ### 12. photoalbum-section-definitions.json
175
175
 
@@ -358,7 +358,6 @@ These are the exact definitions whose target cells above remain empty. “Not fo
358
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
359
  | `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
360
  | `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
- | `pages-section-definitions.json` | `pages_section` | No matching Wix Pages content fetch/render adapter was found. Generic CMS or RichContent renderers do not identify the original page entity or its data contract. |
362
361
  | `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. |
363
362
  | `photoalbum-section-definitions.json` | `photoAlbums_photos_list` | 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. |
364
363
  | `photoalbum-section-definitions.json` | `photoAlbums_carousel_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. |
@@ -116,6 +116,15 @@ user request. Preserve the collected files as evidence. Read the [versioned
116
116
  reference index](https://apps.wix.com/_api/himi-serve/docs/one-app-reference.md) and its five guides before interpreting
117
117
  settings or claiming coverage.
118
118
 
119
+ When raw source placements reference `pages_section`, collection also saves
120
+ `pages-content.json`. It selects `settings.value.pageId`, reads only those unique
121
+ documents as the site's Visitor, and retains every original placement separately.
122
+ The evidence records capture time, source identity, a serialized response JSON SHA-256,
123
+ decoded content and an editable native RichContent component per placement.
124
+ The hash identifies that captured response, not an invented server revision.
125
+ Recollect to refresh editorial content; a changed response produces a changed hash.
126
+ Never import the evidence directory into runtime code.
127
+
119
128
  An unavailable supporting runtime view is recorded as `runtime_context_unavailable`
120
129
  with its platform and typed failure code; that enriched file is absent. Continue
121
130
  from authored intent and mark the platform behavior as unverified in the report.
@@ -500,16 +509,69 @@ Apply these interpretation rules:
500
509
  alone does not prove native navigation or control availability. Record any
501
510
  unavailable device check as unverified.
502
511
  - Business context contains `phoneNumber`, `website`, `services`, optional public
503
- `contact`, and an optional validated public `memberCount`, with their permitted
504
- nested values. Structure wins on authored phone/URL/design.
512
+ `contact`, an optional validated public `memberCount`, and optional `displayName`
513
+ from the same-site Business title, with their permitted nested values.
514
+ Use `displayName` for business identity; structure wins on authored screen titles,
515
+ phone/URL/design.
505
516
  Installed services do not add features. Installation IDs are not record IDs.
506
- Do not restore excluded Business titles, colors, identity or member fields.
517
+ Do not restore other excluded Business colors, identity or member fields.
507
518
  - Referenced editor-authored documents, such as Pages rich text, are presentation
508
519
  evidence too. Fetch only the referenced documents through verified read-only
509
520
  routes, record their IDs and versions, and reconstruct their authored content
510
521
  as editable native components. Keep changing business records live; do not
511
522
  substitute captured catalogs, memberships or transaction data for runtime reads.
512
523
 
524
+ ### Referenced Pages content
525
+
526
+ `pages_section` is an editor-authored document selected by
527
+ `settings.value.pageId`. It is not a CMS collection, Blog post or placeholder.
528
+ Use `pages-content.json` to reconstruct each ready placement as editable app
529
+ source, in its original layout array order, interleaved with the other sections.
530
+ Repeated page IDs may share one captured document but retain separate placement
531
+ IDs. Preserve source visibility/access rules and every screen alias that uses
532
+ that layout; do not multiply canonical placements by navigation aliases.
533
+
534
+ The collector's fixed read is
535
+ `GET https://apps.wix.com/social-content/api/v1/pages/{pageId}` using the source
536
+ site's Visitor signed instance. The response contains
537
+ `{pageId, content: {title, body: {contentType, content}}}`. It verifies the returned
538
+ page ID. `RICH_CONTENT` and `DRAFTJS` contain serialized JSON; `PLAIN_TEXT` stays
539
+ literal. The pinned [original reader](https://github.com/wix-private/wix-one-app-pages/blob/5055bf29109c51c48f0009a403bb70976c54e8dc/packages/wix-one-app-pages/src/services/pages-api.ts "audience-lint-ok: optional pinned upstream evidence, not a local authoring dependency") documents this contract.
540
+ No page listing, source mutation or manager credential fallback is needed.
541
+
542
+ Use the emitted `placement.component`, or rebuild it with the screens SDK's
543
+ `decodeOneAppPage(response, pageId)` and `oneAppPageSection(page, {id})`.
544
+ Copy the resulting component into an editable app source file, then insert it
545
+ into the actual generated screen's payload. Keep it outside collection-bound
546
+ rows that a worker replaces on load. Rendering a separate unused sample or merely
547
+ changing the catalog classification does not restore a migrated placement.
548
+ The component uses literal `RichContent.documentJson`; no source prose is a
549
+ service binding. The existing viewer preserves text, supported formatting, media
550
+ and safe http(s)/mailto/tel links. Legacy bare website domains gain HTTPS before
551
+ encoding; `normalizedWebsiteLinks` records the count. Relative paths, credentials
552
+ and unsafe schemes do not gain an implicit origin. Root editor metadata is omitted from rendered
553
+ content. Keep normal RichContent links or use the existing rich-link handler;
554
+ do not replace them with generic navigation or a WebView.
555
+
556
+ `ready` and genuine `empty` are successful captures. The section helper renders an
557
+ empty page's message from optional `emptyTitle` / `emptyDescription`; localize
558
+ that copy to the app and preserve the source empty-state intent. `invalid` (including missing
559
+ or malformed selectors/content), `unsupported`, `forbidden`, `unauthorized`,
560
+ `not_found` and `unavailable` remain distinct gaps. A failed document has no
561
+ emitted component. Preserve the gap and resolve it before claiming its coverage;
562
+ never substitute an empty paragraph, demo copy or owner read. Supported DraftJS
563
+ text, styled links, flat lists, headings and images are converted; nested lists
564
+ and unknown plugins remain explicit `unsupported` gaps.
565
+
566
+ This reconstruction captures editorial presentation only. Changing catalogs,
567
+ transactions and member records remain live. It uses the existing cross-platform
568
+ RichContent component and does not require a new Pages endpoint in native/web
569
+ runtime transports. A CLI/SDK source merge still needs its normal publication,
570
+ and an existing customer app must be regenerated, checked and published before
571
+ its text changes. Validate the actual generated screens and record every source
572
+ placement's text/link and native checks in the coverage ledger; collection alone
573
+ is not customer acceptance or permission to mark all rows supported.
574
+
513
575
  ### Navigation from top tabs
514
576
 
515
577
  `navigation-plan.json` records the source navigation kind and the planned target.
@@ -984,6 +1046,43 @@ cannot be inspected: call the builder in your generator and serialize the value
984
1046
  returns. Run the generator inside the app (after `npm install`), so
985
1047
  `@wix/himalaya/screens` resolves to the app's own dependency.
986
1048
 
1049
+ ### Preserve the template features the source exposes
1050
+
1051
+ Treat trimming as a source-feature decision, not a list of screens reached during
1052
+ one visitor walkthrough. Before removing a template destination, compare the source
1053
+ Account and native widget journeys under the appropriate visitor/member state.
1054
+ Keep the supported destination and its data/permission contract when the source
1055
+ exposes it. Do not add unrelated verticals merely because the template has them.
1056
+
1057
+ | Source feature | Preserve in the generated app |
1058
+ | --- | --- |
1059
+ | Business identity | Use the collected `business-context.json.displayName` for business identity and bind live chat header/sender/app-info values to the actual business or host state. Preserve the source logo as an image or image binding. Keep the authored app/screen title separate. The technical app slug is an identifier, not a replacement display name. Avoid a global string replacement across source, routes and URLs. |
1060
+ | Account Orders and Subscriptions | Retain source-exposed My activity links and their supported `himi_orders` / `himi_my_subscriptions` destinations, including their workers and sign-in/return routing. A visitor gate is not evidence that a member destination should be deleted. Installed services alone do not prove source exposure; compare the source Account. |
1061
+ | Programs | Reuse the supported Program card/detail and worker behavior. Preserve access type and the actual login/request/join/pay/Continue action for the visitor or participant state. Do not replace these with an image/title-only card or a generic authenticated Continue. Preserve paid/private policy; do not invoke a live purchase or membership mutation as part of a read-only comparison. |
1062
+ | Feed attachments | Reuse the template's rich post card and its `RichContent`/attachment presentation. Preserve inline images, files and their destinations together with text and actions. A text-only replacement is a functional loss even when it can read the post body. |
1063
+
1064
+ After applying identity and before claiming completion, run:
1065
+
1066
+ ```sh
1067
+ himi one-app validate-identity --evidence-dir .himi/one-app --content . --json
1068
+ ```
1069
+
1070
+ The command verifies the collected business-name hash, builds the current content,
1071
+ and checks emitted screen titles, supported component display fields and the known
1072
+ business-chat identity seed for accidental literal slugs. It ignores route IDs,
1073
+ URLs and arbitrary API/action values. Exact authored source copy containing the
1074
+ slug and a real business whose display name is its slug are preserved. An unrelated
1075
+ source page title cannot waive the business-chat identity or its name seed. An older
1076
+ collection without a business display name remains unverified; recollect rather
1077
+ than editing its evidence or guessing a name.
1078
+
1079
+ This is a narrow identity check. It cannot prove every dynamic or translated
1080
+ string, branding image, account journey, access policy or attachment is correct.
1081
+ Inspect the actual generated descriptors and workers, exercise recorded examples,
1082
+ then compare those four source journeys in the real host. Keep their missing
1083
+ behavior explicit in coverage and the report. A stock template screenshot or a
1084
+ passing identity check cannot substitute for the fresh reconstruction/device pass.
1085
+
987
1086
  ### Screen-content contract
988
1087
 
989
1088
  Use the current [screen design guide](https://apps.wix.com/_api/himi-serve/docs/designing-himi-screens.md) and the app's
@@ -1108,6 +1207,116 @@ configuration, workers, evidence, screenshots or logs.
1108
1207
 
1109
1208
  ## 4. Verify the reconstructed app and publish its draft
1110
1209
 
1210
+ ### Check the publish budget before final evidence
1211
+
1212
+ Plan for the **8,388,608-byte** publish JSON budget before spending time on final
1213
+ screenshots, behavioral runs or native UX records. `.himi` file size and source
1214
+ directory totals are different measurements: publication carries JSON-escaped
1215
+ descriptors, base64 bundles/assets/icon and the compressed source archive together.
1216
+
1217
+ First repoint navigation and retained detail/back-link destinations at the implemented
1218
+ generated screens, including destination IDs configured in shared workers. Keep every required
1219
+ source journey and its shared destinations in `SPEC.md`'s Screen catalogue, including
1220
+ cart, checkout, authentication and detail routes. Inspect a trim plan before applying it:
1221
+
1222
+ ```sh
1223
+ himi template trim --from-spec SPEC.md --links follow --dry-run
1224
+ ```
1225
+
1226
+ Review the retained/removed screens, workers and navigation. Trim only unused template
1227
+ definitions that the generated screens replace; `--links follow` can retain linked
1228
+ destinations that still matter. Review `workerRefs` and `reparented` results, and preserve
1229
+ overflow entries and back navigation instead of accepting their deletion. Do not remove a required vertical or drop its actions
1230
+ merely to fit. Apply the same reviewed command without `--dry-run`, then measure:
1231
+
1232
+ ```sh
1233
+ himi publish-size --app <intended-publish-slug> --json
1234
+ ```
1235
+
1236
+ This builds locally using the same release payload, source collector and JSON serializer
1237
+ as push. It never looks up an owner, claims a slug or publishes; no credentials or final
1238
+ UX/attestation records are required. It can write local build output. It reports
1239
+ `bodyBytes`, `limit`, `remainingBytes`, `overageBytes`, source archive bytes and screen/
1240
+ bundle counts. Failed static record-ID enumeration, missing declared icons/screens/bundles, unavailable font assets, failed declared widgets
1241
+ and failed token snapshots are refused instead of undercounted. The builder's optional
1242
+ token absence (404) is reported without inventing token bytes; it does not certify styling.
1243
+ The default `reserveBytes` is a **65,536-byte planning allowance** for final metadata;
1244
+ `planningBytes` includes it. `--reserve-bytes <n>` changes that allowance.
1245
+
1246
+ Measurement uses an isolated contained build: put the intended configuration and
1247
+ collected static seed IDs in the package explicitly. Host environment substitutions
1248
+ are not its input. A source module that needs host code or live catalog enumeration
1249
+ is refused as `build_failed`; materialize the verified source IDs from the collection
1250
+ before measuring, or report that prerequisite as blocked. Never swallow that refusal
1251
+ and treat missing detail screens as saved bytes.
1252
+
1253
+ `bodyBytes` is exact for the stated slug, source-included draft and **no publish metadata**.
1254
+ The allowance is not a verified upper bound: final label/notes, evidence and migration
1255
+ metadata must fit within the remaining bytes. A changed slug changes both app fields;
1256
+ `minimumBodyBytes` uses empty slugs and no metadata as a lower bound. If even that exceeds
1257
+ the limit, unknown owner metadata cannot rescue the build. A passing size check is
1258
+ conditional on those remaining bytes; the final push still measures its exact envelope
1259
+ and runs every evidence gate. The push command has no size-only dry run; use the
1260
+ measurement command for probes. Transport compression is not evidence that the current push guard accepts
1261
+ an oversized JSON body.
1262
+
1263
+ Init-generated `.agents/`, `.claude/` and `HIMALAYA.md` are already excluded by the
1264
+ source archive policy shown in `himi push --help`.
1265
+ Keep source included; do not work around the budget with `--no-source` or `--force`.
1266
+
1267
+ For a large static 8-bit PNG icon, lossless recompression is optional. Keep the original and candidate
1268
+ under `.himi/` while checking, use no resize, palette reduction, quantization or lossy
1269
+ format, and prove decoded RGBA pixels, dimensions and color profile are unchanged before
1270
+ replacing the declared icon. For example, with the optional `sharp` image tool installed
1271
+ locally (`npm install --prefix .himi/pixel-tools --no-save --package-lock=false sharp@0.33.5`):
1272
+
1273
+ ```sh
1274
+ node --input-type=module - <declared-icon.png> .himi/icon-lossless.png <<'JS'
1275
+ import assert from 'node:assert/strict';
1276
+ import { createRequire } from 'node:module';
1277
+ import { createHash } from 'node:crypto';
1278
+ import { resolve } from 'node:path';
1279
+ import { readFileSync, statSync } from 'node:fs';
1280
+ const sharp = createRequire(resolve('.himi/pixel-tools/tool.cjs'))('sharp');
1281
+ const [input, output] = process.argv.slice(2);
1282
+ assert.notEqual(resolve(input), resolve(output), 'preserve the original');
1283
+ const beforeMeta = await sharp(input).metadata();
1284
+ assert.equal(beforeMeta.format, 'png');
1285
+ assert.equal(beforeMeta.depth, 'uchar', 'retain higher-precision originals');
1286
+ assert.equal(beforeMeta.pages ?? 1, 1, 'retain animated originals');
1287
+ const before = await sharp(input).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1288
+ await sharp(input).keepMetadata().png({ compressionLevel: 9, adaptiveFiltering: true, palette: false }).toFile(output);
1289
+ const afterMeta = await sharp(output).metadata();
1290
+ const after = await sharp(output).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
1291
+ assert.deepEqual(after.info, before.info);
1292
+ assert.deepEqual(afterMeta.icc, beforeMeta.icc);
1293
+ assert.equal(afterMeta.orientation ?? 1, beforeMeta.orientation ?? 1);
1294
+ assert.ok(after.data.equals(before.data), 'decoded RGBA pixels must match exactly');
1295
+ const hash = bytes => createHash('sha256').update(bytes).digest('hex');
1296
+ console.log(JSON.stringify({ pixelsEqual: true, width: after.info.width, height: after.info.height,
1297
+ channels: after.info.channels, beforeBytes: statSync(input).size, afterBytes: statSync(output).size,
1298
+ beforeSHA256: hash(readFileSync(input)), afterSHA256: hash(readFileSync(output)), rgbaSHA256: hash(after.data) }));
1299
+ JS
1300
+ ```
1301
+
1302
+ Only copy the candidate over the declared icon if that proof passes and the file is
1303
+ smaller. If the tool is unavailable, retain the original and report that optimization
1304
+ as unverified. Re-run `himi icon check`, size measurement and affected visual checks
1305
+ after replacing it. Save the equality result and original/candidate hashes.
1306
+
1307
+ If required journeys still exceed the budget, stop before final evidence and report the
1308
+ measured bytes, limit and overage, source archive size, major contributing journeys/
1309
+ assets, reviewed trim results, compression/pixel proof, what was tried and what was
1310
+ preserved. Attach the numerical output and sanitized manifest/inventory; file sizes can
1311
+ explain contributors but never substitute for the assembled measurement. Hand this to
1312
+ the migration project owner for the maintained
1313
+ [Himalaya issue tracker](https://github.com/wix-private/himalaya/issues) (repository access
1314
+ required). If you lack access, return the prepared report to that owner for escalation;
1315
+ do not guess a person's contact or claim a hosted preview exists. This step does not
1316
+ authorize posting an issue, changing the server limit or publishing.
1317
+
1318
+ ### Run the final checks
1319
+
1111
1320
  After the source composition and same-site binding are implemented, use the
1112
1321
  existing authoring loop:
1113
1322
 
@@ -1210,9 +1419,8 @@ himi push --draft --target bo
1210
1419
  Keep the editable source archive included. The publish body is limited to 8,388,608
1211
1420
  bytes. It is about base64(source archive + worker bundles + assets + icons) plus the
1212
1421
  descriptor JSON, so a large app can exceed it: one 33-page migration measured
1213
- 11,259,935 bytes. Measure early, not at the end: after `himi build`, add up
1214
- `dist/bundles/*`, the icons and a zip of the content directory without the excluded
1215
- paths below. `himi push` refuses an oversized body locally, before it sends anything,
1422
+ 11,259,935 bytes. Measure early, not at the end, with `himi publish-size` (see
1423
+ "Check the publish budget before final evidence"). `himi push` refuses an oversized body locally, before it sends anything,
1216
1424
  with `publish_body_too_large`; its `breakdown`
1217
1425
  gives each part's bytes (`appSource`, `screens`, `bundles`, `assets`, `icon`, `other`)
1218
1426
  and the largest entries by name. Reduce it in this order, and repeat affected