@wix/himalaya-cli 0.1292.0 → 0.1294.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -59,6 +59,7 @@ package in one greppable file) or `GET /v1/sdk/<name>` over HTTP; in the CLI,
59
59
  - `@himalaya/time` — Tier-5 temporal helpers.
60
60
  - `@himalaya/ui` — Tier-5 typed binding to the platform's imperative popup capability.
61
61
  - `@himalaya/webview` — Tier-5 typed binding for the page ↔ worker message channel of a worker-backed webview screen (Part 2 / RFC 0025 composition).
62
+ - `@himalaya/webview-page` — browser-side SDK for a trusted page hosted by a worker-backed Himalaya WebView.
62
63
  - `@himalaya/widget` — Tier-5 logic-bundle widget surface.
63
64
  - `@himalaya/wix-llm` — narrow Tier-5 binding for Wix LLM text generation.
64
65
  - `@himalaya/wix-platform` — Wix Platform SDK (RFC 0042), worker-facing bindings.
package/guides/index.json CHANGED
@@ -89,6 +89,12 @@
89
89
  "category": "Workers & SDKs",
90
90
  "file": "importable-sdks.md"
91
91
  },
92
+ {
93
+ "id": "webview-page-sdk",
94
+ "title": "Use native capabilities from a WebView page",
95
+ "category": "Workers & SDKs",
96
+ "file": "webview-page-sdk.md"
97
+ },
92
98
  {
93
99
  "id": "component-reference",
94
100
  "title": "Component reference",
@@ -4073,6 +4073,85 @@
4073
4073
  "jsdoc": "/**\n * Register a handler for messages the page posts via `window.HimalayaWorker.post(msg)`.\n * The latest registered handler wins. Call this once during the bundle's setup.\n */",
4074
4074
  "decl": "export function onPageMessage(cb: (msg: unknown) => void): void;"
4075
4075
  },
