@wix/himalaya-cli 0.1571.0 → 0.1573.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.
Files changed (83) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-module.d.ts +2 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/checkout/src/index.d.ts +4 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/apple-iap-contract.d.ts +1 -1
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/iap-checkout/src/commerce-contract.d.ts +2 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/index.d.ts +1 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/one-app-page.d.ts +61 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-feed-viewer/src/index.d.ts +6 -0
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/social-feed/src/index.d.ts +34 -9
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/checkout.mjs +28 -3
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +2 -1
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +573 -73
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members-feed-viewer.mjs +18 -0
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-social-feed.mjs +24 -1
  14. package/dist/cli.mjs +466 -90
  15. package/dist/preview-assets.generated.bin +0 -0
  16. package/guides/index.json +7 -0
  17. package/guides/map-one-app-coverage.js +117 -0
  18. package/guides/one-app-to-himi-app.md +141 -7
  19. package/guides/recipe-one-app-coverage-mapping.md +74 -0
  20. package/guides/sdk-reference.json +34 -4
  21. package/guides/webview-page-sdk.md +68 -3
  22. package/package.json +2 -2
  23. package/templates/beauty-salon/tier5-src/_shared/iap-worker.ts +2 -0
  24. package/templates/beauty-salon/tier5-src/_shared/payment-setup.ts +6 -0
  25. package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +5 -2
  26. package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  27. package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  28. package/templates/beauty-salon-owner/dev/index.ts +2 -3
  29. package/templates/branded-lite/tier5-src/_shared/iap-worker.ts +2 -0
  30. package/templates/branded-lite/tier5-src/_shared/payment-setup.ts +6 -0
  31. package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +5 -2
  32. package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  33. package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  34. package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +5 -2
  35. package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +28 -11
  36. package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +9 -4
  37. package/templates/community-club/tier5-src/_shared/iap-worker.ts +2 -0
  38. package/templates/community-club/tier5-src/_shared/payment-setup.ts +6 -0
  39. package/templates/community-club/tier5-src/himi-events-detail/index.ts +9 -4
  40. package/templates/community-club-owner/dev/index.ts +2 -3
  41. package/templates/events-meetups/tier5-src/_shared/iap-worker.ts +2 -0
  42. package/templates/events-meetups/tier5-src/_shared/payment-setup.ts +6 -0
  43. package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +9 -4
  44. package/templates/events-meetups-owner/dev/index.ts +2 -3
  45. package/templates/gym-fitness/tier5-src/_shared/iap-worker.ts +2 -0
  46. package/templates/gym-fitness/tier5-src/_shared/payment-setup.ts +6 -0
  47. package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +5 -2
  48. package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  49. package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  50. package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +9 -4
  51. package/templates/gym-fitness-owner/dev/index.ts +2 -3
  52. package/templates/nonprofit-cause/tier5-src/_shared/iap-worker.ts +2 -0
  53. package/templates/nonprofit-cause/tier5-src/_shared/payment-setup.ts +6 -0
  54. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  55. package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +9 -4
  56. package/templates/nonprofit-cause-owner/dev/index.ts +2 -3
  57. package/templates/owner-lite/dev/index.ts +2 -3
  58. package/templates/podcast-creator/tier5-src/_shared/iap-worker.ts +2 -0
  59. package/templates/podcast-creator/tier5-src/_shared/payment-setup.ts +6 -0
  60. package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  61. package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +9 -4
  62. package/templates/podcast-creator-owner/dev/index.ts +2 -3
  63. package/templates/portfolio-creative/tier5-src/_shared/iap-worker.ts +2 -0
  64. package/templates/portfolio-creative/tier5-src/_shared/payment-setup.ts +6 -0
  65. package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  66. package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +9 -4
  67. package/templates/portfolio-creative-owner/dev/index.ts +2 -3
  68. package/templates/restaurant/tier5-src/_shared/iap-worker.ts +2 -0
  69. package/templates/restaurant/tier5-src/_shared/payment-setup.ts +6 -0
  70. package/templates/restaurant/tier5-src/himi-events-detail/index.ts +9 -4
  71. package/templates/restaurant-owner/dev/index.ts +2 -3
  72. package/templates/retail-store/tier5-src/_shared/iap-worker.ts +2 -0
  73. package/templates/retail-store/tier5-src/_shared/payment-setup.ts +6 -0
  74. package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +5 -2
  75. package/templates/retail-store-owner/dev/index.ts +2 -3
  76. package/templates/yoga-studio/tier5-src/_shared/iap-worker.ts +2 -0
  77. package/templates/yoga-studio/tier5-src/_shared/payment-setup.ts +6 -0
  78. package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +5 -2
  79. package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +5 -2
  80. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +5 -2
  81. package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +28 -11
  82. package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +9 -4
  83. package/templates/yoga-studio-owner/dev/index.ts +2 -3
Binary file
package/guides/index.json CHANGED
@@ -296,6 +296,12 @@
296
296
  "category": "Wix SDK",
297
297
  "file": "one-app-to-himi-app.md"
298
298
  },
299
+ {
300
+ "id": "recipe-one-app-coverage-mapping",
301
+ "title": "Recipe: map One App coverage decisions",
302
+ "category": "Recipes",
303
+ "file": "recipe-one-app-coverage-mapping.md"
304
+ },
299
305
  {
300
306
  "id": "one-app-reference",
301
307
  "title": "One App reconstruction references and provenance",
@@ -441,6 +447,7 @@
441
447
  "showcaseFile": "stocks-dashboard.descriptor.json"
442
448
  },
