@wix/himalaya-cli 0.1067.0 → 0.1068.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.
@@ -20,3 +20,4 @@ export * from "./icon-catalog.generated.js";
20
20
  export * from "./app-link.js";
21
21
  export * from "./app-identity.js";
22
22
  export * from "./app-registry-client.js";
23
+ export * from "./site-shell.js";
@@ -0,0 +1,125 @@
1
+ import type { IconName } from "./icon-catalog.generated.js";
2
+ import type { DrawerItem, NavTabItem, ScreenDescriptor, WebViewConfig } from "./schema.js";
3
+ /** Which hosting platform the site runs on. Selects origin allow-lists (and, once
4
+ * RFC 0062 phase 2 lands, the binary-shipped page adapter). */
5
+ export type SiteKind = "wix" | "base44";
6
+ /** Where a page lives in the native chrome. `overflow` pages are routable (deep links,
7
+ * in-site links) but get no tab and no drawer row. */
8
+ export type SitePagePlacement = "tab" | "drawer" | "overflow";
9
+ /** One page of the customer's site, as the app presents it. */
10
+ export interface SitePage {
11
+ /** Stable id — becomes the tab id and the `site_<id>` screen id. `[a-z0-9_]+`. */
12
+ id: string;
13
+ /** Native label (tab / drawer row / screen title). Copy, never a hard-coded string
14
+ * in a descriptor — it comes from the provisioning file. */
15
+ title: string;
16
+ /** Path relative to the site's base URL: `"/"`, `"/shop"`, `"shop"`. Never a full
17
+ * URL — an off-site page would carry the native chrome over someone else's site. */
18
+ path: string;
19
+ /** Design-system icon. Absent ⇒ `"globe"`. */
20
+ icon?: IconName;
21
+ /** Absent ⇒ `"tab"`. */
22
+ placement?: SitePagePlacement;
23
+ /** Per-page overrides, merged OVER the shell preset. */
24
+ webView?: Partial<WebViewConfig>;
25
+ }
26
+ /** What the shell hides from the hosted site so the native chrome is the only chrome.
27
+ * Every flag defaults to `true` (hidden). */
28
+ export interface SiteChrome {
29
+ /** The site's own header on inner pages (the home page keeps it — its menu is the
30
+ * site's primary nav). */
31
+ hideSiteHeader?: boolean;
32
+ /** The site's footer. */
33
+ hideSiteFooter?: boolean;
34
+ /** The Wix ads banner shown on non-premium Wix sites. Configurable on purpose: the
35
+ * default is hidden, and an owner (or a policy decision) can turn it back on
36
+ * without a new binary, because the flag rides the OTA descriptor. */
37
+ hideWixAds?: boolean;
38
+ }
39
+ /** The whole site as a shell sees it. Mirrors the `site` block of a site-shell app's
40
+ * `provisioning.json`. */
41
+ export interface SiteShellConfig {
42
+ /** Base URL of the published site, `https` only. May carry a path — free Wix sites
43
+ * live at `https://<user>.wixsite.com/<site>`. */
44
+ origin: string;
45
+ kind: SiteKind;
46
+ pages: readonly SitePage[];
47
+ chrome?: SiteChrome;
48
+ }
49
+ /** At most four site tabs; the rest belong in the drawer. Four, not the usual phone-bar five,
50
+ * because every tab keeps its WebView warm (`keepAlive`) and the native keep-alive stores
51
+ * hold four views on BOTH platforms (`WebViewKeepAliveStore`, cap 4) — a fifth tab would
52
+ * evict and destroy the least-recent tab's WebView, losing its scroll and form state. */
53
+ export declare const SITE_SHELL_MAX_TABS = 4;
54
+ /** Screen-id prefix for every site page (`site_<page.id>`). */
55
+ export declare const SITE_SCREEN_PREFIX = "site_";
56
+ /** Identity-provider origins a hosted site's OAuth hops need to stay in-WebView.
57
+ * Moved here from `apps/demo-app/dev/live.ts` so every shell shares one list. */
58
+ export declare const OAUTH_IDP_ORIGINS: readonly string[];
59
+ /** Every Base44 app's origins (`*.base44.app`) plus `base44.com`, the OAuth consent + redirect
60
+ * target. The demo app's Base44 cover uses these; a SITE SHELL deliberately does not — see
61
+ * {@link siteAllowedOrigins}. */
62
+ export declare const BASE44_ORIGINS: readonly string[];
63
+ /**
64
+ * Normalize a site base URL: `https` only, lower-cased host, no query/fragment, no
65
+ * trailing slash. Throws on anything else — a base URL that is not a loadable https
66
+ * site would be baked into a binary and break every launch.
67
+ */
68
+ export declare function normalizeSiteOrigin(origin: string): string;
69
+ /** The host a site's pages load from (`user.wixsite.com`, `www.example.com`). */
70
+ export declare function siteHost(origin: string): string;
71
+ /**
72
+ * What a site's WebViews may navigate to without leaving the app — scheme-qualified PATH
73
+ * BOUNDARIES (`https://owner.wixsite.com/my-shop/`), which the native allow-list matchers
74
+ * (`WebViewOriginPolicy.matches` / `allowedOriginMatches`) read as "this path, with or without
75
+ * the trailing slash, and everything under it". So, for the bridge-enabled WebView:
76
+ * • https only — an `http` link or redirect to the same host leaves the app;
77
+ * • a free site stays inside its own path — `/other-site` on the same `<user>.wixsite.com`
78
+ * host is a different site, and leaves the app;
79
+ * • never a prefix of another host — `https://site.com/` does not admit `site.com.evil.io`.
80
+ * A custom Wix domain also admits its apex / `www.` twin, because Wix redirects between them.
81
+ * Nothing else is admitted for any kind: a Base44 tenant gets its own host only, never every
82
+ * `*.base44.app` tenant — Base44 sign-in runs in the system auth session (RFC 0062 phase 5),
83
+ * not in this WebView.
84
+ */
85
+ export declare function siteAllowedOrigins(origin: string, kind: SiteKind): string[];
86
+ /** Join a page path onto the site's base URL. Rejects absolute URLs and `..`. */
87
+ export declare function sitePageUrl(origin: string, path: string): string;
88
+ /** `site_<page.id>`. */
89
+ export declare function siteScreenId(page: Pick<SitePage, "id">): string;
90
+ /**
91
+ * Hide the hosted site's footer and/or the Wix ads banner, and keep them hidden as the
92
+ * page mutates. Ported from `generateElementHidingScript` in the Wix RN app
93
+ * (`@wix/hybrid-mobile-platform`), minus the header branch — header hiding is the
94
+ * native `hideHostedSiteHeader` path, which keeps the home page's header.
95
+ *
96
+ * Interim delivery: this rides `injectJsDocumentEnd`. RFC 0062 phase 2 replaces it
97
+ * with a binary-shipped adapter selected by id (`hostedSiteAdapter` + `hideWixAds`),
98
+ * so no raw page script travels over the air.
99
+ */
100
+ export declare function siteChromeHidingScript(options: {
101
+ hideFooter: boolean;
102
+ hideWixAds: boolean;
103
+ }): string;
104
+ /** Resolved chrome flags — every absent flag is `true`. */
105
+ export declare function resolveSiteChrome(chrome: SiteChrome | undefined): Required<SiteChrome>;
106
+ /**
107
+ * The WebView behaviour every site tab shares. Derived from the demo app's Site tab
108
+ * (`buildWebViewSiteApp`), which runs a real Wix site this way on both platforms:
109
+ * immersive (the site fills the tab; the native tab bar is the chrome), inset so an
110
+ * uncooperative site never renders under the status bar, off-site links leave the app
111
+ * through a prompt, and in-site history gets a floating back button.
112
+ */
113
+ export declare function siteWebViewPreset(site: Pick<SiteShellConfig, "origin" | "kind" | "chrome">): WebViewConfig;
114
+ /** The Tier-3 descriptor for one site page. */
115
+ export declare function defineSiteTab(site: SiteShellConfig, page: SitePage): ScreenDescriptor;
116
+ /** Every site page's descriptor, keyed by screen id. */
117
+ export declare function siteScreens(site: SiteShellConfig): Record<string, ScreenDescriptor>;
118
+ /** Tab-bar and drawer items for a site. Pages beyond {@link SITE_SHELL_MAX_TABS} that
119
+ * asked for a tab fall into the drawer rather than disappearing. */
120
+ export declare function siteNav(site: SiteShellConfig): {
121
+ tabs: NavTabItem[];
122
+ drawer: DrawerItem[];
123
+ };
124
+ /** Throws with a legible message on a site description no shell can build from. */
125
+ export declare function assertSiteShellConfig(site: SiteShellConfig): void;
@@ -12964,10 +12964,213 @@ var AppRegistryClient = class {
12964
12964
  return apps.some((a) => a.id === appId) ? "provisioned" : "unprovisioned";
12965
12965
  }