4076
+ {
4077
+ "name": "PageCapabilityRequest",
4078
+ "kind": "interface",
4079
+ "jsdoc": "",
4080
+ "decl": "export interface PageCapabilityRequest {\n v: 1;\n id: string;\n capability: string;\n method: string;\n args: unknown[];\n}"
4081
+ },
4082
+ {
4083
+ "name": "PageCapabilityResponse",
4084
+ "kind": "interface",
4085
+ "jsdoc": "",
4086
+ "decl": "export interface PageCapabilityResponse {\n v: 1;\n id: string;\n ok: boolean;\n result?: unknown;\n error?: { message: string; code?: string };\n}"
4087
+ },
4088
+ {
4089
+ "name": "ExposedPageCapability",
4090
+ "kind": "interface",
4091
+ "jsdoc": "",
4092
+ "decl": "export interface ExposedPageCapability {\n module: Record<string, unknown>;\n methods: readonly string[];\n}"
4093
+ },
4094
+ {
4095
+ "name": "exposeCapabilitiesToPage",
4096
+ "kind": "function",
4097
+ "jsdoc": "/**\n * Expose selected typed @himalaya/* modules to the hosting page.\n *\n * The worker author must pass both the imported module and its explicit verb allowlist:\n *\n * import * as health from \"@himalaya/health\";\n * exposeCapabilitiesToPage({\n * health: { module: health, methods: [\"availability\", \"query\"] },\n * });\n *\n * The bundle still has to declare `webview` plus every exposed capability. Importing only\n * selected modules keeps native permissions and `himi dist` additions opt-in.\n */",
4098
+ "decl": "export function exposeCapabilitiesToPage(\n capabilities: Readonly<Record<string, ExposedPageCapability>>,\n): void;"
4099
+ },
4100
+ {
4101
+ "name": "version",
4102
+ "kind": "const",
4103
+ "jsdoc": "",
4104
+ "decl": "export const version = 1;"
4105
+ }
4106
+ ]
4107
+ },
4108
+ {
4109
+ "name": "@himalaya/webview-page",
4110
+ "source": "core/sdk/webview-page/src/index.ts",
4111
+ "description": "browser-side SDK for a trusted page hosted by a worker-backed Himalaya WebView.",
4112
+ "api": [
4113
+ {
4114
+ "name": "\"./capabilities.generated.js\"",
4115
+ "kind": "reexport",
4116
+ "jsdoc": "",
4117
+ "decl": "export type { HimalayaPageCapabilities, HimalayaPageSdk } from \"./capabilities.generated.js\";"
4118
+ },
4119
+ {
4120
+ "name": "WebViewPageRequest",
4121
+ "kind": "interface",
4122
+ "jsdoc": "",
4123
+ "decl": "export interface WebViewPageRequest {\n v: 1;\n id: string;\n capability: string;\n method: string;\n args: unknown[];\n}"
4124
+ },
4125
+ {
4126
+ "name": "WebViewPageError",
4127
+ "kind": "interface",
4128
+ "jsdoc": "",
4129
+ "decl": "export interface WebViewPageError {\n message: string;\n code?: string;\n}"
4130
+ },
4131
+ {
4132
+ "name": "WebViewPageResponse",
4133
+ "kind": "interface",
4134
+ "jsdoc": "",
4135
+ "decl": "export interface WebViewPageResponse {\n v: 1;\n id: string;\n ok: boolean;\n result?: unknown;\n error?: WebViewPageError;\n}"
4136
+ },
4137
+ {
4138
+ "name": "call",
4139
+ "kind": "function",
4140
+ "jsdoc": "/** Invoke one method through the companion worker. Primarily useful to generated bindings. */",
4141
+ "decl": "export function call<T>(\n capability: string,\n method: string,\n args: unknown[] = [],\n timeoutMs = 30_000,\n): Promise<T>;"
4142
+ },
4143
+ {
4144
+ "name": "himalaya",
4145
+ "kind": "const",
4146
+ "jsdoc": "/**\n * Typed SDK proxy for every request/response capability in CAPABILITY_REGISTRY.\n * A namespace is usable only when this screen's companion worker explicitly imported and\n * exposed it and the bundle + host both declared it.\n */",
4147
+ "decl": "export const himalaya = new Proxy({ version: 1 as const }, {\n get(target, property) {\n if (property === \"version\") return target.version;\n if (typeof property !== \"string\" || property === \"then\") return undefined;\n return pageMember(property, []);\n },\n}) as HimalayaPageSdk;"
4148
+ },
4149
+ {
4150
+ "name": "health",
4151
+ "kind": "const",
4152
+ "jsdoc": "/** Convenience named export; equivalent to `himalaya.health`. */",
4153
+ "decl": "export const health = himalaya.health;"
4154
+ },
4076
4155
  {
4077
4156
  "name": "version",
4078
4157
  "kind": "const",
@@ -0,0 +1,132 @@
1
+ # Himalaya SDK for hosted WebView pages
2
+
3
+ Use `@wix/himalaya/webview-page` when a trusted webpage hosted inside Brandy (or another
4
+ worker-backed Himalaya WebView) needs an explicitly selected native capability. The webpage never
5
+ talks directly to native code:
6
+
7
+ ```
8
+ page → window.HimalayaWorker → companion Tier-5 worker → @himalaya/* → native registry
9
+ ```
10
+
11
+ The same bundle declaration and host policy that protect an ordinary Tier-5 screen protect the
12
+ page. A namespace present in TypeScript is not authority: the companion worker must import and
13
+ allowlist it, its `logicBundle.declaredCapabilities` must name it, and the binary must serve it.
14
+
15
+ ## Page
16
+
17
+ Install the published umbrella package and import its page entry:
18
+
19
+ ```ts
20
+ import { himalaya } from "@wix/himalaya/webview-page";
21
+
22
+ const available = await himalaya.health.availability();
23
+ if (available.available) {
24
+ await himalaya.health.requestPermission({ read: ["steps"], write: ["workouts"] });
25
+ const result = await himalaya.health.query({
26
+ type: "steps",
27
+ from: new Date("2026-10-11T00:00:00Z").toISOString(),
28
+ to: new Date().toISOString(),
29
+ });
30
+ console.log(result.samples);
31
+ }
32
+ ```
33
+
34
+ The generated `himalaya` type includes every package represented by
35
+ `CAPABILITY_REGISTRY`, including nested APIs such as `himalaya.base44.auth.*`. Calls made in an
36
+ ordinary browser fail immediately with a clear “not inside a worker-backed Himalaya WebView”
37
+ error.
38
+
39
+ ## Companion worker
40
+
41
+ Import only what this website is allowed to use and allowlist only the methods it needs:
42
+
43
+ ```ts
44
+ import * as health from "@himalaya/health";
45
+ import { exposeCapabilitiesToPage } from "@himalaya/webview";
46
+
47
+ exposeCapabilitiesToPage({
48
+ health: {
49
+ module: health,
50
+ methods: ["availability", "requestPermission", "getPermissionStatus", "query", "saveWorkout"],
51
+ },
52
+ });
53
+ ```
54
+
55
+ Build it like any other Tier-5 bundle. Its descriptor must declare both the transport and the
56
+ selected capabilities:
57
+
58
+ ```ts
59
+ logicBundle: {
60
+ url,
61
+ integrity,
62
+ declaredCapabilities: ["webview", "health"],
63
+ }
64
+ ```
65
+
66
+ Do not build one worker that imports every SDK. Static bundle scanning would correctly conclude
67
+ that the release uses every capability, forcing unrelated permissions and native modules into the
68
+ binary. One app-specific worker imports exactly the app’s selected subset.
69
+
70
+ ## Brandy
71
+
72
+ Health has a built-in proof worker, so it needs no custom bundle name:
73
+
74
+ ```json
75
+ {
76
+ "webSdk": {
77
+ "capabilities": ["health"],
78
+ "permissionCopy": {
79
+ "health": "Show your activity and save completed workouts."
80
+ }
81
+ }
82
+ }
83
+ ```
84
+
85
+ For another subset, add the companion bundle to the app and name it:
86
+
87
+ ```json
88
+ {
89
+ "webSdk": {
90
+ "bundle": "customer-page-capabilities",
91
+ "capabilities": ["camera", "share"],
92
+ "permissionCopy": {
93
+ "camera": "Take a photo to attach it to your request."
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ Brandy accepts capabilities `himi dist` can add to an extensible shell. Capabilities requiring
100
+ app-specific boot wiring remain refused until that shell supplies the wiring. Base44 is accepted
101
+ only when the same Brandy contract contains a Base44 binding.
102
+
103
+ ## Security and current transport boundary
104
+
105
+ - Only the trusted main-frame origin receives `HimalayaWorker`; redirects and cross-origin frames
106
+ cannot use it.
107
+ - The worker accepts JSON-compatible arguments and results, caps argument count, validates request
108
+ identifiers and resolves only own properties on an explicit method allowlist.
109
+ - Errors preserve a typed `code` when the SDK provides one.
110
+ - The page cannot use raw `capability.*` messages: Brandy site descriptors keep
111
+ `capabilitiesRequired: []`; native calls go through the companion worker.
112
+ - v1 is request/response. SDK helpers returning `AsyncIterable`, accepting JavaScript callbacks,
113
+ or depending on worker action names need a page-specific adapter built from their lower-level
114
+ start/next/stop verbs. Do not expose such a helper unchanged.
115
+
116
+ The package source includes complete `health-page.ts` and `health-worker.ts` examples matching the
117
+ snippets above.
118
+
119
+ ## Demo app regression page
120
+
121
+ The demo app hosts a page that calls every capability its iOS and Android boots already
122
+ register. Open it with:
123
+
124
+ ```
125
+ himalaya-demo-app://screen/demo_app_webview_sdk
126
+ ```
127
+
128
+ The page is served at `/web/webview-sdk.html`. Its companion worker is
129
+ `demo-app-webview-sdk`, which carries the catalog of allowlisted methods and read-only
130
+ probes. Safe probes run on load.
131
+ Verbs that prompt, write, or open system UI stay allowlisted and are not called. Streaming
132
+ helpers that return a live object stay on the worker.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1292.0",
3
+ "version": "0.1294.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": [
@@ -70,5 +70,5 @@
70
70
  "artifactId": "himalaya-cli"
71
71
  }
72
72
  },
73
- "falconPackageHash": "f2cddbdb94ceba1e08a45fef82f62b1e3debb12f1c9fa20e0984d0c3"
73
+ "falconPackageHash": "09e12b3c3f8e23efc26c5fee02873fd0f086030be36e989ccb20d835"
74
74
  }
@@ -24242,6 +24242,25 @@ const SCREENS = runtimeSetting({
24242
24242
  },
24243
24243
  }) as Record<string, unknown>;
24244
24244
 
24245
+ // The language destination stays registered for deep links and future translations. Keep its
24246
+ // account entry out of a single-language app; adding a locale to config.locale.translations
24247
+ // makes the same entry visible after the app config reloads.
24248
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
24249
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
24250
+ if (localeConfig && configuredLocales.size <= 1) {
24251
+ const removeLanguageEntry = (value: unknown): unknown => {
24252
+ if (Array.isArray(value)) return value.flatMap((child) => {
24253
+ const next = removeLanguageEntry(child);
24254
+ return next === undefined ? [] : [next];
24255
+ });
24256
+ if (!value || typeof value !== "object") return value;
24257
+ const node = value as Record<string, unknown>;
24258
+ if (node.id === "himi_account.language") return undefined;
24259
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
24260
+ };
24261
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
24262
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
24263
+ }
24245
24264
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
24246
24265
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
24247
24266
  { pattern: "^himi_order(?:[_/](.+))?$", display: "himi_order[_{id}]", template: {
@@ -80521,6 +80521,25 @@ const SCREENS = runtimeSetting({
80521
80521
  },
80522
80522
  }) as Record<string, unknown>;
80523
80523
 
80524
+ // The language destination stays registered for deep links and future translations. Keep its
80525
+ // account entry out of a single-language app; adding a locale to config.locale.translations
80526
+ // makes the same entry visible after the app config reloads.
80527
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
80528
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
80529
+ if (localeConfig && configuredLocales.size <= 1) {
80530
+ const removeLanguageEntry = (value: unknown): unknown => {
80531
+ if (Array.isArray(value)) return value.flatMap((child) => {
80532
+ const next = removeLanguageEntry(child);
80533
+ return next === undefined ? [] : [next];
80534
+ });
80535
+ if (!value || typeof value !== "object") return value;
80536
+ const node = value as Record<string, unknown>;
80537
+ if (node.id === "himi_account.language") return undefined;
80538
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
80539
+ };
80540
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
80541
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
80542
+ }
80524
80543
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
80525
80544
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
80526
80545
  { pattern: "^himi_order(?:[_/](.+))?$", display: "himi_order[_{id}]", template: {
@@ -17012,6 +17012,25 @@ const SCREENS = runtimeSetting({
17012
17012
  },
17013
17013
  }) as Record<string, unknown>;
17014
17014
 
17015
+ // The language destination stays registered for deep links and future translations. Keep its
17016
+ // account entry out of a single-language app; adding a locale to config.locale.translations
17017
+ // makes the same entry visible after the app config reloads.
17018
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
17019
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
17020
+ if (localeConfig && configuredLocales.size <= 1) {
17021
+ const removeLanguageEntry = (value: unknown): unknown => {
17022
+ if (Array.isArray(value)) return value.flatMap((child) => {
17023
+ const next = removeLanguageEntry(child);
17024
+ return next === undefined ? [] : [next];
17025
+ });
17026
+ if (!value || typeof value !== "object") return value;
17027
+ const node = value as Record<string, unknown>;
17028
+ if (node.id === "himi_account.language") return undefined;
17029
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
17030
+ };
17031
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
17032
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
17033
+ }
17015
17034
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
17016
17035
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
17017
17036
  { pattern: "^himi_event(?:[_/](.+))?$", display: "himi_event[_{id}]", liveSeed: {"path":"/events/v3/events/query","body":{"query":{"paging":{"limit":50}}},"listKey":"events","prefix":"himi_event_"}, template: {
@@ -15010,6 +15010,25 @@ const SCREENS = runtimeSetting({
15010
15010
  },
15011
15011
  }) as Record<string, unknown>;
15012
15012
 
15013
+ // The language destination stays registered for deep links and future translations. Keep its
15014
+ // account entry out of a single-language app; adding a locale to config.locale.translations
15015
+ // makes the same entry visible after the app config reloads.
15016
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
15017
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
15018
+ if (localeConfig && configuredLocales.size <= 1) {
15019
+ const removeLanguageEntry = (value: unknown): unknown => {
15020
+ if (Array.isArray(value)) return value.flatMap((child) => {
15021
+ const next = removeLanguageEntry(child);
15022
+ return next === undefined ? [] : [next];
15023
+ });
15024
+ if (!value || typeof value !== "object") return value;
15025
+ const node = value as Record<string, unknown>;
15026
+ if (node.id === "himi_account.language") return undefined;
15027
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
15028
+ };
15029
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
15030
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
15031
+ }
15013
15032
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
15014
15033
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
15015
15034
  { pattern: "^himi_event(?:[_/](.+))?$", display: "himi_event[_{id}]", liveSeed: {"path":"/events/v3/events/query","body":{"query":{"paging":{"limit":50}}},"listKey":"events","prefix":"himi_event_"}, template: {
@@ -28843,6 +28843,25 @@ const SCREENS = runtimeSetting({
28843
28843
  },
28844
28844
  }) as Record<string, unknown>;
28845
28845
 
28846
+ // The language destination stays registered for deep links and future translations. Keep its
28847
+ // account entry out of a single-language app; adding a locale to config.locale.translations
28848
+ // makes the same entry visible after the app config reloads.
28849
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
28850
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
28851
+ if (localeConfig && configuredLocales.size <= 1) {
28852
+ const removeLanguageEntry = (value: unknown): unknown => {
28853
+ if (Array.isArray(value)) return value.flatMap((child) => {
28854
+ const next = removeLanguageEntry(child);
28855
+ return next === undefined ? [] : [next];
28856
+ });
28857
+ if (!value || typeof value !== "object") return value;
28858
+ const node = value as Record<string, unknown>;
28859
+ if (node.id === "himi_account.language") return undefined;
28860
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
28861
+ };
28862
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
28863
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
28864
+ }
28846
28865
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
28847
28866
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
28848
28867
  { pattern: "^himi_order(?:[_/](.+))?$", display: "himi_order[_{id}]", template: {
@@ -18297,6 +18297,25 @@ const SCREENS = runtimeSetting({
18297
18297
  },
18298
18298
  }) as Record<string, unknown>;
18299
18299
 
18300
+ // The language destination stays registered for deep links and future translations. Keep its
18301
+ // account entry out of a single-language app; adding a locale to config.locale.translations
18302
+ // makes the same entry visible after the app config reloads.
18303
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
18304
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
18305
+ if (localeConfig && configuredLocales.size <= 1) {
18306
+ const removeLanguageEntry = (value: unknown): unknown => {
18307
+ if (Array.isArray(value)) return value.flatMap((child) => {
18308
+ const next = removeLanguageEntry(child);
18309
+ return next === undefined ? [] : [next];
18310
+ });
18311
+ if (!value || typeof value !== "object") return value;
18312
+ const node = value as Record<string, unknown>;
18313
+ if (node.id === "himi_account.language") return undefined;
18314
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
18315
+ };
18316
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
18317
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
18318
+ }
18300
18319
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
18301
18320
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
18302
18321
  { pattern: "^himi_event(?:[_/](.+))?$", display: "himi_event[_{id}]", liveSeed: {"path":"/events/v3/events/query","body":{"query":{"paging":{"limit":50}}},"listKey":"events","prefix":"himi_event_"}, template: {
@@ -18650,6 +18650,25 @@ const SCREENS = runtimeSetting({
18650
18650
  },
18651
18651
  }) as Record<string, unknown>;
18652
18652
 
18653
+ // The language destination stays registered for deep links and future translations. Keep its
18654
+ // account entry out of a single-language app; adding a locale to config.locale.translations
18655
+ // makes the same entry visible after the app config reloads.
18656
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
18657
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
18658
+ if (localeConfig && configuredLocales.size <= 1) {
18659
+ const removeLanguageEntry = (value: unknown): unknown => {
18660
+ if (Array.isArray(value)) return value.flatMap((child) => {
18661
+ const next = removeLanguageEntry(child);
18662
+ return next === undefined ? [] : [next];
18663
+ });
18664
+ if (!value || typeof value !== "object") return value;
18665
+ const node = value as Record<string, unknown>;
18666
+ if (node.id === "himi_account.language") return undefined;
18667
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
18668
+ };
18669
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
18670
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
18671
+ }
18653
18672
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
18654
18673
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
18655
18674
  { pattern: "^himi_event(?:[_/](.+))?$", display: "himi_event[_{id}]", liveSeed: {"path":"/events/v3/events/query","body":{"query":{"paging":{"limit":50}}},"listKey":"events","prefix":"himi_event_"}, template: {
@@ -21025,6 +21025,25 @@ const SCREENS = runtimeSetting({
21025
21025
  },
21026
21026
  }) as Record<string, unknown>;
21027
21027
 
21028
+ // The language destination stays registered for deep links and future translations. Keep its
21029
+ // account entry out of a single-language app; adding a locale to config.locale.translations
21030
+ // makes the same entry visible after the app config reloads.
21031
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
21032
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
21033
+ if (localeConfig && configuredLocales.size <= 1) {
21034
+ const removeLanguageEntry = (value: unknown): unknown => {
21035
+ if (Array.isArray(value)) return value.flatMap((child) => {
21036
+ const next = removeLanguageEntry(child);
21037
+ return next === undefined ? [] : [next];
21038
+ });
21039
+ if (!value || typeof value !== "object") return value;
21040
+ const node = value as Record<string, unknown>;
21041
+ if (node.id === "himi_account.language") return undefined;
21042
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
21043
+ };
21044
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
21045
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
21046
+ }
21028
21047
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
21029
21048
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
21030
21049
  { pattern: "^himi_order(?:[_/](.+))?$", display: "himi_order[_{id}]", template: {
@@ -15374,6 +15374,25 @@ const SCREENS = runtimeSetting({
15374
15374
  },
15375
15375
  }) as Record<string, unknown>;
15376
15376
 
15377
+ // The language destination stays registered for deep links and future translations. Keep its
15378
+ // account entry out of a single-language app; adding a locale to config.locale.translations
15379
+ // makes the same entry visible after the app config reloads.
15380
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
15381
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
15382
+ if (localeConfig && configuredLocales.size <= 1) {
15383
+ const removeLanguageEntry = (value: unknown): unknown => {
15384
+ if (Array.isArray(value)) return value.flatMap((child) => {
15385
+ const next = removeLanguageEntry(child);
15386
+ return next === undefined ? [] : [next];
15387
+ });
15388
+ if (!value || typeof value !== "object") return value;
15389
+ const node = value as Record<string, unknown>;
15390
+ if (node.id === "himi_account.language") return undefined;
15391
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
15392
+ };
15393
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
15394
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
15395
+ }
15377
15396
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
15378
15397
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
15379
15398
  { pattern: "^himi_menu_item(?:[_/](.+))?$", display: "himi_menu_item[_{id}]", liveSeed: {"path":"/restaurants/menus/v1/items/query","body":{"paging":{"limit":50}},"listKey":"items","prefix":"himi_menu_item_"}, template: {
@@ -14040,6 +14040,25 @@ const SCREENS = runtimeSetting({
14040
14040
  },
14041
14041
  }) as Record<string, unknown>;
14042
14042
 
14043
+ // The language destination stays registered for deep links and future translations. Keep its
14044
+ // account entry out of a single-language app; adding a locale to config.locale.translations
14045
+ // makes the same entry visible after the app config reloads.
14046
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
14047
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
14048
+ if (localeConfig && configuredLocales.size <= 1) {
14049
+ const removeLanguageEntry = (value: unknown): unknown => {
14050
+ if (Array.isArray(value)) return value.flatMap((child) => {
14051
+ const next = removeLanguageEntry(child);
14052
+ return next === undefined ? [] : [next];
14053
+ });
14054
+ if (!value || typeof value !== "object") return value;
14055
+ const node = value as Record<string, unknown>;
14056
+ if (node.id === "himi_account.language") return undefined;
14057
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
14058
+ };
14059
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
14060
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
14061
+ }
14043
14062
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
14044
14063
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
14045
14064
  { pattern: "^himi_order(?:[_/](.+))?$", display: "himi_order[_{id}]", template: {
@@ -23895,6 +23895,25 @@ const SCREENS = runtimeSetting({
23895
23895
  },
23896
23896
  }) as Record<string, unknown>;
23897
23897
 
23898
+ // The language destination stays registered for deep links and future translations. Keep its
23899
+ // account entry out of a single-language app; adding a locale to config.locale.translations
23900
+ // makes the same entry visible after the app config reloads.
23901
+ const localeConfig = config.locale as { default?: string; translations?: readonly string[] } | undefined;
23902
+ const configuredLocales = new Set([localeConfig?.default ?? "en", ...(localeConfig?.translations ?? [])]);
23903
+ if (localeConfig && configuredLocales.size <= 1) {
23904
+ const removeLanguageEntry = (value: unknown): unknown => {
23905
+ if (Array.isArray(value)) return value.flatMap((child) => {
23906
+ const next = removeLanguageEntry(child);
23907
+ return next === undefined ? [] : [next];
23908
+ });
23909
+ if (!value || typeof value !== "object") return value;
23910
+ const node = value as Record<string, unknown>;
23911
+ if (node.id === "himi_account.language") return undefined;
23912
+ return Object.fromEntries(Object.entries(node).map(([key, child]) => [key, removeLanguageEntry(child)]));
23913
+ };
23914
+ const account = SCREENS["himi_account"] as { payload?: unknown } | undefined;
23915
+ if (account?.payload) account.payload = removeLanguageEntry(account.payload);
23916
+ }
23898
23917
  interface LiveSeed { path: string; body: unknown; listKey: string; prefix: string; limit?: number }
23899
23918
  const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; staticSeedIds?: string[]; template: unknown }[] = [
23900
23919
  { pattern: "^himi_service(?:[_/](.+))?$", display: "himi_service[_{id}]", liveSeed: {"path":"/bookings/v2/services/query","body":{"query":{"paging":{"limit":50}}},"listKey":"services","prefix":"himi_service_"}, template: {