443
449
  "examples": [
450
+ "map-one-app-coverage.js",
444
451
  "counter.service.ts",
445
452
  "counter-t5.descriptor.json",
446
453
  "stocks-dashboard.descriptor.json",
@@ -0,0 +1,117 @@
1
+ // Local reference recipe, not a readiness check. See himi docs recipe-one-app-coverage-mapping.
2
+ import { execFileSync } from "node:child_process";
3
+ import { lstatSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const object = value => value !== null && typeof value === "object" && !Array.isArray(value);
8
+ const sameTarget = (a, b) => a?.screenId === b?.screenId && a?.controlId === b?.controlId;
9
+ const needsControl = row => row.kind === "interaction" || row.kind === "top-bar";
10
+
11
+ function idsIn(value, ids = new Set()) {
12
+ if (Array.isArray(value)) value.forEach(item => idsIn(item, ids));
13
+ else if (object(value)) {
14
+ const component = typeof value.body?.type === "string" || (typeof value.type === "string" && object(value.props));
15
+ if (component && typeof value.id === "string" && !value.id.includes("$")) ids.add(value.id);
16
+ for (const [key, item] of Object.entries(value)) if (key !== "params" && key !== "metadata") idsIn(item, ids);
17
+ }
18
+ return ids;
19
+ }
20
+
21
+ function controlIds(screen) {
22
+ const ids = idsIn([screen.payload, screen.stickyHeader, screen.stickyFooter]);
23
+ for (const action of screen.topBar?.actions ?? []) {
24
+ if (typeof action.id === "string" && !action.id.includes("$")) ids.add(action.id);
25
+ }
26
+ return ids;
27
+ }
28
+
29
+ /** Inputs are explicit decisions and the full authored descriptor map, with effective top bars. */
30
+ export function mapCoverage(ledger, decisions, screens) {
31
+ if (ledger?.version !== 1 || ledger.workflow !== "one-app-to-himi-app" || !Array.isArray(ledger.requirements)
32
+ || !Array.isArray(decisions) || !object(screens)) throw new Error("Expected a coverage ledger, decision array and authored screen map.");
33
+ const coverage = structuredClone(ledger);
34
+ const rows = coverage.requirements;
35
+ if (new Set(rows.map(row => row.id)).size !== rows.length) throw new Error("Duplicate ledger row IDs; run coverage check.");
36
+ const issues = [];
37
+ const selected = new Map();
38
+ const invalidate = row => {
39
+ row.status = "unresolved";
40
+ delete row.target;
41
+ delete row.verification;
42
+ delete row.adaptation;
43
+ };
44
+ const targetProblem = (row, target) => {
45
+ if (!object(target) || Object.keys(target).some(key => !["screenId", "controlId"].includes(key))) return "Expected only screenId and optional controlId.";
46
+ if (typeof target.screenId !== "string" || !target.screenId.trim()) return "An explicit target screenId is required.";
47
+ const screen = Object.hasOwn(screens, target.screenId) && screens[target.screenId];
48
+ if (!object(screen) || screen.screenId !== target.screenId) return "Target screen is absent from the authored descriptor map.";
49
+ if (needsControl(row) && (typeof target.controlId !== "string" || !target.controlId.trim())) return "This row requires an explicit controlId.";
50
+ if (target.controlId !== undefined && !controlIds(screen).has(target.controlId)) return "Target control ID is absent from that screen, including its effective top bar.";
51
+ };
52
+ // A removed target cannot retain a passing record merely because no decision mentions it.
53
+ for (const row of rows) if (row.kind !== "design" && targetProblem(row, row.target)) invalidate(row);
54
+ for (const [index, decision] of decisions.entries()) {
55
+ if (!object(decision)) { issues.push({ decision: index, reason: "Expected an explicit row/source decision." }); continue; }
56
+ const matches = rows.filter(row => typeof decision.id === "string" ? row.id === decision.id
57
+ : typeof decision.source === "string" && row.source === decision.source && row.kind === decision.kind && row.platform === decision.platform);
58
+ if (matches.length !== 1) { issues.push({ decision: index, reason: "Unknown or ambiguous row/source; use the exact row ID or kind/source/platform." }); continue; }
59
+ const row = matches[0];
60
+ if (row.kind === "design") { issues.push({ decision: index, id: row.id, reason: "Design rows belong to validate-design, not screen mapping." }); continue; }
61
+ const allowed = typeof decision.id === "string" ? ["id", "target"] : ["source", "kind", "platform", "target"];
62
+ const problem = Object.keys(decision).some(key => !allowed.includes(key)) ? "Decisions cannot set status, verification, adaptation or other row fields."
63
+ : targetProblem(row, decision.target);
64
+ if (selected.has(row.id)) {
65
+ selected.set(row.id, null);
66
+ invalidate(row);
67
+ issues.push({ decision: index, id: row.id, reason: "Duplicate decisions for this row; left unresolved." });
68
+ } else {
69
+ selected.set(row.id, problem ? null : decision.target);
70
+ if (problem) { invalidate(row); issues.push({ decision: index, id: row.id, reason: problem }); }
71
+ }
72
+ }
73
+ for (const [id, target] of selected) {
74
+ if (!target) continue;
75
+ const row = rows.find(row => row.id === id);
76
+ if (!sameTarget(row.target, target)) {
77
+ invalidate(row);
78
+ row.target = { ...target };
79
+ }
80
+ }
81
+ return {
82
+ coverage,
83
+ issues,
84
+ unmapped: rows.filter(row => row.kind !== "design" && !row.target).map(({ id, kind, source, platform }) => ({ id, kind, source, ...(platform ? { platform } : {}) })),
85
+ // Inventory checks cannot validate retained proof against the author's current built release.
86
+ needsVerification: rows.map(row => row.id),
87
+ };
88
+ }
89
+
90
+ export function main(args) {
91
+ const [content, evidence, decisionsFile, screensFile, ...flags] = args;
92
+ if (!screensFile || flags.some(flag => flag !== "--write") || flags.length > 1) throw new Error("Usage: node map-one-app-coverage.js <content> <evidence-dir> <decisions.json> <screens.json> [--write]");
93
+ // Reuse the installed CLI's actual source hashes, derived row IDs and schema checks. No sync/write.
94
+ execFileSync(process.env.HIMI_BIN ?? "himi", ["one-app", "coverage", "check", "--content", content, "--evidence-dir", evidence, "--json", "--skip-version-check"], { stdio: ["ignore", "pipe", "pipe"] });
95
+ const ledgerFile = resolve(content, "ONE-APP-COVERAGE.json");
96
+ if (!lstatSync(ledgerFile).isFile() || lstatSync(ledgerFile).isSymbolicLink()) throw new Error("The ledger must be a regular file.");
97
+ const read = file => JSON.parse(readFileSync(file, "utf8"));
98
+ const result = mapCoverage(read(ledgerFile), read(decisionsFile), read(screensFile));
99
+ if (flags.includes("--write")) {
100
+ for (let directory = resolve(content);; directory = dirname(directory)) {
101
+ const stat = lstatSync(directory);
102
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Use a physical content directory without symlinked parents for --write.");
103
+ if (directory === dirname(directory)) break;
104
+ }
105
+ writeFileSync(ledgerFile, JSON.stringify(result.coverage, null, 2) + "\n");
106
+ }
107
+ console.log(JSON.stringify({ ...result, written: flags.includes("--write") }, null, 2));
108
+ return result.issues.length || result.unmapped.length ? 1 : 0;
109
+ }
110
+
111
+ if (process.argv[1] && realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1])) {
112
+ try { process.exitCode = main(process.argv.slice(2)); }
113
+ catch (error) {
114
+ console.error(JSON.stringify({ ok: false, code: "coverage_mapping_input_invalid", error: error.message }));
115
+ process.exitCode = 1;
116
+ }
117
+ }
@@ -136,6 +136,50 @@ Missing or virtual map points stay absent; an address alone is not coordinates.
136
136
  If a transient failure makes this read unavailable, the manifest records
