@wix/himalaya-cli 0.1086.0 → 0.1087.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.
@@ -58,6 +58,7 @@ export interface NavigationConfig {
58
58
  sidebar?: SidebarConfig;
59
59
  navLogicBundle?: import("@wix/himalaya/core").LogicBundleRef;
60
60
  appIconBadgeSources?: string[];
61
+ site?: import("@wix/himalaya/core").SiteNavConfig;
61
62
  /** Signed release allowlist for contained browser Tier-5 `_stream` requests. */
62
63
  streamPolicy?: StreamPolicy;
63
64
  splash?: SplashConfig;
@@ -211,6 +211,9 @@ export interface DerivedAppMeta {
211
211
  /** Forwarded from AppConfig.navigation.appIconBadgeSources — RFC 0010 (app-icon
212
212
  * amendment). Which nav badges also drive the OS app-icon badge. */
213
213
  appIconBadgeSources?: string[];
214
+ /** Forwarded from AppConfig.navigation.site — RFC 0062 site routing: which incoming URLs
215
+ * are the site's own pages, and the screen each one opens in. */
216
+ site?: import("@wix/himalaya/core").SiteNavConfig;
214
217
  /** Forwarded from AppConfig.navigation.streamPolicy — the signed browser stream allowlist. */
215
218
  streamPolicy?: import("@wix/himalaya/core").StreamPolicy;
216
219
  /** Forwarded from AppConfig.`splash` — the in-app splash surface. NOTE the
@@ -2503,10 +2503,35 @@ export interface NavigationConfig {
2503
2503
  appIconBadgeSources?: string[];
2504
2504
  /** Signed release allowlist for contained browser Tier-5 `_stream` requests. */
2505
2505
  streamPolicy?: StreamPolicy;
2506
+ /** RFC 0062 (site routing) — a site shell's URL space. A push tap, app link or
2507
+ * custom-scheme link whose URL is an https page on one of `site.origins` opens the
2508
+ * screen owning its path, and that screen's WebView loads the full URL. Absent ⇒
2509
+ * links resolve as before. Build it with `siteNavConfig()` from
2510
+ * `@himalaya/core/site-shell`; resolution is `resolveSiteLink()`. */
2511
+ site?: SiteNavConfig;
2506
2512
  scheme: string;
2507
2513
  deepLinkPatterns: DeepLinkPattern[];
2508
2514
  linkableScreens: LinkableScreen[];
2509
2515
  }
2516
+ /** RFC 0062 — how an incoming site URL finds its screen. */
2517
+ export interface SiteNavConfig {
2518
+ /** https PATH BOUNDARIES (`https://owner.wixsite.com/my-shop/`), the same form as the
2519
+ * site's WebView `allowedOrigins`: exact host, and the path is the boundary or under it. */
2520
+ origins: string[];
2521
+ /** Longest path-segment prefix wins, on the path RELATIVE to the matched origin.
2522
+ * `/` matches the home page only. */
2523
+ routes: SiteRoute[];
2524
+ /** The screen an on-site URL no route owns opens in. `""`/absent ⇒ the `/` route's
2525
+ * screen; no `/` route either ⇒ the link does not resolve. */
2526
+ fallbackScreenId?: string;
2527
+ }
2528
+ /** One entry of {@link SiteNavConfig.routes}. */
2529
+ export interface SiteRoute {
2530
+ /** Site-relative path prefix: `"/"`, `"/shop"`, `"/blog"`. */
2531
+ path: string;
2532
+ /** The screen whose WebView loads the URL. */
2533
+ screenId: string;
2534
+ }
2510
2535
  /** RFC 0051 — one surface's nav projection. Every field is optional-by-emptiness,
2511
2536
  * so a partial override (only `navKind`, say) inherits the rest. */
2512
2537
  export interface SurfaceNavConfig {
@@ -1,5 +1,5 @@
1
1
  import type { IconName } from "./icon-catalog.generated.js";
2
- import type { DrawerItem, NavTabItem, ScreenDescriptor, WebViewConfig } from "./schema.js";
2
+ import type { DrawerItem, LinkableScreen, NavTabItem, ScreenDescriptor, SiteNavConfig, WebViewConfig } from "./schema.js";
3
3
  /** Which hosting platform the site runs on. Selects origin allow-lists (and, once
4
4
  * RFC 0062 phase 2 lands, the binary-shipped page adapter). */
5
5
  export type SiteKind = "wix" | "base44";
@@ -53,6 +53,10 @@ export interface SiteShellConfig {
53
53
  export declare const SITE_SHELL_MAX_TABS = 4;
54
54
  /** Screen-id prefix for every site page (`site_<page.id>`). */
55
55
  export declare const SITE_SCREEN_PREFIX = "site_";
56
+ /** The screen an on-site link no page owns opens in (a product page, a blog post): one
57
+ * WebView pushed over the Home tab that loads the link's URL. Deliberately not `site_<id>`
58
+ * — `page` is a legal page id, so the prefix alone would collide; `assertSitePage` reserves it. */
59
+ export declare const SITE_PAGE_SCREEN_ID = "site_page";
56
60
  /** Identity-provider origins a hosted site's OAuth hops need to stay in-WebView.
57
61
  * Moved here from `apps/demo-app/dev/live.ts` so every shell shares one list. */
58
62
  export declare const OAUTH_IDP_ORIGINS: readonly string[];
@@ -99,8 +103,42 @@ export declare function resolveSiteChrome(chrome: SiteChrome | undefined): Requi
99
103
  export declare function siteWebViewPreset(site: Pick<SiteShellConfig, "origin" | "kind" | "chrome">): WebViewConfig;
100
104
  /** The Tier-3 descriptor for one site page. */
101
105
  export declare function defineSiteTab(site: SiteShellConfig, page: SitePage): ScreenDescriptor;
102
- /** Every site page's descriptor, keyed by screen id. */
106
+ /** The {@link SITE_PAGE_SCREEN_ID} descriptor. Its `url` is the site's home page — what it
107
+ * shows if it is ever opened with no link; a routed link replaces it natively (RFC 0062 site
108
+ * routing). Not kept alive: each routed link mounts it fresh, so it never shows the previous
109
+ * link's page while the new one loads. */
110
+ export declare function defineSitePageScreen(site: SiteShellConfig): ScreenDescriptor;
111
+ /** Every site page's descriptor plus the {@link SITE_PAGE_SCREEN_ID} fallback, keyed by
112
+ * screen id. */
103
113
  export declare function siteScreens(site: SiteShellConfig): Record<string, ScreenDescriptor>;
114
+ /** `NavigationConfig.site` for a site shell (RFC 0062 site routing): the site's origins, one
115
+ * route per page, and {@link SITE_PAGE_SCREEN_ID} as the fallback. Feed it to
116
+ * `defineApp({ navigation: { site } })`; every platform resolves links against it. */
117
+ export declare function siteNavConfig(site: SiteShellConfig): SiteNavConfig;
118
+ /** `navigation.linkableScreens` for a site shell: every page (tab pages carry their `tabId`, so
119
+ * a link selects the tab) plus the fallback screen, which lands on the Home tab. */
120
+ export declare function siteLinkableScreens(site: SiteShellConfig): LinkableScreen[];
121
+ /** Where an incoming site link goes. `url` is the link itself, normalized — the page the
122
+ * screen's WebView loads. */
123
+ export interface SiteLinkTarget {
124
+ screenId: string;
125
+ url: string;
126
+ }
127
+ /**
128
+ * Resolve an incoming URL against `NavigationConfig.site` — the TS reference for the native
129
+ * `SiteRouting.resolve` on iOS and Android; all three run core/schema/fixtures/site-route-cases.json.
130
+ *
131
+ * 1. https only, no credentials, and on one of `site.origins` (exact host, case-insensitive; an
132
+ * explicit `:443` is the default port; the path is the origin's boundary or under it).
133
+ * Anything else is not a site link → `null`.
134
+ * 2. The path relative to that origin picks the route with the LONGEST segment prefix, compared
135
+ * segment by segment after percent-decoding. `/` matches the home page only.
136
+ * 3. No route → `fallbackScreenId`, else the `/` route, else `null`.
137
+ *
138
+ * A path carrying a `.`/`..` segment is refused: a WebView resolves it, and on a free
139
+ * `<user>.wixsite.com` host `/my-site/../other` is somebody else's site.
140
+ */
141
+ export declare function resolveSiteLink(url: string, site: SiteNavConfig | undefined): SiteLinkTarget | null;
104
142
  /** Tab-bar and drawer items for a site. Pages beyond {@link SITE_SHELL_MAX_TABS} that
105
143
  * asked for a tab fall into the drawer rather than disappearing. */
106
144
  export declare function siteNav(site: SiteShellConfig): {
@@ -7579,6 +7579,7 @@ function validateConfigShape(config) {
7579
7579
  );
7580
7580
  }
7581
7581
  }
7582
+ if (nav.site !== void 0) validateSiteNav(nav.site);
7582
7583
  }
7583
7584
  if (config.splash) {
7584
7585
  validateSplash(config.splash, config.designTokens?.colors, config.designTokensPath);
@@ -7603,6 +7604,33 @@ function validateConfigShape(config) {
7603
7604
  }
7604
7605
  }
7605
7606
  }
