@wix/himalaya-cli 0.1022.0 → 0.1024.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.
package/dist/cli.mjs CHANGED
@@ -6004,6 +6004,22 @@ var init_validator = __esm({
6004
6004
  enum: ["selected", "disabled", "updates_frequently", "plays_sound", "starts_media"]
6005
6005
  }
6006
6006
  },
6007
+ // RFC 0058 §4 — focus and announcement (core.proto Modifiers 70–73). `modifiers`
6008
+ // is `additionalProperties: false`, so a descriptor carrying one of these would be
6009
+ // REJECTED on every arm at once until the validator knows it — which makes this
6010
+ // part of the vocabulary rather than a downstream consumer of it.
6011
+ focusOrder: { type: "integer" },
6012
+ focusGroup: { type: "boolean" },
6013
+ initialFocus: { type: "boolean" },
6014
+ announce: {
6015
+ type: "object",
6016
+ additionalProperties: false,
6017
+ properties: {
6018
+ politeness: { type: "string", enum: ["polite", "assertive"] },
6019
+ text: { type: "string" },
6020
+ trigger: { type: "string", enum: ["on_change", "on_appear", "both", "none"] }
6021
+ }
6022
+ },
6007
6023
  // #1052 — explicit drop shadow (colour token + radius + offset), alongside the `shadow` token form.