12966
12966
  };
12967
+
12968
+ // ../../core/ts/src/site-shell.ts
12969
+ var SITE_SHELL_MAX_TABS = 4;
12970
+ var SITE_SCREEN_PREFIX = "site_";
12971
+ var OAUTH_IDP_ORIGINS = [
12972
+ "accounts.google.com",
12973
+ "*.google.com",
12974
+ "*.googleusercontent.com",
12975
+ "*.gstatic.com"
12976
+ ];
12977
+ var BASE44_ORIGINS = ["*.base44.app", "*.base44.com"];
12978
+ var PAGE_ID = /^[a-z0-9_]+$/;
12979
+ function normalizeSiteOrigin(origin) {
12980
+ const raw = (typeof origin === "string" ? origin : "").trim();
12981
+ let url;
12982
+ try {
12983
+ url = new URL(raw);
12984
+ } catch {
12985
+ throw new Error(`site origin ${JSON.stringify(origin)} is not a URL`);
12986
+ }
12987
+ if (url.protocol !== "https:") {
12988
+ throw new Error(`site origin ${JSON.stringify(origin)} must be https`);
12989
+ }
12990
+ if (url.username || url.password) {
12991
+ throw new Error(`site origin ${JSON.stringify(origin)} must not carry credentials`);
12992
+ }
12993
+ if (url.search || url.hash) {
12994
+ throw new Error(`site origin ${JSON.stringify(origin)} must not carry a query or fragment`);
12995
+ }
12996
+ const path = url.pathname.replace(/\/+$/, "");
12997
+ return `https://${url.host.toLowerCase()}${path}`;
12998
+ }
12999
+ function siteHost(origin) {
13000
+ return new URL(normalizeSiteOrigin(origin)).host;
13001
+ }
13002
+ function siteAllowedOrigins(origin, kind) {
13003
+ const base = new URL(normalizeSiteOrigin(origin));
13004
+ const host = base.host;
13005
+ const path = base.pathname.replace(/\/+$/, "");
13006
+ const hosts = [host];
13007
+ const isWixFreeHost = host.endsWith(".wixsite.com") || host.endsWith(".wixstudio.com");
13008
+ if (!isWixFreeHost && kind === "wix") hosts.push(host.startsWith("www.") ? host.slice(4) : `www.${host}`);
13009
+ return hosts.map((h) => `https://${h}${path}/`);
13010
+ }
13011
+ function sitePageUrl(origin, path) {
13012
+ const base = normalizeSiteOrigin(origin);
13013
+ const raw = (typeof path === "string" ? path : "").trim();
13014
+ if (/^[a-z][a-z0-9+.-]*:/i.test(raw) || raw.startsWith("//")) {
13015
+ throw new Error(`site page path ${JSON.stringify(path)} must be relative to the site`);
13016
+ }
13017
+ if (raw.includes("\\")) {
13018
+ throw new Error(`site page path ${JSON.stringify(path)} must not contain a backslash`);
13019
+ }
13020
+ const trimmed = raw.replace(/^\/+/, "");
13021
+ const joined = trimmed ? `${base}/${trimmed}` : base;
13022
+ const resolved = new URL(joined);
13023
+ const baseUrl = new URL(base);
13024
+ const basePath = baseUrl.pathname.replace(/\/+$/, "");
13025
+ const inside = resolved.pathname === basePath || resolved.pathname === `${basePath}/` || resolved.pathname.startsWith(`${basePath}/`);
13026
+ if (resolved.host !== baseUrl.host || !inside) {
13027
+ throw new Error(`site page path ${JSON.stringify(path)} resolves outside the site`);
13028
+ }
13029
+ return joined;
13030
+ }
13031
+ function siteScreenId(page) {
13032
+ return `${SITE_SCREEN_PREFIX}${page.id}`;
13033
+ }
13034
+ function siteChromeHidingScript(options) {
13035
+ if (!options.hideFooter && !options.hideWixAds) return "";
13036
+ return `(function(){
13037
+ if (window.__himiSiteChromeHider) return;
13038
+ window.__himiSiteChromeHider = true;
13039
+ var HIDE_FOOTER = ${options.hideFooter ? "true" : "false"};
13040
+ var HIDE_ADS = ${options.hideWixAds ? "true" : "false"};
13041
+ function hide(el, collapse) {
13042
+ if (!el) return;
13043
+ el.style.display = 'none';
13044
+ el.style.visibility = 'hidden';
13045
+ if (collapse) { el.style.height = '0px'; el.style.overflow = 'hidden'; }
13046
+ }
13047
+ function pass() {
13048
+ if (HIDE_FOOTER) {
13049
+ hide(document.getElementsByTagName('footer')[0] ||
13050
+ document.querySelector('footer[id^="comp-"]') ||
13051
+ document.querySelector('[class*="wixui-footer"]') ||
13052
+ document.querySelector('[data-mesh-id*="SITE_FOOTER"]') ||
13053
+ document.querySelector('[id*="SITE_FOOTER"]'), false);
13054
+ }
13055
+ if (HIDE_ADS) {
13056
+ hide(document.getElementById('WIX_ADS') ||
13057
+ document.querySelector('.WIX_ADS') ||
13058
+ document.querySelector('[class*="WIX_ADS"]'), true);
13059
+ }
13060
+ }
13061
+ var scheduled = false;
13062
+ function schedule() {
13063
+ if (scheduled) return;
13064
+ scheduled = true;
13065
+ requestAnimationFrame(function(){ scheduled = false; pass(); });
13066
+ }
13067
+ function start() {
13068
+ pass();
13069
+ new MutationObserver(schedule).observe(document.documentElement, { childList: true, subtree: true });
13070
+ }
13071
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
13072
+ else start();
13073
+ })();`;
13074
+ }
13075
+ function resolveSiteChrome(chrome) {
13076
+ return {
13077
+ hideSiteHeader: chrome?.hideSiteHeader ?? true,
13078
+ hideSiteFooter: chrome?.hideSiteFooter ?? true,
13079
+ hideWixAds: chrome?.hideWixAds ?? true
13080
+ };
13081
+ }
13082
+ function siteWebViewPreset(site) {
13083
+ const chrome = resolveSiteChrome(site.chrome);
13084
+ const hidingScript = site.kind === "wix" ? siteChromeHidingScript({ hideFooter: chrome.hideSiteFooter, hideWixAds: chrome.hideWixAds }) : siteChromeHidingScript({ hideFooter: chrome.hideSiteFooter, hideWixAds: false });
13085
+ const config = {
13086
+ layout: "inset",
13087
+ immersive: true,
13088
+ hideHostedSiteHeader: chrome.hideSiteHeader,
13089
+ pullToRefresh: true,
13090
+ showProgressBar: true,
13091
+ syncStatusBarAppearance: true,
13092
+ allowedOrigins: siteAllowedOrigins(site.origin, site.kind),
13093
+ externalLinks: "system_browser",
13094
+ promptBeforeExternalLink: true,
13095
+ // A real external site, not Himalaya screens — its links stay in its WebView.
13096
+ interceptInternalLinks: false,
13097
+ floatingBackButton: { enabled: true },
13098
+ autoReloadOnCrash: true,
13099
+ domStorageEnabled: true,
13100
+ // Each tab keeps its own warm WebView across tab switches (keyed by screen id on both
13101
+ // platforms; see SITE_SHELL_MAX_TABS for why the tab count stays within the store's cap).
13102
+ keepAlive: true
13103
+ };
13104
+ if (hidingScript) config.injectJsDocumentEnd = hidingScript;
13105
+ return config;
13106
+ }
13107
+ function defineSiteTab(site, page) {
13108
+ assertSitePage(page);
13109
+ const preset = siteWebViewPreset(site);
13110
+ return {
13111
+ screenId: siteScreenId(page),
13112
+ renderer: "webview_native_bridge",
13113
+ title: page.title,
13114
+ url: sitePageUrl(site.origin, page.path),
13115
+ capabilitiesRequired: [],
13116
+ schemaVersion: 1,
13117
+ // Wix serves page HTML `no-cache`; the descriptor itself is cheap to revalidate.
13118
+ cacheControl: { maxAge: 600, staleWhileRevalidate: 86400 },
13119
+ webViewConfig: { ...preset, ...page.webView ?? {} }
13120
+ };
13121
+ }
13122
+ function siteScreens(site) {
13123
+ assertSiteShellConfig(site);
13124
+ return Object.fromEntries(site.pages.map((page) => [siteScreenId(page), defineSiteTab(site, page)]));
13125
+ }
13126
+ function siteNav(site) {
13127
+ assertSiteShellConfig(site);
13128
+ const tabs = [];
13129
+ const drawer = [];
13130
+ for (const page of site.pages) {
13131
+ const placement = page.placement ?? "tab";
13132
+ const icon = page.icon ?? "globe";
13133
+ if (placement === "tab" && tabs.length < SITE_SHELL_MAX_TABS) {
13134
+ tabs.push({ id: page.id, screenId: siteScreenId(page), label: page.title, icon });
13135
+ }
13136
+ if (placement !== "overflow") {
13137
+ drawer.push({ id: `drawer_${page.id}`, screenId: siteScreenId(page), label: page.title, icon });
13138
+ }
13139
+ }
13140
+ return { tabs, drawer };
13141
+ }
13142
+ function assertSitePage(page) {
13143
+ if (!page || typeof page.id !== "string" || !PAGE_ID.test(page.id)) {
13144
+ throw new Error(`site page id ${JSON.stringify(page?.id)} must match ${PAGE_ID}`);
13145
+ }
13146
+ if (typeof page.title !== "string" || !page.title.trim()) {
13147
+ throw new Error(`site page ${page.id} needs a title`);
13148
+ }
13149
+ }
13150
+ function assertSiteShellConfig(site) {
13151
+ normalizeSiteOrigin(site.origin);
13152
+ if (site.kind !== "wix" && site.kind !== "base44") {
13153
+ throw new Error(`site kind ${JSON.stringify(site.kind)} must be "wix" or "base44"`);
13154
+ }
13155
+ if (!Array.isArray(site.pages) || site.pages.length === 0) {
13156
+ throw new Error("a site shell needs at least one page");
13157
+ }
13158
+ const seen = /* @__PURE__ */ new Set();
13159
+ for (const page of site.pages) {
13160
+ assertSitePage(page);
13161
+ if (seen.has(page.id)) throw new Error(`site page id ${page.id} is declared twice`);
13162
+ seen.add(page.id);
13163
+ sitePageUrl(site.origin, page.path);
13164
+ }
13165
+ if (!site.pages.some((page) => (page.placement ?? "tab") === "tab")) {
13166
+ throw new Error('a site shell needs at least one page with placement "tab"');
13167
+ }
13168
+ }
12967
13169
  export {
12968
13170
  ALL_ORIENTATIONS,
12969
13171
  APP_ID_PATTERN,
12970
13172
  AppRegistryClient,
13173
+ BASE44_ORIGINS,
12971
13174
  CHASSIS_GEOMETRY,
12972
13175
  CORE_COMPONENTS,
12973
13176
  DEFAULT_DISTRIBUTION,
@@ -12988,6 +13191,7 @@ export {
12988
13191
  LAUNCH_MANIFEST_SCHEMA,
12989
13192
  LAUNCH_NAMESPACE,
12990
13193
  LINK_MOUNT_PATHS,
13194
+ OAUTH_IDP_ORIGINS,
12991
13195
  ORIGIN_PARAM_KEYS,
12992
13196
  PACKAGE_SHARE_ID_PATTERN,
12993
13197
  PLAYER_PREVIEW_SEGMENT_PATTERN,
@@ -12995,6 +13199,8 @@ export {
12995
13199
  PREVIEW_SURFACES,
12996
13200
  PROJECTION_SNAPSHOT_SIZE,
12997
13201
  RTL_LOCALES,
13202
+ SITE_SCREEN_PREFIX,
13203
+ SITE_SHELL_MAX_TABS,
12998
13204
  SIZE_CLASS_BREAKPOINTS,
12999
13205
  SPLASH_MAX_HOLD_MS_MAX,
13000
13206
  SPLASH_MIN_VISIBLE_MS_MAX,
@@ -13013,6 +13219,7 @@ export {
13013
13219
  WORKER_SUBTREE_COMMON_ACTION_PATHS,
13014
13220
  WORKER_SUBTREE_SAFE_TYPE_RULES,
13015
13221
  activeMediaItem,
13222
+ assertSiteShellConfig,
13016
13223
  baselineMediaCapabilities,
13017
13224
  bucket,
13018
13225
  buildHimiPreviewUrl,
@@ -13029,6 +13236,7 @@ export {
13029
13236
  decodeLaunchParams,
13030
13237
  defaultMessage,
13031
13238
  defineApp,
13239
+ defineSiteTab,
13032
13240
  effectiveFieldRule,
13033
13241
  errorMessageFor,
13034
13242
  evaluate,
@@ -13052,6 +13260,7 @@ export {
13052
13260
  isWidgetFamilySurface,
13053
13261
  layoutDirection,
13054
13262
  mergeIdiomOverlay,
13263
+ normalizeSiteOrigin,
13055
13264
  parseNumeric,
13056
13265
  primarySubtag,
13057
13266
  resolveAppIdentity,
@@ -13063,6 +13272,7 @@ export {
13063
13272
  resolveMode,
13064
13273
  resolveOrientationEnvelope,
13065
13274
  resolveResponsiveSize,
13275
+ resolveSiteChrome,
13066
13276
  resolveSizeClass,
13067
13277
  resolveSurfaceEnvelope,
13068
13278
  resolveSurfaceNav,
@@ -13071,6 +13281,14 @@ export {
13071
13281
  screenDescriptorSchema,
13072
13282
  selectIdiomOverlay,
13073
13283
  shouldRevealError,
13284
+ siteAllowedOrigins,
13285
+ siteChromeHidingScript,
13286
+ siteHost,
13287
+ siteNav,
13288
+ sitePageUrl,
13289
+ siteScreenId,
13290
+ siteScreens,
13291
+ siteWebViewPreset,
13074
13292
  stripOriginParams,
13075
13293
  summarize,
13076
13294
  surfaceById,
package/dist/cli.mjs CHANGED
@@ -3870,6 +3870,14 @@ var init_dist_jobs = __esm({
3870
3870
  bundleId: { ios: "ent.wix.ownerlite.dev", android: "com.wix.himi.ownerlite.dev" },
3871
3871
  provisioningEnv: "HIMI_OWNER_LITE_PROVISIONING_JSON"
3872
3872
  },
3873
+ // RFC 0062 — the site shell: the customer's own website as native-framed Tier-3 tabs.
3874
+ // Brandable (its package.json declares prepare:customer), so dist bakes the fetched
3875
+ // contract's identity, icon, brand colour and `site` block into the binary.
3876
+ brandy: {
3877
+ appDir: "apps/himi-brandy",
3878
+ bundleId: { ios: "ent.wix.brandy.dev", android: "com.wix.himi.brandy.dev" },
3879
+ provisioningEnv: "HIMI_BRANDY_PROVISIONING_JSON"
3880
+ },
3873
3881
  player: {
3874
3882
  appDir: "apps/himi-player",
3875
3883
  bundleId: { ios: "dev.himalaya.player.dev", android: "dev.himalaya.player.dev" },
@@ -22305,7 +22313,7 @@ function bindingPlaceholder(v, ctx) {
22305
22313
  if (ctx.runtimeDegraded) return null;
22306
22314
  return /* @__PURE__ */ jsx("span", { style: { color: ctx.color("textSecondary", "#9aa2ad"), fontStyle: "italic", overflowWrap: "anywhere", wordBreak: "break-word" }, children: v.replace(/^\$/, "") });
22307
22315
  }
22308
- function mdScan(src) {
22316
+ function mdScan(src, linkStyle = "") {
22309
22317
  const re = new RegExp(MD_INLINE.source, "g");
22310
22318
  let out = "";
22311
22319
  let last = 0;
@@ -22317,21 +22325,22 @@ function mdScan(src) {
22317
22325
  } else if (label2 !== void 0) {
22318
22326
  if (/^https?:\/\//i.test(url)) {
22319
22327
  const href = mdEsc(url).replace(/"/g, "&quot;");
22320
- out += `<a href="${href}" target="_blank" rel="noopener noreferrer">${mdScan(label2)}</a>`;
22328
+ out += `<a href="${href}" target="_blank" rel="noopener noreferrer"${linkStyle}>${mdScan(label2, linkStyle)}</a>`;
22321
22329
  } else {
22322
22330
  out += mdEsc(whole);
22323
22331
  }
22324
22332
  } else if (bold !== void 0) {
22325
- out += `<b>${mdScan(bold)}</b>`;
22333
+ out += `<b>${mdScan(bold, linkStyle)}</b>`;
22326
22334
  } else {
22327
- out += `<i>${mdScan(italic)}</i>`;
22335
+ out += `<i>${mdScan(italic, linkStyle)}</i>`;
22328
22336
  }
22329
22337
  last = m.index + whole.length;
22330
22338
  }
22331
22339
  return out + mdEsc(src.slice(last));
22332
22340
  }
22333
- function mdInline(text5) {
22334
- return { __html: mdScan(text5) };
22341
+ function mdInline(text5, linkColor) {
22342
+ const safe = typeof linkColor === "string" && /^[#a-zA-Z0-9(),.%\s-]+$/.test(linkColor) ? linkColor : "";
22343
+ return { __html: mdScan(text5, safe ? ` style="color:${safe}"` : "") };
22335
22344
  }
22336
22345
  function sevColor(ctx, sev) {
22337
22346
  switch (sev) {
@@ -22346,6 +22355,20 @@ function sevColor(ctx, sev) {
22346
22355
  return ctx.color("accentPrimary", "#116DFF");
22347
22356
  }
22348
22357
  }
22358
+ function accentTextColor(ctx) {
22359
+ return ctx.color("accentText", ctx.color("accentPrimary", "#116DFF"));
22360
+ }
22361
+ function sevTextColor(ctx, sev) {
22362
+ switch (sev) {
22363
+ case "success":
22364
+ case "warning":
22365
+ case "danger":
22366
+ case "error":
22367
+ return sevColor(ctx, sev);
22368
+ default:
22369
+ return accentTextColor(ctx);
22370
+ }
22371
+ }
22349
22372
  function iconGlyph(name) {
22350
22373
  if (typeof name !== "string" || !name) return "\xB7";
22351
22374
  const key2 = name.toLowerCase().replace(/[_.\s-]/g, "");
@@ -25730,7 +25753,7 @@ var init_controls = __esm({
25730
25753
  "aria-selected": on3,
25731
25754
  tabIndex: on3 ? 0 : -1,
25732
25755
  onClick: () => select(i),
25733
- style: { all: "unset", display: "flex", gap: 6, alignItems: "center", minWidth: 44, minHeight: 44, padding: "10px 0", fontFamily: FONT(ctx), fontWeight: 600, fontSize: 14, cursor: "pointer", color: on3 ? C("accentPrimary", "#116DFF") : C("textSecondary", "#777"), borderBottom: on3 ? `2px solid ${C("accentPrimary", "#116DFF")}` : "2px solid transparent", marginBottom: -1 },
25756
+ style: { all: "unset", display: "flex", gap: 6, alignItems: "center", minWidth: 44, minHeight: 44, padding: "10px 0", fontFamily: FONT(ctx), fontWeight: 600, fontSize: 14, cursor: "pointer", color: on3 ? accentTextColor(ctx) : C("textSecondary", "#777"), borderBottom: on3 ? `2px solid ${C("accentPrimary", "#116DFF")}` : "2px solid transparent", marginBottom: -1 },
25734
25757
  children: [
25735
25758
  asText2(t.label),
25736
25759
  t.badge && /* @__PURE__ */ jsx4("span", { "aria-hidden": true, style: { fontSize: 10, fontWeight: 700, background: C("surfaceTertiary", "#eee"), color: C("textSecondary", "#666"), padding: "1px 6px", borderRadius: 999 }, children: t.badge })
@@ -27803,9 +27826,6 @@ function actionClick(ctx, action) {
27803
27826
  ctx.onAction(resolveItemDeep2(action, ctx));
27804
27827
  };
27805
27828
  }
27806
- function accentTextColor(ctx) {
27807
- return ctx.color("accentText", ctx.color("accentPrimary", "#116DFF"));
27808
- }
27809
27829
  function actionTextButton(ctx, action, label2, color, extra) {
27810
27830
  if (action == null) return null;
27811
27831
  const onClick = actionClick(ctx, action);
@@ -28486,7 +28506,7 @@ function renderInner(node, ctx) {
28486
28506
  /* @__PURE__ */ jsx6("div", { style: { width: 46, height: 46, borderRadius: 13, background: C("surfaceTertiary", "#eee"), display: "grid", placeItems: "center", margin: "0 auto 12px", color: C("textSecondary", "#9aa2ad") }, children: iconSvg(props.icon, { size: 24 }) }),
28487
28507
  /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), fontWeight: 600, fontSize: 16, color: C("textPrimary", "#111") }, children: asBoundText(props.title, ctx) || "Empty" }),
28488
28508
  props.subtitle && /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), fontSize: 13, color: C("textSecondary", "#888"), marginTop: 3 }, children: txt(props.subtitle, ctx) }),
28489
- actionTextButton(ctx, props.action, asBoundText(props.actionLabel, ctx) || "Take action", C("accentPrimary", "#116DFF"), { margin: "12px auto 0", textAlign: "center" })
28509
+ actionTextButton(ctx, props.action, asBoundText(props.actionLabel, ctx) || "Take action", accentTextColor(ctx), { margin: "12px auto 0", textAlign: "center" })
28490
28510
  ] });
28491
28511
  // ───────────── atoms ─────────────
28492
28512
  case "Text":
@@ -28496,7 +28516,7 @@ function renderInner(node, ctx) {
28496
28516
  return /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), lineHeight: 1.5, overflowWrap: "break-word", color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), ...wrap }, children: runs.map((r, i) => {
28497
28517
  const marks = Array.isArray(r.marks) ? r.marks : [];
28498
28518
  const f = ctx.font(r.style);
28499
- return /* @__PURE__ */ jsx6("span", { style: { fontSize: f.remSize, fontWeight: marks.includes("bold") ? 700 : f.weight, fontStyle: marks.includes("italic") ? "italic" : void 0, textDecoration: marks.includes("underline") ? "underline" : void 0, ...f.family ? { fontFamily: fontStack(f.family, ctx.idiom) } : {}, ...r.color ? { color: C(r.color) } : {}, ...marks.includes("code") ? { fontFamily: "ui-monospace, monospace", background: C("surfaceTertiary", "#eee"), padding: "1px 5px", borderRadius: 4 } : {} }, children: txt(r.text, ctx) }, i);
28519
+ return /* @__PURE__ */ jsx6("span", { style: { fontSize: f.remSize, fontWeight: marks.includes("bold") ? 700 : f.weight, fontStyle: marks.includes("italic") ? "italic" : void 0, textDecoration: marks.includes("underline") || marks.includes("link") ? "underline" : void 0, ...f.family ? { fontFamily: fontStack(f.family, ctx.idiom) } : {}, ...r.color ? { color: C(r.color) } : marks.includes("link") ? { color: accentTextColor(ctx) } : {}, ...marks.includes("code") ? { fontFamily: "ui-monospace, monospace", background: C("surfaceTertiary", "#eee"), padding: "1px 5px", borderRadius: 4 } : {} }, children: txt(r.text, ctx) }, i);
28500
28520
  }) });
28501
28521
  }
28502
28522
  case "Image":
@@ -28525,11 +28545,14 @@ function renderInner(node, ctx) {
28525
28545
  const liveCount = typeof props.text === "string" && /^\$(?:service|flag)\b/.test(props.text);
28526
28546
  const badgeLabel = m?.accessibilityLabel ?? m?.accessibility_label;
28527
28547
  const badgeRole = roleDeclared ? void 0 : liveCount ? "status" : typeof badgeLabel === "string" && badgeLabel.trim() !== "" ? "img" : void 0;
28528
- return /* @__PURE__ */ jsx6("span", { role: badgeRole, style: { display: "inline-flex", alignItems: "center", padding: "3px 9px", borderRadius: 999, fontSize: 12, fontWeight: 700, fontFamily: bodyFont(ctx), background: `color-mix(in srgb, ${c} 16%, ${C("surfacePrimary", "#fff")})`, color: c, ...wrap }, children: ctx.empty ? "" : txt(props.text, ctx) || "Badge" });
28548
+ const fg = _bt ? c : sevTextColor(ctx, props.style ?? "info");
28549
+ return /* @__PURE__ */ jsx6("span", { role: badgeRole, style: { display: "inline-flex", alignItems: "center", padding: "3px 9px", borderRadius: 999, fontSize: 12, fontWeight: 700, fontFamily: bodyFont(ctx), background: `color-mix(in srgb, ${c} 16%, ${C("surfacePrimary", "#fff")})`, color: fg, ...wrap }, children: ctx.empty ? "" : txt(props.text, ctx) || "Badge" });
28529
28550
  }
28530
28551
  case "Tag": {
28531
- const c = C(boundTokenName(props.tokenPath, props.token, ctx) ?? "accentPrimary", "#116DFF");
28532
- return /* @__PURE__ */ jsx6("span", { style: { display: "inline-flex", alignItems: "center", padding: "5px 11px", borderRadius: 999, fontSize: 13, fontWeight: 600, fontFamily: bodyFont(ctx), background: `color-mix(in srgb, ${c} 14%, ${C("surfacePrimary", "#fff")})`, border: `1px solid color-mix(in srgb, ${c} 30%, transparent)`, color: c, ...wrap }, children: ctx.empty ? "" : txt(props.text, ctx) || "Tag" });
28552
+ const tagToken = boundTokenName(props.tokenPath, props.token, ctx);
28553
+ const c = C(tagToken ?? "accentPrimary", "#116DFF");
28554
+ const tagFg = tagToken ? c : accentTextColor(ctx);
28555
+ return /* @__PURE__ */ jsx6("span", { style: { display: "inline-flex", alignItems: "center", padding: "5px 11px", borderRadius: 999, fontSize: 13, fontWeight: 600, fontFamily: bodyFont(ctx), background: `color-mix(in srgb, ${c} 14%, ${C("surfacePrimary", "#fff")})`, border: `1px solid color-mix(in srgb, ${c} 30%, transparent)`, color: tagFg, ...wrap }, children: ctx.empty ? "" : txt(props.text, ctx) || "Tag" });
28533
28556
  }
28534
28557
  case "ProgressBar":
28535
28558
  return /* @__PURE__ */ jsx6(ProgressBarNode, { props, ctx, wrap });
@@ -28780,7 +28803,7 @@ function renderInner(node, ctx) {
28780
28803
  return /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: 6, width: "100%", ...wrap }, children: branch ? renderNode(props[branch], ctx) : cfg && typeof cfg === "object" ? /* @__PURE__ */ jsxs3("div", { style: { textAlign: "center", padding: "24px 12px", color: C("textSecondary", "#9aa2ad") }, children: [
28781
28804
  cfg.title && /* @__PURE__ */ jsx6("div", { style: { fontSize: 15, fontWeight: 600, color: C("textPrimary", "#111") }, children: asText2(cfg.title) }),
28782
28805
  (cfg.subtitle ?? cfg.message) && /* @__PURE__ */ jsx6("div", { style: { fontSize: 13, marginTop: 4 }, children: asText2(cfg.subtitle ?? cfg.message) }),
28783
- actionTextButton(ctx, cfg.retry ?? cfg.action, asText2(cfg.retryLabel) || asText2(cfg.actionLabel) || (cfg.retry != null ? "Retry" : "Take action"), C("accentPrimary", "#116DFF"), { margin: "12px auto 0", textAlign: "center" })
28806
+ actionTextButton(ctx, cfg.retry ?? cfg.action, asText2(cfg.retryLabel) || asText2(cfg.actionLabel) || (cfg.retry != null ? "Retry" : "Take action"), accentTextColor(ctx), { margin: "12px auto 0", textAlign: "center" })
28784
28807
  ] }) : null });
28785
28808
  }