7607
+ function validateSiteNav(site) {
7608
+ if (!site || typeof site !== "object" || !Array.isArray(site.origins) || !Array.isArray(site.routes)) {
7609
+ throw new Error("defineApp: navigation.site must be { origins: string[], routes: SiteRoute[] } \u2014 build it with siteNavConfig().");
7610
+ }
7611
+ if (site.origins.length === 0) throw new Error("defineApp: navigation.site.origins must name at least one origin.");
7612
+ for (const origin of site.origins) {
7613
+ let url = null;
7614
+ try {
7615
+ url = new URL(origin);
7616
+ } catch {
7617
+ url = null;
7618
+ }
7619
+ if (!url || url.protocol !== "https:" || !origin.endsWith("/") || url.search || url.hash || url.username || url.port) {
7620
+ throw new Error(
7621
+ `defineApp: navigation.site origin "${origin}" must be an https path boundary like "https://owner.wixsite.com/my-site/".`
7622
+ );
7623
+ }
7624
+ }
7625
+ for (const route of site.routes) {
7626
+ if (typeof route?.path !== "string" || typeof route.screenId !== "string" || !route.screenId) {
7627
+ throw new Error('defineApp: every navigation.site route needs a string "path" and a non-empty "screenId".');
7628
+ }
7629
+ }
7630
+ if (site.fallbackScreenId !== void 0 && typeof site.fallbackScreenId !== "string") {
7631
+ throw new Error("defineApp: navigation.site.fallbackScreenId must be a string.");
7632
+ }
7633
+ }
7606
7634
  var SPLASH_MIN_VISIBLE_MS_MAX = 2e3;