6008
6024
  boxShadow: {
6009
6025
  type: "object",
@@ -8851,6 +8867,7 @@ var init_validate_descriptor_core = __esm({
8851
8867
  scrollToDatePath: "string",
8852
8868
  stickyHeaders: "boolean"
8853
8869
  },
8870
+ // RFC 0058 §4 — `modal` is the focus-trap tri-state on each of the four overlays.
8854
8871
  AlertDialog: {
8855
8872
  cancelAction: "any",
8856
8873
  cancelLabel: "string",
@@ -8858,6 +8875,7 @@ var init_validate_descriptor_core = __esm({
8858
8875
  confirmLabel: "string",
8859
8876
  destructive: "boolean",
8860
8877
  message: "string",
8878
+ modal: "boolean",
8861
8879
  title: "string"
8862
8880
  },
8863
8881
  Avatar: { imageUrl: "string", initials: "string", size: "number" },
@@ -8887,6 +8905,7 @@ var init_validate_descriptor_core = __esm({
8887
8905
  detents: "array",
8888
8906
  dismissible: "boolean",
8889
8907
  edge: "string",
8908
+ modal: "boolean",
8890
8909
  onDismiss: "any",
8891
8910
  presented: "boolean",
8892
8911
  presentedPath: "string",
@@ -9170,7 +9189,7 @@ var init_validate_descriptor_core = __esm({
9170
9189
  startIndex: "number",
9171
9190
  startPositionSeconds: "number"
9172
9191
  },
9173
- Menu: { anchor: "any", items: "array", itemsPath: "string", style: "string" },
9192
+ Menu: { anchor: "any", items: "array", itemsPath: "string", modal: "boolean", style: "string" },
9174
9193
  MonthCalendar: {
9175
9194
  decorations: "array",
9176
9195
  decorationsPath: "string",
@@ -9223,6 +9242,7 @@ var init_validate_descriptor_core = __esm({
9223
9242
  anchor: "any",
9224
9243
  content: "any",
9225
9244
  edge: "string",
9245
+ modal: "boolean",
9226
9246
  onDismiss: "any",
9227
9247
  presented: "boolean",
9228
9248
  presentedPath: "string"
@@ -9653,6 +9673,7 @@ var init_validate_descriptor_core = __esm({
9653
9673
  "blur",
9654
9674
  "cornerRadius",
9655
9675
  "enterDelayMs",
9676
+ "focusOrder",
9656
9677
  "headingLevel",
9657
9678
  "height",
9658
9679
  "maxWidth",
@@ -17303,6 +17324,21 @@ function absoluteUrlRejection(path2) {
17303
17324
  }
17304
17325
  return OWNER_SCOPED_WIX_HOST.test(host) ? "owner-only" : "open-proxy";
17305
17326
  }
17327
+ async function mintAnonymousVisitor(clientId, fetchImpl = fetch) {
17328
+ const res = await fetchImpl(`${WIX_API}/oauth2/token`, {
17329
+ method: "POST",
17330
+ headers: { "Content-Type": "application/json" },
17331
+ body: JSON.stringify({ clientId, grantType: "anonymous" })
17332
+ });
17333
+ const text5 = await res.text();
17334
+ let j = {};
17335
+ try {
17336
+ j = text5 ? JSON.parse(text5) : {};
17337
+ } catch {
17338
+ }
17339
+ if (res.status !== 200 || !j.access_token) throw new Error(`visitor mint failed (${res.status})`);
17340
+ return j.access_token;
17341
+ }
17306
17342
  async function wixVisitorCall(method, path2, body, token, fetchImpl = fetch, forwardedHeaders = {}) {
17307
17343
  if (/^[a-z][a-z0-9+.-]*:\/\//i.test(path2)) throw new Error("absolute URLs are not allowed");
17308
17344
  const url = `${WIX_API}${path2.startsWith("/") ? path2 : `/${path2}`}`;
@@ -17379,43 +17415,13 @@ function wixClientIdOf(appConfigJson) {
17379
17415
  if (typeof clientId !== "string" || !clientId) return { error: "no_client_id" };
17380
17416
  return { clientId };
17381
17417
  }
17382
- var WIX_API, ONE_APP_SESSION, OWNER_SCOPED_WIX_HOST, VisitorTokenCache, WIX_FORWARDABLE_HEADERS, OWNER_REFRESH_MARGIN_MS, OWNER_RENEW_MIN_AGE_MS, OWNER_MINT_TIMEOUT_MS, OwnerInstanceCache, OWNER_SITES_QUERY_PATH, OWNER_SITES_COUNT_PATH, OwnerSitesCache;
17418
+ var WIX_API, ONE_APP_SESSION, OWNER_SCOPED_WIX_HOST, WIX_FORWARDABLE_HEADERS, OWNER_REFRESH_MARGIN_MS, OWNER_RENEW_MIN_AGE_MS, OWNER_MINT_TIMEOUT_MS, OwnerInstanceCache, OWNER_SITES_QUERY_PATH, OWNER_SITES_COUNT_PATH, OwnerSitesCache;
17383
17419
  var init_wix_bridge = __esm({
17384
17420
  "../../core/serve-runtime/src/wix-bridge.ts"() {
17385
17421
  init_sha256();
17386
17422
  WIX_API = "https://www.wixapis.com";
17387
17423
  ONE_APP_SESSION = "https://apps.wix.com/_api/one-app-session-web/v3/businesses";
17388
17424
  OWNER_SCOPED_WIX_HOST = /(^|\.)wix\.com$/i;
17389
- VisitorTokenCache = class {
17390
- constructor(fetchImpl = fetch) {
17391
- this.fetchImpl = fetchImpl;
17392
- }
17393
- cache = /* @__PURE__ */ new Map();
17394
- async token(clientId, now = Date.now()) {
17395
- const hit = this.cache.get(clientId);
17396
- if (hit && hit.expiresAt > now) return hit.token;
17397
- const res = await this.fetchImpl(`${WIX_API}/oauth2/token`, {
17398
- method: "POST",
17399
- headers: { "Content-Type": "application/json" },
17400
- body: JSON.stringify({ clientId, grantType: "anonymous" })
17401
- });
17402
- const text5 = await res.text();
17403
- let j = {};
17404
- try {
17405
- j = text5 ? JSON.parse(text5) : {};
17406
- } catch {
17407
- }
17408
- if (res.status !== 200 || !j.access_token)
17409
- throw new Error(`visitor mint failed (${res.status})`);
17410
- const ttlMs = Math.max(60, (j.expires_in ?? 4 * 3600) - 60) * 1e3;
17411
- this.cache.set(clientId, { token: j.access_token, expiresAt: now + ttlMs });
17412
- return j.access_token;
17413
- }
17414
- /** Drop a cached token (e.g. after a 401 from wixapis) so the next call re-mints. */
17415
- invalidate(clientId) {
17416
- this.cache.delete(clientId);
17417
- }
17418
- };
17419
17425
  WIX_FORWARDABLE_HEADERS = /* @__PURE__ */ new Set([
17420
17426
  "x-wix-language",
17421
17427
  "x-wix-linguist",
@@ -24142,6 +24148,9 @@ function autoGrowBox(contentHeight, lineHeight, verticalPadding, maxLines) {
24142
24148
  const cap = Math.max(floor, lineHeight * lines + verticalPadding);
24143
24149
  return { height: Math.max(floor, Math.min(contentHeight, cap)), scrolls: contentHeight > cap };
24144
24150
  }
24151
+ function wrapModalMenu(modal, label2, placement, menu) {
24152
+ return modal ? /* @__PURE__ */ jsx6("div", { role: "dialog", "aria-modal": "true", "aria-label": label2, style: placement, children: menu }) : menu;
24153
+ }
24145
24154
  function bindMediaSlot(value, sessionId) {
24146
24155
  if (typeof value === "string") {
24147
24156
  const prefix = `$media:${sessionId}.`;
@@ -24269,11 +24278,13 @@ function withExtras(name, render) {
24269
24278
  }
24270
24279
  const roleIsSynthetic = next[SYNTHETIC_TAP_ROLE] === true;
24271
24280
  delete next[SYNTHETIC_TAP_ROLE];
24281
+ const tabIndexIsSynthetic = next[SYNTHETIC_TABINDEX] === true;
24282
+ delete next[SYNTHETIC_TABINDEX];
24272
24283
  if (additive) next.style = additive;
24273
24284
  const ownsInteractiveSemantics = isNativeInteractiveElement(el) || hasInteractiveReactDescendant(el);
24274
24285
  if (ownsInteractiveSemantics) {
24275
24286
  if (roleIsSynthetic) delete next.role;
24276
- delete next.tabIndex;
24287
+ if (tabIndexIsSynthetic) delete next.tabIndex;
24277
24288
  delete next.onKeyDown;
24278
24289
  }
24279
24290
  if (pass.style) next.style = { ...additive ?? prev.style ?? {}, ...pass.style };
@@ -24709,6 +24720,57 @@ function calKeyActivate(enabled, activate) {
24709
24720
  activate();
24710
24721
  };
24711
24722
  }
24723
+ function restoreFocusOnDismiss(scope, opener) {
24724
+ const Observer = document.defaultView?.MutationObserver;
24725
+ if (!Observer) return;
24726
+ presentedOpeners.set(scope, opener);
24727
+ if (presentedObserver) return;
24728
+ presentedObserver = new Observer(() => {
24729
+ for (const [s, o] of presentedOpeners) {
24730
+ if (s.isConnected) continue;
24731
+ presentedOpeners.delete(s);
24732
+ const active = document.activeElement;
24733
+ if ((active == null || active === document.body) && o.isConnected && typeof o.focus === "function") o.focus();
24734
+ }
24735
+ if (presentedOpeners.size === 0) {
24736
+ presentedObserver?.disconnect();
24737
+ presentedObserver = null;
24738
+ }
24739
+ });
24740
+ presentedObserver.observe(document.body, { childList: true, subtree: true });
24741
+ }
24742
+ function ensureInitialFocusRuntime() {
24743
+ if (initialFocusScheduled || typeof document === "undefined") return;
24744
+ initialFocusScheduled = true;
24745
+ setTimeout(() => {
24746
+ initialFocusScheduled = false;
24747
+ const targets = Array.from(document.querySelectorAll("[data-himalaya-initial-focus]"));
24748
+ const active = document.activeElement;
24749
+ const unplaced = active == null || active === document.body;
24750
+ const fresh = targets.filter((t) => !initialFocusSeen.has(t));
24751
+ let presented;
24752
+ let presentedScope = null;
24753
+ for (const t of fresh) {
24754
+ const scope = t.closest(PRESENTED_SCOPE);
24755
+ if (scope && !(active && scope.contains(active))) {
24756
+ presented = t;
24757
+ presentedScope = scope;
24758
+ }
24759
+ }
24760
+ for (const t of fresh) initialFocusSeen.add(t);
24761
+ const el = presented ?? (unplaced ? fresh[0] : void 0);
24762
+ if (el && typeof el.focus === "function") {
24763
+ if (presented && presentedScope && !unplaced && active instanceof HTMLElement) {
24764
+ restoreFocusOnDismiss(presentedScope, active);
24765
+ }
24766
+ el.focus();
24767
+ }
24768
+ }, 0);
24769
+ }
24770
+ function ariaModal(modal, dflt) {
24771
+ const on3 = modal === true || modal === false ? modal : dflt;
24772
+ return on3 ? "true" : void 0;
24773
+ }
24712
24774
  function applyA11ySemantics(node, ctx, extra) {
24713
24775
  const m = node?.modifiers;
24714
24776
  if (!m) return void 0;
@@ -24764,6 +24826,32 @@ function applyA11ySemantics(node, ctx, extra) {
24764
24826
  }
24765
24827
  }
24766
24828
  }
24829
+ const focusOrder = m.focusOrder ?? m.focus_order;
24830
+ if (typeof focusOrder === "number" && Number.isFinite(focusOrder)) {
24831
+ extra.tabIndex = Math.max(1, Math.trunc(focusOrder) + 1);
24832
+ }
24833
+ if ((m.focusGroup ?? m.focus_group) === true && extra.role == null) extra.role = "group";
24834
+ if ((m.initialFocus ?? m.initial_focus) === true) {
24835
+ extra["data-himalaya-initial-focus"] = "1";
24836
+ if (extra.tabIndex == null) {
24837
+ extra.tabIndex = -1;
24838
+ extra[SYNTHETIC_TABINDEX] = true;
24839
+ }
24840
+ ensureInitialFocusRuntime();
24841
+ }
24842
+ const announce = m.announce;
24843
+ if (announce && typeof announce === "object" && !Array.isArray(announce)) {
24844
+ const trigger = str7(announce.trigger) ?? "on_change";
24845
+ if (trigger === "none") {
24846
+ extra["aria-live"] = "off";
24847
+ } else {
24848
+ extra["aria-live"] = str7(announce.politeness) === "assertive" ? "assertive" : "polite";
24849
+ extra["aria-atomic"] = "true";
24850
+ extra["aria-relevant"] = trigger === "on_appear" ? "additions" : trigger === "both" ? "additions text" : "text";
24851
+ const text5 = str7(announce.text);
24852
+ if (text5 && extra["aria-label"] == null) extra["aria-label"] = text5;
24853
+ }
24854
+ }
24767
24855
  return hint;
24768
24856
  }
24769
24857
  function renderNodeInner(node, ctx) {
@@ -24856,7 +24944,10 @@ function renderNodeInner(node, ctx) {
24856
24944
  extra.role = "button";
24857
24945
  if (typeof el.type !== "string") extra[SYNTHETIC_TAP_ROLE] = true;
24858
24946
  }
24859
- extra.tabIndex = 0;
24947
+ if (extra.tabIndex == null) {
24948
+ extra.tabIndex = 0;
24949
+ if (typeof el.type !== "string") extra[SYNTHETIC_TABINDEX] = true;
24950
+ }
24860
24951
  const prevOnKeyDown = el.props.onKeyDown;
24861
24952
  extra.onKeyDown = (e) => {
24862
24953
  prevOnKeyDown?.(e);
@@ -24904,6 +24995,7 @@ function renderNodeInner(node, ctx) {
24904
24995
  if (composed) extra.style = composed;
24905
24996
  }
24906
24997
  }
24998
+ if (typeof el.type === "string") delete extra[SYNTHETIC_TABINDEX];
24907
24999
  let out = Object.keys(extra).length ? React5.cloneElement(el, extra) : el;
24908
25000
  if (a11yHint) {
24909
25001
  out = /* @__PURE__ */ jsx6(A11yHintDescription, { hint: a11yHint, idBase: componentId, children: out });
@@ -25333,7 +25425,7 @@ function renderInner(node, ctx) {
25333
25425
  case "Popover": {
25334
25426
  return /* @__PURE__ */ jsxs3("div", { style: { display: "inline-flex", flexDirection: "column", alignItems: "flex-start", gap: 6, ...wrap }, children: [
25335
25427
  props.anchor ? renderNode(props.anchor, ctx) : null,
25336
- /* @__PURE__ */ jsx6("div", { role: "dialog", "aria-label": props.title != null ? asBoundText(props.title, ctx) : "Popover", style: { minWidth: 160, maxWidth: 280, background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: 12, boxShadow: "0 6px 20px rgba(0,0,0,.14)", padding: 12 }, children: props.content ? renderNode(props.content, ctx) : null })
25428
+ /* @__PURE__ */ jsx6("div", { role: "dialog", "aria-modal": ariaModal(props.modal, false), "aria-label": props.title != null ? asBoundText(props.title, ctx) : "Popover", style: { minWidth: 160, maxWidth: 280, background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: 12, boxShadow: "0 6px 20px rgba(0,0,0,.14)", padding: 12 }, children: props.content ? renderNode(props.content, ctx) : null })
25337
25429
  ] });
25338
25430
  }
25339
25431
  // ───────────── chips & selection (RFC 0030) ─────────────
@@ -25601,7 +25693,7 @@ function renderInner(node, ctx) {
25601
25693
  ] });
25602
25694
  }
25603
25695
  case "AlertDialog":
25604
- return /* @__PURE__ */ jsxs3("div", { role: "alertdialog", "aria-modal": "true", "aria-label": props.title != null ? asBoundText(props.title, ctx) : "Alert", style: { background: C("surfacePrimary", "#fff"), borderRadius: ctx.radius("lg", 14), padding: 18, width: "100%", boxSizing: "border-box", border: `1px solid ${C("border", "#e3e3e3")}`, boxShadow: "0 12px 30px -12px rgba(0,0,0,.4)", ...wrap }, children: [
25696
+ return /* @__PURE__ */ jsxs3("div", { role: "alertdialog", "aria-modal": ariaModal(props.modal, true), "aria-label": props.title != null ? asBoundText(props.title, ctx) : "Alert", style: { background: C("surfacePrimary", "#fff"), borderRadius: ctx.radius("lg", 14), padding: 18, width: "100%", boxSizing: "border-box", border: `1px solid ${C("border", "#e3e3e3")}`, boxShadow: "0 12px 30px -12px rgba(0,0,0,.4)", ...wrap }, children: [
25605
25697
  /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), fontWeight: 700, fontSize: 17, color: C("textPrimary", "#111") }, children: txt(props.title, ctx) }),
25606
25698
  /* @__PURE__ */ jsx6("div", { style: { fontFamily: bodyFont(ctx), fontSize: 14, lineHeight: 1.45, color: C("textSecondary", "#666"), margin: "6px 0 16px" }, children: txt(props.message, ctx) }),
25607
25699
  /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 10 }, children: [
@@ -25624,7 +25716,7 @@ function renderInner(node, ctx) {
25624
25716
  }
25625
25717
  }
25626
25718
  }
25627
- var useIsomorphicLayoutEffect, ResponsiveGrid, SPLIT_EXPANDED_MIN_WIDTH, SPLIT_COLUMN_GAP, ResponsiveSplitView, ABSOLUTE_URL_RE, appearKeyframesInjected, pressStylesInjected, FOCUS_RING_CSS, focusStylesInjected, HLIST_CHILD_CSS, hListStylesInjected, listAnimateRuntimeInstalled, transitionKeyframesInjected, countUpRuntimeInjected, MEASURE_PREFIX, measureRuntimeInjected, revealRuntimeInjected, vectorAnimStylesInjected, scrollProgressRuntimeInjected, confettiRuntimeInjected, lottieRuntimeInjected, riveRuntimeInjected, gradientKeyframesInjected, parallaxRuntimeInjected, spinKeyframesInjected, marqueeKeyframesInjected, alignItems, zJustifyItems, hAlignItems, bodyFont, kids, rowKids, staggeredKids, asTextL10n, asBoundText, useBoundString, InterpolatedTextView, txt, selectFragmentCase, fragmentValueHash, fragmentSlotContent, FragmentSlotNode, WorkerSubtreeNode, resolveItemDeep2, resolveSwipeActions, resolveModBindings, resolvedComponentId, directServiceBindings, ReactiveDirectScalars, HiddenGate, KEYBOARD_INPUT_MODES, AUTOCAPITALIZE_VALUES, ENTER_KEY_HINTS, AUTOGROW_CEILING, EditableField, MenuNode, LoadMoreSentinel, FieldErrorNode, FormView, MediaSlotProvider, MEDIA_ITEM_BINDABLE, MediaPlayerView, VideoNode, PdfViewNode, WebViewNode, InteractivePicker, InteractiveDatePicker, InteractiveSearchField, InteractiveStepper, SYNTHETIC_TAP_ROLE, INFERRED_DECORATIVE, ICON_GLYPH, INTERACTIVE_ROLES, ALWAYS_INTERACTIVE_COMPONENT_TYPES, controlNode, ToggleNode, SliderNode, SelectionControlNode, TabsNode, SegmentedControlNode, DisclosureGroupNode, GridNode, SplitViewNode, TextNode, ImageNode, AvatarNode, IconNode, ButtonNode, OtpInputNode, ProgressBarNode, ActivityIndicatorNode, BottomSheetNode, ChipsInputNode, ChipNode, TableNode, GaugeRing, GaugeNode, RATING_METRICS, ratingMetrics, RatingNode, AvatarStripNode, CHART_STROKE_CYCLE, chartStrokeAt, chartBarCenter, ChartNode, donutSliceOpacity, DonutChartNode, LegendMarker, LegendNode, cellAlign, DataTableNode, voiceNoteState, voiceNoteDelivery, VOICE_NOTE_DEFAULT_BARS, clampBarCount, VoiceNoteNode, TimelineNode, WheelPickerNode, ColorPickerNode, FilterChipsNode, TILE_PX, MAX_MERCATOR_LAT, DEFAULT_TILE_MAX_ZOOM, CIRCLE_SEGMENTS, METRES_PER_DEG_LAT, MARKER_HIT_RADIUS, SHAPE_HIT_RADIUS, MapNode, CollapsingHeaderNode, UnsupportedOnSurface, CALENDAR_DATE_ONLY_RE, CAL_DEFAULT_MAX_DOTS, CAL_AGENDA_INITIAL_DAYS, CAL_AGENDA_PAGE_DAYS, CAL_DEFAULT_SNAP_MINUTES, CAL_UNASSIGNED_LANE_ID, MonthCalendarNode, TimeGridNode, AgendaNode, ARIA_ROLE, A11yHintDescription, WebRenderer;
25719
+ var useIsomorphicLayoutEffect, ResponsiveGrid, SPLIT_EXPANDED_MIN_WIDTH, SPLIT_COLUMN_GAP, ResponsiveSplitView, ABSOLUTE_URL_RE, appearKeyframesInjected, pressStylesInjected, FOCUS_RING_CSS, focusStylesInjected, HLIST_CHILD_CSS, hListStylesInjected, listAnimateRuntimeInstalled, transitionKeyframesInjected, countUpRuntimeInjected, MEASURE_PREFIX, measureRuntimeInjected, revealRuntimeInjected, vectorAnimStylesInjected, scrollProgressRuntimeInjected, confettiRuntimeInjected, lottieRuntimeInjected, riveRuntimeInjected, gradientKeyframesInjected, parallaxRuntimeInjected, spinKeyframesInjected, marqueeKeyframesInjected, alignItems, zJustifyItems, hAlignItems, bodyFont, kids, rowKids, staggeredKids, asTextL10n, asBoundText, useBoundString, InterpolatedTextView, txt, selectFragmentCase, fragmentValueHash, fragmentSlotContent, FragmentSlotNode, WorkerSubtreeNode, resolveItemDeep2, resolveSwipeActions, resolveModBindings, resolvedComponentId, directServiceBindings, ReactiveDirectScalars, HiddenGate, KEYBOARD_INPUT_MODES, AUTOCAPITALIZE_VALUES, ENTER_KEY_HINTS, AUTOGROW_CEILING, EditableField, MenuNode, LoadMoreSentinel, FieldErrorNode, FormView, MediaSlotProvider, MEDIA_ITEM_BINDABLE, MediaPlayerView, VideoNode, PdfViewNode, WebViewNode, InteractivePicker, InteractiveDatePicker, InteractiveSearchField, InteractiveStepper, SYNTHETIC_TAP_ROLE, SYNTHETIC_TABINDEX, INFERRED_DECORATIVE, ICON_GLYPH, INTERACTIVE_ROLES, ALWAYS_INTERACTIVE_COMPONENT_TYPES, controlNode, ToggleNode, SliderNode, SelectionControlNode, TabsNode, SegmentedControlNode, DisclosureGroupNode, GridNode, SplitViewNode, TextNode, ImageNode, AvatarNode, IconNode, ButtonNode, OtpInputNode, ProgressBarNode, ActivityIndicatorNode, BottomSheetNode, ChipsInputNode, ChipNode, TableNode, GaugeRing, GaugeNode, RATING_METRICS, ratingMetrics, RatingNode, AvatarStripNode, CHART_STROKE_CYCLE, chartStrokeAt, chartBarCenter, ChartNode, donutSliceOpacity, DonutChartNode, LegendMarker, LegendNode, cellAlign, DataTableNode, voiceNoteState, voiceNoteDelivery, VOICE_NOTE_DEFAULT_BARS, clampBarCount, VoiceNoteNode, TimelineNode, WheelPickerNode, ColorPickerNode, FilterChipsNode, TILE_PX, MAX_MERCATOR_LAT, DEFAULT_TILE_MAX_ZOOM, CIRCLE_SEGMENTS, METRES_PER_DEG_LAT, MARKER_HIT_RADIUS, SHAPE_HIT_RADIUS, MapNode, CollapsingHeaderNode, UnsupportedOnSurface, CALENDAR_DATE_ONLY_RE, CAL_DEFAULT_MAX_DOTS, CAL_AGENDA_INITIAL_DAYS, CAL_AGENDA_PAGE_DAYS, CAL_DEFAULT_SNAP_MINUTES, CAL_UNASSIGNED_LANE_ID, MonthCalendarNode, TimeGridNode, AgendaNode, ARIA_ROLE, initialFocusScheduled, initialFocusSeen, PRESENTED_SCOPE, presentedOpeners, presentedObserver, A11yHintDescription, WebRenderer;
25628
25720
  var init_renderNode = __esm({
25629
25721
  "../../core/web-renderer/src/renderNode.tsx"() {
25630
25722
  "use strict";
@@ -25989,7 +26081,9 @@ var init_renderNode = __esm({
25989
26081
  AUTOCAPITALIZE_VALUES = /* @__PURE__ */ new Set(["none", "words", "sentences", "characters"]);
25990
26082
  ENTER_KEY_HINTS = /* @__PURE__ */ new Set(["done", "go", "next", "send", "search"]);
25991
26083
  AUTOGROW_CEILING = 12;
25992
- EditableField = ({ props, ctx, wrap, multiline, id: htmlId, "data-component-id": componentId, "data-component-type": componentType }) => {
26084
+ EditableField = ({ props, ctx, wrap, multiline, id: htmlId, "data-component-id": componentId, "data-component-type": componentType, tabIndex: tabIndexProp, "data-hm-synthetic-tabindex": tabIndexIsSynthetic, "data-himalaya-initial-focus": initialFocusMarker, "aria-live": ariaLive, "aria-atomic": ariaAtomic, "aria-relevant": ariaRelevant, "aria-label": ariaLabelProp }) => {
26085
+ const tabIndex = tabIndexIsSynthetic === true ? void 0 : tabIndexProp;
26086
+ const a11yFwd = { tabIndex, "data-himalaya-initial-focus": initialFocusMarker, "aria-live": ariaLive, "aria-atomic": ariaAtomic, "aria-relevant": ariaRelevant };
25993
26087
  const C = ctx.color;
25994
26088
  const ctrl = props.text ?? props.value;
25995
26089
  const cfg = ctrl && typeof ctrl === "object" ? ctrl : {};
@@ -26134,7 +26228,7 @@ var init_renderNode = __esm({
26134
26228
  /* @__PURE__ */ jsxs3("div", { style: box, children: [
26135
26229
  props.leadingIcon && accIcon(props.leadingIcon),
26136
26230
  props.prefix && /* @__PURE__ */ jsx6("span", { style: { color: muted, flex: "none" }, children: String(props.prefix) }),
26137
- multilineBox ? /* @__PURE__ */ jsx6("textarea", { "data-component-id": componentId, id: htmlId, "data-component-type": componentType, ref: inputRef, value: local, onChange: onInput, onFocus: onFieldFocus, onBlur: onFieldBlur, placeholder: placeholder2, rows: 2, "aria-label": placeholder2, "aria-invalid": errored || void 0, "aria-describedby": errorId, inputMode, autoCapitalize, maxLength, autoCorrect: suggestOff ? "off" : suggestOn ? "on" : void 0, spellCheck: suggestOff ? false : suggestOn ? true : void 0, autoComplete: suggestOff ? "off" : void 0, style: inputBase }) : /* @__PURE__ */ jsx6("input", { "data-component-id": componentId, id: htmlId, "data-component-type": componentType, ref: inputRef, type: secure ? "password" : "text", value: local, onChange: onInput, onKeyDown, onFocus: onFieldFocus, onBlur: onFieldBlur, placeholder: placeholder2, "aria-label": placeholder2, "aria-invalid": errored || void 0, "aria-describedby": errorId, inputMode, autoCapitalize, maxLength, autoCorrect: suggestOff ? "off" : suggestOn ? "on" : void 0, spellCheck: suggestOff ? false : suggestOn ? true : void 0, autoComplete: suggestOff ? "off" : void 0, enterKeyHint, style: inputBase }),
26231
+ multilineBox ? /* @__PURE__ */ jsx6("textarea", { "data-component-id": componentId, id: htmlId, "data-component-type": componentType, ...a11yFwd, ref: inputRef, value: local, onChange: onInput, onFocus: onFieldFocus, onBlur: onFieldBlur, placeholder: placeholder2, rows: 2, "aria-label": ariaLabelProp ?? placeholder2, "aria-invalid": errored || void 0, "aria-describedby": errorId, inputMode, autoCapitalize, maxLength, autoCorrect: suggestOff ? "off" : suggestOn ? "on" : void 0, spellCheck: suggestOff ? false : suggestOn ? true : void 0, autoComplete: suggestOff ? "off" : void 0, style: inputBase }) : /* @__PURE__ */ jsx6("input", { "data-component-id": componentId, id: htmlId, "data-component-type": componentType, ...a11yFwd, ref: inputRef, type: secure ? "password" : "text", value: local, onChange: onInput, onKeyDown, onFocus: onFieldFocus, onBlur: onFieldBlur, placeholder: placeholder2, "aria-label": ariaLabelProp ?? placeholder2, "aria-invalid": errored || void 0, "aria-describedby": errorId, inputMode, autoCapitalize, maxLength, autoCorrect: suggestOff ? "off" : suggestOn ? "on" : void 0, spellCheck: suggestOff ? false : suggestOn ? true : void 0, autoComplete: suggestOff ? "off" : void 0, enterKeyHint, style: inputBase }),
26138
26232
  props.suffix && /* @__PURE__ */ jsx6("span", { style: { color: muted, flex: "none" }, children: String(props.suffix) }),
26139
26233
  props.clearButton && local && /* @__PURE__ */ jsx6("span", { onClick: () => {
26140
26234
  setLocal("");
@@ -26166,15 +26260,47 @@ var init_renderNode = __esm({
26166
26260
  document.removeEventListener("keydown", onKey);
26167
26261
  };
26168
26262
  }, [open]);
26263
+ const modal = ariaModal(props.modal, false) === "true";
26264
+ const anchorLabel = props.anchor?.modifiers?.accessibilityLabel || "More actions";
26265
+ const popupPlacement = { position: "absolute", top: "100%", right: 0, zIndex: 20, marginTop: 4 };
26266
+ const anchorRef = React5.useRef(null);
26267
+ const menuRef = React5.useRef(null);
26268
+ const itemsKey = items.map((it, i) => `${it?.id ?? i}:${it?.disabled ? 0 : 1}`).join("|");
26269
+ React5.useEffect(() => {
26270
+ if (!open || !modal) return;
26271
+ const menu = menuRef.current;
26272
+ if (!menu || menu.contains(document.activeElement)) return;
26273
+ (menu.querySelector('[role="menuitem"][tabindex="0"]') ?? menu).focus();
26274
+ }, [open, modal, itemsKey]);
26275
+ React5.useEffect(() => {
26276
+ if (!open || !modal) return;
26277
+ return () => {
26278
+ const active = document.activeElement;
26279
+ if (active == null || active === document.body || !document.contains(active)) anchorRef.current?.focus();
26280
+ };
26281
+ }, [open, modal]);
26282
+ const trapTab = (e) => {
26283
+ if (!modal || e.key !== "Tab") return;
26284
+ const items2 = Array.from(menuRef.current?.querySelectorAll('[role="menuitem"][tabindex="0"]') ?? []);
26285
+ if (!items2.length) {
26286
+ e.preventDefault();
26287
+ return;
26288
+ }
26289
+ const at = items2.indexOf(document.activeElement);
26290
+ const next = e.shiftKey ? at <= 0 ? items2.length - 1 : at - 1 : at < 0 || at === items2.length - 1 ? 0 : at + 1;
26291
+ e.preventDefault();
26292
+ items2[next].focus();
26293
+ };
26169
26294
  return /* @__PURE__ */ jsxs3("span", { ref: hostRef, style: { position: "relative", display: "inline-flex", ...wrap }, children: [
26170
26295
  /* @__PURE__ */ jsx6(
26171
26296
  "span",
26172
26297
  {
26298
+ ref: anchorRef,
26173
26299
  role: "button",
26174
26300
  tabIndex: 0,
26175
26301
  "aria-haspopup": "menu",
26176
26302
  "aria-expanded": open,
26177
- "aria-label": props.anchor?.modifiers?.accessibilityLabel || "More actions",
26303
+ "aria-label": anchorLabel,
26178
26304
  onClick: (e) => {
26179
26305
  e.stopPropagation();
26180
26306
  setOpen((v) => !v);
@@ -26190,11 +26316,14 @@ var init_renderNode = __esm({
26190
26316
  children: props.anchor ? renderNode(props.anchor, ctx) : iconSvg("more", { size: 20, color: C("textSecondary", "#636b78") })
26191
26317
  }
26192
26318
  ),
26193
- open && /* @__PURE__ */ jsx6(
26319
+ open && wrapModalMenu(modal, anchorLabel, popupPlacement, /* @__PURE__ */ jsx6(
26194
26320
  "div",
26195
26321
  {
26322
+ ref: menuRef,
26196
26323
  role: "menu",
26197
- style: { position: "absolute", top: "100%", right: 0, zIndex: 20, marginTop: 4, display: "flex", flexDirection: "column", minWidth: 180, background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: 12, boxShadow: "0 6px 20px rgba(0,0,0,.12)", overflow: "hidden", padding: 4 },
26324
+ tabIndex: modal ? -1 : void 0,
26325
+ onKeyDown: trapTab,
26326
+ style: { ...modal ? {} : popupPlacement, display: "flex", flexDirection: "column", minWidth: 180, background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: 12, boxShadow: "0 6px 20px rgba(0,0,0,.12)", overflow: "hidden", padding: 4 },
26198
26327
  children: items.map((it, i) => /* @__PURE__ */ jsxs3(
26199
26328
  "div",
26200
26329
  {
@@ -26223,7 +26352,7 @@ var init_renderNode = __esm({
26223
26352
  it?.id ?? i
26224
26353
  ))
26225
26354
  }
26226
- )
26355
+ ))
26227
26356
  ] });
26228
26357
  });
26229
26358
  LoadMoreSentinel = ({ ctx, pagination }) => {
@@ -27100,6 +27229,7 @@ var init_renderNode = __esm({
27100
27229
  ] });
27101
27230
  });
27102
27231
  SYNTHETIC_TAP_ROLE = "data-hm-synthetic-role";
27232
+ SYNTHETIC_TABINDEX = "data-hm-synthetic-tabindex";
27103
27233
  INFERRED_DECORATIVE = "data-hm-inferred-decorative";
27104
27234
  ICON_GLYPH = "data-hm-icon-glyph";
27105
27235
  INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
@@ -27463,7 +27593,7 @@ var init_renderNode = __esm({
27463
27593
  if (gated && presented !== true && presented !== "true") return null;
27464
27594
  const grabber = props.showGrabber !== false;
27465
27595
  const sheet = idiomControls(ctx.idiom).sheet;
27466
- return /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,.35)", zIndex: 50, display: "flex", flexDirection: "column", justifyContent: "flex-end", ...wrap }, children: /* @__PURE__ */ jsxs3("div", { role: "dialog", "aria-modal": "true", "aria-label": props.title != null ? sheetTitle : "Sheet", style: { width: "100%", background: C("surfacePrimary", "#fff"), borderRadius: `${sheet.cornerRadius}px ${sheet.cornerRadius}px 0 0`, boxShadow: "0 -4px 24px rgba(0,0,0,.12)", overflow: "hidden" }, children: [
27596
+ return /* @__PURE__ */ jsx6("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,.35)", zIndex: 50, display: "flex", flexDirection: "column", justifyContent: "flex-end", ...wrap }, children: /* @__PURE__ */ jsxs3("div", { role: "dialog", "aria-modal": ariaModal(props.modal, true), "aria-label": props.title != null ? sheetTitle : "Sheet", style: { width: "100%", background: C("surfacePrimary", "#fff"), borderRadius: `${sheet.cornerRadius}px ${sheet.cornerRadius}px 0 0`, boxShadow: "0 -4px 24px rgba(0,0,0,.12)", overflow: "hidden" }, children: [
27467
27597
  grabber && /* @__PURE__ */ jsx6("div", { style: { display: "flex", justifyContent: "center", padding: "8px 0 4px" }, children: /* @__PURE__ */ jsx6("div", { style: { width: sheet.grabberWidth, height: sheet.grabberHeight, borderRadius: 999, background: C("surfaceTertiary", "#d8dce2") } }) }),
27468
27598
  /* @__PURE__ */ jsx6("div", { style: { padding: "8px 16px 16px" }, children: props.content ? renderNode(props.content, ctx) : null })
27469
27599
  ] }) });
@@ -29001,6 +29131,11 @@ var init_renderNode = __esm({
29001
29131
  grid: "grid",
29002
29132
  gridcell: "gridcell"
29003
29133
  };
29134
+ initialFocusScheduled = false;
29135
+ initialFocusSeen = /* @__PURE__ */ new WeakSet();
29136
+ PRESENTED_SCOPE = '[aria-modal="true"], [role="dialog"], [role="alertdialog"], [role="menu"]';
29137
+ presentedOpeners = /* @__PURE__ */ new Map();
29138
+ presentedObserver = null;
29004
29139
  A11yHintDescription = ({ hint, idBase, children, ...forwarded }) => {
29005
29140
  const descId = `${idBase ?? "hm"}__hint${React5.useId()}`;
29006
29141
  const own3 = children.props;
@@ -39279,11 +39414,22 @@ function liveRun(dir) {
39279
39414
  const build2 = obj(rec?.build);
39280
39415
  try {
39281
39416
  process.kill(pid, 0);
39417
+ const at = text(build2?.at);
39282
39418
  return {
39283
39419
  url,
39420
+ // Which run this is: a restart on the same port serves the same URL from a new process.
39421
+ runId: `${pid}@${text(rec?.startedAt) ?? ""}`,
39284
39422
  buildError: build2?.ok === false ? text(build2.error) ?? "the last rebuild failed" : null,
39285
39423
  // Only `--watch` wrote this record before plain runs did, so a record without the key is one.
39286
- watching: rec?.watching !== false
39424
+ watching: rec?.watching !== false,
39425
+ lastBuild: build2 && at ? {
39426
+ at,
39427
+ ok: build2.ok !== false,
39428
+ ms: typeof build2.ms === "number" && Number.isFinite(build2.ms) ? Math.max(0, Math.round(build2.ms)) : null,
39429
+ screens: (Array.isArray(build2.screens) ? build2.screens : []).filter((x) => typeof x === "string").slice(0, 20),
39430
+ tokens: build2.tokens === true,
39431
+ config: build2.config === true
39432
+ } : null
39287
39433
  };
39288
39434
  } catch {
39289
39435
  return null;
@@ -39400,6 +39546,7 @@ function renderStudioHtml(initial) {
39400
39546
  <meta charset="utf-8">
39401
39547
  <meta name="viewport" content="width=device-width, initial-scale=1">
39402
39548
  <title>${escapeHtml(initial.app.name)} \xB7 build board</title>
39549
+ <link rel="icon" id="fav" href="data:,">
39403
39550
  <style>
39404
39551
  :root{--bg:#f6f5f2;--card:#fff;--ink:#1c1c1a;--muted:#6b6a64;--faint:#a3a19a;--line:#e6e3dc;--ok:#1f7a4d;--okbg:#e3f3ea;--warn:#8a5a00;--warnbg:#fbefd6;--bad:#a12a2a;--badbg:#fbe4e4;--accent:${accent};--shadow:0 1px 2px rgba(0,0,0,.04),0 4px 16px rgba(0,0,0,.04)}
39405
39552
  @media (prefers-color-scheme:dark){:root{--bg:#121211;--card:#1c1c1a;--ink:#ecebe6;--muted:#a3a19a;--faint:#6b6a64;--line:#2c2b28;--okbg:#15301f;--ok:#7fd3a2;--warnbg:#33270f;--warn:#f0c46b;--badbg:#3a1616;--bad:#f09a9a;--shadow:none}}
@@ -39418,6 +39565,7 @@ h1{font-size:20px;font-weight:600;margin:0}.sub{color:var(--muted);font-size:13p
39418
39565
  h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:0 0 10px}
39419
39566
  .phone{width:100%;max-width:300px;aspect-ratio:390/844;border-radius:34px;border:9px solid #111;background:#fff;overflow:hidden;margin:0 auto;display:block}
39420
39567
  .phone img,.phone iframe{width:100%;height:100%;object-fit:cover;object-position:top;border:0;display:block;cursor:zoom-in}
39568
+ .phone.vpf{position:relative}.phone iframe.vp{position:absolute;top:0;left:0;width:390px;height:844px;transform-origin:0 0;transform:scale(var(--k,.72));cursor:auto}
39421
39569
  .cap{text-align:center;color:var(--muted);font-size:12px;margin-top:8px}
39422
39570
  .film{display:flex;gap:6px;overflow-x:auto;margin-top:12px;padding-bottom:4px}.film img{width:48px;aspect-ratio:390/844;object-fit:cover;object-position:top;border-radius:8px;border:1px solid var(--line);cursor:zoom-in}
39423
39571
  .chips{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}.chip{font-size:12px;padding:5px 10px;border-radius:999px;background:var(--card);border:1px solid var(--line);color:var(--muted)}
@@ -39432,12 +39580,26 @@ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;le
39432
39580
  .log .t{color:var(--faint);font-size:12px;min-width:64px;font-variant-numeric:tabular-nums}.log .s{font-size:11px;color:var(--muted);text-transform:capitalize}.log .imgs{display:flex;gap:6px;margin-top:6px}.log .imgs img{height:72px;border-radius:8px;border:1px solid var(--line);cursor:zoom-in}
39433
39581
  .empty{color:var(--faint);font-size:13px;padding:8px 0}
39434
39582
  #lb{position:fixed;inset:0;background:rgba(0,0,0,.72);display:none;align-items:center;justify-content:center;z-index:9;cursor:zoom-out}#lb img{max-height:92vh;max-width:92vw;border-radius:14px;background:#fff}
39583
+ .enter{animation:enter .7s cubic-bezier(.2,.8,.2,1) both}@keyframes enter{from{opacity:0;transform:translateY(10px) scale(.94);filter:grayscale(1)}to{opacity:1;transform:none;filter:none}}
39584
+ .changed .frame{animation:chg .9s ease both}@keyframes chg{35%{opacity:.25;transform:scale(.96)}100%{opacity:1;transform:none}}
39585
+ .step.fresh .bar{animation:fill .7s ease both;transform-origin:left}@keyframes fill{from{transform:scaleX(0)}to{transform:scaleX(1)}}
39586
+ .slidein{animation:slide .45s cubic-bezier(.2,.8,.2,1) both}@keyframes slide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
39587
+ .phone.pulse{animation:pulse 1s ease-out}.phone.pulse.bad{animation-name:pulsebad}@keyframes pulse{from{outline:3px solid var(--ok);outline-offset:0}to{outline:3px solid transparent;outline-offset:10px}}@keyframes pulsebad{from{outline:3px solid var(--bad);outline-offset:0}to{outline:3px solid transparent;outline-offset:10px}}
39588
+ .toast{display:block;width:max-content;max-width:100%;margin:8px auto 0;font-size:12px;padding:4px 10px;border-radius:999px;background:var(--okbg);color:var(--ok);animation:toast 3.4s ease both}.toast.bad{background:var(--badbg);color:var(--bad)}@keyframes toast{0%{opacity:0;transform:translateY(4px)}10%,80%{opacity:1;transform:none}100%{opacity:0}}
39589
+ .icon,.step.cur .bar{transition:background-color .8s}.sw.pop span{animation:pop .6s ease both}@keyframes pop{50%{transform:scale(1.25)}}
39590
+ .quiet{color:var(--warn)}.quiet .dot{background:var(--warn)}.idle .dot{background:var(--faint)}
39591
+ .tog{font:inherit;font-size:12px;color:var(--muted);background:none;border:1px solid var(--line);border-radius:999px;padding:2px 9px;margin:6px 0 0 6px;cursor:pointer}.tog.on{color:var(--ink);border-color:var(--ink)}
39592
+ #fx{position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:8}
39593
+ @media (max-width:560px){header{flex-wrap:wrap}.right{margin-left:0;width:100%;text-align:left;display:flex;flex-wrap:wrap;align-items:center;gap:4px 12px}.right .clock{font-size:18px}.tog{margin:0 6px 0 0}}
39594
+ @media (prefers-reduced-motion:reduce){.enter,.changed .frame,.step.fresh .bar,.slidein,.phone.pulse,.sw.pop span,.toast{animation:none}.icon,.step.cur .bar{transition:none}}
39435
39595
  </style>
39436
39596
  </head>
39437
39597
  <body>
39438
39598
  <div class="wrap">
39439
39599
  <header><img class="icon" id="icon" alt=""><div><h1 id="name"></h1><div class="sub">Build board \xB7 updates itself as the app is built</div></div>
39440
- <div class="right"><div class="clock" id="clock">00:00</div><div class="live" id="live"><span class="dot"></span><span id="updated">live</span></div></div></header>
39600
+ <div class="right"><div class="clock" id="clock">00:00</div><div class="live" id="live"><span class="dot"></span><span id="updated">live</span></div>
39601
+ <div><span class="live idle" id="agent"><span class="dot"></span><span id="agentt">Waiting for the agent</span></span></div>
39602
+ <div><button class="tog" id="snd" type="button" aria-pressed="false">Sound off</button><button class="tog" id="ntf" type="button" aria-pressed="false" hidden>Notices off</button></div></div></header>
39441
39603
  <div class="steps" id="steps"></div>
39442
39604
  <div class="card now"><div style="flex:1"><div class="meta" id="nowmeta">Now</div><div class="msg" id="nowmsg"></div></div></div>
39443
39605
  <div class="grid">
@@ -39450,6 +39612,7 @@ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;le
39450
39612
  <div class="card section"><h2>Postcards</h2><ul class="log" id="log"></ul></div>
39451
39613
  </div></div></div>
39452
39614
  <div id="lb"><img alt=""></div>
39615
+ <canvas id="fx" aria-hidden="true"></canvas>
39453
39616
  <script>window.__himiInitial = ${scriptJson(initial)};</script>
39454
39617
  <script>
39455
39618
  (function(){
@@ -39464,27 +39627,33 @@ function zoomable(img){img.addEventListener("click",function(){var lb=$("lb");lb
39464
39627
  function img(src,cls){var i=el("img",cls);i.src=src;i.alt="";i.loading="lazy";zoomable(i);return i}
39465
39628
  function chip(txt,kind){return el("span","chip"+(kind?" "+kind:""),txt)}
39466
39629
  function render(s){
39467
- document.title=s.app.name+" \xB7 build board";$("name").textContent=s.app.name;
39630
+ var sn=s.current?s.current.stepNumber:0;document.title=(sn?"("+sn+"/8) ":"")+s.app.name+" \xB7 build board";favicon(sn,!!(s.live&&s.live.buildError));$("name").textContent=s.app.name;
39468
39631
  if(s.app.icon){$("icon").src=s.app.icon}else{$("icon").removeAttribute("src")}
39469
39632
  var steps=$("steps");clear(steps);var n=s.current?s.current.stepNumber:0;
39470
39633
  s.steps.forEach(function(name,i){var d=el("div","step"+(i+1<n?" done":i+1===n?" cur":""));d.appendChild(el("div","bar"));d.appendChild(el("div","lbl",(i+1)+". "+name));steps.appendChild(d)});
39471
39634
  var last=s.journal[0];
39472
39635
  $("nowmsg").textContent=last?last.message:"Waiting for the first postcard.";
39473
39636
  $("nowmeta").textContent=last?("Now \xB7 "+(last.step?("step "+last.stepNumber+" of 8, "+last.step+" \xB7 "):"")+ago(last.ts)):"Now";
39474
- var focus=$("focus");clear(focus);var film=$("film");clear(film);
39475
- if(s.live){$("focuslbl").textContent="Live preview";var f=el("div","phone");var fr=document.createElement("iframe");fr.src=s.live.url;fr.title="Live preview";f.appendChild(fr);focus.appendChild(f);if(s.live.buildError){var be=el("div","chip bad",null);be.style.display="block";be.style.marginTop="8px";be.textContent="Last edit did not build, still showing the last good version: "+s.live.buildError;focus.appendChild(be)}focus.appendChild(el("div","cap",(s.live.watching?"Repaints as the app is edited \xB7 ":"The build this run started with; himi run --watch repaints on every edit \xB7 ")+s.live.url))}
39637
+ var focus=$("focus");var film=$("film");clear(film);
39638
+ /* The live frame is KEPT across renders while its URL and its run are unchanged (a restarted run on
39639
+ the same port is a different server, and the old frame would never see its builds). A new iframe is a full page
39640
+ load of the preview: every postcard, rebuild and the once-a-minute re-render used to throw away
39641
+ the in-place repaint, the preview's scroll and wherever the owner had navigated inside it. */
39642
+ var kept=s.live?focus.querySelector("iframe"):null;if(kept&&(kept.getAttribute("src")!==s.live.url||kept.getAttribute("data-run")!==s.live.runId))kept=null;
39643
+ if(kept){while(focus.lastChild&&focus.lastChild!==kept.parentNode)focus.removeChild(focus.lastChild)}else clear(focus);
39644
+ if(s.live){$("focuslbl").textContent="Live preview";if(!kept){var f=el("div","phone vpf");var fr=document.createElement("iframe");fr.className="vp";fr.setAttribute("width","390");fr.setAttribute("height","844");fr.setAttribute("data-run",s.live.runId);fr.src=s.live.url;fr.title="Live preview";f.appendChild(fr);focus.appendChild(f)}fit();if(s.live.buildError){var be=el("div","chip bad",null);be.style.display="block";be.style.marginTop="8px";be.textContent="Last edit did not build, still showing the last good version: "+s.live.buildError;focus.appendChild(be)}focus.appendChild(el("div","cap",(s.live.watching?"Repaints as the app is edited \xB7 ":"The build this run started with; himi run --watch repaints on every edit \xB7 ")+s.live.url))}
39476
39645
  else if(s.focus&&s.focus.frames.length){var fl=s.focus.frames;$("focuslbl").textContent="Latest \xB7 "+s.focus.id;var p=el("div","phone");p.appendChild(img(fl[fl.length-1].img));focus.appendChild(p);focus.appendChild(el("div","cap",fl.length+(fl.length===1?" version":" versions")+" so far"))}
39477
39646
  else{$("focuslbl").textContent="Latest";focus.appendChild(el("div","empty","The first render appears here."))}
39478
39647
  if(s.focus&&s.focus.frames.length>1)s.focus.frames.forEach(function(fm){film.appendChild(img(fm.img))});
39479
39648
  var chips=$("chips");clear(chips);var h=s.health;
39480
39649
  if(h.validate)chips.appendChild(chip(h.validate.ok?"Validate passing":"Validate: "+h.validate.errors+" errors",h.validate.ok?"ok":"bad"));
39481
- if(h.test){var t=h.test;var trend=t.prevGaps!=null&&t.prevGaps!==t.gaps?(", gaps "+t.prevGaps+" \u2192 "+t.gaps):(", "+t.gaps+" gaps");chips.appendChild(chip("Test crawl: "+t.screens+" screens, "+t.actions+" actions"+trend,t.ok?"ok":"bad"))}
39650
+ if(h.test){var t=h.test;var moved=t.prevGaps!=null&&t.prevGaps!==t.gaps;var tc=chip("Test crawl: "+t.screens+" screens, "+t.actions+" actions, "+(moved?"gaps "+t.prevGaps+" \u2192 ":""),t.ok?"ok":"bad");var gn=el("span",null,String(t.gaps));gn.id="gapn";tc.appendChild(gn);if(!moved)tc.appendChild(document.createTextNode(" gaps"));chips.appendChild(tc)}
39482
39651
  h.ux.forEach(function(u){chips.appendChild(chip("UX drive "+u.platform+": "+u.status,u.status==="passed"?"ok":u.status==="failed"?"bad":"warn"))});
39483
39652
  if(h.push)chips.appendChild(chip((h.push.draft?"Draft published":"Published live")+" \xB7 "+h.push.releaseId.slice(0,10),h.push.draft?"warn":"ok"));
39484
39653
  if(!chips.firstChild)chips.appendChild(chip("Checks appear here as validate, test and push run"));
39485
39654
  var wall=$("wall");clear(wall);
39486
39655
  if(!s.screens.length)wall.appendChild(el("div","empty","Screens appear here as they render."));
39487
- s.screens.forEach(function(sc){var d=el("div","shot");var fr=el("div","frame");fr.appendChild(img(sc.img));d.appendChild(fr);d.appendChild(el("div","id",sc.id));
39656
+ s.screens.forEach(function(sc){var d=el("div","shot");d.setAttribute("data-id",sc.id);var fr=el("div","frame");fr.appendChild(img(sc.img));d.appendChild(fr);d.appendChild(el("div","id",sc.id));
39488
39657
  if(recent(sc.firstSeen)&&sc.versions===1)d.appendChild(el("span","badge new","New"));else if(recent(sc.changedAt)&&sc.versions>1)d.appendChild(el("span","badge chg","Changed"));wall.appendChild(d)});
39489
39658
  var brand=$("brand");clear(brand);$("brandcard").style.display=(s.brand.site||s.app.palette.length)?"":"none";
39490
39659
  if(s.brand.site){var si=img(s.brand.site,"site");brand.appendChild(si)}
@@ -39495,14 +39664,61 @@ function render(s){
39495
39664
  s.journal.forEach(function(j){var li=el("li");li.appendChild(el("div","t",hhmm(j.ts)));var body=el("div");body.appendChild(el("div",null,j.message));if(j.step)body.appendChild(el("div","s","step "+j.stepNumber+" \xB7 "+j.step));
39496
39665
  if(j.images.length){var row=el("div","imgs");j.images.forEach(function(u){row.appendChild(img(u))});body.appendChild(row)}li.appendChild(body);log.appendChild(li)});
39497
39666
  }
39498
- window.__himiStudio=function(s){lastSeen=Date.now();if(!cur||cur.v!==s.v){cur=s;render(s)}else{cur.generatedAt=s.generatedAt}};
39667
+ /* Motion, sound and notices answer a CHANGE, never a redraw: effects() compares what is on the page
39668
+ now with what it showed last, so opening the board, the once-a-minute redraw and the two-second
39669
+ poll of an unchanged state do nothing, and each effect names a real event. */
39670
+ var seen=null,actx=null,sound=false,notices=false;try{sound=localStorage.getItem("himiStudioSound")==="1";notices=localStorage.getItem("himiStudioNotify")==="1"}catch(e){}
39671
+ var raf=window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(f){return setTimeout(function(){f(Date.now())},16)};
39672
+ function now(){return window.performance&&performance.now?performance.now():Date.now()}
39673
+ function reduced(){try{return matchMedia("(prefers-reduced-motion: reduce)").matches}catch(e){return false}}
39674
+ function tone(f,t,d,v,type){if(!sound)return;try{if(!actx){var AC=window.AudioContext||window.webkitAudioContext;if(!AC)return;actx=new AC()}var o=actx.createOscillator(),g=actx.createGain(),n=actx.currentTime+(t||0),dd=d||.18;o.type=type||"sine";o.frequency.value=f;g.gain.setValueAtTime(0,n);g.gain.linearRampToValueAtTime(v||.05,n+.01);g.gain.exponentialRampToValueAtTime(.0001,n+dd);o.connect(g);g.connect(actx.destination);o.start(n);o.stop(n+dd+.05)}catch(e){}}
39675
+ var SND={tick:function(){tone(1320,0,.05,.025,"triangle")},post:function(){tone(880,0,.08,.03)},step:function(){tone(523);tone(784,.12)},win:function(){tone(523);tone(659,.1);tone(784,.2);tone(1047,.3,.35)},fail:function(){tone(160,0,.3,.05,"sawtooth")}};
39676
+ /* Notices are the BOARD's opt-in, not just the browser's: a permission granted to this origin once
39677
+ (an earlier board, another run) must not switch them on, and the toggle must switch them off. */
39678
+ function notify(title,body){try{if(notices&&"Notification" in window&&Notification.permission==="granted"&&document.hidden)new Notification(title,{body:body})}catch(e){}}
39679
+ function accentNow(){return (getComputedStyle(document.documentElement).getPropertyValue("--accent")||"").trim()||"#3b6fd8"}
39680
+ /* The preview lays out at a real phone's viewport (390 x 844 CSS px) and is scaled into the frame.
39681
+ Sized to the frame itself it was ~280px wide: narrower than any phone, so screens overflowed
39682
+ sideways and scrolled horizontally. */
39683
+ function fit(){var ph=$("focus").querySelector(".phone.vpf");if(!ph)return;var w=ph.clientWidth,h=ph.clientHeight;if(w&&h)ph.style.setProperty("--k",String(Math.min(w/390,h/844)))}
39684
+ window.addEventListener("resize",fit);
39685
+ function favicon(n,bad){try{var c=document.createElement("canvas");c.width=c.height=32;var x=c.getContext&&c.getContext("2d");if(!x)return;x.lineWidth=5;x.strokeStyle="rgba(128,128,128,.35)";x.beginPath();x.arc(16,16,12,0,6.2832);x.stroke();x.strokeStyle=accentNow();x.beginPath();x.arc(16,16,12,-1.5708,-1.5708+6.2832*n/8);x.stroke();if(bad){x.fillStyle="#d33";x.beginPath();x.arc(25,7,6,0,6.2832);x.fill()}$("fav").href=c.toDataURL("image/png")}catch(e){}}
39686
+ function burst(){var c=$("fx");c.setAttribute("data-bursts",String((+c.getAttribute("data-bursts")||0)+1));if(reduced())return;var x=c.getContext&&c.getContext("2d");if(!x)return;var W=c.width=innerWidth,H=c.height=innerHeight,cols=["#1f7a4d","#3b6fd8","#e0a030","#d4537e",accentNow()],P=[];for(var i=0;i<120;i++)P.push({x:W/2,y:H*.35,vx:(Math.random()-.5)*14,vy:-Math.random()*12-4,c:cols[i%cols.length],r:Math.random()*6.28});var t0=now();(function f(){x.clearRect(0,0,W,H);P.forEach(function(p){p.x+=p.vx;p.y+=p.vy;p.vy+=.35;p.r+=.2;x.save();x.translate(p.x,p.y);x.rotate(p.r);x.fillStyle=p.c;x.fillRect(-4,-2,8,4);x.restore()});if(now()-t0<2200)raf(f);else x.clearRect(0,0,W,H)})()}
39687
+ function countTo(e,a,b){if(!e)return;if(reduced()){e.textContent=String(b);return}var t0=now();(function f(){var k=Math.min(1,(now()-t0)/900);e.textContent=String(Math.round(a+(b-a)*k));if(k<1)raf(f)})()}
39688
+ function snap(s){var sc={},acc=null;s.screens.forEach(function(x){sc[x.id]=x.img});s.app.palette.forEach(function(p){if(p.name==="accentPrimary")acc=p.hex});return{screens:sc,step:s.current?s.current.stepNumber:0,top:s.journal[0]?s.journal[0].ts:null,build:s.live&&s.live.lastBuild?s.live.lastBuild.at:null,run:s.live?s.live.runId:null,push:s.health.push?s.health.push.releaseId:null,gaps:s.health.test?s.health.test.gaps:null,accent:acc}}
39689
+ function buildLine(b){if(!b.ok)return "Build failed \xB7 still showing the last good version";var what=b.screens.length?b.screens.slice(0,3).join(", ")+(b.screens.length>3?" +"+(b.screens.length-3):""):b.tokens?"design tokens":b.config?"app config":"no visible change";return "Rebuilt"+(b.ms!=null?" in "+(b.ms/1000).toFixed(1)+" s":"")+" \xB7 "+what}
39690
+ function effects(s){var is=snap(s),was=seen;seen=is;if(is.accent)document.documentElement.style.setProperty("--accent",is.accent);
39691
+ /* The phone (kept across renders) and the Now line outlive a render: clear what they played last. */
39692
+ var ph=$("focus").querySelector(".phone"),nm=$("nowmsg");if(ph)ph.classList.remove("pulse","bad");nm.classList.remove("slidein");if(!was)return;
39693
+ var ticked=false;[].forEach.call($("wall").children,function(d){var id=d.getAttribute("data-id");if(!id)return;if(!(id in was.screens)){d.classList.add("enter");ticked=true}else if(was.screens[id]!==is.screens[id]){d.classList.add("changed");ticked=true}});if(ticked)SND.tick();
39694
+ if(is.top&&is.top!==was.top){void nm.offsetWidth;nm.classList.add("slidein");var li=$("log").firstChild;if(li&&li.classList)li.classList.add("slidein");SND.post()}
39695
+ if(is.step>was.step){var cs=$("steps").children[is.step-1];if(cs)cs.classList.add("fresh");SND.step();notify(s.app.name+" \xB7 step "+is.step+" of 8",s.journal[0]?s.journal[0].message:s.steps[is.step-1])}
39696
+ /* A rebuild is a later build of the SAME run: a run appearing, or restarting, records its first
39697
+ build too, and nothing was rebuilt then. */
39698
+ if(is.build&&was.build&&is.run===was.run&&is.build!==was.build){var b=s.live.lastBuild;if(ph){void ph.offsetWidth;ph.classList.add("pulse");if(!b.ok)ph.classList.add("bad")}var tt=el("div","toast"+(b.ok?"":" bad"),buildLine(b));$("focus").appendChild(tt);setTimeout(function(){if(tt.parentNode)tt.parentNode.removeChild(tt)},3500);if(b.ok)SND.tick();else{SND.fail();notify(s.app.name+" \xB7 build failed",s.live.buildError||"")}}
39699
+ if(was.gaps!=null&&is.gaps!=null&&is.gaps!==was.gaps)countTo($("gapn"),was.gaps,is.gaps);
39700
+ if(is.accent&&was.accent&&is.accent!==was.accent){var sw=$("brand").querySelector(".sw");if(sw)sw.classList.add("pop");favicon(is.step,!!(s.live&&s.live.buildError))}
39701
+ var published=is.push&&is.push!==was.push,handed=is.step===8&&was.step<8;if(published||handed){burst();SND.win();notify(s.app.name,published?"Draft published":"Handed over")}
39702
+ }
39703
+ function activityAt(s){var m=0;function add(v){var d=v?Date.parse(v):0;if(d>m)m=d}if(s.journal[0])add(s.journal[0].ts);s.screens.forEach(function(x){add(x.changedAt)});if(s.live&&s.live.lastBuild)add(s.live.lastBuild.at);["validate","test","push"].forEach(function(k){if(s.health[k])add(s.health[k].at)});return m}
39704
+ function paintToggles(){var b=$("snd");b.textContent=sound?"Sound on":"Sound off";b.className="tog"+(sound?" on":"");b.setAttribute("aria-pressed",String(sound));var nb=$("ntf");if(!("Notification" in window)){nb.hidden=true;return}var p=Notification.permission,on=notices&&p==="granted";nb.hidden=p==="denied";nb.textContent=on?"Notices on":"Notices off";nb.className="tog"+(on?" on":"");nb.setAttribute("aria-pressed",String(on))}
39705
+ function setNotices(v){notices=v;try{localStorage.setItem("himiStudioNotify",v?"1":"0")}catch(e){}paintToggles()}
39706
+ $("snd").addEventListener("click",function(){sound=!sound;try{localStorage.setItem("himiStudioSound",sound?"1":"0")}catch(e){}if(sound){try{if(actx&&actx.resume)actx.resume()}catch(e){}SND.post()}paintToggles()});
39707
+ /* The click acts on what the button SAYS (the effective state), so a remembered opt-in whose browser
39708
+ permission was since reset reads "off" and one click asks for permission, not two. */
39709
+ $("ntf").addEventListener("click",function(){try{var p=Notification.permission;if(notices&&p==="granted"){setNotices(false);return}if(p==="granted"){setNotices(true);return}if(Notification.permission==="default")Promise.resolve(Notification.requestPermission()).then(function(p){setNotices(p==="granted")})}catch(e){}});
39710
+ document.addEventListener("click",function(){try{if(actx&&actx.state==="suspended")actx.resume()}catch(e){}},{once:true});
39711
+ paintToggles();
39712
+ window.__himiStudio=function(s){lastSeen=Date.now();if(!cur||cur.v!==s.v){cur=s;render(s);effects(s)}else{cur.generatedAt=s.generatedAt}};
39499
39713
  $("lb").addEventListener("click",function(){this.style.display="none"});
39500
39714
  window.__himiStudio(window.__himiInitial);
39501
39715
  setInterval(function(){var sc=document.createElement("script");sc.src="state.js?t="+Date.now();sc.onload=sc.onerror=function(){sc.remove()};document.head.appendChild(sc)},2000);
39502
39716
  var ticks=0;
39503
39717
  setInterval(function(){if(!cur)return;if(++ticks%60===0)render(cur);var start=cur.startedAt?Date.parse(cur.startedAt):Date.now();var s=Math.max(0,Math.floor((Date.now()-start)/1000));var hh=Math.floor(s/3600),mm=Math.floor(s%3600/60),ss=s%60;
39504
39718
  $("clock").textContent=(hh?hh+":":"")+(mm<10&&hh?"0":"")+mm+":"+(ss<10?"0":"")+ss;
39505
- $("updated").textContent="updated "+ago(cur.generatedAt);$("live").className="live"+(Date.now()-lastSeen>10000?" stale":"")},1000);
39719
+ $("updated").textContent="updated "+ago(cur.generatedAt);$("live").className="live"+(Date.now()-lastSeen>10000?" stale":"");
39720
+ var a=activityAt(cur),q=a?Date.now()-a:0,done=cur.current&&cur.current.stepNumber===8;
39721
+ $("agent").className="live"+(!a?" idle":done?"":q>5*60*1000?" quiet":"");$("agentt").textContent=!a?"Waiting for the agent":done?"Handed over":q>5*60*1000?"Quiet for "+Math.floor(q/60000)+" min":"Agent active"},1000);
39506
39722
  })();
39507
39723
  </script>
39508
39724
  </body>
@@ -47241,7 +47457,6 @@ function makeServeHandler(opts) {
47241
47457
  );
47242
47458
  })();
47243
47459
  const webwixFetch = opts.webwixFetch ?? fetch;
47244
- const webwixTokens = new VisitorTokenCache(webwixFetch);
47245
47460
  const webwixOwnerInstances = new OwnerInstanceCache(webwixFetch);
47246
47461
  const webwixOwnerSites = new OwnerSitesCache(webwixFetch);
47247
47462
  const webwixOwnerRungOn = () => opts.webwixOwnerRung !== false;
@@ -47543,7 +47758,7 @@ ${releaseId}`;
47543
47758
  const pkce = await generatePkce();
47544
47759
  let authUrl;
47545
47760
  try {
47546
- const visitor = await webwixTokens.token(resolved2.config.clientId);
47761
+ const visitor = await mintAnonymousVisitor(resolved2.config.clientId, webwixFetch);
47547
47762
  authUrl = await hostedAuthUrl(
47548
47763
  { ...resolved2.config, mode: path2.endsWith("/register") ? "signup" : "login", state, challenge: pkce.challenge },
47549
47764
  visitor,
@@ -47614,7 +47829,14 @@ ${releaseId}`;
47614
47829
  const reqPath = String(call.path ?? "/");
47615
47830
  const body = call.body === void 0 ? void 0 : typeof call.body === "string" ? call.body : JSON.stringify(call.body);
47616
47831
  const forwardedHeaders = normalizeWixForwardedHeaders(call.headers);
47832
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(reqPath)) {
47833
+ return absoluteUrlRejection(reqPath) === "owner-only" ? json3(501, { error: "webwix_owner_only", app: app2 }) : json3(400, { error: "absolute_urls_not_allowed" });
47834
+ }
47617
47835
  const callerCookie = request.headers.get("cookie") ?? "";
47836
+ if (!call.checkOwnerSession && webwixOwnerRungOn() && method === "POST" && isOwnerSitesPath(reqPath) && !/wixSession2=/.test(callerCookie)) {
47837
+ served.rung = "sites";
47838
+ return json3(501, { error: "webwix_owner_no_session", app: app2 });
47839
+ }
47618
47840
  if (!call.checkOwnerSession && webwixOwnerRungOn() && method === "POST" && isOwnerSitesPath(reqPath) && /wixSession2=/.test(callerCookie)) {
47619
47841
  served.rung = "sites";
47620
47842
  try {
@@ -47633,32 +47855,8 @@ ${releaseId}`;
47633
47855
  }
47634
47856
  if (call.ownerMsid !== void 0) {
47635
47857
  served.rung = call.checkOwnerSession === true ? "gate" : "owner";
47636
- if (!webwixOwnerRungOn()) {
47637
- return json3(503, { error: "webwix_owner_disabled" });
47638
- }
47639
- const msid = String(call.ownerMsid);
47640
- if (!isSafeMsid(msid)) return json3(400, { error: "webwix_owner_bad_msid" });
47641
- const cookie = request.headers.get("cookie") ?? "";
47642
- if (!/wixSession2=/.test(cookie)) return json3(501, { error: "webwix_owner_no_session", app: app2 });
47643
- try {
47644
- const instance = await webwixOwnerInstances.instance(msid, cookie);
47645
- if (call.checkOwnerSession === true) return json3(200, { status: 200, body: JSON.stringify({ ready: true }) }, { "cache-control": "no-store" });
47646
- const first = await wixVisitorCall(method, reqPath, body, instance, webwixFetch, forwardedHeaders);
47647
- if (first.status !== 401) return json3(200, first);
47648
- webwixOwnerInstances.invalidate(msid, cookie);
47649
- const fresh = await webwixOwnerInstances.instance(msid, cookie);
47650
- return json3(200, await wixVisitorCall(method, reqPath, body, fresh, webwixFetch, forwardedHeaders));
47651
- } catch (e) {
47652
- const msg = String(e?.message ?? e);
47653
- if (msg.includes("absolute URLs")) {
47654
- return absoluteUrlRejection(reqPath) === "owner-only" ? json3(501, { error: "webwix_owner_only", app: app2 }) : json3(400, { error: "absolute_urls_not_allowed" });
47655
- }
47656
- if (msg.includes("owner mint failed")) {
47657
- if (/owner mint failed \(5\d\d\)/.test(msg)) return json3(502, { error: "webwix_bridge_upstream_failed" });
47658
- return json3(501, { error: "webwix_owner_mint_failed", app: app2 });
47659
- }
47660
- return json3(502, { error: "webwix_bridge_upstream_failed", detail: msg.slice(0, 200) });
47661
- }
47858
+ if (!webwixOwnerRungOn()) return json3(503, { error: "webwix_owner_disabled" });
47859
+ return json3(501, { error: "webwix_owner_direct_only", app: app2 });
47662
47860
  }
47663
47861
  const resolved2 = wixClientIdOf(utf8Decode(cfgBytes));
47664
47862
  if ("error" in resolved2) {
@@ -47675,28 +47873,11 @@ ${releaseId}`;
47675
47873
  try {
47676
47874
  return json3(200, await wixVisitorCall(method, reqPath, body, memberToken, webwixFetch, forwardedHeaders), { "cache-control": "no-store" });
47677
47875
  } catch (e) {
47678
- const msg = String(e?.message ?? e);
47679
- if (msg.includes("absolute URLs")) {
47680
- return absoluteUrlRejection(reqPath) === "owner-only" ? json3(501, { error: "webwix_owner_only", app: app2 }) : json3(400, { error: "absolute_urls_not_allowed" });
47681
- }
47682
- return json3(502, { error: "webwix_bridge_upstream_failed", detail: msg.slice(0, 200) });
47876
+ return json3(502, { error: "webwix_bridge_upstream_failed", detail: String(e?.message ?? e).slice(0, 200) });
47683
47877
  }
47684
47878
  }
47685
47879
  }
47686
- try {
47687
- const token = await webwixTokens.token(resolved2.clientId);
47688
- const first = await wixVisitorCall(method, reqPath, body, token, webwixFetch, forwardedHeaders);
47689
- if (first.status !== 401) return json3(200, first);
47690
- webwixTokens.invalidate(resolved2.clientId);
47691
- const fresh = await webwixTokens.token(resolved2.clientId);
47692
- return json3(200, await wixVisitorCall(method, reqPath, body, fresh, webwixFetch, forwardedHeaders));
47693
- } catch (e) {
47694
- const msg = String(e?.message ?? e);
47695
- if (msg.includes("absolute URLs")) {
47696
- return absoluteUrlRejection(reqPath) === "owner-only" ? json3(501, { error: "webwix_owner_only", app: app2 }) : json3(400, { error: "absolute_urls_not_allowed" });
47697
- }
47698
- return json3(502, { error: "webwix_bridge_upstream_failed", detail: msg.slice(0, 200) });
47699
- }
47880
+ return json3(501, { error: "webwix_visitor_direct_only", app: app2 });
47700
47881
  })();
47701
47882
  status = res.status;
47702
47883
  return res;
@@ -48280,7 +48461,7 @@ function indexHtml(asset, pathname, creds, handoverAttempted = false) {
48280
48461
  void pathname;
48281
48462
  const nonce = scriptNonce();
48282
48463
  const raw = new TextDecoder().decode(assetBytes(asset));
48283
- const head = `<script nonce="${nonce}">${PREVIEW_BASE_JS}</script>` + (creds ? `<script nonce="${nonce}">${previewCredentialsJs(creds)}</script>` : handoverAttempted ? `<script nonce="${nonce}">${stripHandoverParamJs()}</script>` : "");
48464
+ const head = `<script nonce="${nonce}">${PREVIEW_BASE_JS}</script><script nonce="${nonce}">${PREVIEW_ENTRY_RECOVERY_JS}</script>` + (creds ? `<script nonce="${nonce}">${previewCredentialsJs(creds)}</script>` : handoverAttempted ? `<script nonce="${nonce}">${stripHandoverParamJs()}</script>` : "");
48284
48465
  const html = raw.replace(/(=|url\()(["']?)\/preview\//g, "$1$2").replace(/<head(\s[^>]*)?>/i, (tag) => `${tag}${head}`);
48285
48466
  return new Response(html, {
48286
48467
  status: 200,
@@ -48332,7 +48513,7 @@ function previewResponse(pathname, assets, mount = "/preview", creds, handoverAt
48332
48513
  headers: { "content-type": "application/json; charset=utf-8" }
48333
48514
  });
48334
48515
  }
48335
- var T5_FRAME_CSP, PREVIEW_BASE_JS, MOUNT_STRIP;
48516
+ var T5_FRAME_CSP, PREVIEW_BASE_JS, PREVIEW_ENTRY_RECOVERY_JS, MOUNT_STRIP;
48336
48517
  var init_preview2 = __esm({
48337
48518
  "../../packages/serve/src/preview.ts"() {
48338
48519
  T5_FRAME_CSP = "default-src 'none'; script-src 'unsafe-inline' 'unsafe-eval'; connect-src 'none'; img-src 'none'; media-src 'none'; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'";
@@ -48341,6 +48522,7 @@ var init_preview2 = __esm({
48341
48522
  // the EARLIEST mount segment wins, mirroring the SPA's own deployBase (an app literally
48342
48523
  // named "app" — /preview/a/app — must not hijack the base).
48343
48524
  '(function(){var m=location.pathname.match(/^(.*?\\/(?:preview|app))(?:\\/|$)/);var b=document.createElement("base");b.href=(m?m[1]:"")+"/";document.head.insertBefore(b,document.head.firstChild)})();';
48525
+ PREVIEW_ENTRY_RECOVERY_JS = '(function(){var K="himalaya.preview.entryReload";function show(){if(document.getElementById("himalaya-preview-load-failed"))return;var d=document.createElement("div");d.id="himalaya-preview-load-failed";d.setAttribute("role","alert");d.style.cssText="position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;align-items:center;justify-content:center;box-sizing:border-box;padding:32px;text-align:center;font:15px/1.5 system-ui,sans-serif;background:#f5f7fa;color:#111";var p=document.createElement("p");p.textContent="This app couldn\\u2019t load \\u2014 a new version may have just been published.";var b=document.createElement("button");b.type="button";b.textContent="Reload";b.addEventListener("click",function(){location.reload()});d.appendChild(p);d.appendChild(b);(document.body||document.documentElement).appendChild(d)}function failed(){var now=Date.now(),last=0;try{last=+sessionStorage.getItem(K)||0}catch(e){}if(now-last>60000){try{sessionStorage.setItem(K,String(now));location.reload();return}catch(e){}}if(document.body)show();else document.addEventListener("DOMContentLoaded",show)}window.addEventListener("error",function(e){var t=e&&e.target;if(t&&t.tagName==="SCRIPT"&&t.type==="module")failed()},true)})();';
48344
48526
  MOUNT_STRIP = {
48345
48527
  // Precompiled — this runs on every request of the public serving hot path.
48346
48528
  "/preview": /^\/preview\/?/,
@@ -48587,21 +48769,21 @@ var init_preview_assets_generated = __esm({
48587
48769
  "../../packages/serve/src/preview-assets.generated.ts"() {
48588
48770
  init_preview_assets_format();
48589
48771
  PREVIEW_ASSET_MANIFEST = [
48590
- { key: "assets/index-FMHX2G25.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1031131, sha256: "0d386640e1979d4c81abbb29ccd0531913f1a759eba2e490160c7d3fac52618d" },
48591
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1031139, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
48592
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1035749, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
48593
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1524333, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
48594
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1615257, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
48595
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1706241, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
48596
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1797221, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
48597
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1888093, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
48598
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1979073, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
48599
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2087709, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
48600
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2196793, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
48601
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2305841, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
48602
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2413541, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
48603
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2501649, length: 4947, sha256: "1f91f02ba75063dd7b2cca2584fcb3cc692f7faf905e89c23bf790633fd3d0e9" },
48604
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2506596, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
48772
+ { key: "assets/index-hHpGDjBZ.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1038931, sha256: "65ef206c528bdc3e9b7ec6907e734267cd225ed33513982c46ecb3bbdd6d44ee" },
48773
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1038939, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
48774
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1043549, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
48775
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1532133, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
48776
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1623057, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
48777
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1714041, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
48778
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1805021, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
48779
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1895893, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
48780
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1986873, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
48781
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2095509, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
48782
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2204593, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
48783
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2313641, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
48784
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2421341, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
48785
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2509449, length: 4947, sha256: "f07de6435f49d7608a56b7af529229f2ccc10901ab33a9e600e01f10b60975d6" },
48786
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2514396, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
48605
48787
  ];
48606
48788
  }
48607
48789
  });
@@ -63412,9 +63594,10 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
63412
63594
  (--ai writes rows for you to review first)
63413
63595
  himi secrets set|list|rotate --app <app> --name <name> [--environment preview|production]
63414
63596
  encrypted secret metadata; set/rotate read the value from stdin
63415
- himi render <screenId> [--content <dir>] [--scheme dark] [--png <file>] [--tree] [--dump-state] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--load-budget <ms>]
63597
+ himi render <screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--dump-state] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--load-budget <ms>]
63416
63598
  [--remote --app <app> --release <id>]
63417
63599
  headless web render + diagnostics (no sim). Default: local content dir.
63600
+ A bare --png writes .himi/shots/<screenId>.png (what the build board shows).
63418
63601
  --remote fetches GET /v1/preview/\u2026/render from the serve plane (post-push).
63419
63602
  It returns the pushed descriptor STRUCTURE only \u2014 NO png and NO live data
63420
63603
  (the serve has no headless browser); for a pixel view or REAL fetched data
@@ -63593,7 +63776,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
63593
63776
  plane, no dev-server. With no argument it packs the current
63594
63777
  content package first \u2014 edit, run, look at it.
63595
63778
  --watch rebuilds on every save and repaints the open tab
63596
- (a failed build keeps the last good preview). Blocks until Ctrl-C.
63779
+ (a failed build keeps the last good preview). The build board
63780
+ is served beside it at /__himi/studio/ with the preview framed
63781
+ in it. Blocks until Ctrl-C.
63597
63782
  himi flags list|set|ramp|rm [<key>] [--app <a>]
63598
63783
  read/change operational flags (RFC 0013); writes report
63599
63784
  servedNow from the device door, never just ok
@@ -63920,7 +64105,7 @@ var COMMANDS = [
63920
64105
  { name: "ux", summary: "audit or record mobile UX evidence. `audit` checks the public-safe contract, seeded blank acceptance rows, placeholder source copy, app-specific mocks, and iOS/Android audit records. It warns while existing templates migrate; `--strict` promotes missing evidence. `record` stores checks performed on an installed iOS/Android build: `--checks` is required for passed|failed and empty for unavailable, which drove nothing and needs a `--reason` instead. `record` also STAMPS the release it audited (it resolves the id from a build, the same one a push would publish), because an unforced `himi push` of a release with screens refuses when no drive is recorded for THAT release, or when the one that is recorded says `failed` \u2014 the artifact stopped being advice nobody reads (#2955). `unavailable` still satisfies it; only `failed` refuses.", required: "audit [--content <dir>] [--strict] [--json] | record --content <dir> --platform ios|android --status passed|failed|unavailable [--checks back,keyboard,...] [--reason <text>]" },
63921
64106
  { name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--notify]] [--content <dir>]' },
63922
64107
  { name: "studio", summary: "open the BUILD BOARD \u2014 one local page the owner can leave open while an agent builds their app, which fills itself in: every screen as it renders (NEW/CHANGED badges, and each earlier version kept for a filmstrip), the journey stepper and the latest postcard from `himi progress`, the validate/test/UX/draft checks as they run, the site next to the app's home screen with its palette, and the navigation the test crawl followed. Writes .himi/studio/index.html and opens it; the page re-reads its state every two seconds, so nothing needs to be served. OPT-IN BY EXISTENCE: until this command has run, no other command touches .himi/studio/ \u2014 after it, render/test/validate/push/icon/site/ux/progress each refresh the board as they finish (best-effort; never fails a command). Every image is a content-addressed copy in .himi/history/, never a live path into .himi/shots/. Local only, no secrets: it never reads a preview token, handover code or control token. --no-open prints the path instead (SSH, CI). `himi studio reel` writes the MAKING-OF: one self-contained HTML file (every image inlined, nothing fetched) with a flipbook of the home screen version by version, captioned by the postcard in effect at the time, the final screens, and every postcard \u2014 sized to stay attachable, thinning frames first. Defaults to .himi/making-of.html (never archived by push); --out to put it elsewhere.", required: "[--content <dir>] [--no-open] | reel [--out <file>] [--no-open]" },
63923
- { name: "render", summary: "headless-render a screen with the web renderer: render-tree + diagnostics, optional --png <file>, --scheme dark, --tree. Local by default (content dir). Pass --remote --app --release to hit GET /v1/preview/\u2026/render on the serve plane after a push. Boots the worker so $service bindings resolve; --static raw bindings; --live real Wix fetches; --state injects worker state; --input supplies pre-boot navigation params; --width/--device for PNG viewport; --rtl for dir=rtl + $env:isRTL.", required: "<screenId> [--content <dir>] [--scheme dark] [--png <file>] [--tree] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
64108
+ { name: "render", summary: "headless-render a screen with the web renderer: render-tree + diagnostics, optional --png [<file>] (bare: .himi/shots/<id>.png, what the build board shows), --scheme dark, --tree. Local by default (content dir). Pass --remote --app --release to hit GET /v1/preview/\u2026/render on the serve plane after a push. Boots the worker so $service bindings resolve; --static raw bindings; --live real Wix fetches; --state injects worker state; --input supplies pre-boot navigation params; --width/--device for PNG viewport; --rtl for dir=rtl + $env:isRTL.", required: "<screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
63924
64109
  { name: "cms", summary: "inspect, provision, and seed the app's CMS (Wix Data) collections, declared as a `cms` block in config.ts. `query` is READ-ONLY \u2014 by default it fetches ONLY the physical ids `config.ts` declares (item counts + schema drift) and reports `siteScanned: false`; it never lists the site, so an UNDECLARED `himi_<app>_` collection this app owns is not reached without `--all`. `--all` SCANS the site \u2014 every collection it lists is labeled `owned`/`adopted`/`foreign`, and it describes every foreign one \u2014 id, non-system field keys + types, and the site's read role \u2014 so an app with no `cms` block at all can DISCOVER a collection to adopt (`--all` is the one case where a missing `cms` block is not 'nothing to do'). Foreign collections are described up to a cap and are never item-counted (`itemCount: null`), because a row count is one query each; `foreign.count` is how many the scan LISTED \u2014 it follows the list endpoint's pages, and `listingIncomplete: true` says when the walk stopped short of the end, with `unlistedCollections` counting what it left out when Wix gave a paging total \u2014 and `foreign.truncated` says how many of those went undescribed, each still NAMED in `foreign.others` (id + displayName), so a collection the site already has is never hidden behind the cap. The scan is OWNER-scoped \u2014 it needs --site <msid> (or WIX_SITE_ID) plus owner auth via `wix token`; a visitor token can read a public collection's ITEMS but can never enumerate collections, so there is no tokenless discovery path. `create` reconciles declared collections additively and idempotently, and REFUSES any id outside the app's `himi_<app>_` namespace, so it is safe against a production site. It reports declared-vs-site `permissionDrift` but never writes permissions unless asked with --apply-permissions, so a field reconcile can't silently revert access control changed in the dashboard. `seed` inserts the git-tracked `seed:` rows into EMPTY owned collections (--force to add to a non-empty one; --ai writes placeholder rows plus a refinement prompt for you to review). Adopted collections (declared by `wixId`) are read-only and never created, updated, or seeded. Site from --site <msid> or WIX_SITE_ID; owner auth via `wix token`.", required: "query|create|seed [--site <msid>] [--content <dir>] [--all] [--dry-run] [--force] [--allow-destructive] [--apply-permissions] [--only <id>] [--ai] [--count <n>]" },
63925
64110
  { name: "data", summary: "read-only plan or explicitly provision/seed declared KV and Wix Data resources. Preview and production are separate scopes. KV administration uses the operator data plane from --data-url/HIMI_DATA_ADMIN_URL and HIMI_DATA_ADMIN_TOKEN; draft push never calls it.", required: "plan|create|seed [--site <msid>] [--environment preview|production] [--content <dir>] [--data-url <url>] [--dry-run] [--force]" },
63926
64111
  { name: "icons", summary: "list the valid Icon.name vocabulary (an unknown name renders a fallback glyph; validate warns on unknown names) (offline)", required: "[--json]" },
@@ -68447,7 +68632,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68447
68632
  current = fresh;
68448
68633
  servedMissing = outcome.missingBundles;
68449
68634
  for (const [event, data] of watchKit.reloadEvents(diff)) bus.publish(event, data);
68450
- record2(running, { ok: true, at: (/* @__PURE__ */ new Date()).toISOString() });
68635
+ record2(running, { ok: true, at: (/* @__PURE__ */ new Date()).toISOString(), ms: Date.now() - t0, screens: diff.screens, tokens: diff.tokens, config: diff.config });
68451
68636
  await refreshBoard();
68452
68637
  io.error(JSON.stringify({ watch: "rebuilt", ms: Date.now() - t0, screens: diff.screens, tokens: diff.tokens, config: diff.config, tabs: bus.listeners }));
68453
68638
  } catch (e) {