@wix/himalaya-cli 0.1564.0 → 0.1566.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.
@@ -35,12 +35,12 @@ export interface DetailedHeaderOptions {
35
35
  collapsedHeight?: number;
36
36
  }
37
37
  /**
38
- * Build the shared Detailed header used by One App migrations.
38
+ * Build an optional Detailed cover header for screens that explicitly request one.
39
39
  *
40
40
  * The header uses Himalaya's cross-platform `CollapsingHeader` and standard
41
- * components for the detail card, logo, member strip, and actions. A migration
42
- * calls this builder instead of copying header layout into each app. Himalaya's
43
- * hero accepts one image, so pass the first source cover image here.
41
+ * components for the detail card, logo, member strip, and actions. The One App
42
+ * migration workflow uses native navigation bars and does not call this builder.
43
+ * Himalaya's hero accepts one image.
44
44
  */
45
45
  export declare function detailedHeader(opts: DetailedHeaderOptions): Node;
46
46
  export interface ScreenEnvelope {
package/dist/cli.mjs CHANGED
@@ -13604,6 +13604,7 @@ function webViewOriginAllowed(url, origins) {
13604
13604
  }
13605
13605
  var init_webview_origin = __esm({
13606
13606
  "../../core/ts/src/webview-origin.ts"() {
13607
+ "use strict";
13607
13608
  }
13608
13609
  });
13609
13610
 
@@ -138915,6 +138916,7 @@ __export(build_exports2, {
138915
138916
  BAKE_OFFLINE_BASE: () => BAKE_OFFLINE_BASE,
138916
138917
  KNOWN_ICON_NAMES: () => KNOWN_ICON_NAMES,
138917
138918
  SOURCEMAP_ON_DEMAND_PATH: () => SOURCEMAP_ON_DEMAND_PATH,
138919
+ SUPPORTED_COMPONENT_MIN_VERSION: () => SUPPORTED_COMPONENT_MIN_VERSION,
138918
138920
  bakeCatalogs: () => bakeCatalogs,
138919
138921
  bakeNeedsLiveOrigin: () => bakeNeedsLiveOrigin,
138920
138922
  bakeWidgets: () => bakeWidgets,
@@ -138927,6 +138929,7 @@ __export(build_exports2, {
138927
138929
  countingHostAnswers: () => countingHostAnswers,
138928
138930
  countingWixCall: () => countingWixCall,
138929
138931
  declaresHostProvidedIdentity: () => declaresHostProvidedIdentity,
138932
+ explicitMinRuntimeVersion: () => explicitMinRuntimeVersion,
138930
138933
  gitSha: () => gitSha,
138931
138934
  isMonorepoCheckout: () => isMonorepoCheckout,
138932
138935
  l10nLintOptions: () => l10nLintOptions,
@@ -138936,6 +138939,7 @@ __export(build_exports2, {
138936
138939
  listBuildableApps: () => listBuildableApps,
138937
138940
  liveRenderServices: () => liveRenderServices,
138938
138941
  maximumBinaryVersion: () => maximumBinaryVersion,
138942
+ navBundleMinBinaryVersion: () => navBundleMinBinaryVersion,
138939
138943
  navLogicBundleUrl: () => navLogicBundleUrl,
138940
138944
  pinBakedServerBaseUrl: () => pinBakedServerBaseUrl,
138941
138945
  pinnedReleaseFromEnv: () => pinnedReleaseFromEnv,
@@ -138946,6 +138950,7 @@ __export(build_exports2, {
138946
138950
  shellSha: () => shellSha,
138947
138951
  snapshotTokens: () => snapshotTokens,
138948
138952
  tokensSnapshotMessage: () => tokensSnapshotMessage,
138953
+ unsupportedComponentMinVersions: () => unsupportedComponentMinVersions,
138949
138954
  withPinnedRelease: () => withPinnedRelease,
138950
138955
  withStoreNavBundle: () => withStoreNavBundle,
138951
138956
  withoutDevOnlyAppConfig: () => withoutDevOnlyAppConfig
@@ -139216,6 +139221,22 @@ function collectComponentTypes(value2, into = /* @__PURE__ */ new Set()) {
139216
139221
  for (const child of Object.values(obj7)) collectComponentTypes(child, into);
139217
139222
  return into;
139218
139223
  }
139224
+ function unsupportedComponentMinVersions(value2, into = []) {
139225
+ if (!value2 || typeof value2 !== "object") return into;
139226
+ const obj7 = value2;
139227
+ const isComponent2 = typeof obj7.id === "string" && (obj7.body !== null && typeof obj7.body === "object" || typeof obj7.package === "string" && typeof obj7.type === "string");
139228
+ if (isComponent2 && typeof obj7.minVersion === "number" && obj7.minVersion > SUPPORTED_COMPONENT_MIN_VERSION) {
139229
+ into.push({ id: obj7.id, minVersion: obj7.minVersion });
139230
+ }
139231
+ for (const child of Object.values(obj7)) unsupportedComponentMinVersions(child, into);
139232
+ return into;
139233
+ }
139234
+ function explicitMinRuntimeVersion(raw) {
139235
+ if (raw === void 0 || raw.trim() === "") return null;
139236
+ const v2 = raw.trim();
139237
+ if (!/^\d+(?:\.\d+){0,2}$/.test(v2)) throw new Error(`invalid --min-runtime-version "${raw}": expected a numeric version like 1.7.0`);
139238
+ return v2;
139239
+ }
139219
139240
  function collectNativeComponents(value2, into) {
139220
139241
  if (!value2 || typeof value2 !== "object") return;
139221
139242
  const obj7 = value2;
@@ -139238,6 +139259,21 @@ function maximumBinaryVersion(current, candidate) {
139238
139259
  }
139239
139260
  return current;
139240
139261
  }
139262
+ function navBundleMinBinaryVersion(text12) {
139263
+ let cfg;
139264
+ try {
139265
+ cfg = JSON.parse(text12);
139266
+ } catch {
139267
+ return null;
139268
+ }
139269
+ let floor2 = null;
139270
+ for (const ref of [cfg?.app?.logicBundle, cfg?.navigation?.navLogicBundle, cfg?.navLogicBundle]) {
139271
+ if (!ref || typeof ref !== "object") continue;
139272
+ floor2 = maximumBinaryVersion(floor2, ref.minBinaryVersion);
139273
+ floor2 = maximumBinaryVersion(floor2, ref.min_binary_version);
139274
+ }
139275
+ return floor2;
139276
+ }
139241
139277
  async function withStoreNavBundle(text12, address) {
139242
139278
  let cfg;
139243
139279
  try {
@@ -139312,7 +139348,8 @@ async function buildRelease(app, dest, opts = {}) {
139312
139348
  return (await buildFromLoaded(loaded, dest, {
139313
139349
  ...tier5Dir ? { tier5Dir } : {},
139314
139350
  ...opts.fetchFace ? { fetchFace: opts.fetchFace } : {},
139315
- ...opts.keepDevOnlyAppConfig ? { keepDevOnlyAppConfig: true } : {}
139351
+ ...opts.keepDevOnlyAppConfig ? { keepDevOnlyAppConfig: true } : {},
139352
+ ...opts.minRuntimeVersion ? { minRuntimeVersion: opts.minRuntimeVersion } : {}
139316
139353
  })).manifest;
139317
139354
  }
139318
139355
  function countingWixCall(base) {
@@ -139443,6 +139480,13 @@ function readTokensForLint(preloaded) {
139443
139480
  }
139444
139481
  async function buildFromLoaded(preloaded, dest, opts = {}) {
139445
139482
  const app = preloaded.name;
139483
+ const assertComponentMinVersions = (screenId, descriptor) => {
139484
+ const bad2 = unsupportedComponentMinVersions(descriptor);
139485
+ if (!bad2.length) return;
139486
+ throw new Error(
139487
+ `screen ${screenId}: component minVersion is not enforced by any runtime (supported: ${SUPPORTED_COMPONENT_MIN_VERSION}) \u2014 ` + bad2.map((c2) => `${c2.id} asks for ${c2.minVersion}`).join(", ") + `. Declare what the release needs from the runtime as its floor instead: min_binary_version on the logic bundle, or release:build --min-runtime-version.`
139488
+ );
139489
+ };
139446
139490
  const tier5Dir = opts.tier5Dir ?? TIER5_DIR;
139447
139491
  const bundlePath = (name) => {
139448
139492
  const own6 = join28(tier5Dir, `${name}.bundle.js`);
@@ -139629,7 +139673,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
139629
139673
  if (input && typeof input === "object" && !Array.isArray(input)) bundleInputs.set(bundleName, input);
139630
139674
  };
139631
139675
  let handlers;
139632
- let minBinaryVersion = null;
139676
+ let minBinaryVersion = explicitMinRuntimeVersion(opts.minRuntimeVersion);
139633
139677
  const requiredCapabilities2 = new Set(config.capabilities ?? []);
139634
139678
  const capabilityUsage = {
139635
139679
  app: [...config.capabilities ?? []],
@@ -139868,6 +139912,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
139868
139912
  continue;
139869
139913
  }
139870
139914
  const descriptor = await res.json();
139915
+ assertComponentMinVersions(screenId, descriptor);
139871
139916
  collectNativeComponents(descriptor, requiredNativeComponents);
139872
139917
  let bundleName = null;
139873
139918
  const lb = logicBundleOf(descriptor);
@@ -140038,6 +140083,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
140038
140083
  continue;
140039
140084
  }
140040
140085
  const descriptor = await res.json();
140086
+ assertComponentMinVersions(screenId, descriptor);
140041
140087
  collectNativeComponents(descriptor, requiredNativeComponents);
140042
140088
  let bundleName = null;
140043
140089
  const lb = logicBundleOf(descriptor);
@@ -140163,6 +140209,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
140163
140209
  });
140164
140210
  await store.putAppConfig(app, text12);
140165
140211
  appConfigHash = sha2563(text12);
140212
+ minBinaryVersion = maximumBinaryVersion(minBinaryVersion, navBundleMinBinaryVersion(text12));
140166
140213
  if (collect2?.descriptors) for (const id of linkableScreenIdsFrom(text12)) linkable.add(id);
140167
140214
  }
140168
140215
  const ahRes = await devFetch(`${base}/v1/action-handlers/${encodeURIComponent(app)}`, { headers: { "X-Himalaya-App": app } });
@@ -140368,7 +140415,7 @@ function declaresHostProvidedIdentity(auth) {
140368
140415
  const config = auth?.config;
140369
140416
  return typeof config === "object" && config !== null && config.hostProvided === true;
140370
140417
  }
140371
- var BAKE_OFFLINE_BASE, KNOWN_ICON_NAMES, KNOWN_ICONS, APP_SERVICES_LINT_SCREEN, REPO_ROOT5, TIER5_DIR, requireFromHere, SOURCEMAP_ON_DEMAND_PATH, BAKED_SERVER_BASE_URL, SETTLE_TURNS;
140418
+ var BAKE_OFFLINE_BASE, KNOWN_ICON_NAMES, KNOWN_ICONS, APP_SERVICES_LINT_SCREEN, REPO_ROOT5, TIER5_DIR, requireFromHere, SOURCEMAP_ON_DEMAND_PATH, BAKED_SERVER_BASE_URL, SUPPORTED_COMPONENT_MIN_VERSION, SETTLE_TURNS;
140372
140419
  var init_build3 = __esm({
140373
140420
  "../release-build/build.ts"() {
140374
140421
  init_site_view_metadata();
@@ -140412,6 +140459,7 @@ var init_build3 = __esm({
140412
140459
  requireFromHere = createRequire3(import.meta.url);
140413
140460
  SOURCEMAP_ON_DEMAND_PATH = join28(REPO_ROOT5, "core/dev-server/tier5-bundles-src/sourcemap-on-demand.mjs");
140414
140461
  BAKED_SERVER_BASE_URL = "http://baked.himalaya.invalid";
140462
+ SUPPORTED_COMPONENT_MIN_VERSION = 1;
140415
140463
  SETTLE_TURNS = 20;
140416
140464
  }
140417
140465
  });
@@ -103,9 +103,12 @@ support." (Wix refused it, so it stays held), `restoreUpToDateText` "Your purcha
103
103
  "Couldn't reach the App Store. Try again." (also shown when the host cannot sell). A message belongs
104
104
  to the session it ran for: after a sign-in or sign-out the control shows none.
105
105
 
106
- **Not covered yet.** After a reinstall the native managed ledger is gone and its tenant filter hides
107
- the unfinished transactions, so restore finds none of them until that is fixed; it still re-reads
108
- access. On iOS `AppStore.sync()` errors are swallowed natively, so a failed sync currently reads as
106
+ **Reinstall.** A reinstall deletes the native managed ledger. In an app's own bound build, an
107
+ unfinished transaction under the app's bundle id is still the app's own, so restore resubmits it.
108
+ In Player or an explicit host, the tenant filter hides those transactions and restore finds none;
109
+ it still re-reads access ([transaction contract](iap-transaction-contract.md#activation-limits-and-compatibility)).
110
+
111
+ **Not covered yet.** On iOS `AppStore.sync()` errors are swallowed natively, so a failed sync currently reads as
109
112
  up to date rather than the error copy.
110
113
 
111
114
  ## Migrating an existing app copy
@@ -147,7 +147,7 @@ Apply these interpretation rules:
147
147
  only when the resolved source top-bar settings show a title and Structure supplies
148
148
  its value; leave it empty when the source does not show a title or has no title.
149
149
  Do not create a title from the app name, business name, route ID, tab label or
150
- section content. Keep a custom page header separate from the shell top bar.
150
+ section content. Apply the native-navigation policy below to source custom headers.
151
151
  - Keep navigation visibility, reachability and data access separate. Reevaluate
152
152
  membership/plan/badge gates at runtime; an operator's successful read is not an
153
153
  end-user access policy. Never expose protected content beneath a visual gate.
@@ -205,30 +205,34 @@ Apply these interpretation rules:
205
205
  explicitly rather than treating a pass as full design parity. A deliberate difference
206
206
  is waived only by a `design` coverage row with status `adapted` that cites the user's
207
207
  instruction and explains the change. This check compares values, not rendered screens.
208
- - For each configured Detailed header, import and call the shared
209
- `detailedHeader` builder from `@wix/himalaya/screens`. Do not recreate its
210
- component tree in the app. Resolve the source fallback and visibility rules
211
- first, then pass the title, subtitle, first cover image, logo, permitted member
212
- data, location label and configured actions. Set `collapsible: false` when the
213
- source header is not collapsible. The shared hero supports one cover image; list
214
- additional source carousel images as an adaptation gap.
215
-
216
- ```ts
217
- import { detailedHeader } from "@wix/himalaya/screens";
218
-
219
- const header = detailedHeader({
220
- id: "home.header",
221
- title: "Studio name",
222
- subtitle: "Source subtitle",
223
- imageUrl: "https://example.test/cover.jpg",
224
- primaryAction: {
225
- label: "Join",
226
- action: { actionId: "himi.open", params: { screenId: "membership" } },
227
- },
228
- });
229
- ```
230
-
231
- Add this returned component to the screen tree at the source header position.
208
+ - **Native navigation is the migration target.** Use the shell's native top bar
209
+ and, for bottom-tab navigation, `navigation.tabBar.presentation: "native"`.
210
+ Do not recreate the source Detailed header, call `detailedHeader`, or insert a
211
+ `CollapsingHeader` hero/card above the page. This target adaptation supersedes
212
+ the older instruction to reproduce the source cover header; the source
213
+ research still explains how to interpret its settings.
214
+ - Emit resolved native actions in `navigation.topBarDefault` or a complete
215
+ per-screen `topBar` override. The shell owns back/menu controls. Per-screen
216
+ actions support `hiddenPath` when `navigation.topBarDefault.actions` declares
217
+ the same action ID and binding, evaluated by `navigation.navLogicBundle`.
218
+ Keep that registration for screen overrides: screen-only paths are not
219
+ subscribed, and an unresolved path leaves the action visible. Do not invent
220
+ visibility fields. These bindings control presentation only and must not
221
+ authorize data or actions. Preserve runtime access checks, and report any source visibility
222
+ rule that the target cannot represent as a gap. Preserve the title rule above:
223
+ a source cover title does not by itself authorize a native top-bar title.
224
+ - Account for source header content in `.himi/one-app/migration-report.md`.
225
+ Record the custom cover, carousel and overlapping card as an intentional
226
+ native-navigation adaptation there; do not invent header-presentation rows in
227
+ the derived coverage ledger. Preserve meaningful subtitle, location and configured CTA content
228
+ as ordinary page content or supported native actions, with the same runtime
229
+ access rules. Report unsupported avatar/logo/action behavior as a gap; do not
230
+ invent member data, drop a configured CTA silently, or add an in-page toolbar
231
+ to imitate a missing native control.
232
+ - Verify the migrated app's native top bar, bottom tabs, back navigation and
233
+ configured actions on the affected devices, including RTL. A web screenshot
234
+ alone does not prove native navigation or control availability. Record any
235
+ unavailable device check as unverified.
232
236
  - Business context contains only `phoneNumber`, `website`, and `services`, with
233
237
  their permitted nested values. Structure wins on authored phone/URL/design.
234
238
  Installed services do not add features. Installation IDs are not record IDs.
@@ -257,8 +261,11 @@ reference's top-tabs row in R04 and its top-tabs acceptance case:
257
261
  in-screen top tabs.
258
262
  - `target.hiddenPages` become screens without a tab entry. Keep their routes for
259
263
  actions and links.
260
- - The first tab takes the source screen's header. The other tabs take a compact
261
- default top bar, as the editor gives each new screen a default header.
264
+ - The plan attaches the source screen's header settings to the first tab as
265
+ interpretation evidence; it does not require a custom header in the target.
266
+ Apply the native-navigation policy above to that tab too. Resolve every tab's
267
+ effective native top bar, with compact defaults for the other tabs as in the
268
+ editor conversion.
262
269
  - `target.initialTabId` is the first tab; the editor drops the source
263
270
  `initialTabId`. `initial_tab_changed` names the page that used to open first.
264
271
  - `target.showLabels` is the source label setting. Pilates hides labels when it
@@ -127,11 +127,11 @@ Translate the resolved result to Himi. Its inspected schema says a per-screen `t
127
127
  - Resolve the effective source bar for every target screen, including screens created by a navigation conversion. Do not assume that a shared source header means every screen has the same effective bar.
128
128
  - When screens have the same effective actions and style, use one shared `TopBarConfig` value or builder for them. Use it as `top_bar_default` when it is the app-wide bar. Use a complete per-screen `top_bar` only when a screen differs.
129
129
  - Himi reads the bar title from `ScreenDescriptor.title`. Set this field only when the resolved source top-bar settings show a title and Structure supplies that title. For a dynamic source title, use the title resolved by the viewer rules. If the source does not show a title or has no title value, leave the target title empty. Do not invent a title from the app name, business name, route ID, tab label, or section content.
130
- - A source custom header is page content. It is not the shell's top bar. Keep the two surfaces separate. Reuse a page-header component only when multiple screens share that configured header content.
130
+ - A source custom header is page content in the original viewer. It is not the shell's top bar. Apply the current migration's native-navigation adaptation in R09 rather than duplicating the source header above the native bar.
131
131
 
132
132
  ### R09. Treat header content as conditional, configured behavior
133
133
 
134
- Use supplied header inputs within the agreed shared-header scope. If the source config requires content beyond that scope, describe it and the needed target adaptation before expanding the implementation.
134
+ The source rules below describe the original viewer. **For the current One App migration target, use native navigation bars, bottom tabs and the top bar; do not reproduce a custom Detailed cover header.** This target policy supersedes this reference's older shared-header recommendation. Keep the source facts as interpretation evidence and record the target adaptation explicitly.
135
135
 
136
136
  Relevant source behavior to preserve when applicable:
137
137
 
@@ -142,7 +142,9 @@ Relevant source behavior to preserve when applicable:
142
142
  - Logo, invite button, CTA, member count/avatars, and business-profile entry point have separate controls. A CTA needs both its configuration and its show flag.
143
143
  - Member visibility and restaurant/location behavior depend on runtime services and permissions. Structure does not contain the member records needed to populate them; the restricted business export is not a member directory.
144
144
 
145
- For a configured Detailed header, use `detailedHeader` from `@wix/himalaya/screens`. This shared builder uses Himalaya's cross-platform `CollapsingHeader`, a rounded detail card, and standard logo, member, and button components. Every migration must call this builder. Do not copy its component tree into an app. Pass the resolved title, subtitle, first cover image, logo, allowed member data, location label, configured actions, and the page nodes in `content`. Put the remaining screen content in `content` so it scrolls beneath the hero and the hero collapses with the page. Do not place those nodes as siblings of the returned header. Keep member data hidden when the source permissions or runtime services do not allow it. The current hero supports one cover image; keep the source order and record extra carousel images as an adaptation gap. If `isCollapsible` is false, pass `collapsible: false` so the hero keeps one height. [Header domain](https://github.com/wix-private/mobile-modules/blob/3a9713cf8bbb014ce4d3006d356a82a0f02db007/packages/libraries/pilates/src/domain/Header.ts) · [Header view model](https://github.com/wix-private/mobile-modules/blob/3a9713cf8bbb014ce4d3006d356a82a0f02db007/packages/libraries/pilates/src/elements/PilatesHeader/useViewModel.ts) · [Detailed header presenter](https://github.com/wix-private/mobile-modules/blob/3a9713cf8bbb014ce4d3006d356a82a0f02db007/packages/modules/wix-one-app-member-view/src/newStructure/elements/headers/detailedHeader/usePresenter.ts) · [Himi schema](https://github.com/wix-private/himalaya/blob/d3bc68c77151b678d337323615eb9889fa8e38c3/core/schema/core.proto)
145
+ Do not call `detailedHeader` or add a `CollapsingHeader` hero/card in the migrated screen. Configure the shell through `navigation.topBarDefault`, complete per-screen `topBar` overrides, and `navigation.tabBar.presentation: "native"` for bottom tabs. Keep R08a's title and action-resolution rules; the shell owns back/menu controls. The `navigation-plan.json` first-tab source-header reference is evidence, not an instruction to render the old header.
146
+
147
+ Record the omitted custom cover, carousel and overlapping card as an adaptation in `.himi/one-app/migration-report.md`; do not invent header-presentation rows in the derived coverage ledger. Preserve meaningful subtitle, location and configured CTA content as ordinary page content or supported native actions, subject to the original runtime access rules. Per-screen `topBar.actions` support `hiddenPath` when `navigation.topBarDefault.actions` declares the same action ID and binding, evaluated by `navigation.navLogicBundle`. Keep that registration for screen overrides: screen-only paths are not subscribed, and an unresolved path leaves the action visible. Preserve representable source visibility rules through these supported bindings; do not invent visibility fields. These bindings control presentation only, not authorization. Keep runtime access checks and report source visibility rules the target cannot represent as gaps. Unsupported leading avatars, logos or actions remain explicit gaps; do not invent member data or an in-page toolbar. Verify top bars, bottom tabs, back navigation and configured actions on affected native devices, including RTL; report unavailable checks as unverified. [Header domain](https://github.com/wix-private/mobile-modules/blob/3a9713cf8bbb014ce4d3006d356a82a0f02db007/packages/libraries/pilates/src/domain/Header.ts) · [Header view model](https://github.com/wix-private/mobile-modules/blob/3a9713cf8bbb014ce4d3006d356a82a0f02db007/packages/libraries/pilates/src/elements/PilatesHeader/useViewModel.ts) · [Detailed header presenter](https://github.com/wix-private/mobile-modules/blob/3a9713cf8bbb014ce4d3006d356a82a0f02db007/packages/modules/wix-one-app-member-view/src/newStructure/elements/headers/detailedHeader/usePresenter.ts) · [Himi schema](https://github.com/wix-private/himalaya/blob/d3bc68c77151b678d337323615eb9889fa8e38c3/core/schema/core.proto)
146
148
 
147
149
  ## Step 4 — Recreate each section envelope before its content
148
150
 
@@ -4117,7 +4117,7 @@
4117
4117
  {
4118
4118
  "name": "detailedHeader",
4119
4119
  "kind": "function",
4120
- "jsdoc": "/**\n * Build the shared Detailed header used by One App migrations.\n *\n * The header uses Himalaya's cross-platform `CollapsingHeader` and standard\n * components for the detail card, logo, member strip, and actions. A migration\n * calls this builder instead of copying header layout into each app. Himalaya's\n * hero accepts one image, so pass the first source cover image here.\n */",
4120
+ "jsdoc": "/**\n * Build an optional Detailed cover header for screens that explicitly request one.\n *\n * The header uses Himalaya's cross-platform `CollapsingHeader` and standard\n * components for the detail card, logo, member strip, and actions. The One App\n * migration workflow uses native navigation bars and does not call this builder.\n * Himalaya's hero accepts one image.\n */",
4121
4121
  "decl": "export function detailedHeader(opts: DetailedHeaderOptions): Node;"
4122
4122
  },
4123
4123
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1564.0",
3
+ "version": "0.1566.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": "71c5eb27bff3ea0c176e99091c8c457b4d2a6bc36fea244505eb12f9"
74
+ "falconPackageHash": "59d9f7c34824b26777859c276927fa1bde614c8f64c26db04c10bfb3"
75
75
  }
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,
@@ -234,6 +234,9 @@ let signInPresentationRequested: "" | "sheet" | "full_screen" = "";
234
234
  let signInReturnTabRequested = "";
235
235
  let standaloneRequested = false;
236
236
  let pollIdentityRequested = false;
237
+ // iOS can start the arg-less boot load after the appear refresh that asks for the row; the
238
+ // newer boot probe must not land, or be kept, without it.
239
+ let restorePurchasesRequested = false;
237
240
  // Bumped by every mode switch, so a dropped probe never puts back a mode the visitor left.
238
241
  let modeSwitches = 0;
239
242
 
@@ -246,6 +249,7 @@ function appearBase(): Partial<S> {
246
249
  signInScreen: signInScreenRequested,
247
250
  standalone: standaloneRequested,
248
251
  pollIdentity: pollIdentityRequested,
252
+ restorePurchases: restorePurchasesRequested,
249
253
  ...(pollIdentityRequested ? { _poll: { ...IDENTITY_POLL } } : {}),
250
254
  };
251
255
  }
@@ -268,6 +272,7 @@ function appearConfig(s: S, arg: unknown): AppearConfig {
268
272
  // Only the sign-in screen's route carries `mode`, so it marks the form from the boot load on.
269
273
  standaloneRequested ||= s.standalone || appearing || (!routeInputConsumed && !!routeInput().mode);
270
274
  pollIdentityRequested ||= s.pollIdentity || strField(arg, "pollIdentity") === "1";
275
+ restorePurchasesRequested ||= s.restorePurchases || strField(arg, "restorePurchases") === "1";
271
276
  // The form opens on the mode its route asked for each time it appears, sign-in by
272
277
  // default: a pooled worker reopened with the same params must not open on the mode the
273
278
  // last visit toggled to.
@@ -652,8 +657,7 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
652
657
  // Not the boot load: it never carries the copy, and the appear refresh that does is coming.
653
658
  if (strField(arg, "boot") !== "1") headingReady = true;
654
659
  const memberAccountLinks = memberAccountLinksRequested;
655
- const restorePurchases = s.restorePurchases || strField(arg, "restorePurchases") === "1";
656
- if (restorePurchases) enterCommerce();
660
+ if (restorePurchasesRequested) enterCommerce();
657
661
  const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
658
662
  let activityRows: AccountActivityRow[] = [];
659
663
  if (profile.isMember) {
@@ -681,7 +685,6 @@ async function probe(s: S, arg?: unknown, config: AppearConfig = appearConfig(s,
681
685
  email: profile.memberEmail || s.email,
682
686
  message: "",
683
687
  memberAccountLinks,
684
- restorePurchases,
685
688
  ...appearBase(),
686
689
  ...config.mode,
687
690
  ...config.visit,