28786
28809
  case "FragmentSlot":
@@ -30411,7 +30434,8 @@ var init_renderNode = __esm({
30411
30434
  display: "grid",
30412
30435
  placeItems: "center",
30413
30436
  fontSize: 18,
30414
- color: C("accentPrimary", "#116DFF"),
30437
+ color: accentTextColor(ctx),
30438
+ // #3275 — a glyph on a neutral key
30415
30439
  border: `1px solid ${C("border", "#e3e3e3")}`,
30416
30440
  cursor: ctx.onAction && !disabled(at) ? "pointer" : "default",
30417
30441
  opacity: disabled(at) ? 0.4 : 1,
@@ -30596,7 +30620,7 @@ var init_renderNode = __esm({
30596
30620
  const resolved2 = resolveScalarStatic(props.text, ctx);
30597
30621
  return resolved2 == null ? null : String(resolved2);
30598
30622
  })();
30599
- const content = countContent != null ? countContent : icuPattern != null ? /* @__PURE__ */ jsx6(InterpolatedTextView, { pattern: icuPattern, params: props.template?.params ?? {}, ctx }) : mdSource != null ? /* @__PURE__ */ jsx6("span", { dangerouslySetInnerHTML: mdInline(mdSource) }) : txt(props.text, ctx);
30623
+ const content = countContent != null ? countContent : icuPattern != null ? /* @__PURE__ */ jsx6(InterpolatedTextView, { pattern: icuPattern, params: props.template?.params ?? {}, ctx }) : mdSource != null ? /* @__PURE__ */ jsx6("span", { dangerouslySetInnerHTML: mdInline(mdSource, accentTextColor(ctx)) }) : txt(props.text, ctx);
30600
30624
  const tAlign = props.textAlign === "center" ? "center" : props.textAlign === "trailing" ? "right" : props.textAlign === "justify" ? "justify" : props.textAlign === "leading" ? "left" : void 0;
30601
30625
  const tDecor = props.decoration === "strikethrough" ? "line-through" : props.decoration === "underline" ? "underline" : void 0;
30602
30626
  const ln = typeof props.lineSpacing === "number" ? pxToRem((f.line ?? 1.4) * f.size + props.lineSpacing) : f.line;
@@ -30642,7 +30666,7 @@ var init_renderNode = __esm({
30642
30666
  }
30643
30667
  if (props.icon != null && props.url == null && props.asset == null && imgInitials == null) {
30644
30668
  const side2 = typeof w === "number" ? w : 40;
30645
- return /* @__PURE__ */ jsx6("div", { style: { width: side2, height: side2, borderRadius: "50%", background: ph ?? C("surfaceTertiary", "#e6e6e6"), display: "grid", placeItems: "center", flex: "none", ...wrap }, children: iconSvg(props.icon, { size: Math.max(14, Math.round(side2 * 0.5)), color: C("accentPrimary", "#116DFF") }) });
30669
+ return /* @__PURE__ */ jsx6("div", { style: { width: side2, height: side2, borderRadius: "50%", background: ph ?? C("surfaceTertiary", "#e6e6e6"), display: "grid", placeItems: "center", flex: "none", ...wrap }, children: iconSvg(props.icon, { size: Math.max(14, Math.round(side2 * 0.5)), color: accentTextColor(ctx) }) });
30646
30670
  }
30647
30671
  return /* @__PURE__ */ jsx6("div", { style: { width: w, height: boxHeight, aspectRatio: ar, borderRadius: r, background: ph ?? (hasSource ? `linear-gradient(135deg, ${C("surfaceTertiary", "#e6e6e6")}, ${C("surfaceSecondary", "#f2f2f2")})` : "transparent"), color: C("textSecondary", "#999"), display: "flex", alignItems: "center", justifyContent: "center", fontFamily: "ui-monospace, monospace", fontSize: 11, flex: "none", ...wrap }, children: banner || !hasSource ? "" : "\u2022" });
30648
30672
  });
@@ -31319,11 +31343,13 @@ var init_renderNode = __esm({
31319
31343
  const effectivePositionMs = seeked ?? positionMs;
31320
31344
  const progress = scrub ?? (durationMs > 0 ? Math.min(Math.max(effectivePositionMs / durationMs, 0), 1) : 0);
31321
31345
  const clockMs = scrub != null || effectivePositionMs > 0 || isPlaying || isLoading ? Math.min(Math.round(progress * durationMs), durationMs) : durationMs;
31322
- const tint = C(resolveScalarStatic(props.tint, ctx) ?? "accentPrimary", "#116DFF");
31346
+ const tintToken = resolveScalarStatic(props.tint, ctx);
31347
+ const tint = C(tintToken ?? "accentPrimary", "#116DFF");
31323
31348
  const danger = C("danger", "#E62214");
31324
31349
  const secondary = C("textSecondary", "#636B78");
31325
31350
  const onAccent = C("textOnAccent", "#FFFFFF");
31326
31351
  const accent = isFailed ? danger : tint;
31352
+ const accentLabel = isFailed ? danger : tintToken ? tint : accentTextColor(ctx);
31327
31353
  const emit = (action, extra) => {
31328
31354
  if (!ctx.onAction || !action) return;
31329
31355
  const a = resolveItemDeep2(action, ctx);
@@ -31491,7 +31517,7 @@ var init_renderNode = __esm({
31491
31517
  fontSize: 11,
31492
31518
  fontWeight: 600,
31493
31519
  fontFamily: "inherit",
31494
- color: accent,
31520
+ color: accentLabel,
31495
31521
  background: C("surfaceSecondary", "#F5F7FA"),
31496
31522
  borderRadius: 999,
31497
31523
  padding: "3px 7px"
@@ -32156,7 +32182,7 @@ var init_renderNode = __esm({
32156
32182
  }
32157
32183
  )
32158
32184
  ] }),
32159
- /* @__PURE__ */ jsx6("div", { style: { display: "flex", marginLeft: 44, minWidth: days.length * 90 }, children: laneMode ? laneCols.map((lane) => /* @__PURE__ */ jsx6("div", { "data-lane": lane.id, style: { flex: "1 1 0", minWidth: 0, textAlign: "center", fontFamily: bodyFont(ctx), fontSize: 12, fontWeight: 600, color: C("textSecondary", "#888"), padding: "4px 2px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: laneTitle(lane.id) }, lane.id)) : days.map((d, i) => /* @__PURE__ */ jsx6("div", { style: { flex: "1 1 0", minWidth: 0, textAlign: "center", fontFamily: bodyFont(ctx), fontSize: 11, fontWeight: 600, color: dayKeys[i] === nowKey ? C("accentPrimary", "#116DFF") : C("textSecondary", "#888") }, children: d.toLocaleDateString(void 0, { weekday: "short", day: "numeric" }) }, i)) }),
32185
+ /* @__PURE__ */ jsx6("div", { style: { display: "flex", marginLeft: 44, minWidth: days.length * 90 }, children: laneMode ? laneCols.map((lane) => /* @__PURE__ */ jsx6("div", { "data-lane": lane.id, style: { flex: "1 1 0", minWidth: 0, textAlign: "center", fontFamily: bodyFont(ctx), fontSize: 12, fontWeight: 600, color: C("textSecondary", "#888"), padding: "4px 2px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: laneTitle(lane.id) }, lane.id)) : days.map((d, i) => /* @__PURE__ */ jsx6("div", { style: { flex: "1 1 0", minWidth: 0, textAlign: "center", fontFamily: bodyFont(ctx), fontSize: 11, fontWeight: 600, color: dayKeys[i] === nowKey ? accentTextColor(ctx) : C("textSecondary", "#888") }, children: d.toLocaleDateString(void 0, { weekday: "short", day: "numeric" }) }, i)) }),
32160
32186
  days.some((_, i) => (byDay.get(dayKeys[i])?.allDay.length ?? 0) > 0) && /* @__PURE__ */ jsx6("div", { style: { display: "flex", marginLeft: 44, gap: 2, marginBottom: 4, minWidth: days.length * 90 }, children: days.map((d, i) => /* @__PURE__ */ jsx6("div", { style: { flex: "1 1 0", minWidth: 0, display: "flex", flexDirection: "column", gap: 2, padding: "0 1px" }, children: (byDay.get(dayKeys[i])?.allDay ?? []).map((ev, j) => {
32161
32187
  const tappable = !!(ev?.onTap || props.onSelectEvent);
32162
32188
  return /* @__PURE__ */ jsx6(
@@ -33832,6 +33858,12 @@ var init_app_registry_client = __esm({
33832
33858
  }
33833
33859
  });
33834
33860
 
33861
+ // ../../core/ts/src/site-shell.ts
33862
+ var init_site_shell = __esm({
33863
+ "../../core/ts/src/site-shell.ts"() {
33864
+ }
33865
+ });
33866
+
33835
33867
  // ../../core/ts/src/index.ts
33836
33868
  var init_src5 = __esm({
33837
33869
  "../../core/ts/src/index.ts"() {
@@ -33858,6 +33890,7 @@ var init_src5 = __esm({
33858
33890
  init_app_link();
33859
33891
  init_app_identity();
33860
33892
  init_app_registry_client();
33893
+ init_site_shell();
33861
33894
  }
33862
33895
  });
33863
33896
 
@@ -53071,21 +53104,21 @@ var init_preview_assets_generated = __esm({
53071
53104
  "../../packages/serve/src/preview-assets.generated.ts"() {
53072
53105
  init_preview_assets_format();
53073
53106
  PREVIEW_ASSET_MANIFEST = [
53074
- { key: "assets/index-RMW-OZ7N.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1062852, sha256: "5ceea282c8f188d80853097ec1e56e9f363fcd19e5b7d0882134f326db513d89" },
53075
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1062860, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
53076
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1067470, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
53077
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1556054, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
53078
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1646978, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
53079
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1737962, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
53080
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1828942, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
53081
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1919814, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
53082
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2010794, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
53083
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2119430, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
53084
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2228514, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
53085
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2337562, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
53086
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2445262, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
53087
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2533370, length: 4947, sha256: "f7717e4d819fee4e0719a369ca061f514e02ff24f7f0dacaa6469a4a2fca8dcf" },
53088
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2538317, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
53107
+ { key: "assets/index-DlDXdHId.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1063092, sha256: "758c655ba88cbb54101f690281513fcff56aaa8b4a31dd49545d0f30f45e05f8" },
53108
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1063100, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
53109
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1067710, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
53110
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1556294, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
53111
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1647218, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
53112
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1738202, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
53113
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1829182, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
53114
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1920054, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
53115
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2011034, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
53116
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2119670, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
53117
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2228754, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
53118
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2337802, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
53119
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2445502, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
53120
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2533610, length: 4947, sha256: "7a5bedfb0fdf0c376ce40bba70539332bca4b2fad4e4112465fb1875e03aa29b" },
53121
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2538557, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
53089
53122
  ];
53090
53123
  }
53091
53124
  });
@@ -59418,6 +59451,13 @@ async function applyOverlays(input) {
59418
59451
  );
59419
59452
  }
59420
59453
  const appDir2 = join61(repoRoot2, shellAppDir(shell));
59454
+ const committedPath = join61(appDir2, "provisioning.json");
59455
+ const committedSite = existsSync62(committedPath) ? JSON.parse(readFileSync73(committedPath, "utf8")).site : void 0;
59456
+ if (committedSite !== void 0 && overlay.site === void 0) {
59457
+ throw new Error(
59458
+ `shell "${shell}" is a site shell: its contract names the customer's website (a \`site\` block), which this analysis does not produce yet. Write the site block into a contract and run \`npm run prepare:customer -- --contract <file>\` in ${shellAppDir(shell)} (or \`himi dist --shell ${shell}\`).`
59459
+ );
59460
+ }
59421
59461
  const packageDir = dirname33(resolve45(input.overlayPath));
59422
59462
  const { path: contract, staged } = buildMergedContract({ appDir: appDir2, packageDir, overlay });
59423
59463
  wrote.push(...staged);
@@ -67367,7 +67407,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
67367
67407
  carries no attestation, source archive, assets, icon or
67368
67408
  minBinaryVersion \u2014 all named in the output.
67369
67409
  himi dist [--app <a>] [--platform ios,android] [--release <id>] [--allow-draft] [--target <t>]
67370
- [--shell branded-lite|owner-lite|player|demo-app] [--signing wix-managed|byo]
67410
+ [--shell branded-lite|owner-lite|brandy|player|demo-app] [--signing wix-managed|byo]
67371
67411
  [--android-signing qa-keystore|wix-keystore|debug]
67372
67412
  [--borrow-ios-id <bundleId>] [--no-wait] [--force]
67373
67413
  build an INSTALLABLE QA binary (signed IPA / APK) on
@@ -67379,7 +67419,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
67379
67419
  the in-repo default is a local dev plane that isn't
67380
67420
  reachable off-repo.
67381
67421
  --shell picks the native host to sign into (branded-lite |
67382
- owner-lite | player | demo-app). --signing defaults to
67422
+ owner-lite | brandy | player | demo-app). --signing defaults to
67383
67423
  wix-managed: the Wix Apple account mints a per-app
67384
67424
  identity for the build, so you provision nothing.
67385
67425
  --signing byo (your own Apple account) is reserved and
@@ -67667,7 +67707,7 @@ var COMMANDS = [
67667
67707
  { name: "patch", summary: 'change ONE item of a published app \u2014 a screen descriptor, a Tier-5 bundle, tokens, or the app-config baseline \u2014 from a RAW FILE, with no content package and no build. The low-level door: it does not lint your package, because there is no package here; `himi push --only <selector>` is the same edit with the full build + lint gate and is what to reach for when the package is in hand. Derives a new release from the app\'s current LIVE release, or from `--from-release <id>`. An explicit `--draft` or `--live` is REQUIRED \u2014 a patch has no default blast radius \u2014 and `--notes` is required with `--live` on a screen or app-config patch, because off-repo `gitSha` is "dev" and the ledger notes are then the only durable record of bytes that correspond to no commit. A branch does not merge: activating one derived from an older release reverts every item the patch does not move, so that case is refused until `--accept-regressions`. A bundle patch must repoint a screen (`--for-screen`) or the bytes land under a manifest key nothing reads. It cannot ADD a screen (screens are git-only, RFC 0012), never claims a slug \u2014 the app must already be published \u2014 and carries no attestation, source archive, assets, icon or minBinaryVersion, each named in the output.', required: "screen <id> --file <descriptor.json> [--bundle <sha256|none>] | bundle --file <bundle.js> (--for-screen <id>[,<id>] | --no-rebind) | tokens --file <tokens.json> | app-config --file <app-config.json> ; (--draft|--live) --app <a> [--from-release <id>] [--accept-regressions] [--label <n>] [--notes <t>] [--force] [--target <t>] (--control-token)" },
67668
67708
  { name: "flags", summary: "read and change the app's OPERATIONAL flags (RFC 0013) \u2014 `list` prints what the DEVICE door serves, `set <key> --definition` creates or replaces one, `ramp <key>` moves its rollout share or kills it, `rm <key>` deletes it and echoes the definition back so the removal is reversible. Every write is followed by a read of the same door the device uses, and the answer is reported as `servedNow`: flags are stored in the app's git-committed flags.json, so a control plane with no checkout accepts a write it will never serve.", required: "list|set|ramp|rm [<key>] [--definition <json|@file>] [--percent 0-100] [--enabled true|false] [--app <a>] (--control-token)" },
67669
67709
  { name: "visibility", summary: "read (no value) or set whether the public registry (/v1/apps + the Himi Player picker) LISTS the app. `public` lists it for everyone; `unlisted` hides it from listings while direct reads by app id \u2014 deep links, previews \u2014 keep working. App from --app or the content dir. Setting it needs no republish.", required: "[public|unlisted] [--app <a>] [--content <dir>] (--control-token)" },
67670
- { name: "dist", summary: "build an INSTALLABLE QA binary (signed IPA / APK) on the Wix Job Service Mac pool and return an install link \u2014 the serve plane brokers the job, the pool builds it. WAITS for the build by default (~4 min) and prints the install link; `--no-wait` returns a jobId immediately instead. Omitting `--release` builds whatever is LIVE for the app. A named draft is refused unless `--allow-draft` makes that exceptional build explicit. `status <jobId>` / `watch <jobId>` / `list --app <a>` / `cancel <jobId>` inspect and stop builds (`watch` reports up front whether the job mints its own signing identity or borrows one, so a doomed iOS build is cancellable in seconds rather than after the archive). Off-repo, name a target \u2014 `--target wix` for a self-serve Wix login or `--target bo` for the internal employee path (the in-repo default is a local dev plane that isn't reachable off-repo); self-serve callers may build their OWN apps under a tighter budget (2 in flight / 5 per 24h), while a manage-only owner-role credential is refused. `--shell` picks the native host to sign into (branded-lite | owner-lite | player | demo-app). `--signing` defaults to `wix-managed` \u2014 the Wix Apple account mints a per-app signing identity for the build, so the caller provisions no Apple credentials of their own; `--signing byo` (sign with your own Apple account) is reserved and 501s. `--android-signing` is the Android counterpart and a SEPARATE axis (a keystore, not an Apple account): `qa-keystore` (default \u2014 the repo's committed QA key, identical on every pool machine, so a build installs over the previous one), `wix-keystore` (the Wix key the pool machine carries; the build FAILS naming the paths searched if it carries none, rather than quietly signing with something else), or `debug` (the old per-machine key \u2014 QA must uninstall any previous build first). NOT the normal way to ship: screens, Tier-5 bundles, tokens and app-config all go over the air with `himi push` in seconds \u2014 a binary trip is only needed for a NATIVE delta (a new SDUI component, a new declared capability/entitlement, a new bundle id, or a minBinaryVersion floor bump). Refuses to build an app whose launcher icon is missing, a placeholder, or one a store would reject \u2014 the one thing a binary carries that no later OTA push can fix; `--force` builds anyway", required: "[--app <a>] [--platform ios,android] [--release <id>] [--allow-draft] [--target <t>] [--shell branded-lite|owner-lite|player|demo-app] [--signing wix-managed|byo] [--android-signing qa-keystore|wix-keystore|debug] [--borrow-ios-id <bundleId>] [--no-wait] [--force] | status <jobId> [--target <t>] | watch <jobId> [--target <t>] [--interval <ms>] | list --app <a> [--target <t>] | cancel <jobId> [--target <t>]" },
67710
+ { name: "dist", summary: "build an INSTALLABLE QA binary (signed IPA / APK) on the Wix Job Service Mac pool and return an install link \u2014 the serve plane brokers the job, the pool builds it. WAITS for the build by default (~4 min) and prints the install link; `--no-wait` returns a jobId immediately instead. Omitting `--release` builds whatever is LIVE for the app. A named draft is refused unless `--allow-draft` makes that exceptional build explicit. `status <jobId>` / `watch <jobId>` / `list --app <a>` / `cancel <jobId>` inspect and stop builds (`watch` reports up front whether the job mints its own signing identity or borrows one, so a doomed iOS build is cancellable in seconds rather than after the archive). Off-repo, name a target \u2014 `--target wix` for a self-serve Wix login or `--target bo` for the internal employee path (the in-repo default is a local dev plane that isn't reachable off-repo); self-serve callers may build their OWN apps under a tighter budget (2 in flight / 5 per 24h), while a manage-only owner-role credential is refused. `--shell` picks the native host to sign into (branded-lite | owner-lite | brandy | player | demo-app); `brandy` is the site shell \u2014 the customer's own website as native-framed tabs. `--signing` defaults to `wix-managed` \u2014 the Wix Apple account mints a per-app signing identity for the build, so the caller provisions no Apple credentials of their own; `--signing byo` (sign with your own Apple account) is reserved and 501s. `--android-signing` is the Android counterpart and a SEPARATE axis (a keystore, not an Apple account): `qa-keystore` (default \u2014 the repo's committed QA key, identical on every pool machine, so a build installs over the previous one), `wix-keystore` (the Wix key the pool machine carries; the build FAILS naming the paths searched if it carries none, rather than quietly signing with something else), or `debug` (the old per-machine key \u2014 QA must uninstall any previous build first). NOT the normal way to ship: screens, Tier-5 bundles, tokens and app-config all go over the air with `himi push` in seconds \u2014 a binary trip is only needed for a NATIVE delta (a new SDUI component, a new declared capability/entitlement, a new bundle id, or a minBinaryVersion floor bump). Refuses to build an app whose launcher icon is missing, a placeholder, or one a store would reject \u2014 the one thing a binary carries that no later OTA push can fix; `--force` builds anyway", required: "[--app <a>] [--platform ios,android] [--release <id>] [--allow-draft] [--target <t>] [--shell branded-lite|owner-lite|brandy|player|demo-app] [--signing wix-managed|byo] [--android-signing qa-keystore|wix-keystore|debug] [--borrow-ios-id <bundleId>] [--no-wait] [--force] | status <jobId> [--target <t>] | watch <jobId> [--target <t>] [--interval <ms>] | list --app <a> [--target <t>] | cancel <jobId> [--target <t>]" },
67671
67711
  { name: "testflight", summary: 'build, sign and upload an app to TestFlight (internal testers \u2014 no Beta App Review), then WAIT until App Store Connect finishes processing it. Sugar for `store upload --track testflight`. Prompts first: a build number is consumed permanently on a real Apple account and cannot be reused \u2014 `--yes` for CI, `--dry-run` to see the plan without touching anything. The wait is the real check, not a nicety: fastlane prints "Successfully uploaded" for a binary App Store Connect then rejects in processing, and a rejected binary leaves NO build record at all (the reason is emailed). Public App Store review is deliberately NOT automatable here \u2014 `--track appstore` is refused; use /submit-ios-app when you mean to go public', required: "<app> [--track testflight|internal] [--profile <name>] [--yes] [--dry-run] [--skip-build] [--no-wait] [--json]" },
67672
67712
  { name: "store", summary: "the App Store Connect group behind `testflight`. `preflight <app>` checks readiness offline (icon, entitlements, encryption flag, version, store.config ids, listing + review record); `upload <app>` is what `testflight` runs; `status <app>` reads live build + processing state. `--profile` selects WHICH App Store Connect account: an app built for a Wix customer ships under the CUSTOMER's Apple team (Apple guideline 4.2.6 forbids an app-generation service submitting on a client's behalf), declared per-app as ios.credentialProfile in store.config.yaml and resolved from ~/.himalaya/store-credentials/<name>.json \u2014 the .p8 itself never enters the repo", required: "preflight <app> [--profile <n>] | upload <app> [--track <t>] [--profile <n>] [--yes] [--dry-run] | status <app> [--profile <n>]" },
67673
67713
  { name: "guide", summary: "print the Himalaya overview + headless dev loop + available topics (offline)", required: "[--json]" },
Binary file
@@ -6,6 +6,11 @@ app that is **another client of that site** — same catalog, CMS, members, form
6
6
  IA and UX rewritten for a phone. Not a WebView. Not a shrunk desktop. Not
7
7
  `--apply` onto Branded Lite and stop.
8
8
 
9
+ > **Not this page: "my website, as an app, today."** That is the *site shell*
10
+ > (`himi dist --shell brandy`) — the customer's live site in native-framed WebView tabs. It is a separate, day-0 path that
11
+ > graduates to this page's native app one tab at a time; it never replaces it. Everything
12
+ > below is the native app.
13
+
9
14
  This page is the body of `himi docs wix-site-to-himi-app`. The hosted CLI skill
10
15
  ([skill.md](https://apps.wix.com/_api/himi-serve/skill.md)) should send you here
11
16
  when the user gave a site URL / client id. Analyze is collect+judge
@@ -248,7 +253,7 @@ state), and pass `himi test`. Then run
248
253
  - Never invoke Velo HTTP functions **from analyze** (or as a substitute for rewriting Velo *pages*). To create/update/publish the site's HTTP API use `himi site function` (`himi docs wix-site-http-functions`).
249
254
  - Never log contact PII; never paste `secrets-backend`.
250
255
  - Never invent CMS schemas when the book’s CMS chapter is empty but the site has forms.
251
- - Never pixel-clone the website; never wrap it in a WebView.
256
+ - Never pixel-clone the website; never wrap it in a WebView (the WebView product is the separate site shell, `--shell brandy` — not this guide's output).
252
257
  - Never edit the Himalaya monorepo’s committed `apps/himi-branded-lite/provisioning.json`.
253
258
 
254
259
  ## See also
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1067.0",
3
+ "version": "0.1068.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": [
@@ -65,5 +65,5 @@
65
65
  "artifactId": "himalaya-cli"
66
66
  }
67
67
  },
68
- "falconPackageHash": "0c8d3503f5ca13101d4857f4aea03d5974e1c26ac88f40e350b6d4de"
68
+ "falconPackageHash": "d27d980f58b63c6236045887c8cfe93d017ac0d63ffdb1bcba762d90"
69
69
  }