@wix/himalaya-cli 0.1067.0 → 0.1069.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
@@ -484,6 +484,36 @@ var init_ownership = __esm({
484
484
  }
485
485
  });
486
486
 
487
+ // ../../core/serve-storage/src/wip-pointer.ts
488
+ function wipSupported(store) {
489
+ if (!store.getWipRecord || !store.updateWipRecord) return false;
490
+ return store.supportsWip ? store.supportsWip() : true;
491
+ }
492
+ function normalizeWipRecord(value) {
493
+ if (!value || typeof value !== "object") return { wip: null, promoted: [], evictedBefore: null };
494
+ const v = value;
495
+ const wip = v.wip && typeof v.wip.releaseId === "string" ? v.wip : null;
496
+ const promoted = (Array.isArray(v.promoted) ? v.promoted : []).flatMap((entry) => {
497
+ if (typeof entry === "string") return [{ id: entry, at: null }];
498
+ const e = entry;
499
+ return e && typeof e.id === "string" ? [{ id: e.id, at: storedTime(e.at) }] : [];
500
+ });
501
+ return { wip, promoted, evictedBefore: storedTime(v.evictedBefore) };
502
+ }
503
+ async function readWip(store, app) {
504
+ if (!wipSupported(store)) return "unsupported";
505
+ const { wip, promoted } = normalizeWipRecord(await store.getWipRecord(app));
506
+ if (!wip || promoted.some((p) => p.id === wip.releaseId)) return null;
507
+ const live2 = await store.getManifest(app);
508
+ return live2?.releaseId === wip.releaseId ? null : wip;
509
+ }
510
+ var storedTime;
511
+ var init_wip_pointer = __esm({
512
+ "../../core/serve-storage/src/wip-pointer.ts"() {
513
+ storedTime = (value) => typeof value === "string" && !Number.isNaN(Date.parse(value)) ? value : null;
514
+ }
515
+ });
516
+
487
517
  // ../../core/serve-storage/src/store.ts
488
518
  function validateFunctionManifest(manifest) {
489
519
  const dataErrors = validateDataResources(manifest.functionDataResources, manifest.app).filter((issue2) => issue2.severity === "error");
@@ -526,6 +556,14 @@ async function retainedReleaseRefs(store, app, keepRecent = 20, keepReleaseIds =
526
556
  }
527
557
  }
528
558
  }