137
137
  `business_contact_unavailable`. Keep contact coverage unresolved until the data is
138
138
  verified, rather than inventing an address or silently omitting the section.
139
+
140
+ ### Native widget defaults
141
+
142
+ Empty settings do not mean an empty native widget. Coverage derives separate
143
+ `native-widget` obligations for each placement from the runtime checklist below,
144
+ including repeated sections, detached pages and Business Profile placements.
145
+ These are behaviors to compare, not claims that the target implements them.
146
+ The CLI's versioned native-widget inventory supplies these rows automatically.
147
+
148
+ | Source section type | Effective behavior to verify |
149
+ | --- | --- |
150
+ | `bookings_services_list_section` | Search, category and location filters. |
151
+ | `single_group_feed_section`, `groups_feed_section` | Post actions, attachments, comments/post destination, topic filter and the group's Members / Library / Rules / Media destinations. |
152
+ | `platform_general_contact_section`, `platform_general_contact_section_bp` | Business address and map, respecting authored visibility and available coordinates. |
153
+ | `achievements_list_section` | Category filter, including All Categories with no categories; actual visitor/member login, access/join/payment/Continue states; participants line; default limit 2 and See All when applicable. |
154
+ | `achievements_carousel_section` | The same Program state/display rules, default limit 5 and page dots. |
155
+ | `newBlog_postlist_vertical_section` | See All Posts when a limited list has more results, preserving its scope. |
156
+ | `events_list_section` | Date badge, Share and the appropriate RSVP/ticket action. |
157
+ | `platform_general_members_section`, `platform_general_members_section_bp` | Effective member strip and directory journey: totals/search, alphabetical sections, Follow, activity/privacy. Distinguish badge chips from actual viewed-member program progress. |
158
+
159
+ This initial checklist comes from reported native source-device observations
160
+ on 2026-11-02 across two migrations, not editor demo records.
161
+ Program settings are also backed by the pinned
162
+ [Program section implementation](https://github.com/wix-private/wix-one-app-achievements/tree/f844eee1fe7cc7732531c3deaa7dca777e53c548/sections "audience-lint-ok: optional pinned upstream evidence, not a local authoring dependency").
163
+ Its absent category selection means All/future categories; explicit empty selection
164
+ means none. MANUAL order prioritizes selected IDs without excluding other records.
165
+ Use current collected settings and source-device behavior to resolve each instance.
166
+ An explicit disable/override is source evidence: faithfully preserving it requires
167
+ no adaptation waiver, and this checklist must not force the disabled control on.
168
+ Data/access-dependent controls need the relevant source state; never fabricate
169
+ participants or derive program completion from a badge title.
170
+
171
+ The table is not exhaustive. Other section types get an explicit unverified-defaults
172
+ obligation; inventory their effective source behavior rather than assuming there
173
+ are no defaults. Changing the behavior contract creates new requirement identities.
174
+ Recollection invalidates status/check evidence while preserving unchanged mappings.
175
+
176
+ Compare every tab in the actual Player or Branded Lite host with the source app
177
+ on device and live data under the same site and visitor/member identity. The editor
178
+ can show demo members/badges or blank verticals; editor-only observations remain
179
+ unverified. Until missing behavior is implemented, retain an honest unresolved,
180
+ conditional or explicitly authorized adapted row. Do not mark it supported merely
181
+ because its placement exists.
182
+
139
183
  `business-context.json.memberCount` preserves a valid Business aggregate
140
184
  `members.count` when available. For source `platform_general_members_section` or
141
185
  `platform_general_members_section_bp` widgets, collection instead reads the
@@ -235,10 +279,30 @@ Apply these interpretation rules:
235
279
  because a missing scheme falls back to the light value. Rebuild after `--fix`.
236
280
  For Branded apps, known source typography IDs (for example `lato`) become family
237
281
  names (`Lato`); tokens whose names contain title/heading/headline/display/hero use the heading family; other
238
- typography tokens use the body family. Numeric metrics stay unchanged. Unknown IDs,
239
- families outside the Himi font catalog, and missing target typography roles remain unresolved; resolve them before recording
240
- a pass, even when the coverage ledger cites an adaptation. A deliberate difference
241
- between resolved font roles can still be adapted. Build/font validation still has
282
+ typography tokens use the body family. Numeric metrics stay unchanged. Read each
283
+ source heading/body family and check `himi fonts list`; use `himi fonts add <Family>`
284
+ when it is available. A known source family that Himi does not ship (for example
285
+ `spaceGrotesk` → Space Grotesk) is still compared with the actual target family.
286
+ Choose an available replacement, such as `$system`, only with the owner's explicit
287
+ instruction; this is an adaptation, never a font match. Sync the coverage ledger
288
+ against the current source evidence, then mark the corresponding `design` requirement
289
+ `adapted` with `adaptation.instruction` citing that instruction and
290
+ `adaptation.explanation` naming the source, replacement and reason. Bind a base font decision
291
+ to the exact reported `target` string in `adaptation.target` (for example,
292
+ `"body: $system, caption: $system"`); a later replacement requires a new decision.
293
+ When an active idiom changes the font, name it in `adaptation.idioms` and copy its
294
+ reported target into `adaptation.idiomTargets[idiom]`. An overlay that keeps the base
295
+ fonts needs no separate font approval. When the base already matches, an idiom-only
296
+ decision needs only `idioms` and `idiomTargets`, without a base `target`.
297
+ Rerun `himi one-app validate-design` to produce
298
+ the source-to-target report; without that adaptation the difference fails the check.
299
+ `--fix` imports supported source families but never chooses a fallback or writes
300
+ an unavailable family. Unknown source IDs, unsupported target families and missing
301
+ or invalid target typography metrics remain unresolved even with an adaptation.
302
+ Omitted families also remain unresolved: renderer defaults differ across surfaces;
303
+ author `$system` explicitly when that is the approved replacement. `--fix` may fill
304
+ omitted families from a supported source while preserving valid numeric metrics.
305
+ Build/font validation still has
242
306
  to prove the font bytes are available. Unresolved fonts and contrast also fail
243
307
  the plain check used by push, not only `--record`.
244
308
  Branded accent text must reach 4.5:1: `textOnAccent` against the fill, and `accentText` against the target page and declared neutral
@@ -437,6 +501,53 @@ mapping rows are research leads: inspect the current CLI's component, SDK and
437
501
  template contracts before using them. A previously empty row does not prove the
438
502
  feature is impossible today.
439
503
 
504
+ ### Standalone launch tab
505
+
506
+ When the reconstructed navigation has a tab bar, preserve the app's actual tab
507
+ IDs, source order, labels, icons and content. Do not keep the starter template's
508
+ `home` ID or rename the first tab to `home` merely to match a shell default.
509
+ An actual source tab named `home` is valid; the name is not reserved or required.
510
+ A tab ID is not a `screenId`: a Community tab can use `id: "community"` with
511
+ `screenId: "community_home"` and its visible Community label.
512
+ For navigation with no tab bar, do not add a synthetic Home tab.
513
+
514
+ Validate that tab IDs are non-empty and unique. Keep the source-to-target lookup,
515
+ every `tabId` in
516
+ `navigation.linkableScreens`, tab-selection actions and worker navigation requests
517
+ consistent with the actual destination IDs, including detail screens that belong
518
+ to the first tab. Remove stale template references instead of renaming the app's
519
+ tabs to make them match. `himi validate --strict` rejects empty or duplicate tab
520
+ IDs and linkable `tabId` references outside the selectable primary navigation
521
+ on enabled surfaces when chrome lists are configured. An auxiliary drawer row is
522
+ not a compact responsive tab: use an empty/default-stack `tabId` for a pushed
523
+ screen without an owning tab. Explicit primary drawer/sidebar modes and declared
524
+ surface overrides use their own destinations. This config-only membership check
525
+ does not prove source fidelity, dynamic navigation requests, or deep-link behavior
526
+ on every surface; verify those independently. For example,
527
+ the navigation fragment for a Community-first app is:
528
+
529
+ ```json
530
+ {
531
+ "tabBar": {
532
+ "items": [
533
+ { "id": "community", "screenId": "community_home", "label": "Community", "icon": "people" },
534
+ { "id": "profile", "screenId": "member_profile", "label": "Profile", "icon": "person" }
535
+ ]
536
+ },
537
+ "linkableScreens": [
538
+ { "screenId": "community_home", "isPublic": true, "paramKeys": [], "parentScreenId": "", "tabId": "community" },
539
+ { "screenId": "community_post", "isPublic": true, "paramKeys": ["id"], "parentScreenId": "community_home", "tabId": "community" }
540
+ ]
541
+ }
542
+ ```
543
+
544
+ Current standalone bottom-tab chrome selects the first visible configured tab
545
+ when its stored selection is unknown. Do not infer this fallback for sidebar or
546
+ responsive-wide chrome: verify its launch selection independently. A stored
547
+ template default such as `home` must not override the app's navigation or leave
548
+ it blank. If a binary does not handle the app's actual IDs, fix or update that
549
+ shell and report the incompatibility; do not disguise it by renaming the first tab.
550
+
440
551
  ## 2. Write the reconstruction plan and resolve gaps
441
552
 
442
553
  Write `.himi/one-app/reconstruction-plan.md` before scaffolding. Describe the app
@@ -578,9 +689,10 @@ Run `himi one-app coverage init --evidence-dir .himi/one-app --content .` to
578
689
  create `ONE-APP-COVERAGE.json` from the verified source graph. After edits or a
579
690
  CLI upgrade, run `coverage sync` and review new rows; `coverage check` fails when
580
691
  the file is absent or source requirements have appeared, disappeared or changed.
581
- Rows cover source graph warnings and app-wide design values as well as routes and
692
+ Rows cover native widget behavior, source graph warnings and app-wide design values as well as routes and
582
693
  controls. This is an inventory of migration obligations, not proof that their target
583
- actions work.
694
+ actions work. For explicit batch target decisions without inventing verification, use the
695
+ [executable ledger mapping recipe](https://apps.wix.com/_api/himi-serve/docs/recipe-one-app-coverage-mapping.md).
584
696
 
585
697
  Preserve the existing app mark after scaffolding: run `himi icon from-one-app
586
698
  --content . --evidence-dir .himi/one-app`. This reads the hash-verified collected
@@ -602,7 +714,7 @@ satisfied by the design check instead. Set its `status` to `supported`, `adapted
602
714
  `unresolved`. For an adaptation, cite the user's instruction and explain the
603
715
  change. Each resolved row needs a `verification` record using the required check
604
716
  ID for its kind (`route-walkthrough`, `visual-comparison`, `behavior-crawl`,
605
- `topbar-control`, `access-state`, `design-token`, or `source-resolution`), a safe relative path to
717
+ `topbar-control`, `access-state`, `design-token`, `source-resolution`, or `native-widget-behavior`), a safe relative path to
606
718
  a JSON check record, and the exact release payload SHA-256 in `releaseHash`.
607
719
  The JSON file's `checks[]` must contain a passed record with that requirement ID,
608
720
  check ID, release hash, target mapping, and a relative `artifact` path to the
@@ -638,6 +750,21 @@ For example, one entry in the referenced JSON file is:
638
750
  The artifact must exist. Review the actual route and state shown in it before
639
751
  recording a pass; a valid record is an index to the check, not a substitute for it.
640
752
 
753
+ For a `native-widget` row use `check: "native-widget-behavior"` and include
754
+ `sourceBaseline` and `targetContext` in that check record. For example:
755
+
756
+ ```json
757
+ {"checks":[{"requirementId":"<widget-row-id>","check":"native-widget-behavior","releaseHash":"<64-hex-sha256>","status":"passed","target":{"screenId":"home"},"artifact":".himi/one-app/device/target.png","sourceBaseline":{"kind":"native-device","artifact":".himi/one-app/device/source.png","siteId":"<same-site-id>","identityContext":"visitor-session-1"},"targetContext":{"siteId":"<same-site-id>","identityContext":"visitor-session-1"}}]}
758
+ ```
759
+
760
+ `identityContext` is a private, opaque label for the compared identity, never a
761
+ credential, email address or session token. Both artifacts must be local safe
762
+ relative paths. The check rejects editor baselines and mismatched site/identity
763
+ labels. It checks record consistency; it cannot establish that screenshots are
764
+ authentic or that the observed behavior matches them. Review the artifacts and
765
+ exercise each effective control, including authored overrides and access states.
766
+ This comparison does not replace the QA APK/native proof below.
767
+
641
768
  After publishing a draft, build a QA APK pinned to that draft release with
642
769
  `himi dist --platform android --release <draft-id> --allow-draft`. Install
643
770
  and walk through every requirement on the device. Save
@@ -851,6 +978,13 @@ transactions against fixtures only. Read-only live checks verify the correct sit
851
978
  entity selection and runtime identity; they do not authorize placing orders,
852
979
  bookings, payments, form submissions or destructive updates during validation.
853
980
 
981
+ For a tabbed migration, cold-launch the standalone app without a launch-screen
982
+ override or incoming deep link. Confirm the first tab is selected and displays
983
+ content. Then switch tabs and open a detail link belonging to the first tab;
984
+ confirm it selects that tab and opens the intended screen. A hosted preview alone
985
+ does not establish standalone launch behavior. Record an unavailable native check
986
+ as unverified in the migration report.
987
+
854
988
  The UX audit requires app-specific network fixtures. `himi test --record` records
855
989
  only boot-time reads with the app's visitor identity, then crawls hermetically.
856
990
  Review its recorded/refused requests; do not run a source getter known to mutate
@@ -0,0 +1,74 @@
1
+ # Recipe: map a One App coverage ledger
2
+
3
+ Use explicit reconstruction decisions to map a large `ONE-APP-COVERAGE.json` without
4
+ rewriting its source-derived IDs. This recipe only assigns targets. It never records a
5
+ passed check, chooses a customer route, or makes an unresolved requirement supported.
6
+
7
+ Run the existing coverage commands first:
8
+
9
+ ```sh
10
+ himi one-app coverage init --evidence-dir .himi/one-app --content .
11
+ # After source recollection or a CLI upgrade, use coverage sync and review its changes.
12
+ himi one-app coverage check --evidence-dir .himi/one-app --content .
13
+ ```
14
+
15
+ The runnable example is installed by `himi init` and refreshed by `himi agent-init` at
16
+ `.agents/skills/himalaya/examples/map-one-app-coverage.js`. It uses the installed
17
+ `himi one-app coverage check` before reading the ledger, so stale hashes, invented rows
18
+ and changed source requirements refuse the operation. It does not execute app workers
19
+ or call Wix. Keep decisions, descriptors and reports under `.himi/`.
20
+
21
+ Write `.himi/one-app/mapping-decisions.json` as an array. Select each row by its exact
22
+ `id`, or by exact `kind`, JSON-pointer `source`, and `platform` when present:
23
+
24
+ ```json
25
+ [
26
+ { "kind": "route", "source": "/screens/source_home", "target": { "screenId": "home" } },
27
+ { "id": "<interaction-row-id-from-the-ledger>", "target": { "screenId": "home", "controlId": "view_products" } }
28
+ ]
29
+ ```
30
+
31
+ Copy source pointers and IDs from the ledger; do not derive them from labels, row
32
+ positions, or guessed routes. An ambiguous source selector requires the row ID. Each
33
+ decision may set only its target, never status, adaptation or verification. A `design`
34
+ row is app-wide and belongs to `validate-design`; leave it out of mapping decisions.
35
+
36
+ Generate `.himi/one-app/mapping-screens.json` from the **same authored descriptor data**
37
+ used by the app generator: `{ "home": { "screenId": "home", "payload": { … } }, … }`.
38
+ Include every authored screen and each screen's effective top bar (its override or the
39
+ actual `navigation.topBarDefault`). Serialize builders at generation time. Do not
40
+ invent a screen/control inventory to make a decision pass. Missing or dynamically
41
+ unresolved IDs stay unmapped. Control-ID presence is only an identity check; a real
42
+ interaction or top-bar check must still prove the behavior.
43
+
44
+ Inspect a dry run first, then write the reviewed mappings:
45
+
46
+ ```sh
47
+ node .agents/skills/himalaya/examples/map-one-app-coverage.js \
48
+ . .himi/one-app .himi/one-app/mapping-decisions.json .himi/one-app/mapping-screens.json \
49
+ > .himi/one-app/mapping-review.json
50
+ # Review coverage, issues, unmapped and needsVerification in that JSON.
51
+ node .agents/skills/himalaya/examples/map-one-app-coverage.js \
52
+ . .himi/one-app .himi/one-app/mapping-decisions.json .himi/one-app/mapping-screens.json --write
53
+ himi one-app coverage check --evidence-dir .himi/one-app --content .
54
+ himi one-app validate-topbar --evidence-dir .himi/one-app --content .
55
+ ```
56
+
57
+ For `--write`, use the physical app directory, without symlinked parent directories;
58
+ the ledger itself must also be a regular file. Running the installed script through a
59
+ symlink is supported. Without `--write` the ledger is unchanged. Exit 1 means there are invalid decisions or
60
+ unmapped rows; the JSON lists them. With `--write`, valid decisions are retained while
61
+ unknown targets, missing controls and duplicate decisions leave their rows unresolved.
62
+ An unknown source/row creates no requirement and is reported in `issues`.
63
+
64
+ A changed mapping loses its old verification and adaptation and becomes `unresolved`.
65
+ An unchanged mapping retains its existing evidence; unrelated valid rows, IDs and source
66
+ hashes are preserved. A target removed from the authored map also loses its old evidence.
67
+ `needsVerification` lists every row: this recipe cannot validate retained proof against
68
+ the current built release. Use `coverage verify --release-hash <sha256>` with the real
69
+ release hash to check it, including unchanged mappings and previously saved records.
70
+ Mapping output is not a readiness verdict. Decide supported/adapted/conditional status
71
+ only after implementing and testing the corresponding behavior, with the required
72
+ artifact and exact release hash. Run `validate-design --fix`, rebuild, then record the
73
+ final design/UX/coverage evidence before pushing; repeat affected records after content
74
+ changes. The main migration guide defines those checks and their limitations.
@@ -2771,7 +2771,7 @@
2771
2771
  "name": "OrderFailureCode",
2772
2772
  "kind": "type",
2773
2773
  "jsdoc": "",
2774
- "decl": "export type OrderFailureCode =\n | \"notEnabled\" | \"notConfigured\" | \"signInRequired\" | \"unsupportedItem\" | \"notAppleRoute\"\n | \"storageUnavailable\" | \"approvalRequired\" | \"orderClosed\" | \"orderRefused\" | \"chargeFailed\"\n | \"paymentRefused\" | \"paymentHeld\" | \"alreadyOwned\" | \"paymentInProgress\" | \"productChanged\"\n | \"invalidAssociation\" | \"needsSupport\" | \"unavailable\" | \"sessionChanged\" | \"outcomeUnknown\";"
2774
+ "decl": "export type OrderFailureCode =\n | \"notEnabled\" | \"notConfigured\" | \"signInRequired\" | \"unsupportedItem\" | \"notAppleRoute\"\n | \"storageUnavailable\" | \"approvalRequired\" | \"orderClosed\" | \"orderRefused\" | \"chargeFailed\"\n | \"paymentRefused\" | \"paymentHeld\" | \"alreadyOwned\" | \"paymentInProgress\" | \"productChanged\"\n | \"sitePaymentUnavailable\" | \"invalidAssociation\" | \"needsSupport\" | \"unavailable\" | \"sessionChanged\" | \"outcomeUnknown\";"
2775
2775
  },
2776
2776
  {
2777
2777
  "name": "PaymentAccess",
@@ -2849,7 +2849,7 @@
2849
2849
  "name": "IapPurchaseCopy",
2850
2850
  "kind": "interface",
2851
2851
  "jsdoc": "/** Labels from the screen's copy pack; the shared layer only chooses between them. */",
2852
- "decl": "export interface IapPurchaseCopy {\n buy: string;\n buyAnother?: string;\n processing: string;\n awaitingApproval: string;\n unavailable: string;\n signInRequired: string;\n retry: string;\n priceChanged: string;\n support: string;\n}"
2852
+ "decl": "export interface IapPurchaseCopy {\n buy: string;\n buyAnother?: string;\n processing: string;\n awaitingApproval: string;\n unavailable: string;\n signInRequired: string;\n retry: string;\n /** Optional for existing SDK consumers; the app supplies a localized recovery message. */\n sitePaymentUnavailable?: string;\n priceChanged: string;\n support: string;\n}"
2853
2853
  },
2854
2854
  {
2855
2855
  "name": "IapPurchaseFields",
@@ -23606,6 +23606,12 @@
23606
23606
  "jsdoc": "/** List the signed-in member's notifications, newest first. MOBILE channel. */",
23607
23607
  "decl": "export async function queryNotifications(limit = 25, options?: WixCallOptions): Promise<MfvNotification[]>;"
23608
23608
  },
23609
+ {
23610
+ "name": "countUnseenNotifications",
23611
+ "kind": "function",
23612
+ "jsdoc": "/** Source member-app MOBILE/UNSEEN aggregate, capped at 100. Requires a bound\n * single-site Member transport; old native binaries and web reject. A missing,\n * malformed or failed result is unavailable, never an authoritative zero. */",
23613
+ "decl": "export async function countUnseenNotifications(options: { site: string }): Promise<number>;"
23614
+ },
23609
23615
  {
23610
23616
  "name": "markRead",
23611
23617
  "kind": "function",
@@ -25554,13 +25560,37 @@
25554
25560
  "jsdoc": "",
25555
25561
  "decl": "export interface WixListFeedItemsResponse {\n feedItems: WixFeedItem[];\n profiles?: WixFeedProfile[];\n nextPageCursor?: string;\n}"
25556
25562
  },
25563
+ {
25564
+ "name": "WixFeedPageOptions",
25565
+ "kind": "interface",
25566
+ "jsdoc": "/** Read controls shared by the source's single-group and central feeds. */",
25567
+ "decl": "export interface WixFeedPageOptions extends WixCallOptions {\n /** Opaque server cursor; pass it back unchanged when loading the next page. */\n cursor?: string;\n invalidatePermissionsCache?: boolean;\n}"
25568
+ },
25569
+ {
25570
+ "name": "WixGroupFeedOptions",
25571
+ "kind": "interface",
25572
+ "jsdoc": "",
25573
+ "decl": "export interface WixGroupFeedOptions extends WixFeedPageOptions {\n topicId?: string;\n}"
25574
+ },
25575
+ {
25576
+ "name": "WixCentralFeedOptions",
25577
+ "kind": "interface",
25578
+ "jsdoc": "",
25579
+ "decl": "export interface WixCentralFeedOptions extends WixFeedPageOptions {\n withSuggestedPosts?: boolean;\n /** Omit to use the central-feed service's default representation. */\n contentType?: \"DRAFTJS\";\n}"
25580
+ },
25557
25581
  {
25558
25582
  "name": "listFeedItems",
25559
25583
  "kind": "function",
25560
- "jsdoc": "/**\n * List a group's activity feed — GET\n * https://social.wix.com/serverless/mobile/feed/v4/feedPosts (the ENRICHED read the\n * real mobile app uses). Each item carries content (Ricos blocks), commentsCount, and\n * reactionsList (count = length); the response's top-level `profiles[]` resolves each\n * item's author.id → userName + profileImage. Verified live (200). Replaces the lean\n * GET /api/v1/feed/items read, which omitted counts + author.\n *\n * @scope member — verified live with a signed-in member session; visitor/anonymous\n * readability of a public group's feed is unconfirmed.\n */",
25561
- "decl": "export function listFeedItems(groupId: string, limit = 20, options?: WixCallOptions): Promise<WixListFeedItemsResponse>;",
25584
+ "jsdoc": "/**\n * List one group's activity feed. Mirrors wix-one-app-feed getFeedPosts:\n * containerId + Groups appDefId + feedType=all + contentType=DRAFTJS, with optional\n * topic and opaque paging cursor. Existing site/cache options remain per-call.\n * Reactions lists must not be treated as authoritative totals.\n *\n * @scope member — verified live with a signed-in member session; visitor readability\n * depends on group permissions and is not established by the central-feed probe.\n */",
25585
+ "decl": "export function listFeedItems(groupId: string, limit = 20, options?: WixGroupFeedOptions): Promise<WixListFeedItemsResponse>;",
25562
25586
  "scope": "member"
25563
25587
  },
25588
+ {
25589
+ "name": "listCentralFeedItems",
25590
+ "kind": "function",
25591
+ "jsdoc": "/**\n * List the site's combined feed, rather than substituting a selected group's feed.\n * Mirrors wix-one-app-feed getCentralFeedPosts: there is no containerId or appDefId.\n * Authentication and business selection belong to the bridge (or per-call `site`).\n *\n * Source: wix-one-app-feed@6ef53170e94e27349d2a6c1cf08038b078d86228,\n * src/services/feed-api.ts. A real Visitor read returned 200 with posts and a cursor.\n * That establishes the server contract only: native consumers need a core release with the exact read-route guard, and\n * production web still refuses social.wix.com. This SDK method does\n * not change those policies or establish membership/write permission. Consumers must\n * surface a transport refusal; they must not substitute a group directory or empty feed.\n */",
25592
+ "decl": "export function listCentralFeedItems(limit = 20, options?: WixCentralFeedOptions): Promise<WixListFeedItemsResponse>;"
25593
+ },
25564
25594
  {
25565
25595
  "name": "createFeedItem",
25566
25596
  "kind": "function",
@@ -94,7 +94,28 @@ every message none of them claims, including a handler registered directly on
94
94
  `himalaya.webview.onPageMessage` after `@himalaya/webview` loaded).
95
95
 
96
96
  ```ts
97
- import { emitToPage, onPageLifecycle, onPageRequest, openPageStream } from "@himalaya/webview";
97
+ import { defineService } from "@himalaya/state";
98
+ import { emitToPage, onPageLifecycle, onPageRequest, openPageStream, type PageStream } from "@himalaya/webview";
99
+
100
+ // The worker has NO setTimeout / setInterval on any renderer. A stream that produces over time
101
+ // is paced by the host clock: an action returns a `_poll`, and each tick sends the next chunk.
102
+ const watching = new Set<PageStream>();
103
+ let armed = false; // re-arming a live id with `immediate` fires an extra tick at once — arm once
104
+ const steps = defineService("steps", {
105
+ state: {},
106
+ actions: {
107
+ arm: (s) => ({ ...s, _poll: { id: "steps", intervalMs: 1000, action: "steps.tick", immediate: true } }),
108
+ tick: (s) => {
109
+ for (const stream of watching) {
110
+ if (stream.closed) watching.delete(stream); // ended, or the page broke out of its loop
111
+ else stream.next({ steps: Math.random() });
112
+ }
113
+ if (watching.size) return s;
114
+ armed = false;
115
+ return { ...s, _poll: { id: "steps", stop: true } };
116
+ },
117
+ },
118
+ });
98
119
 
99
120
  onPageRequest("nano", {
100
121
  // (args, ctx) → value | promise. ctx = { instanceId, requestId, active }.
@@ -102,8 +123,8 @@ onPageRequest("nano", {
102
123
  // Return a stream handle to answer with many chunks.
103
124
  "steps.watch": (_args, ctx) => {
104
125
  const stream = openPageStream(ctx.instanceId, ctx.requestId);
105
- const timer = setInterval(() => stream.next({ steps: Math.random() }), 1000);
106
- stream.onCancel(() => clearInterval(timer)); // the page broke out of its loop
126
+ watching.add(stream);
127
+ if (!armed) { armed = true; void steps.arm(); }
107
128
  return stream;
108
129
  },
109
130
  });
@@ -112,6 +133,9 @@ emitToPage("*", "nano.theme.changed", { dark: true }); // or one instance id
112
133
  const off = onPageLifecycle(({ instanceId, state }) => console.log(instanceId, state)); // mounted … unmounted
113
134
  ```
114
135
 
136
+ A timer in a worker is not a style choice: `setInterval` here throws `Can't find variable` on the
137
+ device, and the page sees the request fail (`_poll`: [Tier-5 guide](tier-5-add-a-screen.md#refresh-on-a-cadence-with-_poll-the-worker-has-no-clock)).
138
+
115
139
  - `ctx.instanceId` is the WebView the **host** says the request came from; a page-sent `iid` is
116
140
  ignored. `ctx.active` is live: `false` between `inactive` and `active` lifecycle events.
117
141
  - A namespace — and an exposed capability id — follows the capability-id pattern
@@ -255,3 +279,44 @@ The page is served at `/web/webview-sdk.html`. Its companion worker is
255
279
  probes. Safe probes run on load.
256
280
  Verbs that prompt, write, or open system UI stay allowlisted and are not called. Streaming
257
281
  helpers that return a live object stay on the worker.
282
+
283
+ ## Embedding worker-bridged WebViews
284
+
285
+ A Tier-5 SDUI screen embeds any number of `WebView` nodes that talk to the **owning screen's
286
+ worker** over the same channel: set `bridge: "worker"` on the node, declare `"webview"`
287
+ in the screen's `logic_bundle`, and give each widget an explicit `instance_id` (inside a
288
+ `LazyList`/`Carousel` item template it must be item-derived, e.g. `"row-$item.id"`). Untrusted or
289
+ LLM-generated inline HTML additionally wants `origin_isolation: "per_instance"` (its own origin +
290
+ storage per widget), `frame_policy: "content"` (top frame pinned, bridge main-frame-only),
291
+ `bridge_security: { profile: "app_content" }` (multi-line-friendly caps) and `console_forwarding`.
292
+ The host injects the page client at document start, so the page imports nothing; an app alias
293
+ (`inject_js_document_start: "window.demoSdk = himalaya.demo;"`) runs after it, and the platform
294
+ knows no app names. On web (phase 1), only an app-controlled **cross-origin `url`** page listed in
295
+ `allowed_origins` is bridged; an inline (`srcdoc`) widget stays unbridged and the worker hears
296
+ `mounted` with `detail: { bridged: false, reason: "web_srcdoc_unbridged" }` — show a fallback.
297
+
298
+ A complete shape to copy — the demo app's **Embedded widgets** screen (`demo_app_embedded_widgets`)
299
+ is built exactly this way:
300
+
301
+ - three isolated inline widgets in a `Carousel` with `live_window: 1`, so only the current widget
302
+ and its neighbours are mounted (swiping to widget 3 unmounts widget 1), plus one bridged
303
+ cross-origin `url` page — all served by **one** worker;
304
+ - the worker answers `onPageRequest("demo", { echo, bump, stream })`: a request, a per-instance
305
+ counter that answers with `emitToPage(ctx.instanceId, "demo.tick", n)`, and a 5-chunk stream the
306
+ page can stop (`break` out of its loop), paced by a host `_poll` as above;
307
+ - `muted_path`, `active_path` and one widget's `reload_key_path` bound to worker state, and an
308
+ `onPageLifecycle` handler that shows each widget's last state;
309
+ - each widget keeps its own `localStorage` counter, which survives a relaunch and is never visible
310
+ to its neighbours.
311
+
312
+ Test such a screen by the document, not only by lifecycle envelopes: put state in a widget's page
313
+ (an echo reply), swipe away and back, and check the state survived where the widget should have
314
+ stayed mounted. **Crash recovery:** when a widget's content process dies, the host sends `crashed`
315
+ and then remounts it (`reloaded`). On iOS and macOS each widget has its own content process, so the
316
+ page exhausting memory (the demo's "Crash widget 3" does) kills only that widget; killing the
317
+ `com.apple.WebKit.WebContent` process serving it by hand on a simulator triggers the same two
318
+ envelopes. **On Android, don't use a memory hog:** one renderer process serves every WebView of the
319
+ app — the screen's own worker included — so the hog takes them all down, and repeated renderer
320
+ crashes exhaust the worker's recovery budget. Trigger it by hand instead: kill the app's sandboxed
321
+ WebView process (`adb shell ps -A | grep sandboxed_process`, then `adb shell kill <pid>`) and watch
322
+ for `crashed` then `reloaded`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1571.0",
3
+ "version": "0.1573.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": [
@@ -71,5 +71,5 @@
71
71
  "artifactId": "himalaya-cli"
72
72
  }
73
73
  },
74
- "falconPackageHash": "fec01235dfeb9ac45a855684328263dcff0fa8e5ecf05db7d6e9b9da"
74
+ "falconPackageHash": "39a44e1a057f7d34899d550863397025bfdec7b4f241a6e45b677246"
75
75
  }
@@ -4,6 +4,7 @@
4
4
  // SDK; this only supplies the copy and the hydration reset.
5
5
  import { hostSessionFromGlobal, type IapPriceCopy } from "@himalaya/iap";
6
6
  import type { IapCommerce, IapPurchaseCopy, IapRestoreResult } from "@himalaya/iap-checkout";
7
+ import { paymentSetupMessage } from "./payment-setup.js";
7
8
  import { copyParam, copyTemplate } from "./himi-copy.js";
8
9
 
9
10
  /** Price labels from the screen's copy pack (`<prefix>.priceLoadingText`, …). */
@@ -25,6 +26,7 @@ export function iapPurchaseCopy(prefix: string): IapPurchaseCopy {
25
26
  awaitingApproval: copyParam(`${prefix}.purchaseAwaitingApprovalText`, "Waiting for approval"),
26
27
  unavailable: copyParam(`${prefix}.purchaseTemporarilyUnavailableText`, "Purchasing is temporarily unavailable."),
27
28
  signInRequired: copyParam(`${prefix}.purchaseSignInText`, "Sign in to buy."),
29
+ sitePaymentUnavailable: paymentSetupMessage(prefix),
28
30
  retry: copyParam(`${prefix}.purchaseRetryText`, "Something went wrong. Try again."),
29
31
  priceChanged: copyParam(`${prefix}.purchasePriceChangedText`, "The price changed. Check it and try again."),
30
32
  support: copyParam(`${prefix}.purchaseSupportText`, "We couldn't finish this purchase. Contact support."),
@@ -0,0 +1,6 @@
1
+ import { copyParam } from "./himi-copy.js";
2
+
3
+ /** No provider or country guess: Wix only established that online payment is unavailable. */
4
+ export function paymentSetupMessage(prefix: string): string {
5
+ return copyParam(`${prefix}.paymentSetupUnavailable`, "This site is not accepting online payments. Go back or contact the business for other payment options.");
6
+ }