7607
7635
  var SPLASH_MAX_HOLD_MS_MAX = 8e3;
7608
7636
  var SPLASH_LAYOUTS = ["centered", "centeredStacked", "logoOnly"];
@@ -12987,6 +13015,7 @@ var AppRegistryClient = class {
12987
13015
  // ../../core/ts/src/site-shell.ts
12988
13016
  var SITE_SHELL_MAX_TABS = 4;
12989
13017
  var SITE_SCREEN_PREFIX = "site_";
13018
+ var SITE_PAGE_SCREEN_ID = "site_page";
12990
13019
  var OAUTH_IDP_ORIGINS = [
12991
13020
  "accounts.google.com",
12992
13021
  "*.google.com",
@@ -13099,9 +13128,98 @@ function defineSiteTab(site, page) {
13099
13128
  webViewConfig: { ...preset, ...page.webView ?? {} }
13100
13129
  };
13101
13130
  }
13131
+ function defineSitePageScreen(site) {
13132
+ const preset = siteWebViewPreset(site);
13133
+ return {
13134
+ screenId: SITE_PAGE_SCREEN_ID,
13135
+ renderer: "webview_native_bridge",
13136
+ title: "",
13137
+ url: normalizeSiteOrigin(site.origin),
13138
+ capabilitiesRequired: [],
13139
+ schemaVersion: 1,
13140
+ cacheControl: { maxAge: 600, staleWhileRevalidate: 86400 },
13141
+ webViewConfig: { ...preset, keepAlive: false }
13142
+ };
13143
+ }
13102
13144
  function siteScreens(site) {
13103
13145
  assertSiteShellConfig(site);
13104
- return Object.fromEntries(site.pages.map((page) => [siteScreenId(page), defineSiteTab(site, page)]));
13146
+ return {
13147
+ ...Object.fromEntries(site.pages.map((page) => [siteScreenId(page), defineSiteTab(site, page)])),
13148
+ [SITE_PAGE_SCREEN_ID]: defineSitePageScreen(site)
13149
+ };
13150
+ }
13151
+ function siteRoutePath(path) {
13152
+ const bare = path.trim().split(/[?#]/, 1)[0] ?? "";
13153
+ const segs = bare.split("/").filter(Boolean);
13154
+ return `/${segs.join("/")}`;
13155
+ }
13156
+ function siteNavConfig(site) {
13157
+ assertSiteShellConfig(site);
13158
+ return {
13159
+ origins: siteAllowedOrigins(site.origin, site.kind),
13160
+ routes: site.pages.map((page) => ({ path: siteRoutePath(page.path), screenId: siteScreenId(page) })),
13161
+ fallbackScreenId: SITE_PAGE_SCREEN_ID
13162
+ };
13163
+ }
13164
+ function siteLinkableScreens(site) {
13165
+ const tabIds = new Set(siteNav(site).tabs.map((tab) => tab.id));
13166
+ const pages = site.pages.map((page) => ({
13167
+ screenId: siteScreenId(page),
13168
+ isPublic: true,
13169
+ paramKeys: [],
13170
+ parentScreenId: "",
13171
+ tabId: tabIds.has(page.id) ? page.id : ""
13172
+ }));
13173
+ return [...pages, { screenId: SITE_PAGE_SCREEN_ID, isPublic: true, paramKeys: [], parentScreenId: "", tabId: "" }];
13174
+ }
13175
+ function pathSegments(path) {
13176
+ return path.split("/").filter(Boolean);
13177
+ }
13178
+ function decodeSegment(segment) {
13179
+ try {
13180
+ return decodeURIComponent(segment);
13181
+ } catch {
13182
+ return segment;
13183
+ }
13184
+ }
13185
+ function resolveSiteLink(url, site) {
13186
+ if (!site || typeof url !== "string") return null;
13187
+ const rawPath = url.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/?#]*/i, "").split(/[?#]/, 1)[0] ?? "";
13188
+ if (rawPath.split("/").some((seg) => /^(\.|%2e){1,2}$/i.test(seg))) return null;
13189
+ let parsed;
13190
+ try {
13191
+ parsed = new URL(url);
13192
+ } catch {
13193
+ return null;
13194
+ }
13195
+ if (parsed.protocol !== "https:" || parsed.username || parsed.password) return null;
13196
+ let rel = null;
13197
+ for (const origin of site.origins ?? []) {
13198
+ let o;
13199
+ try {
13200
+ o = new URL(origin);
13201
+ } catch {
13202
+ continue;
13203
+ }
13204
+ if (o.protocol !== "https:" || o.host !== parsed.host) continue;
13205
+ const base = o.pathname.replace(/\/+$/, "");
13206
+ const path = parsed.pathname;
13207
+ if (path === base || path === `${base}/` || path.startsWith(`${base}/`)) {
13208
+ rel = path.slice(base.length) || "/";
13209
+ break;
13210
+ }
13211
+ }
13212
+ if (rel === null) return null;
13213
+ const relSegs = pathSegments(rel).map(decodeSegment);
13214
+ let best = null;
13215
+ for (const route of site.routes ?? []) {
13216
+ if (!route?.screenId) continue;
13217
+ const segs = pathSegments(siteRoutePath(route.path ?? "")).map(decodeSegment);
13218
+ const matches = segs.length === 0 ? relSegs.length === 0 : segs.length <= relSegs.length && segs.every((seg, i) => seg === relSegs[i]);
13219
+ if (matches && (best === null || segs.length > best.len)) best = { screenId: route.screenId, len: segs.length };
13220
+ }
13221
+ const screenId = best?.screenId || site.fallbackScreenId || (site.routes ?? []).find((route) => route?.screenId && pathSegments(siteRoutePath(route.path ?? "")).length === 0)?.screenId;
13222
+ return screenId ? { screenId, url: url.trim() } : null;
13105
13223
  }
13106
13224
  function siteNav(site) {
13107
13225
  assertSiteShellConfig(site);
@@ -13123,6 +13241,9 @@ function assertSitePage(page) {
13123
13241
  if (!page || typeof page.id !== "string" || !PAGE_ID.test(page.id)) {
13124
13242
  throw new Error(`site page id ${JSON.stringify(page?.id)} must match ${PAGE_ID}`);
13125
13243
  }
13244
+ if (siteScreenId(page) === SITE_PAGE_SCREEN_ID) {
13245
+ throw new Error(`site page id "page" is reserved for the ${SITE_PAGE_SCREEN_ID} fallback screen`);
13246
+ }
13126
13247
  if (typeof page.title !== "string" || !page.title.trim()) {
13127
13248
  throw new Error(`site page ${page.id} needs a title`);
13128
13249
  }
@@ -13179,6 +13300,7 @@ export {
13179
13300
  PREVIEW_SURFACES,
13180
13301
  PROJECTION_SNAPSHOT_SIZE,
13181
13302
  RTL_LOCALES,
13303
+ SITE_PAGE_SCREEN_ID,
13182
13304
  SITE_SCREEN_PREFIX,
13183
13305
  SITE_SHELL_MAX_TABS,
13184
13306
  SIZE_CLASS_BREAKPOINTS,
@@ -13216,6 +13338,7 @@ export {
13216
13338
  decodeLaunchParams,
13217
13339
  defaultMessage,
13218
13340
  defineApp,
13341
+ defineSitePageScreen,
13219
13342
  defineSiteTab,
13220
13343
  effectiveFieldRule,
13221
13344
  errorMessageFor,
@@ -13253,6 +13376,7 @@ export {
13253
13376
  resolveOrientationEnvelope,
13254
13377
  resolveResponsiveSize,
13255
13378
  resolveSiteChrome,
13379
+ resolveSiteLink,
13256
13380
  resolveSizeClass,
13257
13381
  resolveSurfaceEnvelope,
13258
13382
  resolveSurfaceNav,
@@ -13263,7 +13387,9 @@ export {
13263
13387
  shouldRevealError,
13264
13388
  siteAllowedOrigins,
13265
13389
  siteHost,
13390
+ siteLinkableScreens,
13266
13391
  siteNav,
13392
+ siteNavConfig,
13267
13393
  sitePageUrl,
13268
13394
  siteScreenId,
13269
13395
  siteScreens,
@@ -9042,6 +9042,7 @@ function deriveAppMeta(name, m, platforms = {}) {
9042
9042
  sidebar: config.navigation?.sidebar,
9043
9043
  navLogicBundle: config.navigation?.navLogicBundle,
9044
9044
  appIconBadgeSources: config.navigation?.appIconBadgeSources,
9045
+ site: config.navigation?.site,
9045
9046
  streamPolicy: config.navigation?.streamPolicy,
9046
9047
  splash: config.splash,
9047
9048
  locale: config.locale,
package/dist/cli.mjs CHANGED
@@ -6764,6 +6764,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
6764
6764
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
6765
6765
  var init_bindings = __esm({
6766
6766
  "../../core/ts/src/bindings.ts"() {
6767
+ "use strict";
6767
6768
  BINDING_SIGILS = ["service", "flag", "item", "t"];
6768
6769
  BINDING_SIGIL_RE = new RegExp(
6769
6770
  `^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
@@ -19811,6 +19812,7 @@ function deriveAppMeta(name, m, platforms = {}) {
19811
19812
  sidebar: config.navigation?.sidebar,
19812
19813
  navLogicBundle: config.navigation?.navLogicBundle,
19813
19814
  appIconBadgeSources: config.navigation?.appIconBadgeSources,
19815
+ site: config.navigation?.site,
19814
19816
  streamPolicy: config.navigation?.streamPolicy,
19815
19817
  splash: config.splash,
19816
19818
  locale: config.locale,
@@ -24579,6 +24581,7 @@ function shouldRevealError(args) {
24579
24581
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
24580
24582
  var init_validation = __esm({
24581
24583
  "../../core/ts/src/validation.ts"() {
24584
+ "use strict";
24582
24585
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
24583
24586
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
24584
24587
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -28199,6 +28202,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
28199
28202
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
28200
28203
  var init_worker_subtree = __esm({
28201
28204
  "../../core/ts/src/worker-subtree.ts"() {
28205
+ "use strict";
28202
28206
  init_worker_subtree_safe_types_generated();
28203
28207
  WORKER_SUBTREE_CEILINGS = Object.freeze({
28204
28208
  maxEncodedBytes: 65536,
@@ -35361,6 +35365,7 @@ function deriveNavigationConfig(app, bundleCtx) {
35361
35365
  config.navLogicBundle = bundleCtx ? expandNavBundle(app.navLogicBundle, bundleCtx) : app.navLogicBundle;
35362
35366
  }
35363
35367
  if (app.appIconBadgeSources !== void 0) config.appIconBadgeSources = app.appIconBadgeSources;
35368
+ if (app.site !== void 0) config.site = app.site;
35364
35369
  if (app.streamPolicy !== void 0) config.streamPolicy = app.streamPolicy;
35365
35370
  if (app.splash !== void 0) config.splash = app.splash;
35366
35371
  if (app.locale !== void 0) config.locale = app.locale;
@@ -37231,7 +37236,7 @@ var init_config2 = __esm({
37231
37236
  // coerce unknown `default` tags rather than failing the envelope.
37232
37237
  "locale"
37233
37238
  ]);
37234
- STRUCTURAL_KEYS = /* @__PURE__ */ new Set(["scheme", "deepLinkPatterns", "linkableScreens", "appLinkHosts", "streamPolicy"]);
37239
+ STRUCTURAL_KEYS = /* @__PURE__ */ new Set(["scheme", "deepLinkPatterns", "linkableScreens", "appLinkHosts", "streamPolicy", "site"]);
37235
37240
  OPERATOR_ONLY_OVERLAY_KEYS = /* @__PURE__ */ new Set([
37236
37241
  "useProdServe",
37237
37242
  "prodServeBaseUrl",
@@ -48526,6 +48531,7 @@ var init_nav_lint = __esm({
48526
48531
  "navLogicBundle",
48527
48532
  "appIconBadgeSources",
48528
48533
  "streamPolicy",
48534
+ "site",
48529
48535
  "scheme",
48530
48536
  "deepLinkPatterns",
48531
48537
  "linkableScreens"
@@ -48550,6 +48556,7 @@ var init_nav_lint = __esm({
48550
48556
  "navLogicBundle",
48551
48557
  "appIconBadgeSources",
48552
48558
  "streamPolicy",
48559
+ "site",
48553
48560
  "scheme",
48554
48561
  "deepLinkPatterns",
48555
48562
  "linkableScreens"
@@ -859,7 +859,7 @@
859
859
  "name": "NavigationConfig",
860
860
  "kind": "interface",
861
861
  "jsdoc": "/** Per-app navigation/deep-link config, served OTA at GET /v1/app-config/:appName. */",
862
- "decl": "export interface NavigationConfig {\n /** Tab-bar structure (which tabs; their icons/labels/badges). */\n tabBar?: TabBarConfig;\n /** Slide-in drawer / hamburger-menu destinations. */\n drawer?: DrawerConfig;\n /** Navigation chrome the shell draws.\n * Omitted ⇒ \"bottomTabs\". The shell (not the descriptor) reads this. */\n navKind?: NavKind;\n /** Destinations for \"sidebar\" mode.\n * Absent ⇒ the shell derives the rail from `tabBar.items`. */\n sidebar?: SidebarConfig;\n /** App-level default top bar; a per-screen\n * `ScreenDescriptor.topBar` fully replaces it (override, not merge). */\n topBarDefault?: TopBarConfig;\n /** The post-OS-splash brand surface (see `SplashConfig`). Absent / not enabled\n * ⇒ today's behavior. Operational per (live-flippable). */\n splash?: SplashConfig;\n /** App-level default locale. Operational per so a Hebrew (or Spanish)\n * branded app can pin RTL/l10n OTA without a launch flag. `default` is a\n * BCP-47 primary subtag; clients coerce unknown tags rather than failing the\n * envelope. `pin: true` skips the device/browser language; in-app `locale.set`\n * still wins. Absent ⇒ today's device-then-en behavior. */\n locale?: LocaleConfig;\n /** Effective orientations, resolved INSIDE\n * the binary's build-time envelope (config.ts `orientationEnvelope`). The\n * envelope is the ceiling and can never be OTA (iOS reads the plist before\n * any app code runs); this narrows within it. Always intersected with the\n * envelope — it can never widen it. Absent/empty ⇒ the envelope itself. */\n orientation?: OrientationConfig;\n /** Per-surface nav projection, keyed by catalog\n * surface id (`phone` | `tablet` | `desktop` | `mac` | `watch` | `wear` | `rn`).\n * Keyed the way `orientation` is keyed by device family and narrowed the same\n * way: a key outside the app's `platforms.json` envelope is DROPPED, never\n * honored (see `resolveSurfaceNav`). Absent key ⇒ that surface uses the default\n * projection, so an app that never thinks about surfaces is unaffected. */\n surfaces?: Record<string, SurfaceNavConfig>;\n /** The APP-LEVEL nav worker\n * that feeds every `*Path` binding on `NavTabItem` / `DrawerItem` /\n * `TopBarAction`. App-level, not a screen's worker, because chrome outlives\n * every screen: a cart badge must survive leaving the cart, and a flag-hidden\n * tab must stay hidden three pushes deep in another stack. Absent ⇒ paths stay\n * unresolved and the literals render — the worker enriches, it never gates. */\n navLogicBundle?: LogicBundleRef;\n /** Which nav badges ALSO drive the OS app-icon\n * badge. Each entry is `\"<kind>:<id>\"` (`tab` | `drawer` | `topBarAction`, the\n * spelling `navKey` uses); the icon shows the SUM of those slots' resolved counts.\n * Empty/absent ⇒ no mirroring. An entry naming a slot this build does not have is\n * ignored, never a blanked badge. Requires the `push` capability — `defineApp`\n * rejects the combination, because the badge would be set and never shown. */\n appIconBadgeSources?: string[];\n /** Signed release allowlist for contained browser Tier-5 `_stream` requests. */\n streamPolicy?: StreamPolicy;\n scheme: string;\n deepLinkPatterns: DeepLinkPattern[];\n linkableScreens: LinkableScreen[];\n}"
862
+ "decl": "export interface NavigationConfig {\n /** Tab-bar structure (which tabs; their icons/labels/badges). */\n tabBar?: TabBarConfig;\n /** Slide-in drawer / hamburger-menu destinations. */\n drawer?: DrawerConfig;\n /** Navigation chrome the shell draws.\n * Omitted ⇒ \"bottomTabs\". The shell (not the descriptor) reads this. */\n navKind?: NavKind;\n /** Destinations for \"sidebar\" mode.\n * Absent ⇒ the shell derives the rail from `tabBar.items`. */\n sidebar?: SidebarConfig;\n /** App-level default top bar; a per-screen\n * `ScreenDescriptor.topBar` fully replaces it (override, not merge). */\n topBarDefault?: TopBarConfig;\n /** The post-OS-splash brand surface (see `SplashConfig`). Absent / not enabled\n * ⇒ today's behavior. Operational per (live-flippable). */\n splash?: SplashConfig;\n /** App-level default locale. Operational per so a Hebrew (or Spanish)\n * branded app can pin RTL/l10n OTA without a launch flag. `default` is a\n * BCP-47 primary subtag; clients coerce unknown tags rather than failing the\n * envelope. `pin: true` skips the device/browser language; in-app `locale.set`\n * still wins. Absent ⇒ today's device-then-en behavior. */\n locale?: LocaleConfig;\n /** Effective orientations, resolved INSIDE\n * the binary's build-time envelope (config.ts `orientationEnvelope`). The\n * envelope is the ceiling and can never be OTA (iOS reads the plist before\n * any app code runs); this narrows within it. Always intersected with the\n * envelope — it can never widen it. Absent/empty ⇒ the envelope itself. */\n orientation?: OrientationConfig;\n /** Per-surface nav projection, keyed by catalog\n * surface id (`phone` | `tablet` | `desktop` | `mac` | `watch` | `wear` | `rn`).\n * Keyed the way `orientation` is keyed by device family and narrowed the same\n * way: a key outside the app's `platforms.json` envelope is DROPPED, never\n * honored (see `resolveSurfaceNav`). Absent key ⇒ that surface uses the default\n * projection, so an app that never thinks about surfaces is unaffected. */\n surfaces?: Record<string, SurfaceNavConfig>;\n /** The APP-LEVEL nav worker\n * that feeds every `*Path` binding on `NavTabItem` / `DrawerItem` /\n * `TopBarAction`. App-level, not a screen's worker, because chrome outlives\n * every screen: a cart badge must survive leaving the cart, and a flag-hidden\n * tab must stay hidden three pushes deep in another stack. Absent ⇒ paths stay\n * unresolved and the literals render — the worker enriches, it never gates. */\n navLogicBundle?: LogicBundleRef;\n /** Which nav badges ALSO drive the OS app-icon\n * badge. Each entry is `\"<kind>:<id>\"` (`tab` | `drawer` | `topBarAction`, the\n * spelling `navKey` uses); the icon shows the SUM of those slots' resolved counts.\n * Empty/absent ⇒ no mirroring. An entry naming a slot this build does not have is\n * ignored, never a blanked badge. Requires the `push` capability — `defineApp`\n * rejects the combination, because the badge would be set and never shown. */\n appIconBadgeSources?: string[];\n /** Signed release allowlist for contained browser Tier-5 `_stream` requests. */\n streamPolicy?: StreamPolicy;\n /** A site shell's URL space. A push tap, app link or\n * custom-scheme link whose URL is an https page on one of `site.origins` opens the\n * screen owning its path, and that screen's WebView loads the full URL. Absent ⇒\n * links resolve as before. Build it with `siteNavConfig()` from\n * `@himalaya/core/site-shell`; resolution is `resolveSiteLink()`. */\n site?: SiteNavConfig;\n scheme: string;\n deepLinkPatterns: DeepLinkPattern[];\n linkableScreens: LinkableScreen[];\n}"
863
863
  },
864
864
  {
865
865
  "name": "NavKind",
@@ -945,6 +945,18 @@
945
945
  "jsdoc": "",
946
946
  "decl": "export interface LinkableScreen {\n screenId: string;\n /** generic <scheme>://screen/<id> route allowed externally? */\n isPublic: boolean;\n paramKeys: string[];\n parentScreenId: string;\n tabId: string;\n}"
947
947
  },
948
+ {
949
+ "name": "SiteNavConfig",
950
+ "kind": "interface",
951
+ "jsdoc": "/** How an incoming site URL finds its screen. */",
952
+ "decl": "export interface SiteNavConfig {\n /** https PATH BOUNDARIES (`https://owner.wixsite.com/my-shop/`), the same form as the\n * site's WebView `allowedOrigins`: exact host, and the path is the boundary or under it. */\n origins: string[];\n /** Longest path-segment prefix wins, on the path RELATIVE to the matched origin.\n * `/` matches the home page only. */\n routes: SiteRoute[];\n /** The screen an on-site URL no route owns opens in. `\"\"`/absent ⇒ the `/` route's\n * screen; no `/` route either ⇒ the link does not resolve. */\n fallbackScreenId?: string;\n}"
953
+ },
954
+ {
955
+ "name": "SiteRoute",
956
+ "kind": "interface",
957
+ "jsdoc": "/** One entry of {@link SiteNavConfig.routes}. */",
958
+ "decl": "export interface SiteRoute {\n /** Site-relative path prefix: `\"/\"`, `\"/shop\"`, `\"/blog\"`. */\n path: string;\n /** The screen whose WebView loads the URL. */\n screenId: string;\n}"
959
+ },
948
960
  {
949
961
  "name": "LocaleConfig",
950
962
  "kind": "interface",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1086.0",
3
+ "version": "0.1087.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": [
@@ -67,5 +67,5 @@
67
67
  "artifactId": "himalaya-cli"
68
68
  }
69
69
  },
70
- "falconPackageHash": "3f1e3c51227966381de40a69348d27b87c8af89431409b6f97de8442"
70
+ "falconPackageHash": "04896e9083f2ddf31e4aea4b2ae4c0be0980085fbd1ecbfb8391ac53"
71
71
  }