559
+ const offered = await readWip(store, app);
560
+ if (offered && offered !== "unsupported" && !loaded.has(offered.releaseId) && store.getManifestById) {
561
+ const m = await store.getManifestById(app, offered.releaseId);
562
+ if (m) {
563
+ manifests.push(m);
564
+ loaded.add(m.releaseId);
565
+ }
566
+ }
529
567
  for (const releaseId of new Set(keepReleaseIds)) {
530
568
  if (loaded.has(releaseId)) continue;
531
569
  if (!store.getManifestById) throw new Error(`cannot retain function job release ${releaseId}: versioned manifest lookup is unsupported`);
@@ -554,6 +592,7 @@ var TEMPLATE_APP_PREFIX;
554
592
  var init_store = __esm({
555
593
  "../../core/serve-storage/src/store.ts"() {
556
594
  init_ownership();
595
+ init_wip_pointer();
557
596
  TEMPLATE_APP_PREFIX = "himi-template-";
558
597
  }
559
598
  });
@@ -764,34 +803,28 @@ var init_file_store = __esm({
764
803
  else ledger.push(entry);
765
804
  writeFileSync2(this.ledgerPath(app), JSON.stringify(ledger, null, 2));
766
805
  }
767
- // ---- RFC 0061 §3.1 owner-follow `wip` pointer ----
768
- // Inside the app directory, so `remove` and `reset` take it with the app. `clearWipIf` is a CAS
769
- // for the way this backend is deployed — ONE writer process (serve-control; serve only reads):
770
- // the read, compare and delete below are synchronous with no await between them, so no other
806
+ // ---- RFC 0061 §3.1 owner-follow `wip` record ----
807
+ // Inside the app directory, so `remove` and `reset` take it with the app. `updateWipRecord` is a
808
+ // CAS for the way this backend is deployed — ONE writer process (serve-control; serve only
809
+ // reads): the read, `fn` and write below are synchronous with no await between them, so no other
771
810
  // request in that process can land in the middle. It is not a cross-process lock.
772
- wipPath(app) {
773
- return join2(this.appDir(app), "_wip.json");
774
- }
775
811
  readWipSync(app) {
776
812
  const b = this.read(app, "_wip.json");
777
813
  if (!b) return null;
778
814
  try {
779
- const wip = JSON.parse(b.toString("utf8"));
780
- return wip && typeof wip.releaseId === "string" ? wip : null;
815
+ return JSON.parse(b.toString("utf8"));
781
816
  } catch {
782
817
  return null;
783
818
  }
784
819
  }
785
- async putWip(app, wip) {
786
- mkdirSync2(this.appDir(app), { recursive: true });
787
- writeFileSync2(this.wipPath(app), JSON.stringify(wip, null, 2));
788
- }
789
- async getWip(app) {
820
+ async getWipRecord(app) {
790
821
  return this.readWipSync(app);
791
822
  }
792
- async clearWipIf(app, releaseId) {
793
- if (this.readWipSync(app)?.releaseId !== releaseId) return false;
794
- rmSync(this.wipPath(app), { force: true });
823
+ async updateWipRecord(app, fn) {
824
+ const next = fn(this.readWipSync(app));
825
+ if (next === void 0) return false;
826
+ mkdirSync2(this.appDir(app), { recursive: true });
827
+ writeFileSync2(join2(this.appDir(app), "_wip.json"), JSON.stringify(next, null, 2));
795
828
  return true;
796
829
  }
797
830
  bundleUrl(_app, sha2565) {
@@ -3870,6 +3903,14 @@ var init_dist_jobs = __esm({
3870
3903
  bundleId: { ios: "ent.wix.ownerlite.dev", android: "com.wix.himi.ownerlite.dev" },
3871
3904
  provisioningEnv: "HIMI_OWNER_LITE_PROVISIONING_JSON"
3872
3905
  },
3906
+ // RFC 0062 — the site shell: the customer's own website as native-framed Tier-3 tabs.
3907
+ // Brandable (its package.json declares prepare:customer), so dist bakes the fetched
3908
+ // contract's identity, icon, brand colour and `site` block into the binary.
3909
+ brandy: {
3910
+ appDir: "apps/himi-brandy",
3911
+ bundleId: { ios: "ent.wix.brandy.dev", android: "com.wix.himi.brandy.dev" },
3912
+ provisioningEnv: "HIMI_BRANDY_PROVISIONING_JSON"
3913
+ },
3873
3914
  player: {
3874
3915
  appDir: "apps/himi-player",
3875
3916
  bundleId: { ios: "dev.himalaya.player.dev", android: "dev.himalaya.player.dev" },
@@ -22305,7 +22346,7 @@ function bindingPlaceholder(v, ctx) {
22305
22346
  if (ctx.runtimeDegraded) return null;
22306
22347
  return /* @__PURE__ */ jsx("span", { style: { color: ctx.color("textSecondary", "#9aa2ad"), fontStyle: "italic", overflowWrap: "anywhere", wordBreak: "break-word" }, children: v.replace(/^\$/, "") });
22307
22348
  }
22308
- function mdScan(src) {
22349
+ function mdScan(src, linkStyle = "") {
22309
22350
  const re = new RegExp(MD_INLINE.source, "g");
22310
22351
  let out = "";
22311
22352
  let last = 0;
@@ -22317,21 +22358,22 @@ function mdScan(src) {
22317
22358
  } else if (label2 !== void 0) {
22318
22359
  if (/^https?:\/\//i.test(url)) {
22319
22360
  const href = mdEsc(url).replace(/"/g, "&quot;");
22320
- out += `<a href="${href}" target="_blank" rel="noopener noreferrer">${mdScan(label2)}</a>`;
22361
+ out += `<a href="${href}" target="_blank" rel="noopener noreferrer"${linkStyle}>${mdScan(label2, linkStyle)}</a>`;
22321
22362
  } else {
22322
22363
  out += mdEsc(whole);
22323
22364
  }
22324
22365
  } else if (bold !== void 0) {
22325
- out += `<b>${mdScan(bold)}</b>`;
22366
+ out += `<b>${mdScan(bold, linkStyle)}</b>`;
22326
22367
  } else {
22327
- out += `<i>${mdScan(italic)}</i>`;
22368
+ out += `<i>${mdScan(italic, linkStyle)}</i>`;
22328
22369
  }
22329
22370
  last = m.index + whole.length;
22330
22371
  }
22331
22372
  return out + mdEsc(src.slice(last));
22332
22373
  }
22333
- function mdInline(text5) {
22334
- return { __html: mdScan(text5) };
22374
+ function mdInline(text5, linkColor) {
22375
+ const safe = typeof linkColor === "string" && /^[#a-zA-Z0-9(),.%\s-]+$/.test(linkColor) ? linkColor : "";
22376
+ return { __html: mdScan(text5, safe ? ` style="color:${safe}"` : "") };
22335
22377
  }
22336
22378
  function sevColor(ctx, sev) {
22337
22379
  switch (sev) {
@@ -22346,6 +22388,20 @@ function sevColor(ctx, sev) {
22346
22388
  return ctx.color("accentPrimary", "#116DFF");
22347
22389
  }
22348
22390
  }
22391
+ function accentTextColor(ctx) {
22392
+ return ctx.color("accentText", ctx.color("accentPrimary", "#116DFF"));
22393
+ }
22394
+ function sevTextColor(ctx, sev) {
22395
+ switch (sev) {
22396
+ case "success":
22397
+ case "warning":
22398
+ case "danger":
22399
+ case "error":
22400
+ return sevColor(ctx, sev);
22401
+ default:
22402
+ return accentTextColor(ctx);
22403
+ }
22404
+ }
22349
22405
  function iconGlyph(name) {
22350
22406
  if (typeof name !== "string" || !name) return "\xB7";
22351
22407
  const key2 = name.toLowerCase().replace(/[_.\s-]/g, "");
@@ -25730,7 +25786,7 @@ var init_controls = __esm({
25730
25786
  "aria-selected": on3,
25731
25787
  tabIndex: on3 ? 0 : -1,
25732
25788
  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 },
25789
+ 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
25790
  children: [
25735
25791
  asText2(t.label),
25736
25792
  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 +27859,6 @@ function actionClick(ctx, action) {
27803
27859
  ctx.onAction(resolveItemDeep2(action, ctx));
27804
27860
  };
27805
27861
  }
27806
- function accentTextColor(ctx) {
27807
- return ctx.color("accentText", ctx.color("accentPrimary", "#116DFF"));
27808
- }
27809
27862
  function actionTextButton(ctx, action, label2, color, extra) {
27810
27863
  if (action == null) return null;
27811
27864
  const onClick = actionClick(ctx, action);
@@ -28486,7 +28539,7 @@ function renderInner(node, ctx) {
28486
28539
  /* @__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
28540
  /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), fontWeight: 600, fontSize: 16, color: C("textPrimary", "#111") }, children: asBoundText(props.title, ctx) || "Empty" }),
28488
28541
  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" })
28542
+ actionTextButton(ctx, props.action, asBoundText(props.actionLabel, ctx) || "Take action", accentTextColor(ctx), { margin: "12px auto 0", textAlign: "center" })
28490
28543
  ] });
28491
28544
  // ───────────── atoms ─────────────
28492
28545
  case "Text":
@@ -28496,7 +28549,7 @@ function renderInner(node, ctx) {
28496
28549
  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
28550
  const marks = Array.isArray(r.marks) ? r.marks : [];
28498
28551
  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);
28552
+ 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
28553
  }) });
28501
28554
  }
28502
28555
  case "Image":
@@ -28525,11 +28578,14 @@ function renderInner(node, ctx) {
28525
28578
  const liveCount = typeof props.text === "string" && /^\$(?:service|flag)\b/.test(props.text);
28526
28579
  const badgeLabel = m?.accessibilityLabel ?? m?.accessibility_label;
28527
28580
  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" });
28581
+ const fg = _bt ? c : sevTextColor(ctx, props.style ?? "info");
28582
+ 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
28583
  }
28530
28584
  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" });
28585
+ const tagToken = boundTokenName(props.tokenPath, props.token, ctx);
28586
+ const c = C(tagToken ?? "accentPrimary", "#116DFF");
28587
+ const tagFg = tagToken ? c : accentTextColor(ctx);
28588
+ 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
28589
  }
28534
28590
  case "ProgressBar":
28535
28591
  return /* @__PURE__ */ jsx6(ProgressBarNode, { props, ctx, wrap });
@@ -28780,7 +28836,7 @@ function renderInner(node, ctx) {
28780
28836
  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
28837
  cfg.title && /* @__PURE__ */ jsx6("div", { style: { fontSize: 15, fontWeight: 600, color: C("textPrimary", "#111") }, children: asText2(cfg.title) }),
28782
28838
  (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" })
28839
+ 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
28840
  ] }) : null });
28785
28841
  }
28786
28842
  case "FragmentSlot":
@@ -30411,7 +30467,8 @@ var init_renderNode = __esm({
30411
30467
  display: "grid",
30412
30468
  placeItems: "center",
30413
30469
  fontSize: 18,
30414
- color: C("accentPrimary", "#116DFF"),
30470
+ color: accentTextColor(ctx),
30471
+ // #3275 — a glyph on a neutral key
30415
30472
  border: `1px solid ${C("border", "#e3e3e3")}`,
30416
30473
  cursor: ctx.onAction && !disabled(at) ? "pointer" : "default",
30417
30474
  opacity: disabled(at) ? 0.4 : 1,
@@ -30596,7 +30653,7 @@ var init_renderNode = __esm({
30596
30653
  const resolved2 = resolveScalarStatic(props.text, ctx);
30597
30654
  return resolved2 == null ? null : String(resolved2);
30598
30655
  })();
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);
30656
+ 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
30657
  const tAlign = props.textAlign === "center" ? "center" : props.textAlign === "trailing" ? "right" : props.textAlign === "justify" ? "justify" : props.textAlign === "leading" ? "left" : void 0;
30601
30658
  const tDecor = props.decoration === "strikethrough" ? "line-through" : props.decoration === "underline" ? "underline" : void 0;
30602
30659
  const ln = typeof props.lineSpacing === "number" ? pxToRem((f.line ?? 1.4) * f.size + props.lineSpacing) : f.line;
@@ -30642,7 +30699,7 @@ var init_renderNode = __esm({
30642
30699
  }
30643
30700
  if (props.icon != null && props.url == null && props.asset == null && imgInitials == null) {
30644
30701
  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") }) });
30702
+ 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
30703
  }
30647
30704
  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
30705
  });
@@ -31319,11 +31376,13 @@ var init_renderNode = __esm({
31319
31376
  const effectivePositionMs = seeked ?? positionMs;
31320
31377
  const progress = scrub ?? (durationMs > 0 ? Math.min(Math.max(effectivePositionMs / durationMs, 0), 1) : 0);
31321
31378
  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");
31379
+ const tintToken = resolveScalarStatic(props.tint, ctx);
31380
+ const tint = C(tintToken ?? "accentPrimary", "#116DFF");
31323
31381
  const danger = C("danger", "#E62214");
31324
31382
  const secondary = C("textSecondary", "#636B78");
31325
31383
  const onAccent = C("textOnAccent", "#FFFFFF");
31326
31384
  const accent = isFailed ? danger : tint;
31385
+ const accentLabel = isFailed ? danger : tintToken ? tint : accentTextColor(ctx);
31327
31386
  const emit = (action, extra) => {
31328
31387
  if (!ctx.onAction || !action) return;
31329
31388
  const a = resolveItemDeep2(action, ctx);
@@ -31491,7 +31550,7 @@ var init_renderNode = __esm({
31491
31550
  fontSize: 11,
31492
31551
  fontWeight: 600,
31493
31552
  fontFamily: "inherit",
31494
- color: accent,
31553
+ color: accentLabel,
31495
31554
  background: C("surfaceSecondary", "#F5F7FA"),
31496
31555
  borderRadius: 999,
31497
31556
  padding: "3px 7px"
@@ -32156,7 +32215,7 @@ var init_renderNode = __esm({
32156
32215
  }
32157
32216
  )
32158
32217
  ] }),
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)) }),
32218
+ /* @__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
32219
  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
32220
  const tappable = !!(ev?.onTap || props.onSelectEvent);
32162
32221
  return /* @__PURE__ */ jsx6(
@@ -33832,6 +33891,12 @@ var init_app_registry_client = __esm({
33832
33891
  }
33833
33892
  });
33834
33893
 
33894
+ // ../../core/ts/src/site-shell.ts
33895
+ var init_site_shell = __esm({
33896
+ "../../core/ts/src/site-shell.ts"() {
33897
+ }
33898
+ });
33899
+
33835
33900
  // ../../core/ts/src/index.ts
33836
33901
  var init_src5 = __esm({
33837
33902
  "../../core/ts/src/index.ts"() {
@@ -33858,6 +33923,7 @@ var init_src5 = __esm({
33858
33923
  init_app_link();
33859
33924
  init_app_identity();
33860
33925
  init_app_registry_client();
33926
+ init_site_shell();
33861
33927
  }
33862
33928
  });
33863
33929
 
@@ -38191,12 +38257,6 @@ var init_src6 = __esm({
38191
38257
  }
38192
38258
  });
38193
38259
 
38194
- // ../../core/serve-storage/src/wip-pointer.ts
38195
- var init_wip_pointer = __esm({
38196
- "../../core/serve-storage/src/wip-pointer.ts"() {
38197
- }
38198
- });
38199
-
38200
38260
  // ../../core/serve-storage/src/release-patch.ts
38201
38261
  import { createHash as createHash17 } from "node:crypto";
38202
38262
  function sha256hex(b) {
@@ -53071,21 +53131,21 @@ var init_preview_assets_generated = __esm({
53071
53131
  "../../packages/serve/src/preview-assets.generated.ts"() {
53072
53132
  init_preview_assets_format();
53073
53133
  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" }
53134
+ { key: "assets/index-DlDXdHId.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1063092, sha256: "758c655ba88cbb54101f690281513fcff56aaa8b4a31dd49545d0f30f45e05f8" },
53135
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1063100, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
53136
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1067710, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
53137
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1556294, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
53138
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1647218, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
53139
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1738202, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
53140
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1829182, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
53141
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1920054, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
53142
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2011034, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
53143
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2119670, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
53144
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2228754, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
53145
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2337802, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
53146
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2445502, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
53147
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2533610, length: 4947, sha256: "7a5bedfb0fdf0c376ce40bba70539332bca4b2fad4e4112465fb1875e03aa29b" },
53148
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2538557, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
53089
53149
  ];
53090
53150
  }
53091
53151
  });
@@ -59418,6 +59478,13 @@ async function applyOverlays(input) {
59418
59478
  );
59419
59479
  }
59420
59480
  const appDir2 = join61(repoRoot2, shellAppDir(shell));
59481
+ const committedPath = join61(appDir2, "provisioning.json");
59482
+ const committedSite = existsSync62(committedPath) ? JSON.parse(readFileSync73(committedPath, "utf8")).site : void 0;
59483
+ if (committedSite !== void 0 && overlay.site === void 0) {
59484
+ throw new Error(
59485
+ `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}\`).`
59486
+ );
59487
+ }
59421
59488
  const packageDir = dirname33(resolve45(input.overlayPath));
59422
59489
  const { path: contract, staged } = buildMergedContract({ appDir: appDir2, packageDir, overlay });
59423
59490
  wrote.push(...staged);
@@ -67367,7 +67434,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
67367
67434
  carries no attestation, source archive, assets, icon or
67368
67435
  minBinaryVersion \u2014 all named in the output.
67369
67436
  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]
67437
+ [--shell branded-lite|owner-lite|brandy|player|demo-app] [--signing wix-managed|byo]
67371
67438
  [--android-signing qa-keystore|wix-keystore|debug]
67372
67439
  [--borrow-ios-id <bundleId>] [--no-wait] [--force]
67373
67440
  build an INSTALLABLE QA binary (signed IPA / APK) on
@@ -67379,7 +67446,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
67379
67446
  the in-repo default is a local dev plane that isn't
67380
67447
  reachable off-repo.
67381
67448
  --shell picks the native host to sign into (branded-lite |
67382
- owner-lite | player | demo-app). --signing defaults to
67449
+ owner-lite | brandy | player | demo-app). --signing defaults to
67383
67450
  wix-managed: the Wix Apple account mints a per-app
67384
67451
  identity for the build, so you provision nothing.
67385
67452
  --signing byo (your own Apple account) is reserved and
@@ -67667,7 +67734,7 @@ var COMMANDS = [
67667
67734
  { 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
67735
  { 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
67736
  { 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>]" },
67737
+ { 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
67738
  { 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
67739
  { 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
67740
  { 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.1069.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": "d66a8a95769c3eb84b52d4da2233db6689f1f833a2b254304aff2bc0"
69
69
  }