@wix/himalaya-cli 0.1026.0 → 0.1028.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.
Files changed (74) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +9 -0
  2. package/dist/cli.mjs +1128 -666
  3. package/dist/preview-assets.generated.bin +0 -0
  4. package/guides/app-config.md +6 -0
  5. package/guides/headless-loop.md +3 -1
  6. package/package.json +2 -2
  7. package/templates/beauty-salon/dev/index.ts +331 -199
  8. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +18 -8
  9. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +28 -3
  10. package/templates/beauty-salon-owner/dev/index.ts +857 -458
  11. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +18 -8
  12. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +28 -3
  13. package/templates/branded-lite/dev/index.ts +1539 -623
  14. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +18 -8
  15. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +28 -3
  16. package/templates/community-club/dev/index.ts +237 -142
  17. package/templates/community-club/tier5-src/_shared/collection-contract.ts +18 -8
  18. package/templates/community-club/tier5-src/_shared/collection-service.ts +28 -3
  19. package/templates/community-club-owner/dev/index.ts +857 -458
  20. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +18 -8
  21. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +28 -3
  22. package/templates/events-meetups/dev/index.ts +202 -129
  23. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +18 -8
  24. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +28 -3
  25. package/templates/events-meetups-owner/dev/index.ts +815 -458
  26. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +18 -8
  27. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +28 -3
  28. package/templates/gym-fitness/dev/index.ts +461 -212
  29. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +18 -8
  30. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +28 -3
  31. package/templates/gym-fitness-owner/dev/index.ts +758 -338
  32. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +18 -8
  33. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +28 -3
  34. package/templates/himi-chat/dev/index.ts +26 -1
  35. package/templates/nonprofit-cause/dev/index.ts +246 -153
  36. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +18 -8
  37. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +28 -3
  38. package/templates/nonprofit-cause-owner/dev/index.ts +716 -338
  39. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +18 -8
  40. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +28 -3
  41. package/templates/owner-lite/dev/index.ts +758 -338
  42. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +18 -8
  43. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +28 -3
  44. package/templates/podcast-creator/dev/index.ts +255 -164
  45. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +18 -8
  46. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +28 -3
  47. package/templates/podcast-creator-owner/dev/index.ts +857 -458
  48. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +18 -8
  49. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +28 -3
  50. package/templates/portfolio-creative/dev/index.ts +287 -175
  51. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +18 -8
  52. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +28 -3
  53. package/templates/portfolio-creative-owner/dev/index.ts +857 -458
  54. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +18 -8
  55. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +28 -3
  56. package/templates/restaurant/dev/index.ts +200 -126
  57. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +18 -8
  58. package/templates/restaurant/tier5-src/_shared/collection-service.ts +28 -3
  59. package/templates/restaurant-owner/dev/index.ts +758 -338
  60. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +18 -8
  61. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +28 -3
  62. package/templates/retail-store/dev/index.ts +113 -138
  63. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +18 -8
  64. package/templates/retail-store/tier5-src/_shared/collection-service.ts +28 -3
  65. package/templates/retail-store-owner/dev/index.ts +716 -338
  66. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +18 -8
  67. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +28 -3
  68. package/templates/surface-showcase/dev/index.ts +27 -0
  69. package/templates/yoga-studio/dev/index.ts +427 -198
  70. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +18 -8
  71. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +28 -3
  72. package/templates/yoga-studio-owner/dev/index.ts +716 -338
  73. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +18 -8
  74. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +28 -3
package/dist/cli.mjs CHANGED
@@ -5351,7 +5351,6 @@ function walk(node, registry, path2, out) {
5351
5351
  var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
5352
5352
  var init_tokens = __esm({
5353
5353
  "../../core/ts/src/tokens.ts"() {
5354
- "use strict";
5355
5354
  SYSTEM_FONT_FAMILY = "$system";
5356
5355
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
5357
5356
  }
@@ -5378,7 +5377,6 @@ function resolveTranslation(value, catalog, sourceCatalog) {
5378
5377
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
5379
5378
  var init_bindings = __esm({
5380
5379
  "../../core/ts/src/bindings.ts"() {
5381
- "use strict";
5382
5380
  BINDING_SIGILS = ["service", "flag", "item", "t"];
5383
5381
  BINDING_SIGIL_RE = new RegExp(
5384
5382
  `^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
@@ -22580,6 +22578,40 @@ var init_icons = __esm({
22580
22578
  }
22581
22579
  });
22582
22580
 
22581
+ // ../../core/web-renderer/src/webviewFrame.ts
22582
+ function currentOrigin() {
22583
+ try {
22584
+ const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
22585
+ return origin && origin !== "null" ? origin : null;
22586
+ } catch {
22587
+ return null;
22588
+ }
22589
+ }
22590
+ function webviewFrameSandbox(url, grants2 = [], pageOrigin = currentOrigin()) {
22591
+ const parsed = url.replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/g, "").replace(/[\t\n\r]/g, "");
22592
+ const absolute = /^[a-z][a-z0-9+.-]*:/i.test(parsed);
22593
+ let target;
22594
+ try {
22595
+ target = new URL(parsed, pageOrigin ?? "http://relative.invalid");
22596
+ } catch {
22597
+ return null;
22598
+ }
22599
+ if (!NETWORK_SCHEMES.has(target.protocol)) return null;
22600
+ const crossOrigin = pageOrigin !== null && absolute && target.origin !== pageOrigin;
22601
+ const tokens = ["allow-scripts", ...grants2.filter((g) => g !== "allow-scripts" && g !== "allow-same-origin")];
22602
+ if (crossOrigin) tokens.splice(1, 0, "allow-same-origin");
22603
+ return { src: crossOrigin ? target.href : url, sandbox: tokens.join(" ") };
22604
+ }
22605
+ function isWebviewUrlAllowed(url) {
22606
+ return webviewFrameSandbox(url, [], null) !== null;
22607
+ }
22608
+ var NETWORK_SCHEMES;
22609
+ var init_webviewFrame = __esm({
22610
+ "../../core/web-renderer/src/webviewFrame.ts"() {
22611
+ NETWORK_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:"]);
22612
+ }
22613
+ });
22614
+
22583
22615
  // ../../core/web-renderer/src/a11y/controls.tsx
22584
22616
  import React3 from "react";
22585
22617
  import { jsx as jsx4, jsxs } from "react/jsx-runtime";
@@ -22907,6 +22939,65 @@ var init_controls = __esm({
22907
22939
  }
22908
22940
  });
22909
22941
 
22942
+ // ../../core/web-renderer/src/a11y/modalStack.ts
22943
+ function pushModalLayer(el) {
22944
+ const layer = { el: typeof el === "function" ? el : () => el };
22945
+ layers.push(layer);
22946
+ sync();
22947
+ return {
22948
+ isTop: () => layers[layers.length - 1] === layer,
22949
+ release: () => {
22950
+ const i = layers.indexOf(layer);
22951
+ if (i < 0) return;
22952
+ layers.splice(i, 1);
22953
+ sync();
22954
+ }
22955
+ };
22956
+ }
22957
+ function topElement() {
22958
+ for (let i = layers.length - 1; i >= 0; i--) {
22959
+ const el = layers[i].el();
22960
+ if (el?.isConnected) return el;
22961
+ }
22962
+ return null;
22963
+ }
22964
+ function sync() {
22965
+ watcher?.disconnect();
22966
+ watcher = null;
22967
+ for (const el of madeInert) el.removeAttribute("inert");
22968
+ madeInert.clear();
22969
+ const top = topElement();
22970
+ if (!top) return;
22971
+ const scope = top.parentElement?.closest(`[${MODAL_SCOPE_ATTR}]`) ?? top.ownerDocument.body;
22972
+ const levels = [];
22973
+ let node = top;
22974
+ while (node !== scope && node.parentElement) {
22975
+ const parent = node.parentElement;
22976
+ levels.push(parent);
22977
+ for (const sibling of Array.from(parent.children)) {
22978
+ if (sibling === node || SKIP.has(sibling.tagName) || sibling.hasAttribute("inert")) continue;
22979
+ sibling.setAttribute("inert", "");
22980
+ madeInert.add(sibling);
22981
+ }
22982
+ node = parent;
22983
+ }
22984
+ const Observer = top.ownerDocument.defaultView?.MutationObserver;
22985
+ if (Observer && levels.length) {
22986
+ watcher = new Observer(() => sync());
22987
+ for (const level of levels) watcher.observe(level, { childList: true });
22988
+ }
22989
+ }
22990
+ var MODAL_SCOPE_ATTR, SKIP, layers, madeInert, watcher;
22991
+ var init_modalStack = __esm({
22992
+ "../../core/web-renderer/src/a11y/modalStack.ts"() {
22993
+ MODAL_SCOPE_ATTR = "data-himi-modal-scope";
22994
+ SKIP = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "LINK", "META", "TEMPLATE", "NOSCRIPT", "HEAD"]);
22995
+ layers = [];
22996
+ madeInert = /* @__PURE__ */ new Set();
22997
+ watcher = null;
22998
+ }
22999
+ });
23000
+
22910
23001
  // ../../core/web-renderer/src/listGestures.tsx
22911
23002
  import { useRef, useState as useState2 } from "react";
22912
23003
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -24158,8 +24249,8 @@ function autoGrowBox(contentHeight, lineHeight, verticalPadding, maxLines) {
24158
24249
  const cap = Math.max(floor, lineHeight * lines + verticalPadding);
24159
24250
  return { height: Math.max(floor, Math.min(contentHeight, cap)), scrolls: contentHeight > cap };
24160
24251
  }
24161
- function wrapModalMenu(modal, label2, placement, menu) {
24162
- return modal ? /* @__PURE__ */ jsx6("div", { role: "dialog", "aria-modal": "true", "aria-label": label2, style: placement, children: menu }) : menu;
24252
+ function wrapModalMenu(modal, label2, placement, ref, menu) {
24253
+ return modal ? /* @__PURE__ */ jsx6("div", { ref, role: "dialog", "aria-modal": "true", "aria-label": label2, style: placement, children: menu }) : menu;
24163
24254
  }
24164
24255
  function bindMediaSlot(value, sessionId) {
24165
24256
  if (typeof value === "string") {
@@ -24263,9 +24354,10 @@ function withExtras(name, render) {
24263
24354
  const additive = addIntrinsicPadding(el.props.style, wrap ?? {});
24264
24355
  const inferredDecorative = el.props[INFERRED_DECORATIVE] === true;
24265
24356
  const iconGlyph2 = el.props[ICON_GLYPH] === true;
24357
+ const roleWhenNamed = el.props[ROLE_WHEN_NAMED];
24266
24358
  if (!Object.keys(pass).length) {
24267
- if (inferredDecorative || iconGlyph2) {
24268
- return React5.cloneElement(el, { [INFERRED_DECORATIVE]: void 0, [ICON_GLYPH]: void 0, ...additive ? { style: additive } : {} });
24359
+ if (inferredDecorative || iconGlyph2 || roleWhenNamed != null) {
24360
+ return React5.cloneElement(el, { [INFERRED_DECORATIVE]: void 0, [ICON_GLYPH]: void 0, [ROLE_WHEN_NAMED]: void 0, ...additive ? { style: additive } : {} });
24269
24361
  }
24270
24362
  return additive ? React5.cloneElement(el, { style: additive }) : /* @__PURE__ */ jsx6(Fragment3, { children: el });
24271
24363
  }
@@ -24277,6 +24369,14 @@ function withExtras(name, render) {
24277
24369
  const nodeSpeaksForItself = pass["aria-label"] != null || passRole != null && passRole !== "presentation" && passRole !== "none" || typeof pass.onClick === "function" || typeof pass.onDoubleClick === "function";
24278
24370
  if (pass["aria-hidden"] == null && nodeSpeaksForItself) next["aria-hidden"] = void 0;
24279
24371
  }
24372
+ if (roleWhenNamed != null) {
24373
+ next[ROLE_WHEN_NAMED] = void 0;
24374
+ const isControl = typeof pass.onClick === "function" || typeof pass.onDoubleClick === "function";
24375
+ const roleAuthored = pass[AUTHORED_ROLE] === true;
24376
+ if (typeof roleWhenNamed === "string" && pass["aria-label"] != null && pass.role == null && prev.role == null && !isControl && !roleAuthored) {
24377
+ next.role = roleWhenNamed;
24378
+ }
24379
+ }
24280
24380
  if (iconGlyph2) {
24281
24381
  next[ICON_GLYPH] = void 0;
24282
24382
  if (pass.role === "presentation" && typeof pass.onClick !== "function" && typeof pass.onDoubleClick !== "function") {
@@ -24290,6 +24390,7 @@ function withExtras(name, render) {
24290
24390
  delete next[SYNTHETIC_TAP_ROLE];
24291
24391
  const tabIndexIsSynthetic = next[SYNTHETIC_TABINDEX] === true;
24292
24392
  delete next[SYNTHETIC_TABINDEX];
24393
+ delete next[AUTHORED_ROLE];
24293
24394
  if (additive) next.style = additive;
24294
24395
  const ownsInteractiveSemantics = isNativeInteractiveElement(el) || hasInteractiveReactDescendant(el);
24295
24396
  if (ownsInteractiveSemantics) {
@@ -24774,8 +24875,36 @@ function ensureInitialFocusRuntime() {
24774
24875
  restoreFocusOnDismiss(presentedScope, active);
24775
24876
  }
24776
24877
  el.focus();
24878
+ if (document.activeElement === el) enterAuthoredOrderOnTab(el);
24879
+ }
24880
+ }, 0);
24881
+ }
24882
+ function enterAuthoredOrderOnTab(target) {
24883
+ const doc = target.ownerDocument;
24884
+ const scope = target.closest(PRESENTED_SCOPE);
24885
+ const root = scope ?? doc;
24886
+ const onKeyDown = (e) => {
24887
+ if (e.key !== "Tab" || e.shiftKey || e.altKey || e.ctrlKey || e.metaKey || e.defaultPrevented || e.target !== target) return;
24888
+ if (target.tabIndex > 0) return;
24889
+ const ordered = Array.from(root.querySelectorAll("[tabindex]")).filter((c) => c.tabIndex > 0 && c.closest(PRESENTED_SCOPE) === scope).sort((a, b) => a.tabIndex - b.tabIndex);
24890
+ for (const c of ordered) {
24891
+ c.focus();
24892
+ if (doc.activeElement === c) {
24893
+ e.preventDefault();
24894
+ e.stopPropagation();
24895
+ return;
24896
+ }
24777
24897
  }
24898
+ };
24899
+ const onBlur = () => setTimeout(() => {
24900
+ if (doc.activeElement !== target) disarm();
24778
24901
  }, 0);
24902
+ const disarm = () => {
24903
+ target.removeEventListener("keydown", onKeyDown);
24904
+ target.removeEventListener("blur", onBlur);
24905
+ };
24906
+ target.addEventListener("keydown", onKeyDown);
24907
+ target.addEventListener("blur", onBlur);
24779
24908
  }
24780
24909
  function ariaModal(modal, dflt) {
24781
24910
  const on3 = modal === true || modal === false ? modal : dflt;
@@ -24810,6 +24939,7 @@ function applyA11ySemantics(node, ctx, extra) {
24810
24939
  if (typeof headingLevel === "number" && headingLevel >= 1 && headingLevel <= 6) {
24811
24940
  extra["aria-level"] = String(headingLevel);
24812
24941
  }
24942
+ if (role != null && Object.prototype.hasOwnProperty.call(ARIA_ROLE, role)) extra[AUTHORED_ROLE] = true;
24813
24943
  const hint = str7(m.accessibilityHint ?? m.accessibility_hint);
24814
24944
  const value = str7(m.accessibilityValue ?? m.accessibility_value);
24815
24945
  if (value) extra["aria-valuetext"] = value;
@@ -25005,7 +25135,10 @@ function renderNodeInner(node, ctx) {
25005
25135
  if (composed) extra.style = composed;
25006
25136
  }
25007
25137
  }
25008
- if (typeof el.type === "string") delete extra[SYNTHETIC_TABINDEX];
25138
+ if (typeof el.type === "string") {
25139
+ delete extra[SYNTHETIC_TABINDEX];
25140
+ delete extra[AUTHORED_ROLE];
25141
+ }
25009
25142
  let out = Object.keys(extra).length ? React5.cloneElement(el, extra) : el;
25010
25143
  if (a11yHint) {
25011
25144
  out = /* @__PURE__ */ jsx6(A11yHintDescription, { hint: a11yHint, idBase: componentId, children: out });
@@ -25726,7 +25859,7 @@ function renderInner(node, ctx) {
25726
25859
  }
25727
25860
  }
25728
25861
  }
25729
- 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;
25862
+ 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, ROLE_WHEN_NAMED, AUTHORED_ROLE, 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;
25730
25863
  var init_renderNode = __esm({
25731
25864
  "../../core/web-renderer/src/renderNode.tsx"() {
25732
25865
  "use strict";
@@ -25743,8 +25876,10 @@ var init_renderNode = __esm({
25743
25876
  init_env();
25744
25877
  init_actionParams();
25745
25878
  init_icons();
25879
+ init_webviewFrame();
25746
25880
  init_icon_catalog_generated();
25747
25881
  init_controls();
25882
+ init_modalStack();
25748
25883
  init_listGestures();
25749
25884
  init_worker_subtree();
25750
25885
  init_preview_surfaces();
@@ -26275,6 +26410,7 @@ var init_renderNode = __esm({
26275
26410
  const popupPlacement = { position: "absolute", top: "100%", right: 0, zIndex: 20, marginTop: 4 };
26276
26411
  const anchorRef = React5.useRef(null);
26277
26412
  const menuRef = React5.useRef(null);
26413
+ const dialogRef = React5.useRef(null);
26278
26414
  const itemsKey = items.map((it, i) => `${it?.id ?? i}:${it?.disabled ? 0 : 1}`).join("|");
26279
26415
  React5.useEffect(() => {
26280
26416
  if (!open || !modal) return;
@@ -26284,7 +26420,9 @@ var init_renderNode = __esm({
26284
26420
  }, [open, modal, itemsKey]);
26285
26421
  React5.useEffect(() => {
26286
26422
  if (!open || !modal) return;
26423
+ const layer = pushModalLayer(() => dialogRef.current);
26287
26424
  return () => {
26425
+ layer.release();
26288
26426
  const active = document.activeElement;
26289
26427
  if (active == null || active === document.body || !document.contains(active)) anchorRef.current?.focus();
26290
26428
  };
@@ -26322,11 +26460,11 @@ var init_renderNode = __esm({
26322
26460
  setOpen((v) => !v);
26323
26461
  }
26324
26462
  },
26325
- style: { display: "inline-flex", alignItems: "center", cursor: "pointer" },
26463
+ style: { display: "inline-flex", alignItems: "center", justifyContent: "center", minWidth: 44, minHeight: 44, cursor: "pointer" },
26326
26464
  children: props.anchor ? renderNode(props.anchor, ctx) : iconSvg("more", { size: 20, color: C("textSecondary", "#636b78") })
26327
26465
  }
26328
26466
  ),
26329
- open && wrapModalMenu(modal, anchorLabel, popupPlacement, /* @__PURE__ */ jsx6(
26467
+ open && wrapModalMenu(modal, anchorLabel, popupPlacement, dialogRef, /* @__PURE__ */ jsx6(
26330
26468
  "div",
26331
26469
  {
26332
26470
  ref: menuRef,
@@ -26958,13 +27096,14 @@ var init_renderNode = __esm({
26958
27096
  const inlineHtml = props.inlineHtml != null && isBinding(props.inlineHtml) ? boundHtml : props.inlineHtml;
26959
27097
  const urlStr = typeof url === "string" && url.length > 0 && !isBinding(url) ? url : void 0;
26960
27098
  const htmlStr = typeof inlineHtml === "string" && inlineHtml.length > 0 && !isBinding(inlineHtml) ? inlineHtml : void 0;
27099
+ const urlFrame = urlStr ? webviewFrameSandbox(urlStr) : null;
26961
27100
  const h = typeof props.height === "number" && props.height > 0 ? props.height : void 0;
26962
27101
  const ar = typeof props.aspectRatio === "number" && props.aspectRatio > 0 ? props.aspectRatio : void 0;
26963
27102
  const frameStyle = { width: "100%", boxSizing: "border-box", border: "none", borderRadius: ctx.radius("md", 8), background: "#fff", ...h ? { height: h } : ar ? { aspectRatio: String(ar) } : { height: 200 } };
26964
27103
  const bandColor = typeof props.coverBandColor === "string" && props.coverBandColor.length > 0 ? props.coverBandColor : void 0;
26965
27104
  return /* @__PURE__ */ jsxs3("div", { style: { width: "100%", position: "relative", ...bandColor ? { background: bandColor, borderRadius: ctx.radius("md", 8), overflow: "hidden" } : {}, ...wrap }, children: [
26966
27105
  bandColor ? /* @__PURE__ */ jsx6("div", { "data-testid": "webview-cover-band", style: { height: 8, background: bandColor } }) : null,
26967
- htmlStr ? /* @__PURE__ */ jsx6("iframe", { title: "webview", srcDoc: htmlStr, sandbox: "allow-scripts", style: frameStyle }) : urlStr ? /* @__PURE__ */ jsx6("iframe", { title: "webview", src: urlStr, sandbox: "allow-scripts allow-same-origin", style: frameStyle }) : /* @__PURE__ */ jsx6("div", { style: { ...frameStyle, display: "grid", placeItems: "center", color: "#999", fontFamily: "ui-monospace, monospace", fontSize: 11, border: "1px dashed #ccc" }, children: "WebView (no url)" })
27106
+ htmlStr ? /* @__PURE__ */ jsx6("iframe", { title: "webview", srcDoc: htmlStr, sandbox: "allow-scripts", style: frameStyle }) : urlFrame ? /* @__PURE__ */ jsx6("iframe", { title: "webview", src: urlFrame.src, sandbox: urlFrame.sandbox, style: frameStyle }) : /* @__PURE__ */ jsx6("div", { style: { ...frameStyle, display: "grid", placeItems: "center", color: "#999", fontFamily: "ui-monospace, monospace", fontSize: 11, border: "1px dashed #ccc" }, children: urlStr ? "WebView (url not allowed)" : "WebView (no url)" })
26968
27107
  ] });
26969
27108
  });
26970
27109
  InteractivePicker = withExtras("InteractivePicker", ({ props, ctx, wrap }) => {
@@ -27242,6 +27381,8 @@ var init_renderNode = __esm({
27242
27381
  SYNTHETIC_TABINDEX = "data-hm-synthetic-tabindex";
27243
27382
  INFERRED_DECORATIVE = "data-hm-inferred-decorative";
27244
27383
  ICON_GLYPH = "data-hm-icon-glyph";
27384
+ ROLE_WHEN_NAMED = "data-hm-role-when-named";
27385
+ AUTHORED_ROLE = "data-hm-authored-role";
27245
27386
  INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
27246
27387
  "button",
27247
27388
  "checkbox",
@@ -27388,7 +27529,7 @@ var init_renderNode = __esm({
27388
27529
  const tAlign = props.textAlign === "center" ? "center" : props.textAlign === "trailing" ? "right" : props.textAlign === "justify" ? "justify" : props.textAlign === "leading" ? "left" : void 0;
27389
27530
  const tDecor = props.decoration === "strikethrough" ? "line-through" : props.decoration === "underline" ? "underline" : void 0;
27390
27531
  const ln = typeof props.lineSpacing === "number" ? pxToRem((f.line ?? 1.4) * f.size + props.lineSpacing) : f.line;
27391
- return /* @__PURE__ */ jsx6("div", { ...countAttrs, style: { fontFamily: fontStack(f.family, ctx.idiom), fontSize: f.remSize, fontWeight: f.weight, lineHeight: ln, letterSpacing: f.letterSpacing, color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), whiteSpace: "pre-wrap", overflowWrap: "anywhere", wordBreak: "break-word", minWidth: 0, ...tAlign ? { textAlign: tAlign } : {}, ...tDecor ? { textDecoration: tDecor } : {}, ...props.maxLines ? { display: "-webkit-box", WebkitLineClamp: props.maxLines, WebkitBoxOrient: "vertical", overflow: "hidden" } : {}, ...wrap }, children: content == null || content === "" ? collapse ? null : "\xA0" : content });
27532
+ return /* @__PURE__ */ jsx6("div", { ...countAttrs, style: { fontFamily: fontStack(f.family, ctx.idiom), fontSize: f.remSize, fontWeight: f.weight, lineHeight: ln, letterSpacing: f.letterSpacing, color: C(resolveScalarStatic(props.color, ctx) ?? "textPrimary", "#13171e"), whiteSpace: props.maxLines === 1 ? "nowrap" : "pre-wrap", overflowWrap: "anywhere", wordBreak: "break-word", minWidth: 0, ...tAlign ? { textAlign: tAlign } : {}, ...tDecor ? { textDecoration: tDecor } : {}, ...props.maxLines === 1 ? { overflow: "hidden", textOverflow: "ellipsis" } : props.maxLines ? { display: "-webkit-box", WebkitLineClamp: props.maxLines, WebkitBoxOrient: "vertical", overflow: "hidden" } : {}, ...wrap }, children: content == null || content === "" ? collapse ? null : "\xA0" : content });
27392
27533
  });
27393
27534
  ImageNode = withExtras("ImageNode", ({ props, ctx, wrap }) => {
27394
27535
  const C = ctx.color;
@@ -27449,7 +27590,7 @@ var init_renderNode = __esm({
27449
27590
  const iconName = isBinding(props.name) ? String(bound ?? "") : props.name;
27450
27591
  const contentDescription = useResolvedScalar(props.contentDescription, ctx);
27451
27592
  const described = contentDescription != null && String(contentDescription).trim() !== "";
27452
- const a11y = described ? { role: "img", "aria-label": String(contentDescription), [ICON_GLYPH]: true } : { "aria-hidden": "true", [INFERRED_DECORATIVE]: true, [ICON_GLYPH]: true };
27593
+ const a11y = described ? { role: "img", "aria-label": String(contentDescription), [ICON_GLYPH]: true } : { "aria-hidden": "true", [INFERRED_DECORATIVE]: true, [ICON_GLYPH]: true, [ROLE_WHEN_NAMED]: "img" };
27453
27594
  return /* @__PURE__ */ jsx6("span", { ...a11y, style: { display: "inline-grid", placeItems: "center", width: size + 6, height: size + 6, lineHeight: 1, flex: "none", ...wrap }, children: iconSvg(iconName, { size, color: C(resolveScalarStatic(props.tint, ctx) ?? "textPrimary", "#636b78") }) });
27454
27595
  });
27455
27596
  ButtonNode = withExtras("ButtonNode", ({ props, ctx, wrap }) => {
@@ -27585,11 +27726,13 @@ var init_renderNode = __esm({
27585
27726
  const thick = Math.max(3, Math.round(size / 8));
27586
27727
  const bound = useResolvedScalar(props.valuePath, ctx);
27587
27728
  const aiRaw = props.valuePath != null ? bound : props.value;
27588
- const determinate = !props.indeterminate && typeof aiRaw === "number";
27729
+ const aiNum = typeof aiRaw === "number" ? aiRaw : typeof aiRaw === "string" && aiRaw.trim() !== "" ? Number(aiRaw) : void 0;
27730
+ const determinate = !props.indeterminate && aiNum !== void 0 && Number.isFinite(aiNum);
27589
27731
  if (determinate) {
27590
- const pct = Math.max(0, Math.min(1, aiRaw));
27732
+ const pct = Math.max(0, Math.min(1, aiNum));
27591
27733
  const ringMask = `radial-gradient(farthest-side, transparent calc(100% - ${thick}px), #000 calc(100% - ${thick}px))`;
27592
- return /* @__PURE__ */ jsx6("div", { role: "progressbar", style: { width: size, height: size, borderRadius: "50%", flex: "none", background: `conic-gradient(${tint} ${pct * 360}deg, ${track} 0deg)`, WebkitMask: ringMask, mask: ringMask, ...wrap } });
27734
+ const a11y = { "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(pct * 100) };
27735
+ return /* @__PURE__ */ jsx6("div", { role: "progressbar", ...a11y, style: { width: size, height: size, borderRadius: "50%", flex: "none", background: `conic-gradient(${tint} ${pct * 360}deg, ${track} 0deg)`, WebkitMask: ringMask, mask: ringMask, ...wrap } });
27593
27736
  }
27594
27737
  ensureSpinKeyframes();
27595
27738
  return /* @__PURE__ */ jsx6("div", { role: "progressbar", style: { width: size, height: size, borderRadius: "50%", border: `${thick}px solid ${track}`, borderTopColor: tint, boxSizing: "border-box", flex: "none", animation: ctx.empty ? void 0 : "himalaya-spin 0.8s linear infinite", ...wrap } });
@@ -28264,18 +28407,23 @@ var init_renderNode = __esm({
28264
28407
  const next = multiSelect ? isSel ? selected.filter((x) => x !== id) : [...selected, id] : isSel ? [] : [id];
28265
28408
  ctx.onAction({ ...onChange, args: { ...onChange.args ?? {}, selectedIds: next } });
28266
28409
  };
28267
- return /* @__PURE__ */ jsx6("div", { role: "group", style: { display: "flex", gap: 8, flexWrap: "wrap", width: "100%", ...wrap }, children: chips.map((c, i) => {
28268
- const on3 = selected.includes(c.id);
28269
- const chipStyle = { display: "flex", gap: 6, alignItems: "center", fontFamily: bodyFont(ctx), fontWeight: 500, fontSize: 13, border: `1px solid ${on3 ? "transparent" : C("border", "#ddd")}`, background: on3 ? C("accentPrimary", "#116DFF") : "transparent", color: on3 ? C("textOnAccent", "#fff") : C("textSecondary", "#666"), padding: "6px 12px", borderRadius: 999 };
28270
- const inner = /* @__PURE__ */ jsxs3(Fragment3, { children: [
28271
- asText2(c.label),
28272
- c.count != null && /* @__PURE__ */ jsxs3(Fragment3, { children: [
28273
- " ",
28274
- /* @__PURE__ */ jsx6("span", { style: { opacity: 0.7, fontSize: 11 }, children: c.count })
28275
- ] })
28276
- ] });
28277
- return interactive ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-pressed": on3, onClick: () => toggle(c.id), style: { all: "unset", cursor: "pointer", boxSizing: "border-box", ...chipStyle }, children: inner }, c.id ?? i) : /* @__PURE__ */ jsx6("span", { style: chipStyle, children: inner }, c.id ?? i);
28278
- }) });
28410
+ return (
28411
+ // ONE horizontally scrolling row, like the iOS ScrollView(.horizontal) and the
28412
+ // Android LazyRow. Wrapping here turned a long filter set into a ragged grid and
28413
+ // pushed a sibling count onto two lines (#3080, #3004).
28414
+ /* @__PURE__ */ jsx6("div", { role: "group", style: { display: "flex", gap: 8, flexWrap: "nowrap", overflowX: "auto", scrollbarWidth: "none", width: "100%", minWidth: 0, ...wrap }, children: chips.map((c, i) => {
28415
+ const on3 = selected.includes(c.id);
28416
+ const chipStyle = { display: "flex", flex: "none", whiteSpace: "nowrap", gap: 6, alignItems: "center", fontFamily: bodyFont(ctx), fontWeight: 500, fontSize: 13, border: `1px solid ${on3 ? "transparent" : C("border", "#ddd")}`, background: on3 ? C("accentPrimary", "#116DFF") : "transparent", color: on3 ? C("textOnAccent", "#fff") : C("textSecondary", "#666"), padding: "6px 12px", borderRadius: 999 };
28417
+ const inner = /* @__PURE__ */ jsxs3(Fragment3, { children: [
28418
+ asText2(c.label),
28419
+ c.count != null && /* @__PURE__ */ jsxs3(Fragment3, { children: [
28420
+ " ",
28421
+ /* @__PURE__ */ jsx6("span", { style: { opacity: 0.7, fontSize: 11 }, children: c.count })
28422
+ ] })
28423
+ ] });
28424
+ return interactive ? /* @__PURE__ */ jsx6("button", { type: "button", "aria-pressed": on3, onClick: () => toggle(c.id), style: { all: "unset", cursor: "pointer", boxSizing: "border-box", ...chipStyle }, children: inner }, c.id ?? i) : /* @__PURE__ */ jsx6("span", { style: chipStyle, children: inner }, c.id ?? i);
28425
+ }) })
28426
+ );
28279
28427
  });
28280
28428
  TILE_PX = 256;
28281
28429
  MAX_MERCATOR_LAT = 85.05112878;
@@ -29279,6 +29427,7 @@ __export(src_exports, {
29279
29427
  isBinding: () => isBinding,
29280
29428
  isNodeLike: () => isNodeLike,
29281
29429
  isSerifFamily: () => isSerifFamily,
29430
+ isWebviewUrlAllowed: () => isWebviewUrlAllowed,
29282
29431
  makeCtx: () => makeCtx,
29283
29432
  makeModStyle: () => makeModStyle,
29284
29433
  makeTokenResolvers: () => makeTokenResolvers,
@@ -29287,7 +29436,8 @@ __export(src_exports, {
29287
29436
  renderDescriptorToHtml: () => renderDescriptorToHtml,
29288
29437
  renderNode: () => renderNode,
29289
29438
  servicePath: () => servicePath,
29290
- useBoundValue: () => useBoundValue
29439
+ useBoundValue: () => useBoundValue,
29440
+ webviewFrameSandbox: () => webviewFrameSandbox
29291
29441
  });
29292
29442
  var init_src3 = __esm({
29293
29443
  "../../core/web-renderer/src/index.ts"() {
@@ -29301,6 +29451,7 @@ var init_src3 = __esm({
29301
29451
  init_AppRegistry();
29302
29452
  init_helpers();
29303
29453
  init_icons();
29454
+ init_webviewFrame();
29304
29455
  }
29305
29456
  });
29306
29457
 
@@ -32789,18 +32940,18 @@ function startBundleArtifactWatcher(state) {
32789
32940
  });
32790
32941
  };
32791
32942
  try {
32792
- const watcher = watch(TIER5_ROOT2, { persistent: false }, (_event, filename) => {
32943
+ const watcher2 = watch(TIER5_ROOT2, { persistent: false }, (_event, filename) => {
32793
32944
  const bundle = builtBundleName(filename);
32794
32945
  if (!bundle) return;
32795
32946
  pending.add(bundle);
32796
32947
  if (timer) clearTimeout(timer);
32797
32948
  timer = setTimeout(flush, 120);
32798
32949
  });
32799
- watcher.on("error", (err) => {
32950
+ watcher2.on("error", (err) => {
32800
32951
  console.warn(`[himalaya-dev] bundle watcher stopped: ${err.message}`);
32801
32952
  });
32802
32953
  console.log(`[himalaya-dev] watching built bundles: ${TIER5_ROOT2}`);
32803
- return watcher;
32954
+ return watcher2;
32804
32955
  } catch (err) {
32805
32956
  console.warn(`[himalaya-dev] bundle watcher unavailable: ${err.message}`);
32806
32957
  return void 0;
@@ -32835,7 +32986,7 @@ function startBundleSourceWatcher(state) {
32835
32986
  building = false;
32836
32987
  };
32837
32988
  try {
32838
- const watcher = watch(TIER5_SRC_ROOT, { persistent: false, recursive: true }, (_event, filename) => {
32989
+ const watcher2 = watch(TIER5_SRC_ROOT, { persistent: false, recursive: true }, (_event, filename) => {
32839
32990
  if (!filename || !/\.(ts|tsx)$/.test(filename)) return;
32840
32991
  const name = filename.split(sep7)[0];
32841
32992
  if (!name || name.startsWith(".")) return;
@@ -32843,11 +32994,11 @@ function startBundleSourceWatcher(state) {
32843
32994
  if (timer) clearTimeout(timer);
32844
32995
  timer = setTimeout(flush, 200);
32845
32996
  });
32846
- watcher.on("error", (err) => {
32997
+ watcher2.on("error", (err) => {
32847
32998
  console.warn(`[himalaya-dev] bundle source watcher stopped: ${err.message}`);
32848
32999
  });
32849
33000
  console.log(`[himalaya-dev] watching T5 bundle sources: ${TIER5_SRC_ROOT}`);
32850
- return watcher;
33001
+ return watcher2;
32851
33002
  } catch (err) {
32852
33003
  console.warn(`[himalaya-dev] bundle source watcher unavailable: ${err.message}`);
32853
33004
  return void 0;
@@ -33015,7 +33166,7 @@ function startAppSourceWatcher(state) {
33015
33166
  }
33016
33167
  };
33017
33168
  try {
33018
- const watcher = watch(appDir2, { persistent: false, recursive: true }, (_event, filename) => {
33169
+ const watcher2 = watch(appDir2, { persistent: false, recursive: true }, (_event, filename) => {
33019
33170
  if (!filename) return;
33020
33171
  const rel = String(filename).split(sep7).join("/");
33021
33172
  if (!classifyAppPath(rel)) return;
@@ -33023,11 +33174,11 @@ function startAppSourceWatcher(state) {
33023
33174
  if (timer) clearTimeout(timer);
33024
33175
  timer = setTimeout(flush, 200);
33025
33176
  });
33026
- watcher.on("error", (err) => {
33177
+ watcher2.on("error", (err) => {
33027
33178
  console.warn(`[himalaya-dev] app source watcher stopped: ${err.message}`);
33028
33179
  });
33029
33180
  console.log(`[himalaya-dev] watching app source: ${appDir2}`);
33030
- return watcher;
33181
+ return watcher2;
33031
33182
  } catch (err) {
33032
33183
  console.warn(`[himalaya-dev] app source watcher unavailable: ${err.message}`);
33033
33184
  return void 0;
@@ -35697,10 +35848,10 @@ function cmsAnswer(seed2, method, bare, body) {
35697
35848
  pagingMetadata: { count: rows.length, tooManyToCount: false }
35698
35849
  };
35699
35850
  }
35700
- function layeredTransport(layers) {
35701
- const rules = layers.rules?.length ? [...layers.rules] : null;
35702
- const cms = layers.cmsSeed && Object.keys(layers.cmsSeed).length ? layers.cmsSeed : null;
35703
- const canned = layers.canned && Object.keys(layers.canned).length ? layers.canned : null;
35851
+ function layeredTransport(layers2) {
35852
+ const rules = layers2.rules?.length ? [...layers2.rules] : null;
35853
+ const cms = layers2.cmsSeed && Object.keys(layers2.cmsSeed).length ? layers2.cmsSeed : null;
35854
+ const canned = layers2.canned && Object.keys(layers2.canned).length ? layers2.canned : null;
35704
35855
  const label2 = [rules && "mocked", cms && "cms", canned && "canned"].filter(Boolean).join("+") || "offline";
35705
35856
  const call = async (method, path2, body) => {
35706
35857
  const m = String(method).toUpperCase();
@@ -39030,6 +39181,332 @@ var init_net_freeze = __esm({
39030
39181
  }
39031
39182
  });
39032
39183
 
39184
+ // src/studio-page.ts
39185
+ function renderStudioHtml(initial) {
39186
+ const accent = initial.app.palette.find((p) => p.name === "accentPrimary")?.hex ?? "#3b6fd8";
39187
+ return `<!doctype html>
39188
+ <html lang="en">
39189
+ <head>
39190
+ <meta charset="utf-8">
39191
+ <meta name="viewport" content="width=device-width, initial-scale=1">
39192
+ <title>${escapeHtml(initial.app.name)} \xB7 build board</title>
39193
+ <link rel="icon" id="fav" href="data:,">
39194
+ <style>
39195
+ :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)}
39196
+ @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}}
39197
+ *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
39198
+ .wrap{max-width:1180px;margin:0 auto;padding:20px 20px 60px}
39199
+ header{display:flex;align-items:center;gap:12px;margin-bottom:16px}
39200
+ .icon{width:40px;height:40px;border-radius:10px;background:var(--accent);object-fit:cover;flex:none}
39201
+ h1{font-size:20px;font-weight:600;margin:0}.sub{color:var(--muted);font-size:13px}
39202
+ .right{margin-left:auto;text-align:right}.clock{font-size:22px;font-variant-numeric:tabular-nums;font-weight:600}
39203
+ .live{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:12px}.dot{width:8px;height:8px;border-radius:50%;background:var(--ok)}.stale .dot{background:var(--faint)}
39204
+ .steps{display:grid;grid-template-columns:repeat(8,1fr);gap:6px;margin:4px 0 16px}.step .bar{height:6px;border-radius:3px;background:var(--line)}.step.done .bar{background:var(--ok)}.step.cur .bar{background:var(--accent)}
39205
+ .step .lbl{font-size:11px;color:var(--faint);margin-top:4px;text-transform:capitalize}.step.cur .lbl{color:var(--ink);font-weight:600}
39206
+ .card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px;box-shadow:var(--shadow)}
39207
+ .now{display:flex;gap:12px;align-items:flex-start;margin-bottom:16px}.now .msg{font-size:16px}.now .meta{color:var(--muted);font-size:12px}
39208
+ .grid{display:grid;grid-template-columns:320px 1fr;gap:16px;align-items:start}@media (max-width:860px){.grid{grid-template-columns:1fr}.steps .lbl{display:none}}
39209
+ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:0 0 10px}
39210
+ .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}
39211
+ .phone img,.phone iframe{width:100%;height:100%;object-fit:cover;object-position:top;border:0;display:block;cursor:zoom-in}
39212
+ .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}
39213
+ .cap{text-align:center;color:var(--muted);font-size:12px;margin-top:8px}
39214
+ .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}
39215
+ .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)}
39216
+ .chip.ok{background:var(--okbg);color:var(--ok);border-color:transparent}.chip.warn{background:var(--warnbg);color:var(--warn);border-color:transparent}.chip.bad{background:var(--badbg);color:var(--bad);border-color:transparent}
39217
+ .wall{display:grid;grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:14px}
39218
+ .shot{text-align:center}.shot .frame{aspect-ratio:390/844;border-radius:16px;border:5px solid #111;overflow:hidden;background:#fff}.shot img{width:100%;height:100%;object-fit:cover;object-position:top;display:block;cursor:zoom-in}
39219
+ .shot .id{font-size:12px;margin-top:6px;word-break:break-all}.badge{display:inline-block;font-size:10px;padding:1px 7px;border-radius:999px;margin-top:3px}.badge.new{background:var(--okbg);color:var(--ok)}.badge.chg{background:var(--warnbg);color:var(--warn)}
39220
+ .section{margin-bottom:16px}.brand{display:flex;gap:14px;align-items:center;flex-wrap:wrap}.brand .site{width:220px;border-radius:10px;border:1px solid var(--line);cursor:zoom-in}
39221
+ .sw{display:flex;gap:6px}.sw span{width:22px;height:22px;border-radius:50%;border:1px solid var(--line)}
39222
+ .nav{display:flex;flex-wrap:wrap;gap:6px}.nav span{font-size:12px;padding:3px 8px;border-radius:6px;background:var(--bg);border:1px solid var(--line)}
39223
+ .log{list-style:none;margin:0;padding:0}.log li{display:flex;gap:10px;padding:10px 0;border-top:1px solid var(--line)}.log li:first-child{border-top:0}
39224
+ .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}
39225
+ .empty{color:var(--faint);font-size:13px;padding:8px 0}
39226
+ #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}
39227
+ .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}}
39228
+ .changed .frame{animation:chg .9s ease both}@keyframes chg{35%{opacity:.25;transform:scale(.96)}100%{opacity:1;transform:none}}
39229
+ .step.fresh .bar{animation:fill .7s ease both;transform-origin:left}@keyframes fill{from{transform:scaleX(0)}to{transform:scaleX(1)}}
39230
+ .slidein{animation:slide .45s cubic-bezier(.2,.8,.2,1) both}@keyframes slide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
39231
+ .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}}
39232
+ .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}}
39233
+ .icon,.step.cur .bar{transition:background-color .8s}.sw.pop span{animation:pop .6s ease both}@keyframes pop{50%{transform:scale(1.25)}}
39234
+ .quiet{color:var(--warn)}.quiet .dot{background:var(--warn)}.idle .dot{background:var(--faint)}
39235
+ .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)}
39236
+ #fx{position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:8}
39237
+ @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}}
39238
+ .scrub{display:flex;align-items:center;gap:8px;margin:-6px 0 4px}.scrub[hidden]{display:none}.scrub input{flex:1;min-width:0}
39239
+ .scrub .rd{font-size:12px;color:var(--muted);min-width:160px;font-variant-numeric:tabular-nums}
39240
+ .marks{position:relative;height:8px;margin:0 9px 12px}.marks span{position:absolute;top:0;width:7px;height:7px;border-radius:50%;background:var(--faint);transform:translateX(-50%)}.marks .ms{background:var(--warn)}.marks .bad{background:var(--bad)}
39241
+ .viewing{font-size:13px;padding:6px 10px;border-radius:8px;background:var(--warnbg);color:var(--warn);margin:0 0 12px}.viewing[hidden]{display:none}.viewing a{color:inherit}
39242
+ .newdot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--bad);margin-left:6px;vertical-align:middle}.newdot[hidden]{display:none}
39243
+ #focus[hidden],#still[hidden]{display:none}
39244
+ .reveal{position:fixed;inset:0;z-index:7;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.5)}.reveal[hidden]{display:none}
39245
+ .rv{background:var(--card);border-radius:16px;padding:18px;max-width:760px;width:calc(100% - 32px)}.rv h3{margin:0;font-size:18px}.rv .row{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-top:12px}
39246
+ .rv figure{margin:0}.rv figcaption{font-size:12px;color:var(--muted);margin-bottom:6px}.rv .site img{width:100%;border-radius:10px;border:1px solid var(--line)}
39247
+ .rv .ph{position:relative;aspect-ratio:390/844;max-height:60vh;border-radius:22px;border:6px solid #111;overflow:hidden;background:#fff;margin:0 auto}.rv .ph img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:top}
39248
+ .rv .after{clip-path:inset(0 100% 0 0);transition:clip-path 1.4s cubic-bezier(.6,0,.2,1)}.rv.on .after{clip-path:inset(0 0 0 0)}
39249
+ @media (prefers-reduced-motion:reduce){.rv .after{transition:none}.enter,.changed .frame,.step.fresh .bar,.slidein,.phone.pulse,.sw.pop span,.toast{animation:none}.icon,.step.cur .bar{transition:none}}
39250
+ </style>
39251
+ </head>
39252
+ <body>
39253
+ <div class="wrap">
39254
+ <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>
39255
+ <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>
39256
+ <div><span class="live idle" id="agent"><span class="dot"></span><span id="agentt">Waiting for the agent</span></span></div>
39257
+ <div><button class="tog" id="snd" type="button" aria-pressed="false">Sound off</button><button class="tog" id="say" type="button" aria-pressed="false" hidden>Read aloud: off</button><button class="tog" id="ntf" type="button" aria-pressed="false" hidden>Notices off</button></div></div></header>
39258
+ <div class="steps" id="steps"></div>
39259
+ <div class="scrub" id="scrub" hidden><button class="tog" id="replay" type="button">Replay</button><input type="range" id="slider" min="0" max="1" step="1" value="1" aria-label="Build timeline"><span class="rd" id="readout"></span><button class="tog" id="golive" type="button">Live<span class="newdot" id="newdot" hidden></span></button></div>
39260
+ <div class="marks" id="marks"></div>
39261
+ <div class="viewing" id="viewing" hidden>Viewing <span id="viewingt"></span> \xB7 <a href="#" id="backlive">Back to live</a></div>
39262
+ <div class="card now"><div style="flex:1"><div class="meta" id="nowmeta">Now</div><div class="msg" id="nowmsg"></div></div></div>
39263
+ <div class="grid">
39264
+ <div><div class="card"><h2 id="focuslbl">Latest</h2><div id="focus"></div><div id="still" hidden></div><div class="film" id="film"></div>
39265
+ <button class="tog" id="pop" type="button" hidden>Pop out phone</button></div></div>
39266
+ <div>
39267
+ <div class="chips" id="chips"></div>
39268
+ <div class="card section"><h2>Screens</h2><div class="wall" id="wall"></div></div>
39269
+ <div class="card section" id="brandcard"><h2>Brand</h2><div class="brand" id="brand"></div></div>
39270
+ <div class="card section" id="navcard"><h2>Navigation the test crawl followed</h2><div class="nav" id="nav"></div></div>
39271
+ <div class="card section"><h2>Postcards</h2><ul class="log" id="log"></ul></div>
39272
+ </div></div></div>
39273
+ <div id="lb"><img alt=""></div>
39274
+ <div class="reveal" id="reveal" hidden role="dialog" aria-labelledby="rvh"><div class="rv" id="rv"><div style="display:flex;justify-content:space-between;align-items:center;gap:12px"><h3 id="rvh">Your brand, in your app</h3><button class="tog" id="rvx" type="button">Close</button></div><div class="row" id="rvrow"></div></div></div>
39275
+ <canvas id="fx" aria-hidden="true"></canvas>
39276
+ <script>window.__himiInitial = ${scriptJson(initial)};</script>
39277
+ <script>
39278
+ (function(){
39279
+ var cur=null, lastSeen=Date.now();
39280
+ function $(id){return document.getElementById(id)}
39281
+ function el(tag,cls,txt){var e=document.createElement(tag);if(cls)e.className=cls;if(txt!=null)e.textContent=txt;return e}
39282
+ function clear(e){while(e.firstChild)e.removeChild(e.firstChild)}
39283
+ function nowMs(){return asOf||Date.now()}
39284
+ function ago(ts){var s=Math.max(0,Math.round((nowMs()-Date.parse(ts))/1000));if(s<60)return s+"s ago";var m=Math.round(s/60);if(m<60)return m+" min ago";return Math.round(m/60)+" h ago"}
39285
+ function hhmm(ts){var d=new Date(ts);return d.toLocaleTimeString([], {hour:"2-digit",minute:"2-digit"})}
39286
+ function recent(ts){return nowMs()-Date.parse(ts)<10*60*1000}
39287
+ function zoomable(img){img.addEventListener("click",function(){var lb=$("lb");lb.firstChild.src=img.src;lb.style.display="flex"})}
39288
+ function img(src,cls){var i=el("img",cls);i.src=src;i.alt="";i.loading="lazy";zoomable(i);return i}
39289
+ function chip(txt,kind){return el("span","chip"+(kind?" "+kind:""),txt)}
39290
+ function render(s){
39291
+ 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;
39292
+ if(s.app.icon){$("icon").src=s.app.icon}else{$("icon").removeAttribute("src")}
39293
+ var steps=$("steps");clear(steps);var n=s.current?s.current.stepNumber:0;
39294
+ 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)});
39295
+ var last=s.journal[0];
39296
+ $("nowmsg").textContent=last?last.message:"Waiting for the first postcard.";
39297
+ $("nowmeta").textContent=last?("Now \xB7 "+(last.step?("step "+last.stepNumber+" of 8, "+last.step+" \xB7 "):"")+ago(last.ts)):"Now";
39298
+ var film=$("film");clear(film);
39299
+ if(!rewinding){var n0=s.timelineFrames||0;$("scrub").hidden=n0<2;$("slider").max=String(Math.max(1,n0-1));$("slider").value=$("slider").max}
39300
+ if(rewinding){paintStill(s)}else{$("still").hidden=true;$("focus").hidden=false;var focus=$("focus");
39301
+ /* The live frame is KEPT across renders while its URL and its run are unchanged (a restarted run on
39302
+ the same port is a different server, and the old frame would never see its builds). A new iframe is a full page
39303
+ load of the preview: every postcard, rebuild and the once-a-minute re-render used to throw away
39304
+ the in-place repaint, the preview's scroll and wherever the owner had navigated inside it. */
39305
+ if(pip&&(!s.live||pip.frame.getAttribute("src")!==s.live.url||pip.frame.getAttribute("data-run")!==s.live.runId))bringBack();
39306
+ if(pip){paintStrip(s)}else{
39307
+ 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;
39308
+ if(kept){while(focus.lastChild&&focus.lastChild!==kept.parentNode)focus.removeChild(focus.lastChild)}else clear(focus);
39309
+ 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))}
39310
+ 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"))}
39311
+ else{$("focuslbl").textContent="Latest";focus.appendChild(el("div","empty","The first render appears here."))}
39312
+ if(s.focus&&s.focus.frames.length>1)s.focus.frames.forEach(function(fm){film.appendChild(img(fm.img))});}}
39313
+ var chips=$("chips");clear(chips);var h=s.health;
39314
+ if(h.validate)chips.appendChild(chip(h.validate.ok?"Validate passing":"Validate: "+h.validate.errors+" errors",h.validate.ok?"ok":"bad"));
39315
+ 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)}
39316
+ h.ux.forEach(function(u){chips.appendChild(chip("UX drive "+u.platform+": "+u.status,u.status==="passed"?"ok":u.status==="failed"?"bad":"warn"))});
39317
+ 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"));
39318
+ if(!chips.firstChild)chips.appendChild(chip("Checks appear here as validate, test and push run"));
39319
+ var wall=$("wall");clear(wall);
39320
+ if(!s.screens.length)wall.appendChild(el("div","empty","Screens appear here as they render."));
39321
+ 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));
39322
+ 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)});
39323
+ var brand=$("brand");clear(brand);$("brandcard").style.display=(s.brand.site||s.app.palette.length)?"":"none";
39324
+ if(s.brand.site){var si=img(s.brand.site,"site");brand.appendChild(si)}
39325
+ if(s.brand.site&&s.focus&&s.focus.frames.length){brand.appendChild(el("span","sub","\u2192"));var pf=el("div","shot");pf.style.width="90px";var ff=el("div","frame");ff.appendChild(img(s.focus.frames[s.focus.frames.length-1].img));pf.appendChild(ff);brand.appendChild(pf)}
39326
+ if(s.app.palette.length){var sw=el("div","sw");s.app.palette.forEach(function(c){var d=el("span");d.style.background=c.hex;d.title=c.name+" "+c.hex;sw.appendChild(d)});brand.appendChild(sw)}
39327
+ var nav=$("nav");clear(nav);$("navcard").style.display=s.nav.length?"":"none";s.nav.forEach(function(e){nav.appendChild(el("span",null,e.from+" \u2192 "+e.to))});
39328
+ var log=$("log");clear(log);if(!s.journal.length)log.appendChild(el("li","empty","Postcards the agent journals appear here."));
39329
+ 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));
39330
+ 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)});
39331
+ paintPop(s);
39332
+ }
39333
+ /* Motion, sound and notices answer a CHANGE, never a redraw: effects() compares what is on the page
39334
+ now with what it showed last, so opening the board, the once-a-minute redraw and the two-second
39335
+ poll of an unchanged state do nothing, and each effect names a real event. */
39336
+ var seen=null,actx=null,sound=false,notices=false;
39337
+ var tlStates=null,rewinding=false,replaying=false,asOf=0,replayTimer=null;
39338
+ function foldAll(frames){var out=[],st=null,meta={};(frames||[]).forEach(function(f){
39339
+ var ns={step:null,now:null,screens:{},palette:[],checks:{validate:null,test:null,push:null},build:null};
39340
+ if(!f.key&&st){ns.step=st.step;ns.now=st.now;for(var k in st.screens)ns.screens[k]=st.screens[k];ns.palette=st.palette;ns.checks=st.checks;ns.build=st.build}
39341
+ if("step" in f)ns.step=f.step;if("now" in f)ns.now=f.now;if(f.palette)ns.palette=f.palette;if(f.checks)ns.checks=f.checks;if("build" in f)ns.build=f.build;
39342
+ var sc=f.screens||{};for(var id in sc){if(sc[id]===null)delete ns.screens[id];else ns.screens[id]=sc[id]}
39343
+ for(var j in ns.screens){var m=meta[j];if(!m)meta[j]={first:f.ts,changed:f.ts,img:ns.screens[j],n:1};else if(m.img!==ns.screens[j]){m.changed=f.ts;m.img=ns.screens[j];m.n++}}
39344
+ var copy={};for(var q in meta)copy[q]={first:meta[q].first,changed:meta[q].changed,n:meta[q].n};
39345
+ ns.meta=copy;ns.ts=f.ts;ns.reveal=f.reveal||null;out.push(ns);st=ns});return out}
39346
+ function viewAt(i){var st=tlStates[i],b=cur;
39347
+ var screens=Object.keys(st.screens).sort().map(function(id){var m=st.meta[id];return{id:id,img:st.screens[id],firstSeen:m.first,changedAt:m.changed,versions:m.n}});
39348
+ var t=st.checks.test,prevGaps=null;for(var k=i-1;k>=0&&t;k--){var pt=tlStates[k].checks.test;if(pt&&pt.gaps!==t.gaps){prevGaps=pt.gaps;break}}
39349
+ var fid=b.focus?b.focus.id:null,fimg=fid&&st.screens[fid];
39350
+ return{v:"tl-"+i,generatedAt:st.ts,app:{name:b.app.name,icon:b.app.icon,palette:st.palette.map(function(p){return{name:p[0],hex:p[1]}})},steps:b.steps,
39351
+ current:st.step?{step:st.step.name,stepNumber:st.step.n,of:8}:null,startedAt:b.startedAt,
39352
+ journal:journalAt(st,b),screens:screens,focus:fimg?{id:fid,frames:[{ts:st.ts,img:fimg}]}:null,
39353
+ health:{validate:st.checks.validate?{ok:st.checks.validate.ok,errors:st.checks.validate.errors,warnings:0,at:st.ts}:null,
39354
+ test:t?{ok:t.ok,screens:t.screens,actions:t.actions,gaps:t.gaps,prevGaps:prevGaps,errors:0,at:st.ts}:null,ux:[],
39355
+ push:st.checks.push?{app:"",releaseId:st.checks.push.releaseId,draft:st.checks.push.draft,at:st.ts}:null},
39356
+ brand:b.brand,nav:b.nav,live:null,pastBuild:st.build,reveal:st.reveal?{at:st.ts,site:st.reveal.site,before:st.reveal.before,after:st.reveal.after}:null,timelineFrames:b.timelineFrames}}
39357
+ /* The board keeps only the newest postcards; the timeline remembers the one showing at each moment. */
39358
+ function journalAt(st,b){var jr=b.journal.filter(function(x){return x.ts<=st.ts});if(st.now&&(!jr[0]||jr[0].ts!==st.now.ts))jr.unshift({ts:st.now.ts,message:st.now.message,images:st.now.images,step:st.step?st.step.name:null,stepNumber:st.step?st.step.n:null});return jr}
39359
+ function loadTimeline(cb){window.__himiTimeline=function(frames){tlStates=foldAll(frames);cb()};var sc=document.createElement("script");sc.src="timeline.js?t="+Date.now();sc.onload=sc.onerror=function(){sc.remove()};document.head.appendChild(sc)}
39360
+ function readout(i){var st=tlStates[i],t0=Date.parse(tlStates[0].ts);return hhmm(st.ts)+(st.step?" \xB7 step "+st.step.n+", "+st.step.name:"")+" \xB7 "+Math.round((Date.parse(st.ts)-t0)/60000)+" min in"}
39361
+ function marks(){var m=$("marks");clear(m);var n=tlStates.length;tlStates.forEach(function(st,i){var p=tlStates[i-1],kind=null;
39362
+ if(st.build&&st.build.ok===false&&(!p||p.build!==st.build))kind="bad";
39363
+ else if(st.reveal||(st.checks.push&&(!p||!p.checks.push||p.checks.push.releaseId!==st.checks.push.releaseId))||(st.step&&st.step.n===8&&(!p||!p.step||p.step.n<8)))kind="ms";
39364
+ else if(st.now&&(!p||!p.now||p.now.ts!==st.now.ts))kind="";
39365
+ if(kind===null)return;var d=el("span",kind||null);d.style.left=(n>1?i/(n-1)*100:0)+"%";m.appendChild(d)})}
39366
+ function paintStill(s){$("focus").hidden=true;var still=$("still");still.hidden=false;clear(still);$("focuslbl").textContent="Home at "+hhmm(s.generatedAt);
39367
+ if(s.focus&&s.focus.frames.length){var p=el("div","phone");p.appendChild(img(s.focus.frames[0].img));still.appendChild(p)}else still.appendChild(el("div","empty","No picture of the home screen yet at this moment."))}
39368
+ function rewindTo(i){if(!tlStates||tlStates.length<2)return null;i=Math.max(0,Math.min(tlStates.length-1,i));rewinding=true;asOf=Date.parse(tlStates[i].ts);
39369
+ var sl=$("slider");sl.max=String(tlStates.length-1);sl.value=String(i);var r=readout(i);$("readout").textContent=r;sl.setAttribute("aria-valuetext",r);
39370
+ $("viewing").hidden=false;$("viewingt").textContent=hhmm(tlStates[i].ts);var s=viewAt(i);render(s);return s}
39371
+ function enterRewind(i){loadTimeline(function(){if(!tlStates||tlStates.length<2)return;marks();rewindTo(i==null?tlStates.length-1:i<0?tlStates.length-1+i:i)})}
39372
+ function stopReplay(){replaying=false;if(replayTimer){clearTimeout(replayTimer);replayTimer=null}$("replay").textContent="Replay"}
39373
+ function toLive(){stopReplay();rewinding=false;asOf=0;$("viewing").hidden=true;$("newdot").hidden=true;$("still").hidden=true;$("focus").hidden=false;$("readout").textContent="";seen=snap(cur);if(seen.accent)document.documentElement.style.setProperty("--accent",seen.accent);render(cur)}
39374
+ /* The replay plan: about 30 s whatever the build's length. Frames that carry a milestone (a step,
39375
+ a draft, a failed build, the reveal) always play; the rest are sampled down to 120 moments; gaps
39376
+ follow real time, 150 ms-1.5 s each, and are scaled down so the whole never runs past 30 s. */
39377
+ function replayPlan(){var n=tlStates.length,keep={0:1};keep[n-1]=1;
39378
+ for(var i=1;i<n;i++){var st=tlStates[i],p=tlStates[i-1];if(st.reveal||(st.step&&(!p.step||p.step.n!==st.step.n))||(st.checks.push&&(!p.checks.push||p.checks.push.releaseId!==st.checks.push.releaseId))||(st.build&&st.build.ok===false&&p.build!==st.build))keep[i]=1}
39379
+ var idx=[],MAX=120;if(n<=MAX){for(var a=0;a<n;a++)idx.push(a)}else{var room=MAX-Object.keys(keep).length;for(var k=1;k<=room;k++)keep[Math.round(k*(n-1)/(room+1))]=1;idx=Object.keys(keep).map(Number).sort(function(x,y){return x-y})}
39380
+ var t0=Date.parse(tlStates[0].ts),span=Math.max(1,Date.parse(tlStates[n-1].ts)-t0),gaps=[],sum=0;
39381
+ for(var q=1;q<idx.length;q++){var g=Math.min(1500,Math.max(150,(Date.parse(tlStates[idx[q]].ts)-Date.parse(tlStates[idx[q-1]].ts))/span*30000));gaps.push(g);sum+=g}
39382
+ var f=sum>30000?30000/sum:1;return{idx:idx,gaps:gaps.map(function(x){return x*f}),total:sum*f}}
39383
+ function replay(){loadTimeline(function(){if(!tlStates||tlStates.length<2)return;marks();replaying=true;$("replay").textContent="Pause";
39384
+ var scale=window.__himiReplayScale!=null?window.__himiReplayScale:1,plan=replayPlan();window.__himiReplayPlan={idx:plan.idx,totalMs:plan.total};
39385
+ seen=snap(rewindTo(0));var q=0;
39386
+ (function next(){if(!replaying)return;q++;if(q>=plan.idx.length){toLive();return}
39387
+ replayTimer=setTimeout(function(){var s=rewindTo(plan.idx[q]);if(s)effects(s);next()},plan.gaps[q-1]*scale)})()})}
39388
+ /* The floating phone: the ONE live frame moves into an always-on-top window (Document Picture-in-
39389
+ Picture; a small ordinary window elsewhere) and back. Two copies would run the app twice and
39390
+ drift apart as the owner taps; the cost of moving is one reload per move. */
39391
+ var pip=null;
39392
+ function canPip(){return !!window.documentPictureInPicture}
39393
+ function paintStrip(s){if(!pip)return;pip.strip.firstChild.textContent=s.current?s.current.stepNumber+"/8 \xB7 "+s.current.step:"";pip.strip.lastChild.textContent=s.journal[0]?s.journal[0].message:""}
39394
+ function fitPip(){if(!pip)return;var w=pip.w.innerWidth,h=pip.w.innerHeight-44;if(w>0&&h>0)pip.frame.style.transform="scale("+Math.min(w/390,h/844)+")"}
39395
+ function mountPip(w,fr){var d=w.document;d.title=cur.app.name+" \xB7 phone";
39396
+ var st=d.createElement("style");st.textContent="html,body{margin:0;height:100%;background:#111;font:12px -apple-system,Segoe UI,Roboto,sans-serif}.pp{position:absolute;top:0;left:0;right:0;bottom:44px;overflow:hidden}.pp iframe{position:absolute;top:0;left:0;width:390px;height:844px;border:0;transform-origin:0 0}.pp.pulse{outline:3px solid #1f7a4d;outline-offset:-3px}.pp.bad{outline-color:#a12a2a}.strip{position:absolute;left:0;right:0;bottom:0;height:44px;padding:4px 10px;box-sizing:border-box;background:#fff;color:#1c1c1a}.strip div:first-child{color:#6b6a64}.strip div:last-child{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}";
39397
+ d.head.appendChild(st);var box=d.createElement("div");box.className="pp";d.body.appendChild(box);
39398
+ var home=fr.parentNode;box.appendChild(fr);
39399
+ var strip=d.createElement("div");strip.className="strip";strip.appendChild(d.createElement("div"));strip.appendChild(d.createElement("div"));d.body.appendChild(strip);
39400
+ pip={w:w,box:box,frame:fr,strip:strip,home:home};
39401
+ var note=el("div","empty","Phone popped out \xB7 ");var back=el("a",null,"Bring back");back.href="#";back.addEventListener("click",function(e){e.preventDefault();bringBack()});note.appendChild(back);note.id="away";home.appendChild(note);
39402
+ w.addEventListener("resize",fitPip);w.addEventListener("pagehide",bringBack);fitPip();paintStrip(cur);paintPop(cur)}
39403
+ var pipRefused=false;
39404
+ function openWindow(fr,afterPip){var w=window.open("","himiPhone","width=340,height=760");if(!w){$("pop").textContent=afterPip?"Open in a window":"Pop-up blocked";return}mountPip(w,fr)}
39405
+ /* A browser can expose Document PiP and still refuse the window (an embedded webview does): fall
39406
+ back to the small window rather than a click that does nothing and says nothing. The refusal may
39407
+ have spent the click's user activation, so a popup opened from it can be blocked: then the button
39408
+ offers the window, and the next click opens it directly with an activation of its own. */
39409
+ function popOut(){var fr=$("focus").querySelector("iframe.vp");if(!fr||pip)return;
39410
+ if(canPip()&&!pipRefused){Promise.resolve().then(function(){return window.documentPictureInPicture.requestWindow({width:320,height:740})}).then(function(w){mountPip(w,fr)},function(){pipRefused=true;openWindow(fr,true)})}
39411
+ else openWindow(fr)}
39412
+ function bringBack(){if(!pip)return;var p=pip;pip=null;var away=document.getElementById("away");if(away&&away.parentNode)away.parentNode.removeChild(away);
39413
+ p.frame.style.transform="";if(p.home&&p.home.isConnected!==false)p.home.appendChild(p.frame);else if(p.frame.parentNode)p.frame.parentNode.removeChild(p.frame);try{p.w.close()}catch(e){}fit();paintPop(cur)}
39414
+ function paintPop(s){var b=$("pop");b.hidden=!(s&&s.live)||rewinding;b.textContent=pip?"Bring back":canPip()&&!pipRefused?"Pop out phone":"Open in a window"}
39415
+ /* The brand reveal (decided board-side, played here only when it ARRIVES as a change) and opt-in
39416
+ read aloud: postcards and milestones, never rebuild messages, the newer cancelling the older. */
39417
+ var revealTimer=null,speakOn=false;try{speakOn=localStorage.getItem("himiStudioSpeak")==="1"}catch(e){}
39418
+ function fig(cap,cls){var f=el("figure",cls||null);f.appendChild(el("figcaption",null,cap));return f}
39419
+ function phoneImg(srcs){var ph=el("div","ph");srcs.forEach(function(p){var i=el("img",p[1]||null);i.src=p[0];i.alt="";ph.appendChild(i)});return ph}
39420
+ function showReveal(r){var row=$("rvrow"),rv=$("rv");clear(row);rv.classList.remove("on");
39421
+ if(r.site){var fs=fig("Your website","site");var si=el("img");si.src=r.site;si.alt="";fs.appendChild(si);row.appendChild(fs)}
39422
+ var same=!r.before||r.before===r.after;
39423
+ if(same){var f1=fig("Your app, your brand");f1.appendChild(phoneImg([[r.after]]));row.appendChild(f1)}
39424
+ else if(reduced()){var fb=fig("Your app, before");fb.appendChild(phoneImg([[r.before]]));row.appendChild(fb);var fa=fig("Your app, your brand");fa.appendChild(phoneImg([[r.after]]));row.appendChild(fa)}
39425
+ else{var fw=fig("Your app");fw.appendChild(phoneImg([[r.before],[r.after,"after"]]));row.appendChild(fw);setTimeout(function(){rv.classList.add("on")},700)}
39426
+ var box=$("reveal");box.hidden=false;box.setAttribute("data-shown",String((+box.getAttribute("data-shown")||0)+1));clearTimeout(revealTimer);revealTimer=setTimeout(hideReveal,6000)}
39427
+ function hideReveal(){$("reveal").hidden=true;clearTimeout(revealTimer)}
39428
+ function speak(t){if(!speakOn||!t)return;try{var ss=window.speechSynthesis;if(!ss||!window.SpeechSynthesisUtterance)return;ss.cancel();var u=new SpeechSynthesisUtterance(t);u.lang=navigator.language||"en";ss.speak(u)}catch(e){}}try{sound=localStorage.getItem("himiStudioSound")==="1";notices=localStorage.getItem("himiStudioNotify")==="1"}catch(e){}
39429
+ var raf=window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(f){return setTimeout(function(){f(Date.now())},16)};
39430
+ function now(){return window.performance&&performance.now?performance.now():Date.now()}
39431
+ function reduced(){try{return matchMedia("(prefers-reduced-motion: reduce)").matches}catch(e){return false}}
39432
+ 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){}}
39433
+ 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")}};
39434
+ /* Notices are the BOARD's opt-in, not just the browser's: a permission granted to this origin once
39435
+ (an earlier board, another run) must not switch them on, and the toggle must switch them off. */
39436
+ function notify(title,body){if(replaying)return;try{if(notices&&"Notification" in window&&Notification.permission==="granted"&&document.hidden)new Notification(title,{body:body})}catch(e){}}
39437
+ function accentNow(){return (getComputedStyle(document.documentElement).getPropertyValue("--accent")||"").trim()||"#3b6fd8"}
39438
+ /* The preview lays out at a real phone's viewport (390 x 844 CSS px) and is scaled into the frame.
39439
+ Sized to the frame itself it was ~280px wide: narrower than any phone, so screens overflowed
39440
+ sideways and scrolled horizontally. */
39441
+ 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)))}
39442
+ window.addEventListener("resize",fit);
39443
+ 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){}}
39444
+ 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)})()}
39445
+ 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)})()}
39446
+ 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:s.pastBuild?s.pastBuild.at:null,run:s.live?s.live.runId:s.pastBuild?s.pastBuild.run:null,push:s.health.push?s.health.push.releaseId:null,gaps:s.health.test?s.health.test.gaps:null,accent:acc,reveal:s.reveal?s.reveal.at:null}}
39447
+ 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}
39448
+ function effects(s){var is=snap(s),was=seen;seen=is;if(is.accent)document.documentElement.style.setProperty("--accent",is.accent);
39449
+ /* The phone (kept across renders) and the Now line outlive a render: clear what they played last. */
39450
+ var stage=rewinding?$("still"):$("focus"),ph=stage.querySelector(".phone"),nm=$("nowmsg");if(ph)ph.classList.remove("pulse","bad");nm.classList.remove("slidein");if(!was)return;
39451
+ 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();
39452
+ 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()}
39453
+ 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])}
39454
+ /* A rebuild is a later build of the SAME run: a run appearing, or restarting, records its first
39455
+ build too, and nothing was rebuilt then. */
39456
+ if(is.build&&was.build&&is.run===was.run&&is.build!==was.build){var b=s.live&&s.live.lastBuild||s.pastBuild;if(ph){void ph.offsetWidth;ph.classList.add("pulse");if(!b.ok)ph.classList.add("bad")}if(pip){pip.box.classList.remove("pulse","bad");void pip.box.offsetWidth;pip.box.classList.add("pulse");if(!b.ok)pip.box.classList.add("bad");pip.strip.lastChild.textContent=buildLine(b)}var tt=el("div","toast"+(b.ok?"":" bad"),buildLine(b));stage.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&&s.live.buildError||"")}}
39457
+ if(was.gaps!=null&&is.gaps!=null&&is.gaps!==was.gaps)countTo($("gapn"),was.gaps,is.gaps);
39458
+ 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))}
39459
+ 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")}
39460
+ var said=false;if(is.top&&is.top!==was.top){speak((s.current?"Step "+s.current.stepNumber+", "+s.current.step+". ":"")+(s.journal[0]?s.journal[0].message:""));said=true}
39461
+ if((published||handed)&&!said)speak(published?"Your draft is ready to open on your phone.":"Your app is handed over.");
39462
+ if(is.reveal&&is.reveal!==was.reveal)showReveal(s.reveal);
39463
+ }
39464
+ 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}
39465
+ function paintToggles(){var sy=$("say");sy.hidden=!("speechSynthesis" in window);sy.textContent="Read aloud: "+(speakOn?"on":"off");sy.className="tog"+(speakOn?" on":"");sy.setAttribute("aria-pressed",String(speakOn));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))}
39466
+ function setNotices(v){notices=v;try{localStorage.setItem("himiStudioNotify",v?"1":"0")}catch(e){}paintToggles()}
39467
+ $("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()});
39468
+ /* The click acts on what the button SAYS (the effective state), so a remembered opt-in whose browser
39469
+ permission was since reset reads "off" and one click asks for permission, not two. */
39470
+ $("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){}});
39471
+ document.addEventListener("click",function(){try{if(actx&&actx.state==="suspended")actx.resume()}catch(e){}},{once:true});
39472
+ paintToggles();
39473
+ $("slider").addEventListener("input",function(){var i=+this.value;if(replaying)stopReplay();if(!rewinding||!tlStates)enterRewind(i);else rewindTo(i)});
39474
+ $("replay").addEventListener("click",function(){if(replaying){stopReplay();return}replay()});
39475
+ $("golive").addEventListener("click",toLive);
39476
+ $("rvx").addEventListener("click",hideReveal);$("reveal").addEventListener("click",function(e){if(e.target===this)hideReveal()});
39477
+ $("say").addEventListener("click",function(){speakOn=!speakOn;try{localStorage.setItem("himiStudioSpeak",speakOn?"1":"0")}catch(e){}paintToggles();if(speakOn)speak("Read aloud is on.")});
39478
+ $("pop").addEventListener("click",function(){if(pip)bringBack();else popOut()});
39479
+ $("backlive").addEventListener("click",function(e){e.preventDefault();toLive()});
39480
+ document.addEventListener("keydown",function(e){if($("scrub").hidden)return;var tg=e.target,tn=tg&&tg.tagName;
39481
+ if(e.key==="Escape"&&rewinding){toLive();return}
39482
+ if((e.key==="ArrowLeft"||e.key==="ArrowRight")&&!(tg&&tg.id==="slider")){var d=e.key==="ArrowRight"?1:-1;if(!rewinding){if(d<0)enterRewind(d)}else rewindTo(+$("slider").value+d);e.preventDefault()}
39483
+ if(e.key===" "&&tn!=="BUTTON"&&tn!=="INPUT"){e.preventDefault();$("replay").click()}});
39484
+ window.__himiStudio=function(s){lastSeen=Date.now();if(!cur||cur.v!==s.v){cur=s;if(rewinding){$("newdot").hidden=false;return}render(s);effects(s)}else{cur.generatedAt=s.generatedAt}};
39485
+ $("lb").addEventListener("click",function(){this.style.display="none"});
39486
+ window.__himiStudio(window.__himiInitial);
39487
+ 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);
39488
+ var ticks=0;
39489
+ setInterval(function(){if(!cur)return;if(++ticks%60===0&&!rewinding)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;
39490
+ $("clock").textContent=(hh?hh+":":"")+(mm<10&&hh?"0":"")+mm+":"+(ss<10?"0":"")+ss;
39491
+ $("updated").textContent="updated "+ago(cur.generatedAt);$("live").className="live"+(Date.now()-lastSeen>10000?" stale":"");
39492
+ var a=activityAt(cur),q=a?Date.now()-a:0,done=cur.current&&cur.current.stepNumber===8;
39493
+ $("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);
39494
+ })();
39495
+ </script>
39496
+ </body>
39497
+ </html>
39498
+ `;
39499
+ }
39500
+ function escapeHtml(s) {
39501
+ return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
39502
+ }
39503
+ var scriptJson;
39504
+ var init_studio_page = __esm({
39505
+ "src/studio-page.ts"() {
39506
+ scriptJson = (value) => JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
39507
+ }
39508
+ });
39509
+
39033
39510
  // src/progress.ts
39034
39511
  var progress_exports = {};
39035
39512
  __export(progress_exports, {
@@ -39276,6 +39753,152 @@ var init_progress = __esm({
39276
39753
  }
39277
39754
  });
39278
39755
 
39756
+ // src/studio-timeline.ts
39757
+ import { existsSync as existsSync39, readFileSync as readFileSync42 } from "node:fs";
39758
+ import { join as join36 } from "node:path";
39759
+ function stateOf(view) {
39760
+ const j = view.journal[0];
39761
+ const h = view.health;
39762
+ const b = view.live?.lastBuild ?? null;
39763
+ return {
39764
+ step: view.current ? { n: view.current.stepNumber, name: view.current.step } : null,
39765
+ now: j ? { ts: j.ts, message: j.message, images: j.images } : null,
39766
+ screens: Object.fromEntries(view.screens.map((s) => [s.id, s.img])),
39767
+ palette: view.app.palette.map((p) => [p.name, p.hex]),
39768
+ checks: {
39769
+ validate: h.validate ? { ok: h.validate.ok, errors: h.validate.errors } : null,
39770
+ test: h.test ? { screens: h.test.screens, actions: h.test.actions, gaps: h.test.gaps, ok: h.test.ok } : null,
39771
+ push: h.push ? { draft: h.push.draft, releaseId: h.push.releaseId } : null
39772
+ },
39773
+ build: b && view.live ? { ok: b.ok, ms: b.ms, screens: b.screens, at: b.at, run: view.live.runId } : null
39774
+ };
39775
+ }
39776
+ function frameFor(state, prev, ts2, v, key2) {
39777
+ if (key2 || !prev) return { ts: ts2, v, key: true, ...state };
39778
+ const f = { ts: ts2, v };
39779
+ if (!same(state.step, prev.step)) f.step = state.step;
39780
+ if (!same(state.now, prev.now)) f.now = state.now;
39781
+ const screens = {};
39782
+ for (const [id, img] of Object.entries(state.screens)) if (prev.screens[id] !== img) screens[id] = img;
39783
+ for (const id of Object.keys(prev.screens)) if (!(id in state.screens)) screens[id] = null;
39784
+ if (Object.keys(screens).length) f.screens = screens;
39785
+ if (!same(state.palette, prev.palette)) f.palette = state.palette;
39786
+ if (!same(state.checks, prev.checks)) f.checks = state.checks;
39787
+ if (!same(state.build, prev.build)) f.build = state.build;
39788
+ return f;
39789
+ }
39790
+ function fold(prev, f) {
39791
+ const base = f.key || !prev ? EMPTY : prev;
39792
+ const screens = f.key ? {} : { ...base.screens };
39793
+ for (const [id, img] of Object.entries(f.screens ?? {})) {
39794
+ if (img === null) delete screens[id];
39795
+ else screens[id] = img;
39796
+ }
39797
+ return {
39798
+ step: "step" in f ? f.step ?? null : base.step,
39799
+ now: "now" in f ? f.now ?? null : base.now,
39800
+ screens,
39801
+ palette: f.palette ?? base.palette,
39802
+ checks: f.checks ?? base.checks,
39803
+ build: "build" in f ? f.build ?? null : base.build
39804
+ };
39805
+ }
39806
+ function validFrame(f) {
39807
+ if (!isObj(f) || !isStr(f.ts) || !isStr(f.v)) return false;
39808
+ if (f.key !== void 0 && f.key !== true) return false;
39809
+ if ("step" in f && !nullOr(f.step, (x) => typeof x.n === "number" && isStr(x.name))) return false;
39810
+ if ("now" in f && !nullOr(f.now, (x) => isStr(x.ts) && isStr(x.message) && strs(x.images))) return false;
39811
+ if ("screens" in f && !(isObj(f.screens) && Object.values(f.screens).every((x) => x === null || isStr(x)))) return false;
39812
+ if ("palette" in f && !(Array.isArray(f.palette) && f.palette.every((p) => Array.isArray(p) && p.length === 2 && p.every(isStr)))) return false;
39813
+ if ("checks" in f) {
39814
+ const c = f.checks;
39815
+ if (!isObj(c)) return false;
39816
+ if (!nullOr(c.validate ?? null, (x) => typeof x.ok === "boolean" && typeof x.errors === "number")) return false;
39817
+ if (!nullOr(c.test ?? null, (x) => ["screens", "actions", "gaps"].every((k) => typeof x[k] === "number") && typeof x.ok === "boolean")) return false;
39818
+ if (!nullOr(c.push ?? null, (x) => typeof x.draft === "boolean" && isStr(x.releaseId))) return false;
39819
+ }
39820
+ if ("build" in f && !nullOr(f.build, (x) => typeof x.ok === "boolean" && (x.ms === null || typeof x.ms === "number") && strs(x.screens) && isStr(x.at) && isStr(x.run))) return false;
39821
+ if ("reveal" in f && !(isObj(f.reveal) && (f.reveal.site === null || isStr(f.reveal.site)) && isStr(f.reveal.before) && isStr(f.reveal.after))) return false;
39822
+ return true;
39823
+ }
39824
+ function readTimeline(dir) {
39825
+ const file = join36(dir, TIMELINE_FILE);
39826
+ if (!existsSync39(file)) return [];
39827
+ const out = [];
39828
+ for (const line of readFileSync42(file, "utf8").split("\n")) {
39829
+ if (!line.trim()) continue;
39830
+ try {
39831
+ const f = JSON.parse(line);
39832
+ if (validFrame(f)) out.push(f);
39833
+ } catch {
39834
+ }
39835
+ }
39836
+ const first = out.findIndex((f) => f.key === true);
39837
+ return first < 0 ? [] : out.slice(first);
39838
+ }
39839
+ function trim(frames) {
39840
+ if (frames.length <= MAX_FRAMES) return frames;
39841
+ for (let i = 1; i < frames.length; i++) if (frames[i].key && frames.length - i <= MAX_FRAMES) return frames.slice(i);
39842
+ return frames.slice(-MAX_FRAMES);
39843
+ }
39844
+ function writeTimelineJs(dir, frames) {
39845
+ writeFileAtomic(join36(dir, TIMELINE_JS), `window.__himiTimeline && window.__himiTimeline(${scriptJson(frames)});
39846
+ `);
39847
+ }
39848
+ function appendFrame(dir, view, reveal = null) {
39849
+ const frames = readTimeline(dir);
39850
+ const last = frames[frames.length - 1];
39851
+ if (last && last.v === view.v && !reveal) {
39852
+ if (!existsSync39(join36(dir, TIMELINE_JS))) writeTimelineJs(dir, frames);
39853
+ return frames.length;
39854
+ }
39855
+ let prev = null;
39856
+ for (const f of frames) prev = fold(prev, f);
39857
+ const frame = frameFor(stateOf(view), prev, view.generatedAt, view.v, frames.length % KEYFRAME_EVERY === 0);
39858
+ if (reveal) frame.reveal = reveal;
39859
+ const kept = trim([...frames, frame]);
39860
+ writeFileAtomic(join36(dir, TIMELINE_FILE), kept.map((f) => JSON.stringify(f)).join("\n") + "\n");
39861
+ writeTimelineJs(dir, kept);
39862
+ return kept.length;
39863
+ }
39864
+ function brandLanded(view, first) {
39865
+ const was = accentOf(first?.palette);
39866
+ const is = accentOf(view.app.palette);
39867
+ return was !== null && is !== null && was !== is || view.journal.some((j) => j.step === "brand");
39868
+ }
39869
+ function revealFor(view, memory) {
39870
+ if (memory.revealedAt || !memory.brandSeenAt || !view.focus || !view.focus.frames.length) return null;
39871
+ const latest = view.focus.frames[view.focus.frames.length - 1];
39872
+ if (latest.ts < memory.brandSeenAt) return null;
39873
+ const firstVersion = memory.timeline.find((t) => t.id === view.focus.id);
39874
+ const before = firstVersion ? `../history/${firstVersion.sha}.${firstVersion.ext}` : latest.img;
39875
+ return { site: view.brand.site, before, after: latest.img };
39876
+ }
39877
+ var TIMELINE_FILE, TIMELINE_JS, MAX_FRAMES, KEYFRAME_EVERY, same, EMPTY, isStr, isObj, strs, nullOr, accentOf;
39878
+ var init_studio_timeline = __esm({
39879
+ "src/studio-timeline.ts"() {
39880
+ init_progress();
39881
+ init_studio_page();
39882
+ TIMELINE_FILE = join36(".himi", "studio", "timeline.jsonl");
39883
+ TIMELINE_JS = join36(".himi", "studio", "timeline.js");
39884
+ MAX_FRAMES = 2e3;
39885
+ KEYFRAME_EVERY = 50;
39886
+ same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
39887
+ EMPTY = { step: null, now: null, screens: {}, palette: [], checks: { validate: null, test: null, push: null }, build: null };
39888
+ isStr = (x) => typeof x === "string";
39889
+ isObj = (x) => typeof x === "object" && x !== null && !Array.isArray(x);
39890
+ strs = (x) => Array.isArray(x) && x.every(isStr);
39891
+ nullOr = (x, ok) => x === null || isObj(x) && ok(x);
39892
+ accentOf = (palette2) => {
39893
+ for (const p of palette2 ?? []) {
39894
+ const [name, hex2] = Array.isArray(p) ? p : [p.name, p.hex];
39895
+ if (name === "accentPrimary") return hex2;
39896
+ }
39897
+ return null;
39898
+ };
39899
+ }
39900
+ });
39901
+
39279
39902
  // src/studio.ts
39280
39903
  var studio_exports = {};
39281
39904
  __export(studio_exports, {
@@ -39294,8 +39917,8 @@ __export(studio_exports, {
39294
39917
  writeStudioRecord: () => writeStudioRecord
39295
39918
  });
39296
39919
  import { createHash as createHash20 } from "node:crypto";
39297
- import { existsSync as existsSync39, readdirSync as readdirSync20, readFileSync as readFileSync42, realpathSync as realpathSync4, statSync as statSync8 } from "node:fs";
39298
- import { basename as basename9, extname as extname3, join as join36, relative as relative6, resolve as resolve33, sep as sep9 } from "node:path";
39920
+ import { existsSync as existsSync40, readdirSync as readdirSync20, readFileSync as readFileSync43, realpathSync as realpathSync4, statSync as statSync8 } from "node:fs";
39921
+ import { basename as basename9, extname as extname3, join as join37, relative as relative6, resolve as resolve33, sep as sep9 } from "node:path";
39299
39922
  import { pathToFileURL as pathToFileURL7 } from "node:url";
39300
39923
  function realInside(dir, abs) {
39301
39924
  try {
@@ -39320,12 +39943,12 @@ function archiveFile(dir, memory, abs) {
39320
39943
  if (size === 0) return null;
39321
39944
  const key2 = relative6(dir, abs);
39322
39945
  const hit = memory.files?.[key2];
39323
- if (hit && hit.size === size && hit.mtimeMs === mtimeMs && hit.ino === ino && existsSync39(join36(dir, HISTORY_DIR, `${hit.sha}.${hit.ext}`))) {
39946
+ if (hit && hit.size === size && hit.mtimeMs === mtimeMs && hit.ino === ino && existsSync40(join37(dir, HISTORY_DIR, `${hit.sha}.${hit.ext}`))) {
39324
39947
  return { sha: hit.sha, ext: hit.ext, file: `${hit.sha}.${hit.ext}` };
39325
39948
  }
39326
39949
  let archived;
39327
39950
  try {
39328
- archived = archiveBytes(dir, readFileSync42(real));
39951
+ archived = archiveBytes(dir, readFileSync43(real));
39329
39952
  } catch {
39330
39953
  return null;
39331
39954
  }
@@ -39334,17 +39957,17 @@ function archiveFile(dir, memory, abs) {
39334
39957
  return { sha: archived.sha256, ext: archived.ext, file: `${archived.sha256}.${archived.ext}` };
39335
39958
  }
39336
39959
  function loadMemory(dir, now) {
39337
- const raw = obj(readJson2(join36(dir, MEMORY_FILE)));
39960
+ const raw = obj(readJson2(join37(dir, MEMORY_FILE)));
39338
39961
  if (raw && raw.version === 1 && obj(raw.shots) && Array.isArray(raw.timeline)) return raw;
39339
39962
  return { version: 1, createdAt: now, shots: {}, timeline: [] };
39340
39963
  }
39341
39964
  function observeShots(dir, memory, now) {
39342
- const shotsDir = join36(dir, ".himi", "shots");
39965
+ const shotsDir = join37(dir, ".himi", "shots");
39343
39966
  const seen = /* @__PURE__ */ new Set();
39344
- const files = existsSync39(shotsDir) ? readdirSync20(shotsDir).filter((f) => /\.(png|jpe?g)$/i.test(f)).sort() : [];
39967
+ const files = existsSync40(shotsDir) ? readdirSync20(shotsDir).filter((f) => /\.(png|jpe?g)$/i.test(f)).sort() : [];
39345
39968
  for (const f of files) {
39346
39969
  const id = basename9(f, extname3(f));
39347
- const archived = archiveFile(dir, memory, join36(shotsDir, f));
39970
+ const archived = archiveFile(dir, memory, join37(shotsDir, f));
39348
39971
  if (!archived) continue;
39349
39972
  seen.add(id);
39350
39973
  const { sha, ext } = archived;
@@ -39359,14 +39982,14 @@ function observeShots(dir, memory, now) {
39359
39982
  memory.shots[id] = { ...prev, present: true };
39360
39983
  }
39361
39984
  }
39362
- if (existsSync39(shotsDir)) {
39985
+ if (existsSync40(shotsDir)) {
39363
39986
  for (const [id, shot] of Object.entries(memory.shots)) if (!seen.has(id) && shot.present) memory.shots[id] = { ...shot, present: false };
39364
39987
  }
39365
39988
  if (memory.timeline.length > MAX_TIMELINE) memory.timeline = memory.timeline.slice(-MAX_TIMELINE);
39366
39989
  return memory;
39367
39990
  }
39368
39991
  function palette(dir) {
39369
- const colors = obj(obj(readJson2(join36(dir, "tokens.json")))?.colors);
39992
+ const colors = obj(obj(readJson2(join37(dir, "tokens.json")))?.colors);
39370
39993
  if (!colors) return [];
39371
39994
  const pick2 = (key2) => {
39372
39995
  const v = colors[key2];
@@ -39376,7 +39999,7 @@ function palette(dir) {
39376
39999
  return ["accentPrimary", "accentSecondary", "accentSurface", "surfacePrimary", "textPrimary"].map(pick2).filter((x) => !!x);
39377
40000
  }
39378
40001
  function appIdentity(dir) {
39379
- const manifest = obj(readJson2(join36(dir, "himalaya.content.json")));
40002
+ const manifest = obj(readJson2(join37(dir, "himalaya.content.json")));
39380
40003
  const name = text(manifest?.name) ?? basename9(resolve33(dir));
39381
40004
  const icon = text(manifest?.icon)?.trim();
39382
40005
  const iconPath = icon ? resolve33(dir, icon) : null;
@@ -39384,12 +40007,12 @@ function appIdentity(dir) {
39384
40007
  return { name, iconFile: real && statSync8(real).isFile() ? iconPath : null };
39385
40008
  }
39386
40009
  function testRuns(dir) {
39387
- const att = join36(dir, ".himi", "attestations");
39388
- if (!existsSync39(att)) return [];
40010
+ const att = join37(dir, ".himi", "attestations");
40011
+ if (!existsSync40(att)) return [];
39389
40012
  const runs = [];
39390
40013
  for (const f of readdirSync20(att)) {
39391
40014
  if (!f.endsWith(".json") || f === "latest.json") continue;
39392
- const r = obj(readJson2(join36(att, f)));
40015
+ const r = obj(readJson2(join37(att, f)));
39393
40016
  if (!r || r.tool !== "himi-test" || typeof r.runId !== "string") continue;
39394
40017
  const report2 = obj(r.report);
39395
40018
  const coverage = obj(r.coverage);
@@ -39405,20 +40028,20 @@ function testRuns(dir) {
39405
40028
  return runs.sort((a, b) => a.runId.localeCompare(b.runId));
39406
40029
  }
39407
40030
  function siteHome(dir) {
39408
- for (const pages of [join36(dir, ".himi", "business-book", "evidence", "pages"), join36(dir, "evidence", "pages")]) {
39409
- if (!existsSync39(pages)) continue;
40031
+ for (const pages of [join37(dir, ".himi", "business-book", "evidence", "pages"), join37(dir, "evidence", "pages")]) {
40032
+ if (!existsSync40(pages)) continue;
39410
40033
  const home = readdirSync20(pages).filter((f) => /^home.*\.(png|jpe?g)$/i.test(f)).sort()[0];
39411
- if (home) return join36(pages, home);
40034
+ if (home) return join37(pages, home);
39412
40035
  }
39413
40036
  return null;
39414
40037
  }
39415
40038
  function uxStatuses(dir) {
39416
- const platforms = obj(obj(readJson2(join36(dir, ".himi", "ux-audit.json")))?.platforms);
40039
+ const platforms = obj(obj(readJson2(join37(dir, ".himi", "ux-audit.json")))?.platforms);
39417
40040
  if (!platforms) return [];
39418
40041
  return Object.entries(platforms).map(([platform, rec]) => ({ platform, status: text(obj(rec)?.status) ?? "" })).filter((p) => p.status);
39419
40042
  }
39420
40043
  function liveRun(dir) {
39421
- const rec = obj(readJson2(join36(dir, RUN_RECORD)));
40044
+ const rec = obj(readJson2(join37(dir, RUN_RECORD)));
39422
40045
  const url = text(rec?.url);
39423
40046
  const pid = rec?.pid;
39424
40047
  if (!url || !/^https?:\/\//i.test(url) || typeof pid !== "number") return null;
@@ -39468,9 +40091,9 @@ function buildView(dir, memory, now) {
39468
40091
  const runs = testRuns(dir);
39469
40092
  const last = runs[runs.length - 1];
39470
40093
  const prev = runs[runs.length - 2];
39471
- const validate3 = obj(readJson2(join36(dir, VALIDATE_RECORD)));
39472
- const push = obj(readJson2(join36(dir, PUSH_RECORD)));
39473
- const edges = readJson2(join36(dir, EDGES_RECORD));
40094
+ const validate3 = obj(readJson2(join37(dir, VALIDATE_RECORD)));
40095
+ const push = obj(readJson2(join37(dir, PUSH_RECORD)));
40096
+ const edges = readJson2(join37(dir, EDGES_RECORD));
39474
40097
  const nav = [];
39475
40098
  if (Array.isArray(edges)) {
39476
40099
  const seen = /* @__PURE__ */ new Set();
@@ -39508,9 +40131,11 @@ function buildView(dir, memory, now) {
39508
40131
  },
39509
40132
  brand: { site: siteImg },
39510
40133
  nav,
39511
- live: liveRun(dir)
40134
+ live: liveRun(dir),
40135
+ timelineFrames: 0,
40136
+ reveal: memory.reveal ?? null
39512
40137
  };
39513
- const { generatedAt: _ignored, ...stable } = view;
40138
+ const { generatedAt: _ignored, timelineFrames: _count, ...stable } = view;
39514
40139
  const v = createHash20("sha256").update(JSON.stringify(stable)).digest("hex").slice(0, 16);
39515
40140
  return { v, ...view };
39516
40141
  }
@@ -39521,16 +40146,33 @@ function writeBoard(dir, now) {
39521
40146
  if (!realDirIn(dir, STUDIO_DIR)) throw new UnsafeHimiDirError(STUDIO_DIR);
39522
40147
  if (!realDirIn(dir, HISTORY_DIR)) throw new UnsafeHimiDirError(HISTORY_DIR);
39523
40148
  const memory = observeShots(dir, loadMemory(dir, now), now);
39524
- const view = buildView(dir, memory, now);
39525
- writeAtomic(join36(dir, MEMORY_FILE), JSON.stringify(memory));
39526
- writeAtomic(join36(dir, STATE_FILE), `window.__himiStudio && window.__himiStudio(${scriptJson(view)});
40149
+ let view = buildView(dir, memory, now);
40150
+ let reveal = null;
40151
+ try {
40152
+ if (!memory.brandSeenAt && brandLanded(view, readTimeline(dir)[0])) memory.brandSeenAt = now;
40153
+ reveal = revealFor(view, memory);
40154
+ if (reveal) {
40155
+ memory.revealedAt = now;
40156
+ memory.reveal = { at: now, ...reveal };
40157
+ view = buildView(dir, memory, now);
40158
+ }
40159
+ } catch {
40160
+ reveal = null;
40161
+ }
40162
+ try {
40163
+ view.timelineFrames = appendFrame(dir, view, reveal);
40164
+ } catch {
40165
+ view.timelineFrames = 0;
40166
+ }
40167
+ writeAtomic(join37(dir, MEMORY_FILE), JSON.stringify(memory));
40168
+ writeAtomic(join37(dir, STATE_FILE), `window.__himiStudio && window.__himiStudio(${scriptJson(view)});
39527
40169
  `);
39528
- writeAtomic(join36(dir, STUDIO_PAGE), renderStudioHtml(view));
40170
+ writeAtomic(join37(dir, STUDIO_PAGE), renderStudioHtml(view));
39529
40171
  return view;
39530
40172
  }
39531
40173
  function refreshStudio(dir, now = /* @__PURE__ */ new Date()) {
39532
40174
  try {
39533
- if (!existsSync39(join36(dir, STUDIO_PAGE))) return { refreshed: false };
40175
+ if (!existsSync40(join37(dir, STUDIO_PAGE))) return { refreshed: false };
39534
40176
  return { refreshed: true, view: writeBoard(dir, now.toISOString()) };
39535
40177
  } catch {
39536
40178
  return { refreshed: false };
@@ -39538,225 +40180,38 @@ function refreshStudio(dir, now = /* @__PURE__ */ new Date()) {
39538
40180
  }
39539
40181
  function openStudio(dir, now = /* @__PURE__ */ new Date()) {
39540
40182
  const view = writeBoard(dir, now.toISOString());
39541
- const page = join36(dir, STUDIO_PAGE);
40183
+ const page = join37(dir, STUDIO_PAGE);
39542
40184
  return { page, url: pathToFileURL7(page).href, view };
39543
40185
  }
39544
40186
  function writeStudioRecord(dir, record2, value) {
39545
40187
  try {
39546
40188
  if (!realDirIn(dir, ".himi")) return;
39547
- writeAtomic(join36(dir, record2), `${JSON.stringify(value, null, 2)}
40189
+ writeAtomic(join37(dir, record2), `${JSON.stringify(value, null, 2)}
39548
40190
  `);
39549
40191
  } catch {
39550
40192
  }
39551
40193
  }
39552
- function renderStudioHtml(initial) {
39553
- const accent = initial.app.palette.find((p) => p.name === "accentPrimary")?.hex ?? "#3b6fd8";
39554
- return `<!doctype html>
39555
- <html lang="en">
39556
- <head>
39557
- <meta charset="utf-8">
39558
- <meta name="viewport" content="width=device-width, initial-scale=1">
39559
- <title>${escapeHtml(initial.app.name)} \xB7 build board</title>
39560
- <link rel="icon" id="fav" href="data:,">
39561
- <style>
39562
- :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)}
39563
- @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}}
39564
- *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
39565
- .wrap{max-width:1180px;margin:0 auto;padding:20px 20px 60px}
39566
- header{display:flex;align-items:center;gap:12px;margin-bottom:16px}
39567
- .icon{width:40px;height:40px;border-radius:10px;background:var(--accent);object-fit:cover;flex:none}
39568
- h1{font-size:20px;font-weight:600;margin:0}.sub{color:var(--muted);font-size:13px}
39569
- .right{margin-left:auto;text-align:right}.clock{font-size:22px;font-variant-numeric:tabular-nums;font-weight:600}
39570
- .live{display:inline-flex;align-items:center;gap:6px;color:var(--muted);font-size:12px}.dot{width:8px;height:8px;border-radius:50%;background:var(--ok)}.stale .dot{background:var(--faint)}
39571
- .steps{display:grid;grid-template-columns:repeat(8,1fr);gap:6px;margin:4px 0 16px}.step .bar{height:6px;border-radius:3px;background:var(--line)}.step.done .bar{background:var(--ok)}.step.cur .bar{background:var(--accent)}
39572
- .step .lbl{font-size:11px;color:var(--faint);margin-top:4px;text-transform:capitalize}.step.cur .lbl{color:var(--ink);font-weight:600}
39573
- .card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 16px;box-shadow:var(--shadow)}
39574
- .now{display:flex;gap:12px;align-items:flex-start;margin-bottom:16px}.now .msg{font-size:16px}.now .meta{color:var(--muted);font-size:12px}
39575
- .grid{display:grid;grid-template-columns:320px 1fr;gap:16px;align-items:start}@media (max-width:860px){.grid{grid-template-columns:1fr}.steps .lbl{display:none}}
39576
- h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:0 0 10px}
39577
- .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}
39578
- .phone img,.phone iframe{width:100%;height:100%;object-fit:cover;object-position:top;border:0;display:block;cursor:zoom-in}
39579
- .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}
39580
- .cap{text-align:center;color:var(--muted);font-size:12px;margin-top:8px}
39581
- .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}
39582
- .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)}
39583
- .chip.ok{background:var(--okbg);color:var(--ok);border-color:transparent}.chip.warn{background:var(--warnbg);color:var(--warn);border-color:transparent}.chip.bad{background:var(--badbg);color:var(--bad);border-color:transparent}
39584
- .wall{display:grid;grid-template-columns:repeat(auto-fill,minmax(112px,1fr));gap:14px}
39585
- .shot{text-align:center}.shot .frame{aspect-ratio:390/844;border-radius:16px;border:5px solid #111;overflow:hidden;background:#fff}.shot img{width:100%;height:100%;object-fit:cover;object-position:top;display:block;cursor:zoom-in}
39586
- .shot .id{font-size:12px;margin-top:6px;word-break:break-all}.badge{display:inline-block;font-size:10px;padding:1px 7px;border-radius:999px;margin-top:3px}.badge.new{background:var(--okbg);color:var(--ok)}.badge.chg{background:var(--warnbg);color:var(--warn)}
39587
- .section{margin-bottom:16px}.brand{display:flex;gap:14px;align-items:center;flex-wrap:wrap}.brand .site{width:220px;border-radius:10px;border:1px solid var(--line);cursor:zoom-in}
39588
- .sw{display:flex;gap:6px}.sw span{width:22px;height:22px;border-radius:50%;border:1px solid var(--line)}
39589
- .nav{display:flex;flex-wrap:wrap;gap:6px}.nav span{font-size:12px;padding:3px 8px;border-radius:6px;background:var(--bg);border:1px solid var(--line)}
39590
- .log{list-style:none;margin:0;padding:0}.log li{display:flex;gap:10px;padding:10px 0;border-top:1px solid var(--line)}.log li:first-child{border-top:0}
39591
- .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}
39592
- .empty{color:var(--faint);font-size:13px;padding:8px 0}
39593
- #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}
39594
- .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}}
39595
- .changed .frame{animation:chg .9s ease both}@keyframes chg{35%{opacity:.25;transform:scale(.96)}100%{opacity:1;transform:none}}
39596
- .step.fresh .bar{animation:fill .7s ease both;transform-origin:left}@keyframes fill{from{transform:scaleX(0)}to{transform:scaleX(1)}}
39597
- .slidein{animation:slide .45s cubic-bezier(.2,.8,.2,1) both}@keyframes slide{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:none}}
39598
- .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}}
39599
- .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}}
39600
- .icon,.step.cur .bar{transition:background-color .8s}.sw.pop span{animation:pop .6s ease both}@keyframes pop{50%{transform:scale(1.25)}}
39601
- .quiet{color:var(--warn)}.quiet .dot{background:var(--warn)}.idle .dot{background:var(--faint)}
39602
- .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)}
39603
- #fx{position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:8}
39604
- @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}}
39605
- @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}}
39606
- </style>
39607
- </head>
39608
- <body>
39609
- <div class="wrap">
39610
- <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>
39611
- <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>
39612
- <div><span class="live idle" id="agent"><span class="dot"></span><span id="agentt">Waiting for the agent</span></span></div>
39613
- <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>
39614
- <div class="steps" id="steps"></div>
39615
- <div class="card now"><div style="flex:1"><div class="meta" id="nowmeta">Now</div><div class="msg" id="nowmsg"></div></div></div>
39616
- <div class="grid">
39617
- <div><div class="card"><h2 id="focuslbl">Latest</h2><div id="focus"></div><div class="film" id="film"></div></div></div>
39618
- <div>
39619
- <div class="chips" id="chips"></div>
39620
- <div class="card section"><h2>Screens</h2><div class="wall" id="wall"></div></div>
39621
- <div class="card section" id="brandcard"><h2>Brand</h2><div class="brand" id="brand"></div></div>
39622
- <div class="card section" id="navcard"><h2>Navigation the test crawl followed</h2><div class="nav" id="nav"></div></div>
39623
- <div class="card section"><h2>Postcards</h2><ul class="log" id="log"></ul></div>
39624
- </div></div></div>
39625
- <div id="lb"><img alt=""></div>
39626
- <canvas id="fx" aria-hidden="true"></canvas>
39627
- <script>window.__himiInitial = ${scriptJson(initial)};</script>
39628
- <script>
39629
- (function(){
39630
- var cur=null, lastSeen=Date.now();
39631
- function $(id){return document.getElementById(id)}
39632
- function el(tag,cls,txt){var e=document.createElement(tag);if(cls)e.className=cls;if(txt!=null)e.textContent=txt;return e}
39633
- function clear(e){while(e.firstChild)e.removeChild(e.firstChild)}
39634
- function ago(ts){var s=Math.max(0,Math.round((Date.now()-Date.parse(ts))/1000));if(s<60)return s+"s ago";var m=Math.round(s/60);if(m<60)return m+" min ago";return Math.round(m/60)+" h ago"}
39635
- function hhmm(ts){var d=new Date(ts);return d.toLocaleTimeString([], {hour:"2-digit",minute:"2-digit"})}
39636
- function recent(ts){return Date.now()-Date.parse(ts)<10*60*1000}
39637
- function zoomable(img){img.addEventListener("click",function(){var lb=$("lb");lb.firstChild.src=img.src;lb.style.display="flex"})}
39638
- function img(src,cls){var i=el("img",cls);i.src=src;i.alt="";i.loading="lazy";zoomable(i);return i}
39639
- function chip(txt,kind){return el("span","chip"+(kind?" "+kind:""),txt)}
39640
- function render(s){
39641
- 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;
39642
- if(s.app.icon){$("icon").src=s.app.icon}else{$("icon").removeAttribute("src")}
39643
- var steps=$("steps");clear(steps);var n=s.current?s.current.stepNumber:0;
39644
- 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)});
39645
- var last=s.journal[0];
39646
- $("nowmsg").textContent=last?last.message:"Waiting for the first postcard.";
39647
- $("nowmeta").textContent=last?("Now \xB7 "+(last.step?("step "+last.stepNumber+" of 8, "+last.step+" \xB7 "):"")+ago(last.ts)):"Now";
39648
- var focus=$("focus");var film=$("film");clear(film);
39649
- /* The live frame is KEPT across renders while its URL and its run are unchanged (a restarted run on
39650
- the same port is a different server, and the old frame would never see its builds). A new iframe is a full page
39651
- load of the preview: every postcard, rebuild and the once-a-minute re-render used to throw away
39652
- the in-place repaint, the preview's scroll and wherever the owner had navigated inside it. */
39653
- 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;
39654
- if(kept){while(focus.lastChild&&focus.lastChild!==kept.parentNode)focus.removeChild(focus.lastChild)}else clear(focus);
39655
- 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))}
39656
- 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"))}
39657
- else{$("focuslbl").textContent="Latest";focus.appendChild(el("div","empty","The first render appears here."))}
39658
- if(s.focus&&s.focus.frames.length>1)s.focus.frames.forEach(function(fm){film.appendChild(img(fm.img))});
39659
- var chips=$("chips");clear(chips);var h=s.health;
39660
- if(h.validate)chips.appendChild(chip(h.validate.ok?"Validate passing":"Validate: "+h.validate.errors+" errors",h.validate.ok?"ok":"bad"));
39661
- 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)}
39662
- h.ux.forEach(function(u){chips.appendChild(chip("UX drive "+u.platform+": "+u.status,u.status==="passed"?"ok":u.status==="failed"?"bad":"warn"))});
39663
- 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"));
39664
- if(!chips.firstChild)chips.appendChild(chip("Checks appear here as validate, test and push run"));
39665
- var wall=$("wall");clear(wall);
39666
- if(!s.screens.length)wall.appendChild(el("div","empty","Screens appear here as they render."));
39667
- 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));
39668
- 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)});
39669
- var brand=$("brand");clear(brand);$("brandcard").style.display=(s.brand.site||s.app.palette.length)?"":"none";
39670
- if(s.brand.site){var si=img(s.brand.site,"site");brand.appendChild(si)}
39671
- if(s.brand.site&&s.focus&&s.focus.frames.length){brand.appendChild(el("span","sub","\u2192"));var pf=el("div","shot");pf.style.width="90px";var ff=el("div","frame");ff.appendChild(img(s.focus.frames[s.focus.frames.length-1].img));pf.appendChild(ff);brand.appendChild(pf)}
39672
- if(s.app.palette.length){var sw=el("div","sw");s.app.palette.forEach(function(c){var d=el("span");d.style.background=c.hex;d.title=c.name+" "+c.hex;sw.appendChild(d)});brand.appendChild(sw)}
39673
- var nav=$("nav");clear(nav);$("navcard").style.display=s.nav.length?"":"none";s.nav.forEach(function(e){nav.appendChild(el("span",null,e.from+" \u2192 "+e.to))});
39674
- var log=$("log");clear(log);if(!s.journal.length)log.appendChild(el("li","empty","Postcards the agent journals appear here."));
39675
- 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));
39676
- 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)});
39677
- }
39678
- /* Motion, sound and notices answer a CHANGE, never a redraw: effects() compares what is on the page
39679
- now with what it showed last, so opening the board, the once-a-minute redraw and the two-second
39680
- poll of an unchanged state do nothing, and each effect names a real event. */
39681
- var seen=null,actx=null,sound=false,notices=false;try{sound=localStorage.getItem("himiStudioSound")==="1";notices=localStorage.getItem("himiStudioNotify")==="1"}catch(e){}
39682
- var raf=window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(f){return setTimeout(function(){f(Date.now())},16)};
39683
- function now(){return window.performance&&performance.now?performance.now():Date.now()}
39684
- function reduced(){try{return matchMedia("(prefers-reduced-motion: reduce)").matches}catch(e){return false}}
39685
- 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){}}
39686
- 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")}};
39687
- /* Notices are the BOARD's opt-in, not just the browser's: a permission granted to this origin once
39688
- (an earlier board, another run) must not switch them on, and the toggle must switch them off. */
39689
- function notify(title,body){try{if(notices&&"Notification" in window&&Notification.permission==="granted"&&document.hidden)new Notification(title,{body:body})}catch(e){}}
39690
- function accentNow(){return (getComputedStyle(document.documentElement).getPropertyValue("--accent")||"").trim()||"#3b6fd8"}
39691
- /* The preview lays out at a real phone's viewport (390 x 844 CSS px) and is scaled into the frame.
39692
- Sized to the frame itself it was ~280px wide: narrower than any phone, so screens overflowed
39693
- sideways and scrolled horizontally. */
39694
- 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)))}
39695
- window.addEventListener("resize",fit);
39696
- 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){}}
39697
- 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)})()}
39698
- 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)})()}
39699
- 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}}
39700
- 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}
39701
- function effects(s){var is=snap(s),was=seen;seen=is;if(is.accent)document.documentElement.style.setProperty("--accent",is.accent);
39702
- /* The phone (kept across renders) and the Now line outlive a render: clear what they played last. */
39703
- var ph=$("focus").querySelector(".phone"),nm=$("nowmsg");if(ph)ph.classList.remove("pulse","bad");nm.classList.remove("slidein");if(!was)return;
39704
- 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();
39705
- 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()}
39706
- 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])}
39707
- /* A rebuild is a later build of the SAME run: a run appearing, or restarting, records its first
39708
- build too, and nothing was rebuilt then. */
39709
- 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||"")}}
39710
- if(was.gaps!=null&&is.gaps!=null&&is.gaps!==was.gaps)countTo($("gapn"),was.gaps,is.gaps);
39711
- 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))}
39712
- 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")}
39713
- }
39714
- 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}
39715
- 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))}
39716
- function setNotices(v){notices=v;try{localStorage.setItem("himiStudioNotify",v?"1":"0")}catch(e){}paintToggles()}
39717
- $("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()});
39718
- /* The click acts on what the button SAYS (the effective state), so a remembered opt-in whose browser
39719
- permission was since reset reads "off" and one click asks for permission, not two. */
39720
- $("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){}});
39721
- document.addEventListener("click",function(){try{if(actx&&actx.state==="suspended")actx.resume()}catch(e){}},{once:true});
39722
- paintToggles();
39723
- window.__himiStudio=function(s){lastSeen=Date.now();if(!cur||cur.v!==s.v){cur=s;render(s);effects(s)}else{cur.generatedAt=s.generatedAt}};
39724
- $("lb").addEventListener("click",function(){this.style.display="none"});
39725
- window.__himiStudio(window.__himiInitial);
39726
- 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);
39727
- var ticks=0;
39728
- 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;
39729
- $("clock").textContent=(hh?hh+":":"")+(mm<10&&hh?"0":"")+mm+":"+(ss<10?"0":"")+ss;
39730
- $("updated").textContent="updated "+ago(cur.generatedAt);$("live").className="live"+(Date.now()-lastSeen>10000?" stale":"");
39731
- var a=activityAt(cur),q=a?Date.now()-a:0,done=cur.current&&cur.current.stepNumber===8;
39732
- $("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);
39733
- })();
39734
- </script>
39735
- </body>
39736
- </html>
39737
- `;
39738
- }
39739
- function escapeHtml(s) {
39740
- return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
39741
- }
39742
- var STUDIO_DIR, STUDIO_PAGE, STATE_FILE, MEMORY_FILE, VALIDATE_RECORD, PUSH_RECORD, EDGES_RECORD, RUN_RECORD, MAX_TIMELINE, MAX_JOURNAL, MAX_NAV_EDGES, readJson2, obj, num, text, historyUrl, runTime, scriptJson;
40194
+ var STUDIO_DIR, STUDIO_PAGE, STATE_FILE, MEMORY_FILE, VALIDATE_RECORD, PUSH_RECORD, EDGES_RECORD, RUN_RECORD, MAX_TIMELINE, MAX_JOURNAL, MAX_NAV_EDGES, readJson2, obj, num, text, historyUrl, runTime;
39743
40195
  var init_studio = __esm({
39744
40196
  "src/studio.ts"() {
40197
+ init_studio_page();
40198
+ init_studio_timeline();
39745
40199
  init_progress();
39746
- STUDIO_DIR = join36(".himi", "studio");
39747
- STUDIO_PAGE = join36(STUDIO_DIR, "index.html");
39748
- STATE_FILE = join36(STUDIO_DIR, "state.js");
39749
- MEMORY_FILE = join36(STUDIO_DIR, "memory.json");
39750
- VALIDATE_RECORD = join36(".himi", "validate.json");
39751
- PUSH_RECORD = join36(".himi", "last-push.json");
39752
- EDGES_RECORD = join36(".himi", "test-edges.json");
39753
- RUN_RECORD = join36(".himi", "run.json");
40200
+ init_studio_page();
40201
+ STUDIO_DIR = join37(".himi", "studio");
40202
+ STUDIO_PAGE = join37(STUDIO_DIR, "index.html");
40203
+ STATE_FILE = join37(STUDIO_DIR, "state.js");
40204
+ MEMORY_FILE = join37(STUDIO_DIR, "memory.json");
40205
+ VALIDATE_RECORD = join37(".himi", "validate.json");
40206
+ PUSH_RECORD = join37(".himi", "last-push.json");
40207
+ EDGES_RECORD = join37(".himi", "test-edges.json");
40208
+ RUN_RECORD = join37(".himi", "run.json");
39754
40209
  MAX_TIMELINE = 500;
39755
40210
  MAX_JOURNAL = 80;
39756
40211
  MAX_NAV_EDGES = 40;
39757
40212
  readJson2 = (file) => {
39758
40213
  try {
39759
- return JSON.parse(readFileSync42(file, "utf8"));
40214
+ return JSON.parse(readFileSync43(file, "utf8"));
39760
40215
  } catch {
39761
40216
  return null;
39762
40217
  }
@@ -39766,7 +40221,6 @@ var init_studio = __esm({
39766
40221
  text = (v) => typeof v === "string" && v ? v : null;
39767
40222
  historyUrl = (fileName) => `../history/${fileName}`;
39768
40223
  runTime = (runId) => runId.replace(/-[0-9a-f]+$/i, "");
39769
- scriptJson = (value) => JSON.stringify(value).replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
39770
40224
  }
39771
40225
  });
39772
40226
 
@@ -40245,11 +40699,11 @@ __export(captcha_exports, {
40245
40699
  captchaContentInput: () => captchaContentInput,
40246
40700
  runCaptcha: () => runCaptcha
40247
40701
  });
40248
- import { readFileSync as readFileSync43 } from "node:fs";
40702
+ import { readFileSync as readFileSync44 } from "node:fs";
40249
40703
  import { resolve as resolve34 } from "node:path";
40250
40704
  function readObject(path2) {
40251
40705
  try {
40252
- return JSON.parse(readFileSync43(path2, "utf8"));
40706
+ return JSON.parse(readFileSync44(path2, "utf8"));
40253
40707
  } catch {
40254
40708
  return {};
40255
40709
  }
@@ -40393,11 +40847,11 @@ var init_push_setup_apple_credentials = __esm({
40393
40847
  });
40394
40848
 
40395
40849
  // src/push-onboarding.ts
40396
- import { existsSync as existsSync40, mkdirSync as mkdirSync18, readFileSync as readFileSync44, statSync as statSync9, writeFileSync as writeFileSync14, chmodSync as chmodSync2 } from "node:fs";
40397
- import { basename as basename10, dirname as dirname22, join as join37, resolve as resolve35 } from "node:path";
40850
+ import { existsSync as existsSync41, mkdirSync as mkdirSync18, readFileSync as readFileSync45, statSync as statSync9, writeFileSync as writeFileSync14, chmodSync as chmodSync2 } from "node:fs";
40851
+ import { basename as basename10, dirname as dirname22, join as join38, resolve as resolve35 } from "node:path";
40398
40852
  function readJson3(path2) {
40399
40853
  try {
40400
- const parsed = JSON.parse(readFileSync44(path2, "utf8"));
40854
+ const parsed = JSON.parse(readFileSync45(path2, "utf8"));
40401
40855
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
40402
40856
  } catch {
40403
40857
  return {};
@@ -40405,9 +40859,9 @@ function readJson3(path2) {
40405
40859
  }
40406
40860
  function pushInputsFromContent(dir) {
40407
40861
  const root = resolve35(dir);
40408
- if (!existsSync40(join37(root, "himalaya.content.json"))) return { initialized: false };
40409
- const content = readJson3(join37(root, "himalaya.content.json"));
40410
- const provisioning = readJson3(join37(root, "provisioning.json"));
40862
+ if (!existsSync41(join38(root, "himalaya.content.json"))) return { initialized: false };
40863
+ const content = readJson3(join38(root, "himalaya.content.json"));
40864
+ const provisioning = readJson3(join38(root, "provisioning.json"));
40411
40865
  const native = provisioning.native ?? {};
40412
40866
  return {
40413
40867
  initialized: true,
@@ -40482,7 +40936,7 @@ function readKeyFile(path2) {
40482
40936
  }
40483
40937
  let body;
40484
40938
  try {
40485
- body = readFileSync44(path2, "utf8");
40939
+ body = readFileSync45(path2, "utf8");
40486
40940
  } catch {
40487
40941
  return { ok: false, code: "key_file_unreadable", reason: "cannot read the selected key file; check its path and file permissions" };
40488
40942
  }
@@ -40536,13 +40990,13 @@ function buildPushSetupBody(i, keys) {
40536
40990
  };
40537
40991
  }
40538
40992
  function readPushOnboarding(dir) {
40539
- const path2 = join37(resolve35(dir), PUSH_ONBOARDING_PATH);
40540
- if (!existsSync40(path2)) return { version: 1 };
40993
+ const path2 = join38(resolve35(dir), PUSH_ONBOARDING_PATH);
40994
+ if (!existsSync41(path2)) return { version: 1 };
40541
40995
  const parsed = readJson3(path2);
40542
40996
  return { version: 1, ...parsed };
40543
40997
  }
40544
40998
  function writeFile(dir, file) {
40545
- const path2 = join37(resolve35(dir), PUSH_ONBOARDING_PATH);
40999
+ const path2 = join38(resolve35(dir), PUSH_ONBOARDING_PATH);
40546
41000
  mkdirSync18(dirname22(path2), { recursive: true, mode: 448 });
40547
41001
  writeFileSync14(path2, JSON.stringify(file, null, 2) + "\n", { mode: 384 });
40548
41002
  try {
@@ -40571,8 +41025,8 @@ function recordPushDeferral(dir, d) {
40571
41025
  return push;
40572
41026
  }
40573
41027
  function clearPushDeferral(dir) {
40574
- const path2 = join37(resolve35(dir), PUSH_ONBOARDING_PATH);
40575
- if (!existsSync40(path2)) return;
41028
+ const path2 = join38(resolve35(dir), PUSH_ONBOARDING_PATH);
41029
+ if (!existsSync41(path2)) return;
40576
41030
  const { push: _dropped, ...rest } = readPushOnboarding(dir);
40577
41031
  writeFile(dir, { ...rest, version: 1 });
40578
41032
  }
@@ -41305,8 +41759,8 @@ __export(template_trim_exports, {
41305
41759
  routeMatcher: () => routeMatcher,
41306
41760
  runTemplateTrim: () => runTemplateTrim
41307
41761
  });
41308
- import { cpSync, existsSync as existsSync41, mkdirSync as mkdirSync19, readdirSync as readdirSync21, readFileSync as readFileSync45, renameSync as renameSync4, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync15 } from "node:fs";
41309
- import { dirname as dirname23, join as join38 } from "node:path";
41762
+ import { cpSync, existsSync as existsSync42, mkdirSync as mkdirSync19, readdirSync as readdirSync21, readFileSync as readFileSync46, renameSync as renameSync4, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync15 } from "node:fs";
41763
+ import { dirname as dirname23, join as join39 } from "node:path";
41310
41764
  function routeMatcher(pattern) {
41311
41765
  const detail = /^\^([A-Za-z0-9_]+)\(\?:\[_\/\]\(\.\+\)\)\?\$$/.exec(pattern);
41312
41766
  if (detail) {
@@ -42000,20 +42454,20 @@ function homeModulesPass(app, kept, edited) {
42000
42454
  return { text: patched.src, dropped: patched.droppedModules };
42001
42455
  }
42002
42456
  function listWorkers(dir) {
42003
- const root = join38(dir, "tier5-src");
42004
- if (!existsSync41(root)) return { dirs: [], files: {} };
42005
- const dirs = readdirSync21(root).filter((n) => statSync10(join38(root, n)).isDirectory()).sort();
42457
+ const root = join39(dir, "tier5-src");
42458
+ if (!existsSync42(root)) return { dirs: [], files: {} };
42459
+ const dirs = readdirSync21(root).filter((n) => statSync10(join39(root, n)).isDirectory()).sort();
42006
42460
  const files = {};
42007
42461
  const walk2 = (abs, rel) => {
42008
42462
  for (const name of readdirSync21(abs)) {
42009
42463
  if (name === "node_modules") continue;
42010
- const p = join38(abs, name);
42464
+ const p = join39(abs, name);
42011
42465
  const r = `${rel}/${name}`;
42012
42466
  if (statSync10(p).isDirectory()) walk2(p, r);
42013
- else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync45(p, "utf8");
42467
+ else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync46(p, "utf8");
42014
42468
  }
42015
42469
  };
42016
- for (const d of dirs) walk2(join38(root, d), `tier5-src/${d}`);
42470
+ for (const d of dirs) walk2(join39(root, d), `tier5-src/${d}`);
42017
42471
  return { dirs, files };
42018
42472
  }
42019
42473
  function csv(v) {
@@ -42036,16 +42490,16 @@ async function runTemplateTrim(req, deps) {
42036
42490
  links = req.links;
42037
42491
  }
42038
42492
  const dir = req.dir;
42039
- const devPath = join38(dir, "dev/index.ts");
42040
- const configPath = join38(dir, "config.ts");
42041
- if (!existsSync41(devPath) || !existsSync41(configPath)) {
42493
+ const devPath = join39(dir, "dev/index.ts");
42494
+ const configPath = join39(dir, "config.ts");
42495
+ if (!existsSync42(devPath) || !existsSync42(configPath)) {
42042
42496
  return refuse3(new TrimRefusal(`${dir} has no dev/index.ts + config.ts \u2014 point --content at an app scaffolded with \`himi init --template\``));
42043
42497
  }
42044
42498
  if (isTemplateSourceTree(dir)) {
42045
42499
  return refuse3(new TrimRefusal(`${dir} is a template source tree (its identity is still __HIMI_APP_*__ sentinels) \u2014 trim the app \`himi init --template\` scaffolds from it, not the template itself`));
42046
42500
  }
42047
- const dev = readFileSync45(devPath, "utf8");
42048
- const config = readFileSync45(configPath, "utf8");
42501
+ const dev = readFileSync46(devPath, "utf8");
42502
+ const config = readFileSync46(configPath, "utf8");
42049
42503
  const workers = listWorkers(dir);
42050
42504
  let plan;
42051
42505
  try {
@@ -42055,22 +42509,22 @@ async function runTemplateTrim(req, deps) {
42055
42509
  throw e;
42056
42510
  }
42057
42511
  if (req.dryRun) return { code: 0, result: { ok: true, dryRun: true, ...plan.report, validate: null } };
42058
- const hadHimi = existsSync41(join38(dir, ".himi"));
42059
- const backupRel = join38(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
42060
- const backup = join38(dir, backupRel);
42512
+ const hadHimi = existsSync42(join39(dir, ".himi"));
42513
+ const backupRel = join39(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
42514
+ const backup = join39(dir, backupRel);
42061
42515
  const originals = [[devPath, "dev/index.ts"], [configPath, "config.ts"]];
42062
42516
  const moved = [];
42063
42517
  const restore = () => {
42064
42518
  for (const [path2, rel] of originals) {
42065
- const original = readFileSync45(join38(backup, rel));
42066
- if (!existsSync41(path2) || !readFileSync45(path2).equals(original)) writeFileSync15(path2, original);
42519
+ const original = readFileSync46(join39(backup, rel));
42520
+ if (!existsSync42(path2) || !readFileSync46(path2).equals(original)) writeFileSync15(path2, original);
42067
42521
  }
42068
- for (const name of [...moved].reverse()) renameSync4(join38(backup, "tier5-src", name), join38(dir, "tier5-src", name));
42522
+ for (const name of [...moved].reverse()) renameSync4(join39(backup, "tier5-src", name), join39(dir, "tier5-src", name));
42069
42523
  };
42070
42524
  const dropBackup = () => {
42071
42525
  rmSync8(backup, { recursive: true, force: true });
42072
- for (const d of [dirname23(backup), join38(dir, ".himi")]) {
42073
- if (d === join38(dir, ".himi") && hadHimi) break;
42526
+ for (const d of [dirname23(backup), join39(dir, ".himi")]) {
42527
+ if (d === join39(dir, ".himi") && hadHimi) break;
42074
42528
  try {
42075
42529
  if (!readdirSync21(d).length) rmSync8(d, { recursive: true });
42076
42530
  } catch {
@@ -42096,9 +42550,9 @@ async function runTemplateTrim(req, deps) {
42096
42550
  return { code: 1, result: { ok: false, error: `${error} (${message}); every file was restored`, rolledBack: true, ...base(validate4) } };
42097
42551
  };
42098
42552
  try {
42099
- mkdirSync19(join38(backup, "dev"), { recursive: true });
42100
- for (const [path2, rel] of originals) cpSync(path2, join38(backup, rel));
42101
- if (plan.removedWorkers.length) mkdirSync19(join38(backup, "tier5-src"), { recursive: true });
42553
+ mkdirSync19(join39(backup, "dev"), { recursive: true });
42554
+ for (const [path2, rel] of originals) cpSync(path2, join39(backup, rel));
42555
+ if (plan.removedWorkers.length) mkdirSync19(join39(backup, "tier5-src"), { recursive: true });
42102
42556
  } catch (e) {
42103
42557
  dropBackup();
42104
42558
  return { code: 1, result: { ok: false, error: `could not back up the app before trimming it (${e.message}); nothing was changed`, rolledBack: true, ...base() } };
@@ -42106,7 +42560,7 @@ async function runTemplateTrim(req, deps) {
42106
42560
  try {
42107
42561
  const moveDir = deps.moveDir ?? renameSync4;
42108
42562
  for (const name of plan.removedWorkers) {
42109
- moveDir(join38(dir, "tier5-src", name), join38(backup, "tier5-src", name));
42563
+ moveDir(join39(dir, "tier5-src", name), join39(backup, "tier5-src", name));
42110
42564
  moved.push(name);
42111
42565
  }
42112
42566
  if (plan.dev !== dev) writeFileSync15(devPath, plan.dev);
@@ -42117,7 +42571,7 @@ async function runTemplateTrim(req, deps) {
42117
42571
  let failure;
42118
42572
  let validate3;
42119
42573
  try {
42120
- const written = parseApp(readFileSync45(devPath, "utf8"), readFileSync45(configPath, "utf8"));
42574
+ const written = parseApp(readFileSync46(devPath, "utf8"), readFileSync46(configPath, "utf8"));
42121
42575
  const leftovers = leftoverNavRefs(written.nav.value, plan.removedIds);
42122
42576
  if (leftovers.length) failure = { error: "navigation still names screens this trim removed", leftovers };
42123
42577
  else {
@@ -42811,8 +43265,8 @@ __export(pack_exports, {
42811
43265
  });
42812
43266
  import { createHash as createHash21 } from "node:crypto";
42813
43267
  import { deflateRawSync, inflateRawSync } from "node:zlib";
42814
- import { existsSync as existsSync42, mkdirSync as mkdirSync20, readFileSync as readFileSync46, readdirSync as readdirSync22, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync16 } from "node:fs";
42815
- import { dirname as dirname24, join as join39, resolve as resolve37 } from "node:path";
43268
+ import { existsSync as existsSync43, mkdirSync as mkdirSync20, readFileSync as readFileSync47, readdirSync as readdirSync22, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync16 } from "node:fs";
43269
+ import { dirname as dirname24, join as join40, resolve as resolve37 } from "node:path";
42816
43270
  function writeZip(entries) {
42817
43271
  const sorted2 = entries.slice().sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
42818
43272
  const total = sorted2.reduce((n, e) => n + e.bytes.length, 0);
@@ -42906,13 +43360,13 @@ function inflateEntry(buf, entry) {
42906
43360
  return bytes2;
42907
43361
  }
42908
43362
  function readIf(path2) {
42909
- return existsSync42(path2) && statSync11(path2).isFile() ? readFileSync46(path2) : null;
43363
+ return existsSync43(path2) && statSync11(path2).isFile() ? readFileSync47(path2) : null;
42910
43364
  }
42911
43365
  function listDir(dir) {
42912
- return existsSync42(dir) ? readdirSync22(dir).filter((f) => statSync11(join39(dir, f)).isFile()) : [];
43366
+ return existsSync43(dir) ? readdirSync22(dir).filter((f) => statSync11(join40(dir, f)).isFile()) : [];
42913
43367
  }
42914
43368
  function gatherReleaseEntries(appDir2) {
42915
- const manifestPath = join39(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
43369
+ const manifestPath = join40(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
42916
43370
  const manifestBytes = readIf(manifestPath);
42917
43371
  if (!manifestBytes) throw new Error(`no ${HIMI_PACKAGE_MANIFEST_PATH} in ${appDir2} \u2014 build a release there first`);
42918
43372
  const manifest = JSON.parse(manifestBytes.toString("utf8"));
@@ -42936,7 +43390,7 @@ function gatherReleaseEntries(appDir2) {
42936
43390
  }
42937
43391
  const missing = [];
42938
43392
  for (const rel of wanted) {
42939
- const bytes2 = readIf(join39(appDir2, rel));
43393
+ const bytes2 = readIf(join40(appDir2, rel));
42940
43394
  if (bytes2) entries.push({ path: rel, bytes: bytes2 });
42941
43395
  else missing.push(rel);
42942
43396
  }
@@ -42947,12 +43401,12 @@ function gatherReleaseEntries(appDir2) {
42947
43401
  );
42948
43402
  }
42949
43403
  for (const name of SINGLETONS) {
42950
- const bytes2 = readIf(join39(appDir2, name));
43404
+ const bytes2 = readIf(join40(appDir2, name));
42951
43405
  if (bytes2) entries.push({ path: name, bytes: bytes2 });
42952
43406
  }
42953
43407
  const skipped = [];
42954
43408
  for (const sub of ["screens", "bundles", "assets", "widgets"]) {
42955
- for (const f of listDir(join39(appDir2, sub))) {
43409
+ for (const f of listDir(join40(appDir2, sub))) {
42956
43410
  if (!wanted.has(`${sub}/${f}`)) skipped.push(`${sub}/${f}`);
42957
43411
  }
42958
43412
  }
@@ -42985,18 +43439,18 @@ function buildEntries(appDir2, app, opts) {
42985
43439
  return { all, header, manifest, skipped };
42986
43440
  }
42987
43441
  function packRelease(releaseRoot, app, out, opts = {}) {
42988
- const appDir2 = join39(resolve37(releaseRoot), app);
43442
+ const appDir2 = join40(resolve37(releaseRoot), app);
42989
43443
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
42990
43444
  const buf = writeZip(all);
42991
- const isDir2 = existsSync42(out) && statSync11(out).isDirectory();
42992
- const outFile = isDir2 ? join39(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
43445
+ const isDir2 = existsSync43(out) && statSync11(out).isDirectory();
43446
+ const outFile = isDir2 ? join40(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
42993
43447
  mkdirSync20(dirname24(outFile), { recursive: true });
42994
43448
  writeFileSync16(outFile, buf);
42995
43449
  return { app, releaseId: header.releaseId, header, out: outFile, bytes: buf.length, entryCount: all.length, skipped };
42996
43450
  }
42997
43451
  function resolvePackageDirDest(outDir, app, releaseId) {
42998
43452
  const packagedApp = (dir) => {
42999
- const bytes2 = readIf(join39(dir, HIMI_PACKAGE_HEADER_PATH));
43453
+ const bytes2 = readIf(join40(dir, HIMI_PACKAGE_HEADER_PATH));
43000
43454
  if (!bytes2) return void 0;
43001
43455
  try {
43002
43456
  return JSON.parse(bytes2.toString("utf8")).app;
@@ -43005,15 +43459,15 @@ function resolvePackageDirDest(outDir, app, releaseId) {
43005
43459
  }
43006
43460
  };
43007
43461
  const replaceable = (dir) => {
43008
- if (!existsSync42(dir)) return true;
43462
+ if (!existsSync43(dir)) return true;
43009
43463
  if (!statSync11(dir).isDirectory()) return false;
43010
43464
  return readdirSync22(dir).length === 0 || packagedApp(dir) === app;
43011
43465
  };
43012
- if (existsSync42(outDir) && !statSync11(outDir).isDirectory()) {
43466
+ if (existsSync43(outDir) && !statSync11(outDir).isDirectory()) {
43013
43467
  throw new Error(`refusing to write a package tree over ${outDir} \u2014 it is a file, not a directory`);
43014
43468
  }
43015
43469
  if (replaceable(outDir)) return outDir;
43016
- const child = join39(outDir, packageDirName(app, releaseId));
43470
+ const child = join40(outDir, packageDirName(app, releaseId));
43017
43471
  if (!replaceable(child)) {
43018
43472
  throw new Error(
43019
43473
  `refusing to replace ${child}: it is not empty and is not a ${app} package (no ${HIMI_PACKAGE_HEADER_PATH}). Pass --out to a new or empty directory.`
@@ -43027,13 +43481,13 @@ function packReleaseToDir(releaseRoot, app, outDir, opts = {}) {
43027
43481
  "a package carrying the app's source has no --format dir form: readers that predate the source entry drop it before hashing a directory and refuse the package as tampered. Pack it as a .himi archive."
43028
43482
  );
43029
43483
  }
43030
- const appDir2 = join39(resolve37(releaseRoot), app);
43484
+ const appDir2 = join40(resolve37(releaseRoot), app);
43031
43485
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
43032
43486
  const dest = resolvePackageDirDest(resolve37(outDir), app, header.releaseId);
43033
43487
  rmSync9(dest, { recursive: true, force: true });
43034
43488
  let bytes2 = 0;
43035
43489
  for (const entry of all) {
43036
- const target = join39(dest, entry.path);
43490
+ const target = join40(dest, entry.path);
43037
43491
  mkdirSync20(dirname24(target), { recursive: true });
43038
43492
  writeFileSync16(target, entry.bytes);
43039
43493
  bytes2 += entry.bytes.length;
@@ -43056,21 +43510,21 @@ function openPackageBuffer(buf) {
43056
43510
  return openFromEntries(readZip(buf), { requireHeader: true });
43057
43511
  }
43058
43512
  function openPackageFile(file) {
43059
- return openPackageBuffer(readFileSync46(file));
43513
+ return openPackageBuffer(readFileSync47(file));
43060
43514
  }
43061
43515
  function openPackageDir(dir) {
43062
43516
  const root = resolve37(dir);
43063
43517
  const entries = /* @__PURE__ */ new Map();
43064
43518
  const walk2 = (rel) => {
43065
- const abs = rel ? join39(root, rel) : root;
43519
+ const abs = rel ? join40(root, rel) : root;
43066
43520
  for (const name of readdirSync22(abs)) {
43067
43521
  const childRel = rel ? `${rel}/${name}` : name;
43068
- const childAbs = join39(abs, name);
43522
+ const childAbs = join40(abs, name);
43069
43523
  if (statSync11(childAbs).isDirectory()) walk2(childRel);
43070
- else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync46(childAbs));
43524
+ else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync47(childAbs));
43071
43525
  }
43072
43526
  };
43073
- if (!existsSync42(root)) throw new Error(`no such package directory: ${root}`);
43527
+ if (!existsSync43(root)) throw new Error(`no such package directory: ${root}`);
43074
43528
  walk2("");
43075
43529
  const manifestBytes = entries.get(HIMI_PACKAGE_MANIFEST_PATH);
43076
43530
  if (manifestBytes) {
@@ -43112,10 +43566,10 @@ function unpackTo(buf, dest, appFallback) {
43112
43566
  if (!app) throw new Error("package declares no app id (no himi-package.json and no manifest.app)");
43113
43567
  if (!isSafeAppId(app)) throw new Error(`package declares an unsafe app id ${JSON.stringify(app)} \u2014 refusing to unpack it`);
43114
43568
  const root = resolve37(dest);
43115
- const appDir2 = join39(root, app);
43569
+ const appDir2 = join40(root, app);
43116
43570
  rmSync9(appDir2, { recursive: true, force: true });
43117
43571
  for (const [rel, bytes2] of entries) {
43118
- const target = join39(appDir2, rel);
43572
+ const target = join40(appDir2, rel);
43119
43573
  mkdirSync20(dirname24(target), { recursive: true });
43120
43574
  writeFileSync16(target, bytes2);
43121
43575
  }
@@ -43151,20 +43605,20 @@ __export(app_source_exports, {
43151
43605
  writeConflicts: () => writeConflicts,
43152
43606
  zipAppSource: () => zipAppSource
43153
43607
  });
43154
- import { readdirSync as readdirSync23, lstatSync as lstatSync6, readFileSync as readFileSync47, mkdirSync as mkdirSync21, writeFileSync as writeFileSync17, existsSync as existsSync43, rmSync as rmSync10, rmdirSync, unlinkSync as unlinkSync3 } from "node:fs";
43155
- import { join as join40, resolve as resolve38, sep as sep10 } from "node:path";
43608
+ import { readdirSync as readdirSync23, lstatSync as lstatSync6, readFileSync as readFileSync48, mkdirSync as mkdirSync21, writeFileSync as writeFileSync17, existsSync as existsSync44, rmSync as rmSync10, rmdirSync, unlinkSync as unlinkSync3 } from "node:fs";
43609
+ import { join as join41, resolve as resolve38, sep as sep10 } from "node:path";
43156
43610
  import { deflateRawSync as deflateRawSync2 } from "node:zlib";
43157
43611
  function walkSourceTree(dir, file, skip) {
43158
43612
  const walk2 = (relDir) => {
43159
- for (const name of readdirSync23(join40(dir, relDir))) {
43160
- const rel = relDir ? join40(relDir, name) : name;
43161
- const st = lstatSync6(join40(dir, rel));
43613
+ for (const name of readdirSync23(join41(dir, relDir))) {
43614
+ const rel = relDir ? join41(relDir, name) : name;
43615
+ const st = lstatSync6(join41(dir, rel));
43162
43616
  if (st.isSymbolicLink()) {
43163
43617
  skip(rel);
43164
43618
  continue;
43165
43619
  }
43166
43620
  if (st.isDirectory()) {
43167
- if (isDeniedSourceDir(name, relDir) || existsSync43(join40(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
43621
+ if (isDeniedSourceDir(name, relDir) || existsSync44(join41(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
43168
43622
  else walk2(rel);
43169
43623
  continue;
43170
43624
  }
@@ -43182,7 +43636,7 @@ function collectAppSource(dir) {
43182
43636
  const skipped = [];
43183
43637
  walkSourceTree(
43184
43638
  dir,
43185
- (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync47(join40(dir, hostRel)) }),
43639
+ (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync48(join41(dir, hostRel)) }),
43186
43640
  (hostRel) => skipped.push(hostRel)
43187
43641
  );
43188
43642
  entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
@@ -43236,13 +43690,13 @@ function writeAppSource(entries, destDir) {
43236
43690
  const segments = entry.path.split("/");
43237
43691
  let dir = root;
43238
43692
  for (const segment of segments.slice(0, -1)) {
43239
- dir = join40(dir, segment);
43693
+ dir = join41(dir, segment);
43240
43694
  const kind = pathKind(dir);
43241
43695
  if (kind?.isSymbolicLink()) unlinkSync3(dir);
43242
43696
  else if (kind) continue;
43243
43697
  mkdirSync21(dir);
43244
43698
  }
43245
- const target = join40(root, ...segments);
43699
+ const target = join41(root, ...segments);
43246
43700
  if (pathKind(target)?.isSymbolicLink()) unlinkSync3(target);
43247
43701
  writeFileSync17(target, entry.bytes);
43248
43702
  written.push(entry.path);
@@ -43250,7 +43704,7 @@ function writeAppSource(entries, destDir) {
43250
43704
  return written;
43251
43705
  }
43252
43706
  function staleSourcePaths(entries, destDir) {
43253
- if (!existsSync43(destDir)) return [];
43707
+ if (!existsSync44(destDir)) return [];
43254
43708
  const archived = new Set(entries.map((e) => e.path));
43255
43709
  const stale = [];
43256
43710
  walkSourceTree(destDir, (archivePath) => {
@@ -43277,7 +43731,7 @@ function writeConflicts(entries, destDir) {
43277
43731
  let dir = root;
43278
43732
  let stop = false;
43279
43733
  for (const [i, segment] of segments.slice(0, -1).entries()) {
43280
- dir = join40(dir, segment);
43734
+ dir = join41(dir, segment);
43281
43735
  const kind2 = pathKind(dir);
43282
43736
  if (!kind2 || kind2.isSymbolicLink()) {
43283
43737
  stop = true;
@@ -43289,7 +43743,7 @@ function writeConflicts(entries, destDir) {
43289
43743
  break;
43290
43744
  }
43291
43745
  if (stop) continue;
43292
- const kind = pathKind(join40(root, ...segments));
43746
+ const kind = pathKind(join41(root, ...segments));
43293
43747
  if (kind && !kind.isSymbolicLink() && kind.isDirectory()) {
43294
43748
  say(entry.path, "is a directory here, but the release has a file at that path");
43295
43749
  }
@@ -43299,22 +43753,22 @@ function writeConflicts(entries, destDir) {
43299
43753
  function removeStaleSource(entries, destDir) {
43300
43754
  const root = resolve38(destDir);
43301
43755
  const removed = staleSourcePaths(entries, root);
43302
- for (const rel of removed) rmSync10(join40(root, ...rel.split("/")), { force: true });
43756
+ for (const rel of removed) rmSync10(join41(root, ...rel.split("/")), { force: true });
43303
43757
  const needed = /* @__PURE__ */ new Set();
43304
43758
  for (const entry of entries) {
43305
43759
  if (isDeniedSourcePath(entry.path)) continue;
43306
43760
  const segments = entry.path.split("/");
43307
43761
  for (let i = 1; i < segments.length; i++) needed.add(segments.slice(0, i).join("/"));
43308
43762
  }
43309
- if (existsSync43(root)) pruneEmptyDirs(root, "", needed, removed);
43763
+ if (existsSync44(root)) pruneEmptyDirs(root, "", needed, removed);
43310
43764
  return removed.sort();
43311
43765
  }
43312
43766
  function pruneEmptyDirs(root, rel, needed, removed) {
43313
43767
  let empty = true;
43314
- for (const name of readdirSync23(rel ? join40(root, rel) : root)) {
43315
- const childRel = rel ? join40(rel, name) : name;
43768
+ for (const name of readdirSync23(rel ? join41(root, rel) : root)) {
43769
+ const childRel = rel ? join41(rel, name) : name;
43316
43770
  const posix3 = childRel.split(sep10).join("/");
43317
- const kind = lstatSync6(join40(root, childRel));
43771
+ const kind = lstatSync6(join41(root, childRel));
43318
43772
  if (kind.isSymbolicLink() || !kind.isDirectory() || isDeniedSourceDir(name, rel)) {
43319
43773
  empty = false;
43320
43774
  continue;
@@ -43324,7 +43778,7 @@ function pruneEmptyDirs(root, rel, needed, removed) {
43324
43778
  empty = false;
43325
43779
  continue;
43326
43780
  }
43327
- rmdirSync(join40(root, childRel));
43781
+ rmdirSync(join41(root, childRel));
43328
43782
  removed.push(`${posix3}/`);
43329
43783
  }
43330
43784
  return empty;
@@ -43379,13 +43833,13 @@ __export(eject_exports, {
43379
43833
  EJECTABLE: () => EJECTABLE,
43380
43834
  ejectModule: () => ejectModule
43381
43835
  });
43382
- import { existsSync as existsSync44, mkdirSync as mkdirSync22, readFileSync as readFileSync48, writeFileSync as writeFileSync18 } from "node:fs";
43383
- import { dirname as dirname25, join as join41, relative as relative7, resolve as resolve39, sep as sep11 } from "node:path";
43836
+ import { existsSync as existsSync45, mkdirSync as mkdirSync22, readFileSync as readFileSync49, writeFileSync as writeFileSync18 } from "node:fs";
43837
+ import { dirname as dirname25, join as join42, relative as relative7, resolve as resolve39, sep as sep11 } from "node:path";
43384
43838
  function locate(repoRootOrCwd, mod) {
43385
43839
  const inRepo = resolve39(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
43386
- if (existsSync44(inRepo)) return { path: inRepo, form: "source" };
43840
+ if (existsSync45(inRepo)) return { path: inRepo, form: "source" };
43387
43841
  const dist = resolve39(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
43388
- if (existsSync44(dist)) return { path: dist, form: "bundle" };
43842
+ if (existsSync45(dist)) return { path: dist, form: "bundle" };
43389
43843
  return null;
43390
43844
  }
43391
43845
  function specifiersFor(mod) {
@@ -43409,7 +43863,7 @@ function ejectModule(opts) {
43409
43863
  };
43410
43864
  }
43411
43865
  const ext = found.form === "source" ? "ts" : "mjs";
43412
- const target = join41(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
43866
+ const target = join42(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
43413
43867
  mkdirSync22(dirname25(target), { recursive: true });
43414
43868
  const banner = `// EJECTED from @wix/himalaya/${mod}. This app owns this file now.
43415
43869
  //
@@ -43423,15 +43877,15 @@ function ejectModule(opts) {
43423
43877
  // gate still applies. Ejecting buys layout and flow, never the floor.
43424
43878
 
43425
43879
  `;
43426
- const normalized = readFileSync48(found.path, "utf8").replace(
43880
+ const normalized = readFileSync49(found.path, "utf8").replace(
43427
43881
  /(["'])@himalaya\/([a-z0-9-]+(?:\/[a-z0-9-]+)?)\1/g,
43428
43882
  (_m, q, sub) => `${q}@wix/himalaya/${sub}${q}`
43429
43883
  );
43430
43884
  writeFileSync18(target, banner + normalized);
43431
43885
  const rewired = [];
43432
43886
  for (const f of files) {
43433
- if (!existsSync44(f)) continue;
43434
- const before = readFileSync48(f, "utf8");
43887
+ if (!existsSync45(f)) continue;
43888
+ const before = readFileSync49(f, "utf8");
43435
43889
  let after = before;
43436
43890
  let rel = relative7(dirname25(f), target).split(sep11).join("/").replace(/\.tsx?$/, ".js");
43437
43891
  if (!rel.startsWith(".")) rel = `./${rel}`;
@@ -43479,14 +43933,14 @@ __export(test_exports, {
43479
43933
  scopeDescriptors: () => scopeDescriptors,
43480
43934
  writeRecordedMocks: () => writeRecordedMocks
43481
43935
  });
43482
- import { existsSync as existsSync45, mkdirSync as mkdirSync23, readFileSync as readFileSync49, writeFileSync as writeFileSync19 } from "node:fs";
43483
- import { join as join42, resolve as resolve40 } from "node:path";
43936
+ import { existsSync as existsSync46, mkdirSync as mkdirSync23, readFileSync as readFileSync50, writeFileSync as writeFileSync19 } from "node:fs";
43937
+ import { join as join43, resolve as resolve40 } from "node:path";
43484
43938
  function readTestConfig(dir) {
43485
43939
  const p = resolve40(dir, TEST_CONFIG_FILE);
43486
- if (!existsSync45(p)) return {};
43940
+ if (!existsSync46(p)) return {};
43487
43941
  let parsed;
43488
43942
  try {
43489
- parsed = JSON.parse(readFileSync49(p, "utf8"));
43943
+ parsed = JSON.parse(readFileSync50(p, "utf8"));
43490
43944
  } catch (e) {
43491
43945
  throw new Error(`${TEST_CONFIG_FILE} is not valid JSON \u2014 ${e.message}`);
43492
43946
  }
@@ -43529,7 +43983,7 @@ function readTestConfig(dir) {
43529
43983
  function readNetRules(path2) {
43530
43984
  let parsed;
43531
43985
  try {
43532
- parsed = JSON.parse(readFileSync49(path2, "utf8"));
43986
+ parsed = JSON.parse(readFileSync50(path2, "utf8"));
43533
43987
  } catch (e) {
43534
43988
  throw new Error(`--net ${path2} could not be read as JSON \u2014 ${e.message}`);
43535
43989
  }
@@ -43550,7 +44004,7 @@ function readNetRules(path2) {
43550
44004
  function defaultNetRules(dir) {
43551
44005
  for (const rel of ["dev/net-mocks.json", "tests/net-mocks.json"]) {
43552
44006
  const p = resolve40(dir, rel);
43553
- if (existsSync45(p)) {
44007
+ if (existsSync46(p)) {
43554
44008
  const rules = readNetRules(p);
43555
44009
  if (rules.length) return { rules, from: rel };
43556
44010
  }
@@ -43566,7 +44020,7 @@ function readCannedResponses() {
43566
44020
  ];
43567
44021
  for (const url of candidates) {
43568
44022
  try {
43569
- const parsed = JSON.parse(readFileSync49(url, "utf8"));
44023
+ const parsed = JSON.parse(readFileSync50(url, "utf8"));
43570
44024
  const byKey = {};
43571
44025
  for (const [k, v] of Object.entries(parsed)) {
43572
44026
  if (!k.startsWith("//")) byKey[k] = v;
@@ -43658,8 +44112,8 @@ function writeRecordedMocks(opts) {
43658
44112
  mkdir: (p) => mkdirSync23(p, { recursive: true }),
43659
44113
  write: (p, s) => writeFileSync19(p, s)
43660
44114
  };
43661
- fs.mkdir(join42(opts.dir, "dev"));
43662
- fs.write(join42(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
44115
+ fs.mkdir(join43(opts.dir, "dev"));
44116
+ fs.write(join43(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
43663
44117
  return { ok: true, rules, to: "dev/net-mocks.json" };
43664
44118
  }
43665
44119
  function scopeDescriptors(descriptors, requestedIds, invalidScreens) {
@@ -43973,12 +44427,12 @@ async function runCrawl(opts) {
43973
44427
  };
43974
44428
  }
43975
44429
  function describeTransport(net, opts, ran) {
43976
- const layers = [];
43977
- if (net) layers.push(`mocked (${net.from})`);
44430
+ const layers2 = [];
44431
+ if (net) layers2.push(`mocked (${net.from})`);
43978
44432
  const cmsCount = Object.keys(opts.cmsSeed ?? {}).length;
43979
- if (cmsCount) layers.push(`cms (${cmsCount} seeded collection${cmsCount === 1 ? "" : "s"})`);
43980
- if (opts.canned?.count) layers.push(`canned (${opts.canned.count} frozen endpoints, ${opts.canned.from} \u2014 exercises code paths, not API-contract truth)`);
43981
- const base = layers.length ? layers.join(" + ") : "offline (no net fixture \u2014 screens are crawled against a rejecting transport, which exercises their failure UI)";
44433
+ if (cmsCount) layers2.push(`cms (${cmsCount} seeded collection${cmsCount === 1 ? "" : "s"})`);
44434
+ if (opts.canned?.count) layers2.push(`canned (${opts.canned.count} frozen endpoints, ${opts.canned.from} \u2014 exercises code paths, not API-contract truth)`);
44435
+ const base = layers2.length ? layers2.join(" + ") : "offline (no net fixture \u2014 screens are crawled against a rejecting transport, which exercises their failure UI)";
43982
44436
  const hostile = ran.hostile ? " + hostile boot passes (empty-lists, all-500)" : "";
43983
44437
  const relations = ran.relations ? " + relations" : "";
43984
44438
  const deep = ran.deep ? ` + deep chains (depth ${opts.deep})` : "";
@@ -44116,14 +44570,14 @@ var mobile_ux_lint_exports = {};
44116
44570
  __export(mobile_ux_lint_exports, {
44117
44571
  mobileUxIssues: () => mobileUxIssues
44118
44572
  });
44119
- import { existsSync as existsSync46, readFileSync as readFileSync50 } from "node:fs";
44120
- import { join as join43 } from "node:path";
44573
+ import { existsSync as existsSync47, readFileSync as readFileSync51 } from "node:fs";
44574
+ import { join as join44 } from "node:path";
44121
44575
  function mobileUxIssues(dir, strict) {
44122
- const file = join43(dir, "MOBILE-UX.md");
44123
- if (!existsSync46(file)) return [];
44576
+ const file = join44(dir, "MOBILE-UX.md");
44577
+ if (!existsSync47(file)) return [];
44124
44578
  let text5;
44125
44579
  try {
44126
- text5 = readFileSync50(file, "utf8");
44580
+ text5 = readFileSync51(file, "utf8");
44127
44581
  } catch {
44128
44582
  return [];
44129
44583
  }
@@ -45798,8 +46252,8 @@ __export(functions_exports, {
45798
46252
  resolveFunctionsTarget: () => resolveFunctionsTarget,
45799
46253
  runRemoteFunctionOperation: () => runRemoteFunctionOperation
45800
46254
  });
45801
- import { readFileSync as readFileSync51, existsSync as existsSync47 } from "node:fs";
45802
- import { join as join44, resolve as resolve41 } from "node:path";
46255
+ import { readFileSync as readFileSync52, existsSync as existsSync48 } from "node:fs";
46256
+ import { join as join45, resolve as resolve41 } from "node:path";
45803
46257
  function resolveFunctionsTarget(input) {
45804
46258
  const explicitUrl = input.functionsUrl?.trim();
45805
46259
  if (explicitUrl) return { mode: "remote", baseUrl: explicitUrl };
@@ -45818,9 +46272,9 @@ async function loadFunctionsApp(dir) {
45818
46272
  }
45819
46273
  const sources = {};
45820
46274
  for (const definition of config.functions.functions) {
45821
- const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join44(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync47);
46275
+ const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join45(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync48);
45822
46276
  if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
45823
- sources[definition.name] = readFileSync51(source, "utf8");
46277
+ sources[definition.name] = readFileSync52(source, "utf8");
45824
46278
  }
45825
46279
  const data = config.cms || config.data ? mergeDataResources(config) : void 0;
45826
46280
  const dataErrors = validateDataResources(data, loaded.name).filter((issue2) => issue2.severity === "error");
@@ -45972,14 +46426,14 @@ __export(studio_reel_exports, {
45972
46426
  utcDay: () => utcDay
45973
46427
  });
45974
46428
  import { createHash as createHash24 } from "node:crypto";
45975
- import { lstatSync as lstatSync7, readFileSync as readFileSync52 } from "node:fs";
45976
- import { basename as basename11, join as join45 } from "node:path";
46429
+ import { lstatSync as lstatSync7, readFileSync as readFileSync53 } from "node:fs";
46430
+ import { basename as basename11, join as join46 } from "node:path";
45977
46431
  function loadMemory2(dir) {
45978
- const m = readJson4(join45(dir, ".himi", "studio", "memory.json"));
45979
- if (!isObj(m) || m.version !== 1 || !isObj(m.shots) || !Array.isArray(m.timeline)) return null;
46432
+ const m = readJson4(join46(dir, ".himi", "studio", "memory.json"));
46433
+ if (!isObj2(m) || m.version !== 1 || !isObj2(m.shots) || !Array.isArray(m.timeline)) return null;
45980
46434
  const shots = [];
45981
46435
  for (const [id, s] of Object.entries(m.shots)) {
45982
- if (!isObj(s)) continue;
46436
+ if (!isObj2(s)) continue;
45983
46437
  const file = historyName(s.sha, s.ext);
45984
46438
  const firstSeen = str(s.firstSeen);
45985
46439
  if (!file || !firstSeen) continue;
@@ -45988,7 +46442,7 @@ function loadMemory2(dir) {
45988
46442
  }
45989
46443
  const timeline = [];
45990
46444
  for (const t of m.timeline) {
45991
- if (!isObj(t)) continue;
46445
+ if (!isObj2(t)) continue;
45992
46446
  const file = historyName(t.sha, t.ext);
45993
46447
  const ts2 = str(t.ts);
45994
46448
  const id = str(t.id);
@@ -46012,8 +46466,8 @@ function planReel(dir) {
46012
46466
  const memory = loadMemory2(dir);
46013
46467
  const { entries } = readProgress(dir);
46014
46468
  const summary = summarizeProgress(entries);
46015
- const manifest = readJson4(join45(dir, "himalaya.content.json"));
46016
- const tokens = readJson4(join45(dir, "tokens.json"));
46469
+ const manifest = readJson4(join46(dir, "himalaya.content.json"));
46470
+ const tokens = readJson4(join46(dir, "tokens.json"));
46017
46471
  const accentToken = tokens?.colors?.accentPrimary;
46018
46472
  const accentRaw = typeof accentToken === "string" ? accentToken : accentToken?.light;
46019
46473
  const accent = typeof accentRaw === "string" && ACCENT.test(accentRaw) ? accentRaw : DEFAULT_ACCENT;
@@ -46028,7 +46482,7 @@ function planReel(dir) {
46028
46482
  ts: e.ts,
46029
46483
  message: e.message,
46030
46484
  step: str(e.step),
46031
- files: (Array.isArray(e.images) ? e.images : []).map((i) => isObj(i) && typeof i.file === "string" ? basename11(i.file) : "").filter((f) => HISTORY_NAME.test(f))
46485
+ files: (Array.isArray(e.images) ? e.images : []).map((i) => isObj2(i) && typeof i.file === "string" ? basename11(i.file) : "").filter((f) => HISTORY_NAME.test(f))
46032
46486
  }));
46033
46487
  const times = [summary.startedAt, ...timeline.map((t) => t.ts), memory?.createdAt].filter((x) => !!x).sort();
46034
46488
  const startedAt = times[0] ?? null;
@@ -46055,10 +46509,10 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
46055
46509
  const verified = (f) => {
46056
46510
  if (!HISTORY_NAME.test(f)) return null;
46057
46511
  try {
46058
- const path2 = join45(dir, HISTORY_DIR, f);
46512
+ const path2 = join46(dir, HISTORY_DIR, f);
46059
46513
  const st = lstatSync7(path2);
46060
46514
  if (!st.isFile() || st.nlink !== 1) return null;
46061
- const bytes2 = readFileSync52(path2);
46515
+ const bytes2 = readFileSync53(path2);
46062
46516
  return createHash24("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
46063
46517
  } catch {
46064
46518
  return null;
@@ -46178,7 +46632,7 @@ function buildReel(dir, budget = REEL_BUDGET_BYTES) {
46178
46632
  const { plan, images } = inlineImages(dir, planReel(dir), budget);
46179
46633
  return { html: renderReel(plan, images), plan };
46180
46634
  }
46181
- var REEL_BUDGET_BYTES, MAX_FLIP_FRAMES, DEFAULT_ACCENT, ACCENT, HISTORY_NAME, MIME2, mimeOf, readJson4, isObj, str, historyName, cited;
46635
+ var REEL_BUDGET_BYTES, MAX_FLIP_FRAMES, DEFAULT_ACCENT, ACCENT, HISTORY_NAME, MIME2, mimeOf, readJson4, isObj2, str, historyName, cited;
46182
46636
  var init_studio_reel = __esm({
46183
46637
  "src/studio-reel.ts"() {
46184
46638
  init_progress();
@@ -46192,12 +46646,12 @@ var init_studio_reel = __esm({
46192
46646
  mimeOf = (file) => MIME2[file.slice(65)] ?? "image/png";
46193
46647
  readJson4 = (file) => {
46194
46648
  try {
46195
- return JSON.parse(readFileSync52(file, "utf8"));
46649
+ return JSON.parse(readFileSync53(file, "utf8"));
46196
46650
  } catch {
46197
46651
  return null;
46198
46652
  }
46199
46653
  };
46200
- isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
46654
+ isObj2 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
46201
46655
  str = (v) => typeof v === "string" && v ? v : null;
46202
46656
  historyName = (sha, ext) => typeof sha === "string" && typeof ext === "string" && HISTORY_NAME.test(`${sha}.${ext}`) ? `${sha}.${ext}` : null;
46203
46657
  cited = (p) => [
@@ -46223,9 +46677,9 @@ __export(native_decode_exports, {
46223
46677
  runNativeDecode: () => runNativeDecode
46224
46678
  });
46225
46679
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
46226
- import { existsSync as existsSync48, mkdtempSync as mkdtempSync2, readFileSync as readFileSync53, rmSync as rmSync11, writeFileSync as writeFileSync20 } from "node:fs";
46680
+ import { existsSync as existsSync49, mkdtempSync as mkdtempSync2, readFileSync as readFileSync54, rmSync as rmSync11, writeFileSync as writeFileSync20 } from "node:fs";
46227
46681
  import { tmpdir as tmpdir3 } from "node:os";
46228
- import { join as join46 } from "node:path";
46682
+ import { join as join47 } from "node:path";
46229
46683
  import { fileURLToPath as fileURLToPath20 } from "node:url";
46230
46684
  function nativeDecodePackageDir() {
46231
46685
  return fileURLToPath20(new URL("../../../core/runtime/ios/HimalayaCore/", import.meta.url));
@@ -46235,8 +46689,8 @@ function nativeDecodeAndroidHarnessDir() {
46235
46689
  }
46236
46690
  function nativeOracleShipped() {
46237
46691
  return {
46238
- ios: existsSync48(join46(nativeDecodePackageDir(), "Package.swift")),
46239
- android: existsSync48(join46(nativeDecodeAndroidHarnessDir(), "gradlew"))
46692
+ ios: existsSync49(join47(nativeDecodePackageDir(), "Package.swift")),
46693
+ android: existsSync49(join47(nativeDecodeAndroidHarnessDir(), "gradlew"))
46240
46694
  };
46241
46695
  }
46242
46696
  function detectNativeDecode(options = {}) {
@@ -46247,7 +46701,7 @@ function detectNativeDecode(options = {}) {
46247
46701
  return { platform: "ios", available: false, reason: `the iOS decoder oracle requires macOS (darwin); this host is ${hostPlatform}` };
46248
46702
  }
46249
46703
  if (!swiftAvailable) return { platform: "ios", available: false, reason: "swift is not on PATH" };
46250
- const packageAvailable = options.packageAvailable ?? existsSync48(join46(packageDir, "Package.swift"));
46704
+ const packageAvailable = options.packageAvailable ?? existsSync49(join47(packageDir, "Package.swift"));
46251
46705
  if (!packageAvailable) {
46252
46706
  return {
46253
46707
  platform: "ios",
@@ -46259,7 +46713,7 @@ function detectNativeDecode(options = {}) {
46259
46713
  }
46260
46714
  function localPropertiesSdk(harnessDir) {
46261
46715
  try {
46262
- const match = readFileSync53(join46(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
46716
+ const match = readFileSync54(join47(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
46263
46717
  return match?.[1]?.trim().replace(/\\([ :\\])/g, "$1") ?? null;
46264
46718
  } catch {
46265
46719
  return null;
@@ -46267,8 +46721,8 @@ function localPropertiesSdk(harnessDir) {
46267
46721
  }
46268
46722
  function detectAndroidNativeDecode(options = {}) {
46269
46723
  const harnessDir = options.harnessDir ?? nativeDecodeAndroidHarnessDir();
46270
- const gradlew = join46(harnessDir, "gradlew");
46271
- const gradlewAvailable = options.gradlewAvailable ?? existsSync48(gradlew);
46724
+ const gradlew = join47(harnessDir, "gradlew");
46725
+ const gradlewAvailable = options.gradlewAvailable ?? existsSync49(gradlew);
46272
46726
  if (!gradlewAvailable) {
46273
46727
  return {
46274
46728
  platform: "android",
@@ -46278,7 +46732,7 @@ function detectAndroidNativeDecode(options = {}) {
46278
46732
  }
46279
46733
  const androidHome = options.androidHome ?? process.env.ANDROID_HOME ?? "";
46280
46734
  const sdk = androidHome.trim() || (options.localPropertiesSdk !== void 0 ? options.localPropertiesSdk : localPropertiesSdk(harnessDir));
46281
- const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync48(sdk));
46735
+ const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync49(sdk));
46282
46736
  if (!sdkAvailable) {
46283
46737
  return {
46284
46738
  platform: "android",
@@ -46350,14 +46804,14 @@ function parseNativeDecodeOutput(stdout, expectedScreenIds, platform = "ios") {
46350
46804
  return verdicts;
46351
46805
  }
46352
46806
  function runNativeDecode(options) {
46353
- const descriptorDir = mkdtempSync2(join46(tmpdir3(), "himi-native-decode-"));
46807
+ const descriptorDir = mkdtempSync2(join47(tmpdir3(), "himi-native-decode-"));
46354
46808
  const startedAt = Date.now();
46355
46809
  try {
46356
46810
  for (const descriptor of options.descriptors) {
46357
46811
  if (typeof descriptor.descriptorJson !== "string") {
46358
46812
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
46359
46813
  }
46360
- writeFileSync20(join46(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
46814
+ writeFileSync20(join47(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
46361
46815
  }
46362
46816
  options.progress?.("building cached Swift oracle");
46363
46817
  execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
@@ -46369,7 +46823,7 @@ function runNativeDecode(options) {
46369
46823
  encoding: "utf8"
46370
46824
  }).trim();
46371
46825
  options.progress?.(`running real Swift decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
46372
- const stdout = execFileSync5(join46(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
46826
+ const stdout = execFileSync5(join47(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
46373
46827
  stdio: ["ignore", "pipe", "pipe"],
46374
46828
  encoding: "utf8"
46375
46829
  });
@@ -46387,15 +46841,15 @@ function runNativeDecode(options) {
46387
46841
  }
46388
46842
  }
46389
46843
  function runAndroidNativeDecode(options) {
46390
- const descriptorDir = mkdtempSync2(join46(tmpdir3(), "himi-native-decode-android-"));
46391
- const outputPath = join46(descriptorDir, "verdicts.ndjson");
46844
+ const descriptorDir = mkdtempSync2(join47(tmpdir3(), "himi-native-decode-android-"));
46845
+ const outputPath = join47(descriptorDir, "verdicts.ndjson");
46392
46846
  const startedAt = Date.now();
46393
46847
  try {
46394
46848
  for (const descriptor of options.descriptors) {
46395
46849
  if (typeof descriptor.descriptorJson !== "string") {
46396
46850
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
46397
46851
  }
46398
- writeFileSync20(join46(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
46852
+ writeFileSync20(join47(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
46399
46853
  }
46400
46854
  options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
46401
46855
  execFileSync5(options.gradlew, [
@@ -46410,7 +46864,7 @@ function runAndroidNativeDecode(options) {
46410
46864
  stdio: ["ignore", "ignore", "pipe"],
46411
46865
  encoding: "utf8"
46412
46866
  });
46413
- const stdout = readFileSync53(outputPath, "utf8");
46867
+ const stdout = readFileSync54(outputPath, "utf8");
46414
46868
  return {
46415
46869
  platform: "android",
46416
46870
  verdicts: parseNativeDecodeOutput(stdout, options.descriptors.map((descriptor) => descriptor.screenId), "android"),
@@ -46480,13 +46934,13 @@ __export(browser_authoring_exports, {
46480
46934
  });
46481
46935
  import { createServer as createServer3 } from "node:http";
46482
46936
  import { createHash as createHash25, randomBytes as randomBytes4 } from "node:crypto";
46483
- import { existsSync as existsSync49, readdirSync as readdirSync24, readFileSync as readFileSync54, realpathSync as realpathSync5, statSync as statSync12 } from "node:fs";
46484
- import { join as join47, posix, relative as relative8, sep as sep12 } from "node:path";
46937
+ import { existsSync as existsSync50, readdirSync as readdirSync24, readFileSync as readFileSync55, realpathSync as realpathSync5, statSync as statSync12 } from "node:fs";
46938
+ import { join as join48, posix, relative as relative8, sep as sep12 } from "node:path";
46485
46939
  function humanMs(ms) {
46486
46940
  return ms < 1e3 ? `${ms}ms` : `${Math.round(ms / 1e3)}s`;
46487
46941
  }
46488
46942
  function collectWorkerSources(tier5SrcDir, bundleName) {
46489
- const root = join47(tier5SrcDir, bundleName);
46943
+ const root = join48(tier5SrcDir, bundleName);
46490
46944
  const files = {};
46491
46945
  const visitedDirs = /* @__PURE__ */ new Set();
46492
46946
  const walk2 = (abs, rel) => {
@@ -46494,16 +46948,16 @@ function collectWorkerSources(tier5SrcDir, bundleName) {
46494
46948
  if (visitedDirs.has(real)) return;
46495
46949
  visitedDirs.add(real);
46496
46950
  for (const name of readdirSync24(abs).sort()) {
46497
- const childAbs = join47(abs, name);
46951
+ const childAbs = join48(abs, name);
46498
46952
  const childRel = posix.join(rel, name);
46499
46953
  if (statSync12(childAbs).isDirectory()) {
46500
46954
  walk2(childAbs, childRel);
46501
46955
  } else if (/\.(ts|tsx|js|mjs|json)$/.test(name)) {
46502
- files[childRel] = readFileSync54(childAbs, "utf8");
46956
+ files[childRel] = readFileSync55(childAbs, "utf8");
46503
46957
  }
46504
46958
  }
46505
46959
  };
46506
- if (!existsSync49(root)) return null;
46960
+ if (!existsSync50(root)) return null;
46507
46961
  walk2(root, `/tier5-src/${bundleName}`);
46508
46962
  const entry = [`/tier5-src/${bundleName}/index.ts`, `/tier5-src/${bundleName}/index.js`].find((p) => p in files);
46509
46963
  if (!entry) return null;
@@ -46520,20 +46974,20 @@ function collectRelativeSiblings(files, tier5SrcDir) {
46520
46974
  const spec = m[1];
46521
46975
  const virtual = posix.normalize(posix.join(dir, spec));
46522
46976
  if (!virtual.startsWith("/tier5-src/")) continue;
46523
- const onDisk = join47(tier5SrcDir, virtual.slice("/tier5-src/".length));
46977
+ const onDisk = join48(tier5SrcDir, virtual.slice("/tier5-src/".length));
46524
46978
  const candidates = [
46525
46979
  onDisk,
46526
46980
  `${onDisk}.ts`,
46527
46981
  `${onDisk}.js`,
46528
46982
  onDisk.replace(/\.js$/, ".ts"),
46529
- join47(onDisk, "index.ts"),
46530
- join47(onDisk, "index.js")
46983
+ join48(onDisk, "index.ts"),
46984
+ join48(onDisk, "index.js")
46531
46985
  ];
46532
46986
  for (const abs of candidates) {
46533
- if (!existsSync49(abs) || statSync12(abs).isDirectory()) continue;
46987
+ if (!existsSync50(abs) || statSync12(abs).isDirectory()) continue;
46534
46988
  const key2 = `/tier5-src/${relative8(tier5SrcDir, abs).split(sep12).join(posix.sep)}`;
46535
46989
  if (key2 in files) break;
46536
- files[key2] = readFileSync54(abs, "utf8");
46990
+ files[key2] = readFileSync55(abs, "utf8");
46537
46991
  queue.push(key2);
46538
46992
  break;
46539
46993
  }
@@ -46568,7 +47022,7 @@ function collectSdkModules(files, resolveSubpath) {
46568
47022
  function sdkSubpathReader(resolve48) {
46569
47023
  return (sub) => {
46570
47024
  try {
46571
- return readFileSync54(resolve48(`@wix/himalaya/${sub}`), "utf8");
47025
+ return readFileSync55(resolve48(`@wix/himalaya/${sub}`), "utf8");
46572
47026
  } catch {
46573
47027
  return null;
46574
47028
  }
@@ -46740,8 +47194,8 @@ __export(coverage_exports, {
46740
47194
  suppressedNativeKeys: () => suppressedNativeKeys,
46741
47195
  trendAndStore: () => trendAndStore
46742
47196
  });
46743
- import { existsSync as existsSync50, mkdirSync as mkdirSync24, readFileSync as readFileSync55, writeFileSync as writeFileSync21 } from "node:fs";
46744
- import { dirname as dirname26, join as join48 } from "node:path";
47197
+ import { existsSync as existsSync51, mkdirSync as mkdirSync24, readFileSync as readFileSync56, writeFileSync as writeFileSync21 } from "node:fs";
47198
+ import { dirname as dirname26, join as join49 } from "node:path";
46745
47199
  function suppressedNativeKeys(oracle) {
46746
47200
  return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
46747
47201
  }
@@ -46921,11 +47375,11 @@ function assembleLedger(r, options = {}) {
46921
47375
  return out;
46922
47376
  }
46923
47377
  function trendAndStore(dir, entries, suppressed) {
46924
- const p = join48(dir, LEDGER_FILE);
47378
+ const p = join49(dir, LEDGER_FILE);
46925
47379
  let previous = null;
46926
47380
  try {
46927
- if (existsSync50(p)) {
46928
- const parsed = JSON.parse(readFileSync55(p, "utf8"));
47381
+ if (existsSync51(p)) {
47382
+ const parsed = JSON.parse(readFileSync56(p, "utf8"));
46929
47383
  if (Array.isArray(parsed.keys)) previous = parsed.keys.filter((k) => typeof k === "string");
46930
47384
  }
46931
47385
  } catch {
@@ -46964,11 +47418,11 @@ function formatLedger(entries, trend) {
46964
47418
  return out.join("\n");
46965
47419
  }
46966
47420
  function diffAndStoreObservations(dir, current) {
46967
- const p = join48(dir, OBS_FILE);
47421
+ const p = join49(dir, OBS_FILE);
46968
47422
  let previous = null;
46969
47423
  try {
46970
- if (existsSync50(p)) {
46971
- const parsed = JSON.parse(readFileSync55(p, "utf8"));
47424
+ if (existsSync51(p)) {
47425
+ const parsed = JSON.parse(readFileSync56(p, "utf8"));
46972
47426
  if (parsed.screens && typeof parsed.screens === "object" && !Array.isArray(parsed.screens) && Object.values(parsed.screens).every(
46973
47427
  (v) => Array.isArray(v) && v.every((l) => typeof l === "string")
46974
47428
  )) {
@@ -47017,8 +47471,8 @@ var LEDGER_FILE, OBS_FILE;
47017
47471
  var init_coverage = __esm({
47018
47472
  "src/coverage.ts"() {
47019
47473
  init_native_decode();
47020
- LEDGER_FILE = join48(".himi", "test-ledger.json");
47021
- OBS_FILE = join48(".himi", "test-observations.json");
47474
+ LEDGER_FILE = join49(".himi", "test-ledger.json");
47475
+ OBS_FILE = join49(".himi", "test-observations.json");
47022
47476
  }
47023
47477
  });
47024
47478
 
@@ -47029,11 +47483,11 @@ __export(design_kits_exports, {
47029
47483
  kitsDir: () => kitsDir,
47030
47484
  listKits: () => listKits2
47031
47485
  });
47032
- import { existsSync as existsSync51, readFileSync as readFileSync56, readdirSync as readdirSync25 } from "node:fs";
47033
- import { join as join49, basename as basename12 } from "node:path";
47486
+ import { existsSync as existsSync52, readFileSync as readFileSync57, readdirSync as readdirSync25 } from "node:fs";
47487
+ import { join as join50, basename as basename12 } from "node:path";
47034
47488
  import { fileURLToPath as fileURLToPath21 } from "node:url";
47035
47489
  function kitsDir() {
47036
- return KIT_DIR_CANDIDATES.find((p) => existsSync51(p));
47490
+ return KIT_DIR_CANDIDATES.find((p) => existsSync52(p));
47037
47491
  }
47038
47492
  function listKits2() {
47039
47493
  const dir = kitsDir();
@@ -47057,7 +47511,7 @@ In a published @wix/himalaya-cli this is a packaging bug \u2014 the kits should
47057
47511
  if (!kits.includes(name)) {
47058
47512
  return { ok: false, kit: name, kits, problems: [`no design kit named "${name}". Available: ${kits.join(", ")}`] };
47059
47513
  }
47060
- const raw = readFileSync56(join49(dir, `${name}.json`), "utf8");
47514
+ const raw = readFileSync57(join50(dir, `${name}.json`), "utf8");
47061
47515
  let parsed;
47062
47516
  try {
47063
47517
  parsed = JSON.parse(raw);
@@ -47081,9 +47535,9 @@ var init_design_kits = __esm({
47081
47535
  "src/design-kits.ts"() {
47082
47536
  KIT_DIR_CANDIDATES = [
47083
47537
  // in-repo: tools/himi-cli/src/design-kits.ts → <repo>/stdlib/design-kits/
47084
- join49(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
47538
+ join50(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
47085
47539
  // published CLI: <pkg>/dist/cli.mjs → <pkg>/stdlib/design-kits/
47086
- join49(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
47540
+ join50(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
47087
47541
  ];
47088
47542
  }
47089
47543
  });
@@ -47097,11 +47551,11 @@ __export(assets_exports, {
47097
47551
  removeAssetPayload: () => removeAssetPayload,
47098
47552
  verifyReport: () => verifyReport
47099
47553
  });
47100
- import { readFileSync as readFileSync57 } from "node:fs";
47554
+ import { readFileSync as readFileSync58 } from "node:fs";
47101
47555
  import { basename as basename13 } from "node:path";
47102
47556
  function declaredAssets(dir) {
47103
47557
  return collectAssets(dir).map((a) => {
47104
- const bytes2 = readFileSync57(a.path);
47558
+ const bytes2 = readFileSync58(a.path);
47105
47559
  return {
47106
47560
  name: a.name,
47107
47561
  sha256: assetSha256(bytes2),
@@ -47178,7 +47632,7 @@ function verifyReport(dir, app, manifest) {
47178
47632
  };
47179
47633
  }
47180
47634
  function addAssetPayload(manifest, filePath, opts) {
47181
- const bytes2 = readFileSync57(filePath);
47635
+ const bytes2 = readFileSync58(filePath);
47182
47636
  const name = opts.name ?? basename13(filePath);
47183
47637
  const sha2565 = assetSha256(bytes2);
47184
47638
  const record2 = {
@@ -48493,7 +48947,14 @@ function indexHtml(asset, pathname, creds, handoverAttempted = false) {
48493
48947
  headers: {
48494
48948
  "content-type": asset.contentType || "text/html; charset=utf-8",
48495
48949
  "cache-control": creds || handoverAttempted ? "no-store" : "public, max-age=60, stale-while-revalidate=600",
48496
- "content-security-policy": previewCsp(nonce)
48950
+ "content-security-policy": previewCsp(nonce),
48951
+ // This document's URL can name a credential: `?pvt=` (still accepted from an embedder such
48952
+ // as the dashboard frame) or a `?h=` code before the inline script strips it. The browser
48953
+ // default sends the FULL URL as `Referer` on same-origin requests, so every `/v1/*` read,
48954
+ // the gateway's request log and the Tier-5 frame's `document.referrer` would carry it.
48955
+ // `strict-origin` sends the origin alone. Not `no-referrer`: that also turns a same-origin
48956
+ // POST's `Origin` header into `null`, and `/v1/telemetry/ingest` is one (#2386).
48957
+ "referrer-policy": "strict-origin"
48497
48958
  }
48498
48959
  });
48499
48960
  }
@@ -48527,7 +48988,7 @@ function previewResponse(pathname, assets, mount = "/preview", creds, handoverAt
48527
48988
  var T5_FRAME_CSP, PREVIEW_BASE_JS, PREVIEW_ENTRY_RECOVERY_JS, MOUNT_STRIP;
48528
48989
  var init_preview2 = __esm({
48529
48990
  "../../packages/serve/src/preview.ts"() {
48530
- 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'";
48991
+ 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'; sandbox allow-scripts";
48531
48992
  PREVIEW_BASE_JS = // Both serve mounts (/preview framed + /app unframed — RFC 0051 track D) serve the SAME
48532
48993
  // embedded artifact, so one shim derives whichever mount the browser is on. Non-greedy:
48533
48994
  // the EARLIEST mount segment wins, mirroring the SPA's own deployBase (an app literally
@@ -48780,21 +49241,21 @@ var init_preview_assets_generated = __esm({
48780
49241
  "../../packages/serve/src/preview-assets.generated.ts"() {
48781
49242
  init_preview_assets_format();
48782
49243
  PREVIEW_ASSET_MANIFEST = [
48783
- { key: "assets/index-CxGyJNjV.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1041209, sha256: "4eb08b810b7f8e5ae57dc8eef60fe084213c20e0ba7a9bc79b55c89b50cd1291" },
48784
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1041217, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
48785
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1045827, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
48786
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1534411, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
48787
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1625335, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
48788
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1716319, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
48789
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1807299, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
48790
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1898171, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
48791
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1989151, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
48792
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2097787, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
48793
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2206871, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
48794
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2315919, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
48795
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2423619, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
48796
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2511727, length: 4947, sha256: "8f89772181b62b7bda8f1620af71cca03ee0aae2471127d0633e07488edbf290" },
48797
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2516674, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
49244
+ { key: "assets/index-Dn3oPAne.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1044844, sha256: "7b781f264f2aac500b00ea4def7cb6f3c19e46dd4ebb3b797de5da8386056543" },
49245
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1044852, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
49246
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1049462, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
49247
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1538046, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
49248
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1628970, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
49249
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1719954, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
49250
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1810934, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
49251
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1901806, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
49252
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1992786, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
49253
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2101422, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
49254
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2210506, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
49255
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2319554, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
49256
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2427254, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
49257
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2515362, length: 4947, sha256: "25d01e5f772c7b782ca7c68f997765d3838295369054c0855ba63320d071ec22" },
49258
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2520309, length: 17209, sha256: "dffc9718e2faa63ae2cc919e0a842f585ed565aaaa7c53778e90379442c731b6" }
48798
49259
  ];
48799
49260
  }
48800
49261
  });
@@ -48810,9 +49271,9 @@ __export(run_exports, {
48810
49271
  runPackage: () => runPackage,
48811
49272
  withoutProdServe: () => withoutProdServe
48812
49273
  });
48813
- import { cpSync as cpSync2, existsSync as existsSync52, mkdirSync as mkdirSync25, mkdtempSync as mkdtempSync3, readFileSync as readFileSync58, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
49274
+ import { cpSync as cpSync2, existsSync as existsSync53, mkdirSync as mkdirSync25, mkdtempSync as mkdtempSync3, readFileSync as readFileSync59, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
48814
49275
  import { tmpdir as tmpdir4 } from "node:os";
48815
- import { dirname as dirname27, join as join50 } from "node:path";
49276
+ import { dirname as dirname27, join as join51 } from "node:path";
48816
49277
  import { fileURLToPath as fileURLToPath22 } from "node:url";
48817
49278
  function makePackageRunHandler(opts) {
48818
49279
  return makeRunHandler({
@@ -48831,9 +49292,9 @@ async function fetchBytes(url) {
48831
49292
  }
48832
49293
  async function resolvePackage(source, workDir) {
48833
49294
  const isUrl2 = /^https?:\/\//i.test(source);
48834
- if (!isUrl2 && existsSync52(source) && statSync13(source).isDirectory()) {
48835
- const appDirManifest = join50(source, "release-manifest.json");
48836
- if (existsSync52(appDirManifest)) {
49295
+ if (!isUrl2 && existsSync53(source) && statSync13(source).isDirectory()) {
49296
+ const appDirManifest = join51(source, "release-manifest.json");
49297
+ if (existsSync53(appDirManifest)) {
48837
49298
  const opened2 = openPackageDir(source);
48838
49299
  if (!opened2.validation.ok) {
48839
49300
  throw new Error(`package is not valid:
@@ -48842,9 +49303,9 @@ async function resolvePackage(source, workDir) {
48842
49303
  const app2 = opened2.header?.app ?? opened2.manifest?.app;
48843
49304
  if (!app2) throw new Error(`${source} has no app id (no himi-package.json and no manifest.app)`);
48844
49305
  if (!isSafeAppId(app2)) throw new Error(`${source} declares an unsafe app id ${JSON.stringify(app2)} \u2014 refusing to open it`);
48845
- const root2 = workDir ?? mkdtempSync3(join50(tmpdir4(), "himi-run-"));
48846
- const target = join50(root2, app2);
48847
- if (join50(source) !== target) {
49306
+ const root2 = workDir ?? mkdtempSync3(join51(tmpdir4(), "himi-run-"));
49307
+ const target = join51(root2, app2);
49308
+ if (join51(source) !== target) {
48848
49309
  mkdirSync25(root2, { recursive: true });
48849
49310
  rmSync12(target, { recursive: true, force: true });
48850
49311
  cpSync2(source, target, { recursive: true });
@@ -48861,8 +49322,8 @@ async function resolvePackage(source, workDir) {
48861
49322
  }
48862
49323
  throw new Error(`${source} is a directory but has no release-manifest.json \u2014 is it a package?`);
48863
49324
  }
48864
- const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync58(source);
48865
- const root = workDir ?? mkdtempSync3(join50(tmpdir4(), "himi-run-"));
49325
+ const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync59(source);
49326
+ const root = workDir ?? mkdtempSync3(join51(tmpdir4(), "himi-run-"));
48866
49327
  const { app, appDir: appDir2 } = unpackTo(bytes2, root);
48867
49328
  const opened = openPackageDir(appDir2);
48868
49329
  if (!opened.validation.ok) {
@@ -48885,14 +49346,14 @@ async function loadPreviewAssets() {
48885
49346
  const mod = await Promise.resolve().then(() => (init_preview_assets_generated(), preview_assets_generated_exports));
48886
49347
  const here = dirname27(fileURLToPath22(import.meta.url));
48887
49348
  const candidates = [
48888
- join50(here, "preview-assets.generated.bin"),
49349
+ join51(here, "preview-assets.generated.bin"),
48889
49350
  // published: copied next to the bundled CLI
48890
- join50(here, "../../../packages/serve/src/preview-assets.generated.bin")
49351
+ join51(here, "../../../packages/serve/src/preview-assets.generated.bin")
48891
49352
  // in-repo checkout
48892
49353
  ];
48893
49354
  for (const path2 of candidates) {
48894
- if (!existsSync52(path2)) continue;
48895
- const blob = readFileSync58(path2);
49355
+ if (!existsSync53(path2)) continue;
49356
+ const blob = readFileSync59(path2);
48896
49357
  return mod.assemblePreviewAssetsFromBlob(new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength));
48897
49358
  }
48898
49359
  return /* @__PURE__ */ new Map();
@@ -48987,9 +49448,9 @@ __export(run_watch_exports, {
48987
49448
  watchedContentDir: () => watchedContentDir,
48988
49449
  writeRunRecord: () => writeRunRecord
48989
49450
  });
48990
- import { closeSync as closeSync2, constants, existsSync as existsSync53, fstatSync, lstatSync as lstatSync8, statSync as statSync14, mkdirSync as mkdirSync26, openSync as openSync2, readFileSync as readFileSync59, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync13, writeFileSync as writeFileSync22, watch as fsWatch } from "node:fs";
49451
+ import { closeSync as closeSync2, constants, existsSync as existsSync54, fstatSync, lstatSync as lstatSync8, statSync as statSync14, mkdirSync as mkdirSync26, openSync as openSync2, readFileSync as readFileSync60, realpathSync as realpathSync6, renameSync as renameSync5, rmSync as rmSync13, writeFileSync as writeFileSync22, watch as fsWatch } from "node:fs";
48991
49452
  import { randomUUID as randomUUID3 } from "node:crypto";
48992
- import { extname as extname4, join as join51, resolve as resolve42, sep as sep13 } from "node:path";
49453
+ import { extname as extname4, join as join52, resolve as resolve42, sep as sep13 } from "node:path";
48993
49454
  function diffReleases(prev, next) {
48994
49455
  const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
48995
49456
  const changedBundles = new Set(bundles);
@@ -49028,8 +49489,8 @@ function watchContent(dir, onChange, opts = {}) {
49028
49489
  const watch2 = opts.watch ?? ((p, o, l) => fsWatch(p, o, (event, file) => l(event, file === null ? null : String(file))));
49029
49490
  let timer = null;
49030
49491
  const pending = /* @__PURE__ */ new Set();
49031
- const watcher = watch2(dir, { recursive: true }, (_event, file) => {
49032
- if (!file || ignoredChange(file, (rel) => isDir(join51(dir, rel)))) return;
49492
+ const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
49493
+ if (!file || ignoredChange(file, (rel) => isDir(join52(dir, rel)))) return;
49033
49494
  pending.add(file);
49034
49495
  if (timer) clearTimeout(timer);
49035
49496
  timer = setTimeout(() => {
@@ -49042,7 +49503,7 @@ function watchContent(dir, onChange, opts = {}) {
49042
49503
  return {
49043
49504
  close() {
49044
49505
  if (timer) clearTimeout(timer);
49045
- watcher.close();
49506
+ watcher2.close();
49046
49507
  }
49047
49508
  };
49048
49509
  }
@@ -49050,7 +49511,7 @@ function watchedContentDir(source) {
49050
49511
  if (!source || /^https?:\/\//i.test(source)) return null;
49051
49512
  const dir = resolve42(source);
49052
49513
  try {
49053
- return statSync14(dir).isDirectory() && existsSync53(join51(dir, "himalaya.content.json")) ? dir : null;
49514
+ return statSync14(dir).isDirectory() && existsSync54(join52(dir, "himalaya.content.json")) ? dir : null;
49054
49515
  } catch {
49055
49516
  return null;
49056
49517
  }
@@ -49107,7 +49568,7 @@ function serialize(task) {
49107
49568
  });
49108
49569
  }
49109
49570
  function himiDir(dir) {
49110
- const root = join51(dir, ".himi");
49571
+ const root = join52(dir, ".himi");
49111
49572
  try {
49112
49573
  return lstatSync8(root).isDirectory() ? root : null;
49113
49574
  } catch (e) {
@@ -49119,11 +49580,11 @@ function himiDir(dir) {
49119
49580
  function writeRunRecord(dir, record2) {
49120
49581
  const root = himiDir(dir);
49121
49582
  if (!root) return false;
49122
- const tmp = join51(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
49583
+ const tmp = join52(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
49123
49584
  try {
49124
49585
  writeFileSync22(tmp, `${JSON.stringify(record2, null, 2)}
49125
49586
  `, { flag: "wx" });
49126
- renameSync5(tmp, join51(root, "run.json"));
49587
+ renameSync5(tmp, join52(root, "run.json"));
49127
49588
  } catch (e) {
49128
49589
  rmSync13(tmp, { force: true });
49129
49590
  throw e;
@@ -49131,7 +49592,7 @@ function writeRunRecord(dir, record2) {
49131
49592
  return true;
49132
49593
  }
49133
49594
  function removeRunRecord(dir, owner) {
49134
- const root = join51(dir, ".himi");
49595
+ const root = join52(dir, ".himi");
49135
49596
  try {
49136
49597
  if (!lstatSync8(root).isDirectory()) return;
49137
49598
  } catch {
@@ -49146,15 +49607,16 @@ function removeRunRecord(dir, owner) {
49146
49607
  }
49147
49608
  if (typeof pid === "number" && pid !== owner) return;
49148
49609
  }
49149
- rmSync13(join51(root, "run.json"), { force: true });
49610
+ rmSync13(join52(root, "run.json"), { force: true });
49150
49611
  }
49151
49612
  function boardFile(dir, pathname) {
49152
49613
  let rel = null;
49153
- if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join51("studio", "index.html");
49154
- else if (pathname === "/__himi/studio/state.js") rel = join51("studio", "state.js");
49614
+ if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join52("studio", "index.html");
49615
+ else if (pathname === "/__himi/studio/state.js") rel = join52("studio", "state.js");
49616
+ else if (pathname === "/__himi/studio/timeline.js") rel = join52("studio", "timeline.js");
49155
49617
  else {
49156
49618
  const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
49157
- if (m) rel = join51("history", m[1]);
49619
+ if (m) rel = join52("history", m[1]);
49158
49620
  }
49159
49621
  if (!rel) return null;
49160
49622
  const bytes2 = readConfined(resolve42(dir, ".himi"), rel);
@@ -49170,7 +49632,7 @@ function readConfined(root, rel) {
49170
49632
  const parts = rel.split(sep13);
49171
49633
  let at = root;
49172
49634
  for (let i = 0; i < parts.length; i++) {
49173
- at = join51(at, parts[i]);
49635
+ at = join52(at, parts[i]);
49174
49636
  const st = lstatSync8(at);
49175
49637
  if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
49176
49638
  }
@@ -49179,7 +49641,7 @@ function readConfined(root, rel) {
49179
49641
  const fd = openSync2(at, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
49180
49642
  try {
49181
49643
  if (!fstatSync(fd).isFile()) return null;
49182
- return readFileSync59(fd);
49644
+ return readFileSync60(fd);
49183
49645
  } finally {
49184
49646
  closeSync2(fd);
49185
49647
  }
@@ -49230,7 +49692,7 @@ __export(run_browser_exports, {
49230
49692
  runInBrowser: () => runInBrowser
49231
49693
  });
49232
49694
  import { createServer as createServer5 } from "node:http";
49233
- import { existsSync as existsSync54, readFileSync as readFileSync60 } from "node:fs";
49695
+ import { existsSync as existsSync55, readFileSync as readFileSync61 } from "node:fs";
49234
49696
  import { spawnSync as spawnSync3 } from "node:child_process";
49235
49697
  import { dirname as dirname28, resolve as resolve43 } from "node:path";
49236
49698
  import { fileURLToPath as fileURLToPath23 } from "node:url";
@@ -49238,7 +49700,7 @@ function stagedDir() {
49238
49700
  return process.env.HIMI_BROWSER_PLANE_OUT ? resolve43(process.env.HIMI_BROWSER_PLANE_OUT) : resolve43(REPO, "packages", "serve", "dist", "browser-plane");
49239
49701
  }
49240
49702
  function missingStaged() {
49241
- return [...SERVED].filter((f) => !existsSync54(resolve43(stagedDir(), f))).sort();
49703
+ return [...SERVED].filter((f) => !existsSync55(resolve43(stagedDir(), f))).sort();
49242
49704
  }
49243
49705
  function ensureBrowserPlaneStaged() {
49244
49706
  if (missingStaged().length === 0) return { ok: true, detail: "already staged" };
@@ -49328,7 +49790,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
49328
49790
  }
49329
49791
  const name = path2 === "/" || path2 === "/index.html" ? "host.html" : path2.slice(1);
49330
49792
  if (SERVED.has(name)) {
49331
- if (!existsSync54(resolve43(stagedDir(), name))) {
49793
+ if (!existsSync55(resolve43(stagedDir(), name))) {
49332
49794
  const restaged = ensureBrowserPlaneStaged();
49333
49795
  if (!restaged.ok) {
49334
49796
  res.writeHead(503, { "content-type": "application/json" });
@@ -49338,7 +49800,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
49338
49800
  }
49339
49801
  let bytes2;
49340
49802
  try {
49341
- bytes2 = readFileSync60(resolve43(stagedDir(), name));
49803
+ bytes2 = readFileSync61(resolve43(stagedDir(), name));
49342
49804
  } catch (e) {
49343
49805
  res.writeHead(503, { "content-type": "application/json" });
49344
49806
  res.end(JSON.stringify({ error: "browser_plane_unstaged", file: name, detail: String(e) }));
@@ -49798,7 +50260,7 @@ __export(preflight_exports, {
49798
50260
  parseStoreConfig: () => parseStoreConfig,
49799
50261
  requireEnv: () => requireEnv
49800
50262
  });
49801
- import { existsSync as existsSync55, readFileSync as readFileSync61 } from "node:fs";
50263
+ import { existsSync as existsSync56, readFileSync as readFileSync62 } from "node:fs";
49802
50264
  import { isAbsolute as isAbsolute3, relative as relative9, resolve as resolve44 } from "node:path";
49803
50265
  function isInsideRepo(p) {
49804
50266
  const rel = relative9(REPO_ROOT3, resolve44(p));
@@ -49855,10 +50317,10 @@ function parseStoreConfig(text5) {
49855
50317
  }
49856
50318
  function loadStoreConfig(app) {
49857
50319
  const p = storeConfigPath(app);
49858
- if (!existsSync55(p)) {
50320
+ if (!existsSync56(p)) {
49859
50321
  throw new Error(`No store config at ${p}. Create apps/${app}/store/store.config.yaml first (see /setup-store-signing).`);
49860
50322
  }
49861
- return parseStoreConfig(readFileSync61(p, "utf8"));
50323
+ return parseStoreConfig(readFileSync62(p, "utf8"));
49862
50324
  }
49863
50325
  var init_preflight = __esm({
49864
50326
  "../store-release/preflight.ts"() {
@@ -49877,19 +50339,19 @@ __export(credential_profile_exports, {
49877
50339
  profilePath: () => profilePath,
49878
50340
  resolveStoreCredentials: () => resolveStoreCredentials
49879
50341
  });
49880
- import { existsSync as existsSync56, readFileSync as readFileSync62 } from "node:fs";
50342
+ import { existsSync as existsSync57, readFileSync as readFileSync63 } from "node:fs";
49881
50343
  import { homedir as homedir5 } from "node:os";
49882
- import { join as join52 } from "node:path";
50344
+ import { join as join53 } from "node:path";
49883
50345
  function credentialsDir(home = homedir5()) {
49884
- return join52(home, ".himalaya", "store-credentials");
50346
+ return join53(home, ".himalaya", "store-credentials");
49885
50347
  }
49886
50348
  function profilePath(name, home = homedir5()) {
49887
50349
  if (!PROFILE_NAME.test(name)) {
49888
50350
  throw new Error(
49889
- `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join52(credentialsDir(home), "<name>.json")}; it is never a path.`
50351
+ `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join53(credentialsDir(home), "<name>.json")}; it is never a path.`
49890
50352
  );
49891
50353
  }
49892
- return join52(credentialsDir(home), `${name}.json`);
50354
+ return join53(credentialsDir(home), `${name}.json`);
49893
50355
  }
49894
50356
  function parseProfile(text5, label2) {
49895
50357
  let doc;
@@ -49920,8 +50382,8 @@ function resolveStoreCredentials(opts) {
49920
50382
  const { app, cfg } = opts;
49921
50383
  const env = opts.env ?? process.env;
49922
50384
  const home = opts.home ?? homedir5();
49923
- const read = opts.readProfile ?? ((p) => readFileSync62(p, "utf8"));
49924
- const exists = opts.profileExists ?? existsSync56;
50385
+ const read = opts.readProfile ?? ((p) => readFileSync63(p, "utf8"));
50386
+ const exists = opts.profileExists ?? existsSync57;
49925
50387
  const ios = cfg.ios ?? {};
49926
50388
  const flagName = opts.profileFlag?.trim();
49927
50389
  const named = namedProfile(cfg, opts.profileFlag);
@@ -50001,9 +50463,9 @@ __export(preflight_app_exports, {
50001
50463
  preflightApp: () => preflightApp,
50002
50464
  profileAccount: () => profileAccount
50003
50465
  });
50004
- import { readFileSync as readFileSync63, existsSync as existsSync57, readdirSync as readdirSync26, statSync as statSync15 } from "node:fs";
50466
+ import { readFileSync as readFileSync64, existsSync as existsSync58, readdirSync as readdirSync26, statSync as statSync15 } from "node:fs";
50005
50467
  import { createHash as createHash26 } from "node:crypto";
50006
- import { join as join53 } from "node:path";
50468
+ import { join as join54 } from "node:path";
50007
50469
  function analyze(projectYml, storeConfig, icon, account) {
50008
50470
  const f = [];
50009
50471
  const has = (re, s) => re.test(s);
@@ -50067,13 +50529,13 @@ function profileAccount(app, storeConfig, opts = {}) {
50067
50529
  }
50068
50530
  }
50069
50531
  async function preflightApp(app, opts = {}) {
50070
- const projectYml = existsSync57(iosProjectYml(app)) ? readFileSync63(iosProjectYml(app), "utf8") : "";
50071
- const storeConfig = existsSync57(storeConfigPath(app)) ? readFileSync63(storeConfigPath(app), "utf8") : "";
50532
+ const projectYml = existsSync58(iosProjectYml(app)) ? readFileSync64(iosProjectYml(app), "utf8") : "";
50533
+ const storeConfig = existsSync58(storeConfigPath(app)) ? readFileSync64(storeConfigPath(app), "utf8") : "";
50072
50534
  if (!storeConfig) return [{ level: "error", message: `no apps/${app}/store/store.config.yaml \u2014 run /setup-store-signing first.` }];
50073
50535
  const findings = analyze(
50074
50536
  projectYml,
50075
50537
  storeConfig,
50076
- await appIconState(join53(appDir(app), "ios")),
50538
+ await appIconState(join54(appDir(app), "ios")),
50077
50539
  profileAccount(app, storeConfig, opts)
50078
50540
  );
50079
50541
  findings.push(...analyzeListing(metadataFilesPresent(app), listScreenshots(app)));
@@ -50102,17 +50564,17 @@ function analyzeSubmissionRecord(appLevel, review, opts) {
50102
50564
  return f;
50103
50565
  }
50104
50566
  function appLevelMetadataFiles(app, locale = "en-US") {
50105
- const root = join53(storeDir(app), "metadata");
50567
+ const root = join54(storeDir(app), "metadata");
50106
50568
  const out = /* @__PURE__ */ new Set();
50107
- if (existsSync57(root)) {
50569
+ if (existsSync58(root)) {
50108
50570
  for (const n of readdirSync26(root)) if (n.endsWith(".txt")) out.add(n);
50109
50571
  }
50110
- if (existsSync57(join53(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
50572
+ if (existsSync58(join54(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
50111
50573
  return out;
50112
50574
  }
50113
50575
  function reviewInfoFiles(app) {
50114
- const dir = join53(storeDir(app), "review_information");
50115
- if (!existsSync57(dir)) return /* @__PURE__ */ new Set();
50576
+ const dir = join54(storeDir(app), "review_information");
50577
+ if (!existsSync58(dir)) return /* @__PURE__ */ new Set();
50116
50578
  return new Set(readdirSync26(dir).filter((n) => n.endsWith(".txt")));
50117
50579
  }
50118
50580
  function isValidAppStoreSize(w, h) {
@@ -50222,12 +50684,12 @@ function analyzePlayListing(assets) {
50222
50684
  return f;
50223
50685
  }
50224
50686
  function playListingAssets(app, locale = "en-US") {
50225
- const dir = join53(storeDir(app), "play", locale, "images");
50687
+ const dir = join54(storeDir(app), "play", locale, "images");
50226
50688
  const read = (names) => {
50227
50689
  for (const name of names) {
50228
- const p = join53(dir, name);
50690
+ const p = join54(dir, name);
50229
50691
  try {
50230
- if (statSync15(p).isFile()) return { name, bytes: readFileSync63(p) };
50692
+ if (statSync15(p).isFile()) return { name, bytes: readFileSync64(p) };
50231
50693
  } catch {
50232
50694
  }
50233
50695
  }
@@ -50258,26 +50720,26 @@ function analyzeListing(metaFiles, shots) {
50258
50720
  return f;
50259
50721
  }
50260
50722
  function metadataFilesPresent(app, locale = "en-US") {
50261
- const dir = join53(storeDir(app), "metadata", locale);
50262
- if (!existsSync57(dir)) return /* @__PURE__ */ new Set();
50723
+ const dir = join54(storeDir(app), "metadata", locale);
50724
+ if (!existsSync58(dir)) return /* @__PURE__ */ new Set();
50263
50725
  return new Set(readdirSync26(dir).filter((n) => n.endsWith(".txt")));
50264
50726
  }
50265
50727
  function listScreenshots(app, locale = "en-US") {
50266
50728
  const out = [];
50267
- const flat = join53(storeDir(app), "screenshots", locale);
50268
- if (existsSync57(flat)) {
50729
+ const flat = join54(storeDir(app), "screenshots", locale);
50730
+ if (existsSync58(flat)) {
50269
50731
  for (const png of readdirSync26(flat).filter((n) => n.endsWith(".png"))) {
50270
- const sz = pngSize(readFileSync63(join53(flat, png)));
50732
+ const sz = pngSize(readFileSync64(join54(flat, png)));
50271
50733
  if (sz) out.push({ name: png, w: sz.w, h: sz.h });
50272
50734
  }
50273
50735
  }
50274
- const legacy = join53(storeDir(app), "metadata", locale, "screenshots");
50275
- if (existsSync57(legacy)) {
50736
+ const legacy = join54(storeDir(app), "metadata", locale, "screenshots");
50737
+ if (existsSync58(legacy)) {
50276
50738
  for (const deviceDir of readdirSync26(legacy)) {
50277
- const d = join53(legacy, deviceDir);
50739
+ const d = join54(legacy, deviceDir);
50278
50740
  if (!statSync15(d).isDirectory()) continue;
50279
50741
  for (const png of readdirSync26(d).filter((n) => n.endsWith(".png"))) {
50280
- const sz = pngSize(readFileSync63(join53(d, png)));
50742
+ const sz = pngSize(readFileSync64(join54(d, png)));
50281
50743
  if (sz) out.push({ name: `${deviceDir}/${png}`, w: sz.w, h: sz.h });
50282
50744
  }
50283
50745
  }
@@ -50285,13 +50747,13 @@ function listScreenshots(app, locale = "en-US") {
50285
50747
  return out;
50286
50748
  }
50287
50749
  function placeholderHashes2() {
50288
- const templateIos = join53(appDir("_template"), "ios");
50289
- if (!existsSync57(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
50750
+ const templateIos = join54(appDir("_template"), "ios");
50751
+ if (!existsSync58(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
50290
50752
  for (const target of readdirSync26(templateIos)) {
50291
- const p = join53(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
50753
+ const p = join54(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
50292
50754
  try {
50293
50755
  if (!statSync15(p).isFile()) continue;
50294
- return [LEGACY_PLACEHOLDER_SHA2562, createHash26("sha256").update(readFileSync63(p)).digest("hex")];
50756
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash26("sha256").update(readFileSync64(p)).digest("hex")];
50295
50757
  } catch {
50296
50758
  }
50297
50759
  }
@@ -50322,12 +50784,12 @@ async function pixelIssues(bytes2) {
50322
50784
  }
50323
50785
  }
50324
50786
  async function appIconState(iosDir) {
50325
- if (!existsSync57(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
50787
+ if (!existsSync58(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
50326
50788
  for (const target of readdirSync26(iosDir)) {
50327
- const p = join53(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
50789
+ const p = join54(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
50328
50790
  try {
50329
50791
  if (!statSync15(p).isFile()) continue;
50330
- const bytes2 = readFileSync63(p);
50792
+ const bytes2 = readFileSync64(p);
50331
50793
  const hash = createHash26("sha256").update(bytes2).digest("hex");
50332
50794
  const pixels = await pixelIssues(bytes2);
50333
50795
  return {
@@ -50399,7 +50861,7 @@ __export(version_exports, {
50399
50861
  setAndroidVersion: () => setAndroidVersion,
50400
50862
  setIosVersion: () => setIosVersion
50401
50863
  });
50402
- import { readFileSync as readFileSync64, writeFileSync as writeFileSync23 } from "node:fs";
50864
+ import { readFileSync as readFileSync65, writeFileSync as writeFileSync23 } from "node:fs";
50403
50865
  function readIosVersion(projectYml) {
50404
50866
  const marketing = matchScalar(projectYml, "CFBundleShortVersionString");
50405
50867
  const build2 = matchScalar(projectYml, "CFBundleVersion");
@@ -50457,8 +50919,8 @@ function setAndroidVersion(gradle, v) {
50457
50919
  function bumpVersion(app, opts) {
50458
50920
  const iosPath = iosProjectYml(app);
50459
50921
  const gradlePath = androidGradle(app);
50460
- const iosYml = readFileSync64(iosPath, "utf8");
50461
- const gradle = readFileSync64(gradlePath, "utf8");
50922
+ const iosYml = readFileSync65(iosPath, "utf8");
50923
+ const gradle = readFileSync65(gradlePath, "utf8");
50462
50924
  const current = readIosVersion(iosYml);
50463
50925
  const next = {
50464
50926
  marketing: opts.marketing ?? current.marketing,
@@ -50475,7 +50937,7 @@ var init_version = __esm({
50475
50937
  });
50476
50938
 
50477
50939
  // ../store-release/register-bundle.ts
50478
- import { readFileSync as readFileSync65 } from "node:fs";
50940
+ import { readFileSync as readFileSync66 } from "node:fs";
50479
50941
  import { createPrivateKey as createPrivateKey2, sign } from "node:crypto";
50480
50942
  function b64url2(b) {
50481
50943
  return Buffer.from(b).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -50493,7 +50955,7 @@ function bundleIdBody(identifier, name, platform = "IOS") {
50493
50955
  async function registerBundleId(opts) {
50494
50956
  const env = requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
50495
50957
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
50496
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync65(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
50958
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync66(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
50497
50959
  const res = await fetch(`${ASC_API}/v1/bundleIds`, {
50498
50960
  method: "POST",
50499
50961
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
@@ -50547,7 +51009,7 @@ __export(asc_status_exports, {
50547
51009
  ascStatus: () => ascStatus,
50548
51010
  buildVerdict: () => buildVerdict
50549
51011
  });
50550
- import { readFileSync as readFileSync66 } from "node:fs";
51012
+ import { readFileSync as readFileSync67 } from "node:fs";
50551
51013
  function buildVerdict(builds) {
50552
51014
  if (!builds.length) {
50553
51015
  return "NO builds registered \u2014 if you uploaded recently and it's been >15 min, the binary was REJECTED IN PROCESSING (reason is email-only; run validate-binary.ts / scan-purpose-strings.ts to reproduce it).";
@@ -50561,7 +51023,7 @@ function buildVerdict(builds) {
50561
51023
  function client(creds) {
50562
51024
  const env = creds ?? requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
50563
51025
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
50564
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync66(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
51026
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync67(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
50565
51027
  return async (path2) => {
50566
51028
  const r = await fetch(`${ASC}${path2}`, { headers: { Authorization: `Bearer ${jwt}` } });
50567
51029
  return { status: r.status, json: await r.json().catch(() => ({})) };
@@ -50721,7 +51183,7 @@ __export(secrets_exports, {
50721
51183
  runRemoteSecretsCommand: () => runRemoteSecretsCommand,
50722
51184
  runSecretsCommand: () => runSecretsCommand
50723
51185
  });
50724
- import { readFileSync as readFileSync67 } from "node:fs";
51186
+ import { readFileSync as readFileSync68 } from "node:fs";
50725
51187
  function environment(value) {
50726
51188
  if (value === "preview" || value === "production") return value;
50727
51189
  throw new Error("--environment must be preview or production");
@@ -50752,7 +51214,7 @@ async function runSecretsCommand(operation, flags, io, options = {}) {
50752
51214
  io.log(JSON.stringify(await vault.list({ app, environment: env })));
50753
51215
  return 0;
50754
51216
  }
50755
- const value = options.readValue ? options.readValue() : readFileSync67(0, "utf8").replace(/\r?\n$/, "");
51217
+ const value = options.readValue ? options.readValue() : readFileSync68(0, "utf8").replace(/\r?\n$/, "");
50756
51218
  if (operation === "set") await vault.set({ app, environment: env, name, value });
50757
51219
  else if (operation === "rotate") await vault.rotate({ app, environment: env, name, value });
50758
51220
  else throw new Error(`unknown secrets operation ${operation}`);
@@ -50781,7 +51243,7 @@ async function runRemoteSecretsCommand(operation, flags, io, options) {
50781
51243
  }
50782
51244
  let body;
50783
51245
  if (operation !== "list") {
50784
- const value = options.readValue ? options.readValue() : readFileSync67(0, "utf8").replace(/\r?\n$/, "");
51246
+ const value = options.readValue ? options.readValue() : readFileSync68(0, "utf8").replace(/\r?\n$/, "");
50785
51247
  body = JSON.stringify({ value });
50786
51248
  }
50787
51249
  const response = await (options.fetchImpl ?? fetch)(url, { method: operation === "list" ? "GET" : operation === "set" ? "PUT" : "POST", headers: headers2, body });
@@ -50817,15 +51279,15 @@ __export(notify_exports, {
50817
51279
  runNotify: () => runNotify,
50818
51280
  saveAdminSession: () => saveAdminSession
50819
51281
  });
50820
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync27, readFileSync as readFileSync68, writeFileSync as writeFileSync24 } from "node:fs";
51282
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync27, readFileSync as readFileSync69, writeFileSync as writeFileSync24 } from "node:fs";
50821
51283
  import { homedir as homedir6 } from "node:os";
50822
- import { dirname as dirname30, join as join54 } from "node:path";
51284
+ import { dirname as dirname30, join as join55 } from "node:path";
50823
51285
  function defaultSessionsPath() {
50824
- return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join54(homedir6(), ".himalaya", "admin-sessions.json");
51286
+ return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join55(homedir6(), ".himalaya", "admin-sessions.json");
50825
51287
  }
50826
51288
  function loadSessionsFile(path2 = defaultSessionsPath()) {
50827
51289
  try {
50828
- return JSON.parse(readFileSync68(path2, "utf8"));
51290
+ return JSON.parse(readFileSync69(path2, "utf8"));
50829
51291
  } catch {
50830
51292
  return {};
50831
51293
  }
@@ -54383,7 +54845,7 @@ var init_brand_font = __esm({
54383
54845
 
54384
54846
  // src/site-analyze/write-artifacts.ts
54385
54847
  import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync25 } from "node:fs";
54386
- import { join as join55 } from "node:path";
54848
+ import { join as join56 } from "node:path";
54387
54849
  function attachVeloSources(book, sources, put) {
54388
54850
  const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
54389
54851
  for (const f of book.velo.files) {
@@ -54538,12 +55000,12 @@ async function downloadMedia(book, fetchImpl, put) {
54538
55000
  }
54539
55001
  async function writeBusinessBook(result, opts) {
54540
55002
  const out = opts.outDir;
54541
- mkdirSync28(join55(out, "evidence/pages"), { recursive: true });
54542
- mkdirSync28(join55(out, "media/catalog"), { recursive: true });
55003
+ mkdirSync28(join56(out, "evidence/pages"), { recursive: true });
55004
+ mkdirSync28(join56(out, "media/catalog"), { recursive: true });
54543
55005
  const files = [];
54544
55006
  const put = (rel, body) => {
54545
- const p = join55(out, rel);
54546
- mkdirSync28(join55(p, ".."), { recursive: true });
55007
+ const p = join56(out, rel);
55008
+ mkdirSync28(join56(p, ".."), { recursive: true });
54547
55009
  writeFileSync25(p, body);
54548
55010
  files.push(rel);
54549
55011
  };
@@ -54637,8 +55099,8 @@ var init_customer_build = __esm({
54637
55099
  });
54638
55100
 
54639
55101
  // src/site-analyze/apply.ts
54640
- import { existsSync as existsSync58, readFileSync as readFileSync69, writeFileSync as writeFileSync26, copyFileSync as copyFileSync3, mkdirSync as mkdirSync29, realpathSync as realpathSync7, statSync as statSync16 } from "node:fs";
54641
- import { dirname as dirname31, isAbsolute as isAbsolute4, join as join56, resolve as resolve45, sep as sep14 } from "node:path";
55102
+ import { existsSync as existsSync59, readFileSync as readFileSync70, writeFileSync as writeFileSync26, copyFileSync as copyFileSync3, mkdirSync as mkdirSync29, realpathSync as realpathSync7, statSync as statSync16 } from "node:fs";
55103
+ import { dirname as dirname31, isAbsolute as isAbsolute4, join as join57, resolve as resolve45, sep as sep14 } from "node:path";
54642
55104
  import { spawnSync as spawnSync4 } from "node:child_process";
54643
55105
  function unpairedRedirectNote(overlay) {
54644
55106
  if (!overlay.wix?.clientId || overlay.wix.redirectUri) return void 0;
@@ -54653,7 +55115,7 @@ function committedShellProvisioning(path2) {
54653
55115
  function findRepoRoot(cwd) {
54654
55116
  let dir = cwd;
54655
55117
  for (let i = 0; i < 8; i++) {
54656
- if (SHELL_NAMES2.some((name) => existsSync58(join56(dir, shellAppDir(name), "package.json")))) return dir;
55118
+ if (SHELL_NAMES2.some((name) => existsSync59(join57(dir, shellAppDir(name), "package.json")))) return dir;
54657
55119
  const next = dirname31(dir);
54658
55120
  if (next === dir) break;
54659
55121
  dir = next;
@@ -54721,11 +55183,11 @@ function mergeCustomerOwned(committed, overlay) {
54721
55183
  }
54722
55184
  function buildMergedContract(options) {
54723
55185
  const { appDir: appDir2, packageDir, overlay } = options;
54724
- const committedPath = join56(appDir2, "provisioning.json");
54725
- if (!existsSync58(committedPath)) {
55186
+ const committedPath = join57(appDir2, "provisioning.json");
55187
+ if (!existsSync59(committedPath)) {
54726
55188
  throw new Error(`shell has no committed provisioning.json at ${committedPath} to build a contract from`);
54727
55189
  }
54728
- const committed = JSON.parse(readFileSync69(committedPath, "utf8"));
55190
+ const committed = JSON.parse(readFileSync70(committedPath, "utf8"));
54729
55191
  const merged = mergeCustomerOwned(committed, overlay);
54730
55192
  const overlayFontFamily = (overlay.branding ?? {}).fontFamily;
54731
55193
  const branding = merged.branding ?? {};
@@ -54737,8 +55199,8 @@ function buildMergedContract(options) {
54737
55199
  };
54738
55200
  const stage = (fromRel, toRel) => {
54739
55201
  const from = contained(packageDir, fromRel);
54740
- const to = contained(join56(appDir2, STAGED), toRel.slice(STAGED.length + 1));
54741
- if (!from || !to || !existsSync58(from)) return void 0;
55202
+ const to = contained(join57(appDir2, STAGED), toRel.slice(STAGED.length + 1));
55203
+ if (!from || !to || !existsSync59(from)) return void 0;
54742
55204
  let real;
54743
55205
  let realRoot;
54744
55206
  try {
@@ -54760,11 +55222,11 @@ function buildMergedContract(options) {
54760
55222
  };
54761
55223
  const icon = stage("media/icon.png", `${STAGED}/icon.png`);
54762
55224
  if (icon) branding.appIconPath = icon;
54763
- const facesPath = join56(packageDir, "media/fonts/faces.json");
54764
- if (existsSync58(facesPath)) {
55225
+ const facesPath = join57(packageDir, "media/fonts/faces.json");
55226
+ if (existsSync59(facesPath)) {
54765
55227
  let faces = [];
54766
55228
  try {
54767
- const parsed = JSON.parse(readFileSync69(facesPath, "utf8"));
55229
+ const parsed = JSON.parse(readFileSync70(facesPath, "utf8"));
54768
55230
  faces = Array.isArray(parsed) ? parsed : [];
54769
55231
  } catch {
54770
55232
  faces = [];
@@ -54785,9 +55247,9 @@ function buildMergedContract(options) {
54785
55247
  merged.branding = branding;
54786
55248
  const body = `${JSON.stringify(merged, null, 2)}
54787
55249
  `;
54788
- const path2 = join56(packageDir, "provisioning.contract.json");
55250
+ const path2 = join57(packageDir, "provisioning.contract.json");
54789
55251
  writeFileSync26(path2, body);
54790
- const localPath = join56(appDir2, LOCAL_CONTRACT_REL);
55252
+ const localPath = join57(appDir2, LOCAL_CONTRACT_REL);
54791
55253
  mkdirSync29(dirname31(localPath), { recursive: true });
54792
55254
  writeFileSync26(localPath, body);
54793
55255
  staged.push(localPath);
@@ -54797,8 +55259,8 @@ async function applyOverlays(input) {
54797
55259
  const cwd = resolve45(input.cwd);
54798
55260
  const shell = input.shell ?? DEFAULT_BRANDABLE_SHELL;
54799
55261
  assertKnownShell(shell);
54800
- const overlay = JSON.parse(readFileSync69(input.overlayPath, "utf8"));
54801
- const tokens = JSON.parse(readFileSync69(input.tokensPath, "utf8"));
55262
+ const overlay = JSON.parse(readFileSync70(input.overlayPath, "utf8"));
55263
+ const tokens = JSON.parse(readFileSync70(input.tokensPath, "utf8"));
54802
55264
  const wrote = [];
54803
55265
  const notes = [];
54804
55266
  const repoRoot2 = findRepoRoot(cwd);
@@ -54808,7 +55270,7 @@ async function applyOverlays(input) {
54808
55270
  `shell "${shell}" cannot bake a customer brand \u2014 it ships no prepare:customer script`
54809
55271
  );
54810
55272
  }
54811
- const appDir2 = join56(repoRoot2, shellAppDir(shell));
55273
+ const appDir2 = join57(repoRoot2, shellAppDir(shell));
54812
55274
  const packageDir = dirname31(resolve45(input.overlayPath));
54813
55275
  const { path: contract, staged } = buildMergedContract({ appDir: appDir2, packageDir, overlay });
54814
55276
  wrote.push(...staged);
@@ -54830,10 +55292,10 @@ async function applyOverlays(input) {
54830
55292
  note: `prepare:customer --contract with a merged contract (committed provisioning.json untouched)${unpairedInRepo ? ` \xB7 ${unpairedInRepo}` : ""}`
54831
55293
  };
54832
55294
  }
54833
- const pkgProv = join56(cwd, "provisioning.json");
55295
+ const pkgProv = join57(cwd, "provisioning.json");
54834
55296
  assertNotCommitted(pkgProv, input.forbiddenProvisioning);
54835
- if (existsSync58(pkgProv)) {
54836
- const cur = JSON.parse(readFileSync69(pkgProv, "utf8"));
55297
+ if (existsSync59(pkgProv)) {
55298
+ const cur = JSON.parse(readFileSync70(pkgProv, "utf8"));
54837
55299
  const merged = deepMerge4(cur, overlay);
54838
55300
  writeFileSync26(pkgProv, JSON.stringify(merged, null, 2) + "\n");
54839
55301
  wrote.push(pkgProv);
@@ -54841,9 +55303,9 @@ async function applyOverlays(input) {
54841
55303
  writeFileSync26(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
54842
55304
  wrote.push(pkgProv);
54843
55305
  }
54844
- const pkgTokens = join56(cwd, "tokens.json");
54845
- if (existsSync58(pkgTokens) && (tokens.colors || tokens.typography)) {
54846
- const cur = JSON.parse(readFileSync69(pkgTokens, "utf8"));
55306
+ const pkgTokens = join57(cwd, "tokens.json");
55307
+ if (existsSync59(pkgTokens) && (tokens.colors || tokens.typography)) {
55308
+ const cur = JSON.parse(readFileSync70(pkgTokens, "utf8"));
54847
55309
  const merged = deepMerge4(cur, tokens);
54848
55310
  const mergedColors = merged.colors;
54849
55311
  if (mergedColors && typeof mergedColors === "object" && !Array.isArray(mergedColors)) {
@@ -54862,9 +55324,9 @@ async function applyOverlays(input) {
54862
55324
  wrote.push(pkgTokens);
54863
55325
  }
54864
55326
  const homeAction = homeRefreshActionId(shell);
54865
- const homeSrc = join56(cwd, "dev/index.ts");
54866
- if (homeAction && existsSync58(homeSrc)) {
54867
- const prev = readFileSync69(homeSrc, "utf8");
55327
+ const homeSrc = join57(cwd, "dev/index.ts");
55328
+ if (homeAction && existsSync59(homeSrc)) {
55329
+ const prev = readFileSync70(homeSrc, "utf8");
54868
55330
  const patched = patchBrandParams(prev, overlay, homeAction);
54869
55331
  let next = patched.src;
54870
55332
  if (!patched.foreign && overlay.installedVerticals?.length) {
@@ -54887,13 +55349,13 @@ async function applyOverlays(input) {
54887
55349
  notes.push(`${patched.unparsed} onAppear params block(s) in ${homeSrc} could not be read \u2014 any brand values inside them were not applied`);
54888
55350
  }
54889
55351
  }
54890
- if (!input.iconPath || !existsSync58(input.iconPath)) {
55352
+ if (!input.iconPath || !existsSync59(input.iconPath)) {
54891
55353
  const book = input.iconPath ? dirname31(dirname31(resolve45(input.iconPath))) : void 0;
54892
55354
  notes.push(
54893
55355
  book ? `no icon installed \u2014 ${siteLogoAbsence(book).hint}` : "no icon installed and no business book to say why \u2014 `himi icon generate` draws one from the accent, or `himi icon set <file.png>` installs the customer's own mark"
54894
55356
  );
54895
55357
  }
54896
- if (input.iconPath && existsSync58(input.iconPath)) {
55358
+ if (input.iconPath && existsSync59(input.iconPath)) {
54897
55359
  try {
54898
55360
  const installed = await installIcon(cwd, input.iconPath, { derivePlay: true });
54899
55361
  wrote.push(...installed.wrote);
@@ -59048,7 +59510,7 @@ __export(cli_exports, {
59048
59510
  runSiteAnalyze: () => runSiteAnalyze,
59049
59511
  runSiteFunctionCli: () => runSiteFunctionCli
59050
59512
  });
59051
- import { readFileSync as readFileSync70 } from "node:fs";
59513
+ import { readFileSync as readFileSync71 } from "node:fs";
59052
59514
  import { resolve as resolve46 } from "node:path";
59053
59515
  function assetFetchFor(_opts) {
59054
59516
  return pinnedFetch2;
@@ -59135,7 +59597,7 @@ async function runSiteFunctionCli(opts) {
59135
59597
  let source;
59136
59598
  if (opts.sourcePath) {
59137
59599
  const p = resolve46(opts.cwd ?? process.cwd(), opts.sourcePath);
59138
- source = readFileSync70(p, "utf8");
59600
+ source = readFileSync71(p, "utf8");
59139
59601
  }
59140
59602
  let body;
59141
59603
  if (opts.bodyRaw != null) {
@@ -59392,7 +59854,7 @@ __export(site_create_exports, {
59392
59854
  runSiteList: () => runSiteList,
59393
59855
  writeProvisioningBinding: () => writeProvisioningBinding
59394
59856
  });
59395
- import { existsSync as existsSync59, readFileSync as readFileSync71, writeFileSync as writeFileSync27 } from "node:fs";
59857
+ import { existsSync as existsSync60, readFileSync as readFileSync72, writeFileSync as writeFileSync27 } from "node:fs";
59396
59858
  import { resolve as resolvePath2 } from "node:path";
59397
59859
  function oauthClientName(projectName) {
59398
59860
  return `${projectName} client`;
@@ -59541,7 +60003,7 @@ async function runSiteCreate(opts) {
59541
60003
  if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
59542
60004
  return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
59543
60005
  }
59544
- function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync59, readFileSync: readFileSync71, writeFileSync: writeFileSync27 }) {
60006
+ function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync60, readFileSync: readFileSync72, writeFileSync: writeFileSync27 }) {
59545
60007
  const msid = binding2.siteId;
59546
60008
  const path2 = resolvePath2(target);
59547
60009
  const committed = committedShellProvisioning(path2);
@@ -60214,11 +60676,11 @@ var init_context = __esm({
60214
60676
  });
60215
60677
 
60216
60678
  // src/cli.ts
60217
- import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync72, mkdirSync as mkdirSync30, writeFileSync as writeFileSync28, existsSync as existsSync60, rmSync as rmSync14, readdirSync as readdirSync27, statSync as statSync17, lstatSync as lstatSync9, accessSync, constants as constants2, renameSync as renameSync6, cpSync as cpSync3 } from "node:fs";
60679
+ import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync73, mkdirSync as mkdirSync30, writeFileSync as writeFileSync28, existsSync as existsSync61, rmSync as rmSync14, readdirSync as readdirSync27, statSync as statSync17, lstatSync as lstatSync9, accessSync, constants as constants2, renameSync as renameSync6, cpSync as cpSync3 } from "node:fs";
60218
60680
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn6 } from "node:child_process";
60219
60681
  import { createHash as createHash27, randomUUID as randomUUID5 } from "node:crypto";
60220
60682
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
60221
- import { join as join57, resolve as resolve47, relative as relative10, basename as basename14, dirname as dirname32 } from "node:path";
60683
+ import { join as join58, resolve as resolve47, relative as relative10, basename as basename14, dirname as dirname32 } from "node:path";
60222
60684
  import { fileURLToPath as fileURLToPath24 } from "node:url";
60223
60685
 
60224
60686
  // ../serve-cli/src/client.ts
@@ -63494,8 +63956,8 @@ var str6 = (v) => typeof v === "string" ? v : void 0;
63494
63956
  var num2 = (v) => typeof v === "string" && v !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
63495
63957
  function embeddedTemplateCatalog() {
63496
63958
  return availableTemplates().map((name) => {
63497
- const path2 = join57(templateSourceDir(name), TEMPLATE_META_FILE);
63498
- const meta = JSON.parse(readFileSync72(path2, "utf8"));
63959
+ const path2 = join58(templateSourceDir(name), TEMPLATE_META_FILE);
63960
+ const meta = JSON.parse(readFileSync73(path2, "utf8"));
63499
63961
  if (!meta || typeof meta.title !== "string" || typeof meta.description !== "string" || !Array.isArray(meta.tags)) {
63500
63962
  throw new Error(`${path2} must be { title: string, description: string, tags: string[], category?: string }`);
63501
63963
  }
@@ -64164,7 +64626,7 @@ var COMMANDS = [
64164
64626
  ];
64165
64627
  function cliVersion() {
64166
64628
  try {
64167
- return JSON.parse(readFileSync72(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
64629
+ return JSON.parse(readFileSync73(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
64168
64630
  } catch {
64169
64631
  return "unknown";
64170
64632
  }
@@ -64282,7 +64744,7 @@ function ageInDays(publishedAt, now = Date.now()) {
64282
64744
  }
64283
64745
  function packageVersion(path2) {
64284
64746
  try {
64285
- const version2 = JSON.parse(readFileSync72(path2, "utf8")).version;
64747
+ const version2 = JSON.parse(readFileSync73(path2, "utf8")).version;
64286
64748
  return typeof version2 === "string" && version2 ? version2 : void 0;
64287
64749
  } catch {
64288
64750
  return void 0;
@@ -64291,7 +64753,7 @@ function packageVersion(path2) {
64291
64753
  function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("./authoring-runtime/node_modules/@wix/himalaya/package.json", import.meta.url)) {
64292
64754
  let dir = resolve47(fromDir);
64293
64755
  for (; ; ) {
64294
- const version2 = packageVersion(join57(dir, "node_modules", "@wix", "himalaya", "package.json"));
64756
+ const version2 = packageVersion(join58(dir, "node_modules", "@wix", "himalaya", "package.json"));
64295
64757
  if (version2) return { version: version2, source: "local" };
64296
64758
  const parent = dirname32(dir);
64297
64759
  if (parent === dir) break;
@@ -64301,7 +64763,7 @@ function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("
64301
64763
  if (bundled) return { version: bundled, source: "bundled" };
64302
64764
  try {
64303
64765
  const root = execFileSync7("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
64304
- const version2 = root ? packageVersion(join57(root, "@wix", "himalaya", "package.json")) : void 0;
64766
+ const version2 = root ? packageVersion(join58(root, "@wix", "himalaya", "package.json")) : void 0;
64305
64767
  return version2 ? { version: version2, source: "global" } : {};
64306
64768
  } catch {
64307
64769
  return {};
@@ -64348,11 +64810,11 @@ var UPDATE_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1e3;
64348
64810
  var UPDATE_SNOOZE_MS = 24 * 60 * 60 * 1e3;
64349
64811
  var UPDATE_REEXEC_ENV = "HIMI_UPDATED_ONCE";
64350
64812
  function updateCachePath(env = process.env) {
64351
- return env.HIMI_UPDATE_CACHE_FILE ?? join57(homedir7(), ".himalaya", "update-check.json");
64813
+ return env.HIMI_UPDATE_CACHE_FILE ?? join58(homedir7(), ".himalaya", "update-check.json");
64352
64814
  }
64353
64815
  function readUpdateCache(path2) {
64354
64816
  try {
64355
- const parsed = JSON.parse(readFileSync72(path2, "utf8"));
64817
+ const parsed = JSON.parse(readFileSync73(path2, "utf8"));
64356
64818
  return parsed && typeof parsed === "object" ? parsed : {};
64357
64819
  } catch {
64358
64820
  return {};
@@ -64420,12 +64882,12 @@ function createUpdateRunner(spawn7 = spawnSync5) {
64420
64882
  },
64421
64883
  installedVersion: () => {
64422
64884
  const root = globalNodeModulesRoot();
64423
- return root ? packageVersion(join57(root, "@wix", "himalaya-cli", "package.json")) : void 0;
64885
+ return root ? packageVersion(join58(root, "@wix", "himalaya-cli", "package.json")) : void 0;
64424
64886
  },
64425
64887
  reexec: (argv) => {
64426
64888
  const root = globalNodeModulesRoot();
64427
- const entry = root ? join57(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
64428
- if (!entry || !existsSync60(entry)) return void 0;
64889
+ const entry = root ? join58(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
64890
+ if (!entry || !existsSync61(entry)) return void 0;
64429
64891
  const result = spawnSync5(process.execPath, [entry, ...argv], {
64430
64892
  // inherit keeps PIPED STDIN working — commands that read stdin must survive a re-exec.
64431
64893
  stdio: "inherit",
@@ -64472,7 +64934,7 @@ function staleUpdateLockFile(cachePath, now) {
64472
64934
  for (const path2 of [`${cachePath}.lock.break`, `${cachePath}.lock`]) {
64473
64935
  let content;
64474
64936
  try {
64475
- content = readFileSync72(path2, "utf8");
64937
+ content = readFileSync73(path2, "utf8");
64476
64938
  } catch {
64477
64939
  continue;
64478
64940
  }
@@ -64495,7 +64957,7 @@ function acquireUpdateLock(cachePath, now, hooks = {}) {
64495
64957
  const mine = lockStamp(now);
64496
64958
  const readLock = (path2) => {
64497
64959
  try {
64498
- return readFileSync72(path2, "utf8");
64960
+ return readFileSync73(path2, "utf8");
64499
64961
  } catch {
64500
64962
  return void 0;
64501
64963
  }
@@ -64792,9 +65254,9 @@ function diagnoseBody(body) {
64792
65254
  const out = {};
64793
65255
  const emptied = [];
64794
65256
  for (const [key2, value] of Object.entries(body)) {
64795
- const fold = (key2 === "issues" || key2 === "failures" || key2 === "warnings") && folded(value);
64796
- if (fold) emptied.push(key2);
64797
- out[key2] = fold ? [] : value;
65257
+ const fold2 = (key2 === "issues" || key2 === "failures" || key2 === "warnings") && folded(value);
65258
+ if (fold2) emptied.push(key2);
65259
+ out[key2] = fold2 ? [] : value;
64798
65260
  }
64799
65261
  if (emptied.length) out.foldedIntoFindings = emptied;
64800
65262
  return out;
@@ -65033,7 +65495,7 @@ function executableOnPath(name, env = process.env) {
65033
65495
  const suffixes = process.platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
65034
65496
  for (const dir of entries) {
65035
65497
  for (const suffix of suffixes) {
65036
- const candidate = join57(dir, `${name}${suffix}`);
65498
+ const candidate = join58(dir, `${name}${suffix}`);
65037
65499
  try {
65038
65500
  if (!statSync17(candidate).isFile()) continue;
65039
65501
  if (process.platform !== "win32") accessSync(candidate, constants2.X_OK);
@@ -65142,7 +65604,7 @@ async function nativeConfigAdvisories(app, configSource) {
65142
65604
  const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
65143
65605
  const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
65144
65606
  const iosYmlPath = iosProjectYml2(app);
65145
- const iosYml = existsSync60(iosYmlPath) ? readFileSync72(iosYmlPath, "utf8") : "";
65607
+ const iosYml = existsSync61(iosYmlPath) ? readFileSync73(iosYmlPath, "utf8") : "";
65146
65608
  let optedIn = iosYml.includes(LABEL_IOS_SPLASH2);
65147
65609
  if (!optedIn) {
65148
65610
  const { androidManifestPath: androidManifestPath3 } = await Promise.resolve().then(() => (init_gen_native_config(), gen_native_config_exports));
@@ -65156,7 +65618,7 @@ async function nativeConfigAdvisories(app, configSource) {
65156
65618
  }
65157
65619
  function readConfigSource(dir) {
65158
65620
  try {
65159
- return readFileSync72(join57(dir, "config.ts"), "utf8");
65621
+ return readFileSync73(join58(dir, "config.ts"), "utf8");
65160
65622
  } catch {
65161
65623
  return null;
65162
65624
  }
@@ -65168,7 +65630,7 @@ function writePngFile(path2, base64) {
65168
65630
  }
65169
65631
  async function initFromRemoteTemplate(name, parentDir, template, c) {
65170
65632
  const dir = resolve47(parentDir, name);
65171
- if (existsSync60(join57(dir, "himalaya.content.json"))) {
65633
+ if (existsSync61(join58(dir, "himalaya.content.json"))) {
65172
65634
  throw new Error(`content package already exists at ${dir}`);
65173
65635
  }
65174
65636
  const r = await c.fetchTemplateSource(TEMPLATE_APP_PREFIX + template);
@@ -65211,7 +65673,7 @@ function symlinkDestinationIssue(dest) {
65211
65673
  function restoreDestinationIssue(dest, force) {
65212
65674
  const link = symlinkDestinationIssue(dest);
65213
65675
  if (link) return link;
65214
- if (!existsSync60(dest)) return null;
65676
+ if (!existsSync61(dest)) return null;
65215
65677
  if (!statSync17(dest).isDirectory()) {
65216
65678
  return { error: `${dest} exists and is not a directory`, hint: "pass --out/--name to land somewhere else, or remove the file" };
65217
65679
  }
@@ -65226,7 +65688,7 @@ function restoreDestinationIssue(dest, force) {
65226
65688
  function isHimalayaMonorepoCheckout(start) {
65227
65689
  let dir = resolve47(start);
65228
65690
  for (; ; ) {
65229
- if (existsSync60(join57(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync60(join57(dir, "core", "serve", "src", "server.ts"))) return true;
65691
+ if (existsSync61(join58(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync61(join58(dir, "core", "serve", "src", "server.ts"))) return true;
65230
65692
  const parent = dirname32(dir);
65231
65693
  if (parent === dir) return false;
65232
65694
  dir = parent;
@@ -65269,7 +65731,7 @@ function materializeForPreview(appDir2, slug, displayName) {
65269
65731
  Object.entries(files).map(([rel, body]) => [rel, rel === "himalaya.content.json" ? substituteIdentity(body, slug) : body])
65270
65732
  );
65271
65733
  const repoRoot2 = resolve47(fileURLToPath24(new URL("../../..", import.meta.url)));
65272
- const root = join57(repoRoot2, "node_modules", ".cache", "himi-template-preview");
65734
+ const root = join58(repoRoot2, "node_modules", ".cache", "himi-template-preview");
65273
65735
  const stillRunning = (pid) => {
65274
65736
  try {
65275
65737
  process.kill(pid, 0);
@@ -65283,12 +65745,12 @@ function materializeForPreview(appDir2, slug, displayName) {
65283
65745
  if (!entry.isDirectory()) continue;
65284
65746
  const pid = Number(entry.name.startsWith(`${slug}-`) ? entry.name.slice(slug.length + 1) : NaN);
65285
65747
  if (Number.isInteger(pid) && pid > 0 && pid !== process.pid && !stillRunning(pid)) {
65286
- rmSync14(join57(root, entry.name), { recursive: true, force: true });
65748
+ rmSync14(join58(root, entry.name), { recursive: true, force: true });
65287
65749
  }
65288
65750
  }
65289
65751
  } catch {
65290
65752
  }
65291
- const dest = join57(root, `${slug}-${process.pid}`);
65753
+ const dest = join58(root, `${slug}-${process.pid}`);
65292
65754
  rmSync14(dest, { recursive: true, force: true });
65293
65755
  mkdirSync30(dest, { recursive: true });
65294
65756
  materializeSource(forBuild, displayName, dest, slug);
@@ -65296,8 +65758,8 @@ function materializeForPreview(appDir2, slug, displayName) {
65296
65758
  }
65297
65759
  async function appId(dir) {
65298
65760
  try {
65299
- const { readFileSync: readFileSync73 } = await import("node:fs");
65300
- const m = JSON.parse(readFileSync73(join57(dir, "himalaya.content.json"), "utf8"));
65761
+ const { readFileSync: readFileSync74 } = await import("node:fs");
65762
+ const m = JSON.parse(readFileSync74(join58(dir, "himalaya.content.json"), "utf8"));
65301
65763
  if (m.name) return m.name;
65302
65764
  } catch {
65303
65765
  }
@@ -65305,8 +65767,8 @@ async function appId(dir) {
65305
65767
  }
65306
65768
  async function contentVisibility(dir) {
65307
65769
  try {
65308
- const { readFileSync: readFileSync73 } = await import("node:fs");
65309
- const m = JSON.parse(readFileSync73(join57(dir, "himalaya.content.json"), "utf8"));
65770
+ const { readFileSync: readFileSync74 } = await import("node:fs");
65771
+ const m = JSON.parse(readFileSync74(join58(dir, "himalaya.content.json"), "utf8"));
65310
65772
  if (m.visibility === "public" || m.visibility === "unlisted") return m.visibility;
65311
65773
  } catch {
65312
65774
  }
@@ -65530,7 +65992,7 @@ async function recordedReleaseForContentQuietly(dir, resolution, onDrift) {
65530
65992
  try {
65531
65993
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
65532
65994
  const bres = await buildContentForValidation2(dir, { resolution });
65533
- out = mkdtempSync4(join57(tmpdir5(), "himi-ux-record-"));
65995
+ out = mkdtempSync4(join58(tmpdir5(), "himi-ux-record-"));
65534
65996
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
65535
65997
  const outcome = await buildReleaseFromContent2(dir, out, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {}, collect: { lint: true } });
65536
65998
  const releaseId = outcome.manifest.releaseId;
@@ -65708,7 +66170,7 @@ async function producedWidgetFamilies(dir) {
65708
66170
  let js;
65709
66171
  for (const candidate of [`${name}.bundle.js`, `${name}.js`, name]) {
65710
66172
  try {
65711
- js = readFileSync72(join57(outDir, candidate), "utf8");
66173
+ js = readFileSync73(join58(outDir, candidate), "utf8");
65712
66174
  break;
65713
66175
  } catch {
65714
66176
  }
@@ -65739,7 +66201,7 @@ async function producedWidgetFamilies(dir) {
65739
66201
  }
65740
66202
  return { families, source: "dist/bundles" };
65741
66203
  }
65742
- var boardOpen = (dir) => existsSync60(join57(dir, ".himi", "studio", "index.html"));
66204
+ var boardOpen = (dir) => existsSync61(join58(dir, ".himi", "studio", "index.html"));
65743
66205
  var BOARD_REFRESHERS = /* @__PURE__ */ new Set(["render", "test", "validate", "build", "push", "icon", "site", "ux", "progress"]);
65744
66206
  async function leaveForBoard(dir, record2, value) {
65745
66207
  if (!boardOpen(dir)) return;
@@ -66127,7 +66589,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66127
66589
  const sourceDir = resolve47(str6(flags["source-dir"]) ?? appDir2);
66128
66590
  let meta;
66129
66591
  try {
66130
- meta = JSON.parse(readFileSync72(join57(sourceDir, TEMPLATE_META_FILE), "utf8"));
66592
+ meta = JSON.parse(readFileSync73(join58(sourceDir, TEMPLATE_META_FILE), "utf8"));
66131
66593
  } catch {
66132
66594
  io.error(JSON.stringify({ error: `no ${TEMPLATE_META_FILE} in ${sourceDir} \u2014 a template must declare { title, description, tags } to be published`, hint: `add ${TEMPLATE_META_FILE} next to himalaya.content.json (or pass --source-dir <dir>)` }));
66133
66595
  return 2;
@@ -66137,7 +66599,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66137
66599
  return 2;
66138
66600
  }
66139
66601
  try {
66140
- const appearance = extractTemplateAppearance(JSON.parse(readFileSync72(join57(sourceDir, "tokens.json"), "utf8")));
66602
+ const appearance = extractTemplateAppearance(JSON.parse(readFileSync73(join58(sourceDir, "tokens.json"), "utf8")));
66141
66603
  meta = { ...appearance, ...meta };
66142
66604
  } catch {
66143
66605
  }
@@ -66149,7 +66611,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66149
66611
  const force = flags.force === true;
66150
66612
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
66151
66613
  const bres = await buildContentForValidation2(buildDir, { resolution: resolutionFlag(flags) });
66152
- const out = mkdtempSync4(join57(tmpdir5(), "himi-template-"));
66614
+ const out = mkdtempSync4(join58(tmpdir5(), "himi-template-"));
66153
66615
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
66154
66616
  const outcome = await buildReleaseFromContent2(buildDir, out, {
66155
66617
  ...hasWorkers ? { tier5Dir: bundleOutDir2(buildDir) } : {},
@@ -66202,7 +66664,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66202
66664
  let iconHash;
66203
66665
  if (iconPath) {
66204
66666
  try {
66205
- const bytes2 = readFileSync72(iconPath);
66667
+ const bytes2 = readFileSync73(iconPath);
66206
66668
  payload.icon = bytes2.toString("base64");
66207
66669
  iconHash = createHash27("sha256").update(bytes2).digest("hex");
66208
66670
  } catch {
@@ -66435,7 +66897,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66435
66897
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
66436
66898
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
66437
66899
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
66438
- const out = mkdtempSync4(join57(tmpdir5(), "himi-validate-"));
66900
+ const out = mkdtempSync4(join58(tmpdir5(), "himi-validate-"));
66439
66901
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
66440
66902
  const outcome = await buildReleaseFromContent2(dir, out, {
66441
66903
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -66478,7 +66940,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66478
66940
  const path2 = dpxSessionTokenPath();
66479
66941
  let raw;
66480
66942
  try {
66481
- raw = readFileSync72(path2, "utf8");
66943
+ raw = readFileSync73(path2, "utf8");
66482
66944
  } catch {
66483
66945
  throw new Error(`no DPX BO session at ${path2}; run dpx auth login, write dpx auth token to that path, then himi login --dpx --target bo`);
66484
66946
  }
@@ -66598,13 +67060,13 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66598
67060
  case "studio": {
66599
67061
  const { openStudio: openStudio2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
66600
67062
  const dir = contentDir(flags);
66601
- if (!existsSync60(join57(dir, "himalaya.content.json"))) {
67063
+ if (!existsSync61(join58(dir, "himalaya.content.json"))) {
66602
67064
  io.error(JSON.stringify({ ok: false, error: "not_a_content_package", dir, hint: "run from the app directory (or pass --content <dir>) \u2014 the board reads that package's .himi/" }));
66603
67065
  return 2;
66604
67066
  }
66605
67067
  if (pos[1] === "reel") {
66606
67068
  const { buildReel: buildReel2 } = await Promise.resolve().then(() => (init_studio_reel(), studio_reel_exports));
66607
- const out = resolve47(str6(flags.out) ?? join57(dir, ".himi", "making-of.html"));
67069
+ const out = resolve47(str6(flags.out) ?? join58(dir, ".himi", "making-of.html"));
66608
67070
  const { html, plan } = buildReel2(dir);
66609
67071
  mkdirSync30(dirname32(out), { recursive: true });
66610
67072
  writeFileSync28(out, html);
@@ -66654,7 +67116,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66654
67116
  const { appendProgress: appendProgress2, readProgress: readProgress2, summarizeProgress: summarizeProgress2, parseStep: parseStep2, notifyDesktop: notifyDesktop2, appLabel: appLabel2, journalPath: journalPath2, ProgressError: ProgressError2, JOURNEY_STEPS: JOURNEY_STEPS2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
66655
67117
  const dir = contentDir(flags);
66656
67118
  const journal = journalPath2(dir);
66657
- const hint = existsSync60(join57(dir, "himalaya.content.json")) ? {} : { hint: "no himalaya.content.json here \u2014 run from the app directory (or pass --content <dir>) so the build board finds this journal" };
67119
+ const hint = existsSync61(join58(dir, "himalaya.content.json")) ? {} : { hint: "no himalaya.content.json here \u2014 run from the app directory (or pass --content <dir>) so the build board finds this journal" };
66658
67120
  const message = pos.slice(1).join(" ").trim();
66659
67121
  if (!message) {
66660
67122
  if (flags.step !== void 0 || flags.image !== void 0 || flags.notify === true) {
@@ -66746,7 +67208,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66746
67208
  case "test": {
66747
67209
  const dir = contentDir(flags);
66748
67210
  const structuredOutput = flags.json === true || io.isTTY !== true;
66749
- if (existsSync60(join57(dir, "functions"))) {
67211
+ if (existsSync61(join58(dir, "functions"))) {
66750
67212
  const { invokeLocalFunction: invokeLocalFunction2, loadFunctionsApp: loadFunctionsApp2 } = await Promise.resolve().then(() => (init_functions(), functions_exports));
66751
67213
  const loaded = await loadFunctionsApp2(dir);
66752
67214
  const results = [];
@@ -66833,7 +67295,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66833
67295
  }
66834
67296
  }
66835
67297
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
66836
- const out = mkdtempSync4(join57(tmpdir5(), "himi-test-"));
67298
+ const out = mkdtempSync4(join58(tmpdir5(), "himi-test-"));
66837
67299
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
66838
67300
  const outcome = await buildReleaseFromContent2(dir, out, {
66839
67301
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -66878,7 +67340,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
66878
67340
  io.error(JSON.stringify({ error: `screen "${screen.screenId}" has no Tier-5 worker, so there is nothing for the browser loop to build` }));
66879
67341
  return 2;
66880
67342
  }
66881
- const sources = collectWorkerSources2(join57(dir, "tier5-src"), screen.bundleName);
67343
+ const sources = collectWorkerSources2(join58(dir, "tier5-src"), screen.bundleName);
66882
67344
  if (!sources) {
66883
67345
  io.error(JSON.stringify({ error: `no worker source found at tier5-src/${screen.bundleName} \u2014 the browser loop compiles SOURCE, not the built bundle` }));
66884
67346
  return 2;
@@ -67039,7 +67501,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
67039
67501
  const sp = await runShotsPass2({
67040
67502
  descriptors: scopeDescriptors2(outcome.descriptors ?? [], pos.slice(1), invalidScreens),
67041
67503
  tokens: outcome.tokens,
67042
- outDir: join57(dir, ".himi", "shots"),
67504
+ outDir: join58(dir, ".himi", "shots"),
67043
67505
  width: 390,
67044
67506
  layers: { rules: shotRules, ...cmsSeed ? { cmsSeed } : {}, ...canned ? { canned: canned.byKey } : {} },
67045
67507
  ...budgetMs ? { budgetMs } : {},
@@ -67077,7 +67539,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
67077
67539
  releaseId: result.releaseId,
67078
67540
  releasePayload: release,
67079
67541
  descriptors: attestedDescriptors,
67080
- screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join57(dir, ".himi", "shots", name)])),
67542
+ screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join58(dir, ".himi", "shots", name)])),
67081
67543
  coverage: {
67082
67544
  screens: crawledScreens.size,
67083
67545
  actionsDispatched: result.actions.exercised,
@@ -67167,7 +67629,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
67167
67629
  return 2;
67168
67630
  }
67169
67631
  try {
67170
- const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync72(stateArg.replace(/^@/, ""), "utf8");
67632
+ const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync73(stateArg.replace(/^@/, ""), "utf8");
67171
67633
  injectedState = JSON.parse(raw);
67172
67634
  } catch (e) {
67173
67635
  io.error(JSON.stringify({ error: `--state must be inline JSON or a path to a .json file \u2014 ${e.message}` }));
@@ -67182,7 +67644,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
67182
67644
  return 2;
67183
67645
  }
67184
67646
  try {
67185
- const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync72(inputArg.replace(/^@/, ""), "utf8");
67647
+ const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync73(inputArg.replace(/^@/, ""), "utf8");
67186
67648
  const parsed = JSON.parse(raw);
67187
67649
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("must be a JSON object");
67188
67650
  injectedInput = parsed;
@@ -67231,7 +67693,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
67231
67693
  }
67232
67694
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
67233
67695
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
67234
- const out = mkdtempSync4(join57(tmpdir5(), "himi-render-"));
67696
+ const out = mkdtempSync4(join58(tmpdir5(), "himi-render-"));
67235
67697
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
67236
67698
  const outcome = await buildReleaseFromContent2(dir, out, {
67237
67699
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -67245,7 +67707,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
67245
67707
  const diagnostics = (outcome.lint ?? []).find((s) => s.screenId === screenId)?.issues ?? [];
67246
67708
  let pngFile;
67247
67709
  let attestationPngFile;
67248
- const shotPath = join57(dir, ".himi", "shots", `${screenId}.png`);
67710
+ const shotPath = join58(dir, ".himi", "shots", `${screenId}.png`);
67249
67711
  const pngTarget = typeof flags.png === "string" ? str6(flags.png) : flags.png === true ? shotPath : void 0;
67250
67712
  if (pngTarget && r.pngBase64) {
67251
67713
  pngFile = writePngFile(pngTarget, r.pngBase64);
@@ -67581,7 +68043,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
67581
68043
  io.error(JSON.stringify(linked));
67582
68044
  return 1;
67583
68045
  }
67584
- if (existsSync60(dest)) {
68046
+ if (existsSync61(dest)) {
67585
68047
  if (!statSync17(dest).isDirectory()) {
67586
68048
  io.error(JSON.stringify({
67587
68049
  error: `${dest} exists and is not a directory`,
@@ -67594,7 +68056,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
67594
68056
  io.error(JSON.stringify({ error: `${dest} already exists and is not empty`, hint: "pass --force to write into it anyway (merging, and naming what it keeps), --replace to restore exactly what was archived, or --out/--name to land somewhere else" }));
67595
68057
  return 1;
67596
68058
  }
67597
- if (replace && occupied && !existsSync60(join57(dest, "himalaya.content.json"))) {
68059
+ if (replace && occupied && !existsSync61(join58(dest, "himalaya.content.json"))) {
67598
68060
  io.error(JSON.stringify({
67599
68061
  error: `${dest} is not a content package \u2014 refusing to --replace it`,
67600
68062
  hint: "--replace only clears a directory that holds himalaya.content.json. Check --out/--name, or pass --force to merge into it instead."
@@ -67733,7 +68195,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
67733
68195
  const force = flags.force === true;
67734
68196
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
67735
68197
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
67736
- const out = mkdtempSync4(join57(tmpdir5(), "himi-push-"));
68198
+ const out = mkdtempSync4(join58(tmpdir5(), "himi-push-"));
67737
68199
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
67738
68200
  const outcome = await buildReleaseFromContent2(dir, out, {
67739
68201
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -68254,7 +68716,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68254
68716
  }
68255
68717
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
68256
68718
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
68257
- const built = mkdtempSync4(join57(tmpdir5(), "himi-pack-"));
68719
+ const built = mkdtempSync4(join58(tmpdir5(), "himi-pack-"));
68258
68720
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
68259
68721
  const outcome = await buildReleaseFromContent2(dir, built, {
68260
68722
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -68457,7 +68919,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68457
68919
  }));
68458
68920
  return 1;
68459
68921
  }
68460
- const archive = readFileSync72(resolve47(file));
68922
+ const archive = readFileSync73(resolve47(file));
68461
68923
  const result = await client2().createPackageShare({
68462
68924
  app: opened.header.app,
68463
68925
  releaseId: opened.header.releaseId,
@@ -68511,11 +68973,11 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68511
68973
  const app = await appId(dir2);
68512
68974
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
68513
68975
  const bres = await buildContentBundles2(dir2, { resolution: resolutionFlag(flags) });
68514
- temp = mkdtempSync4(join57(tmpdir5(), "himi-run-build-"));
68976
+ temp = mkdtempSync4(join58(tmpdir5(), "himi-run-build-"));
68515
68977
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
68516
68978
  const initial = await buildReleaseFromContent2(dir2, temp, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir2) } : {} });
68517
68979
  servedMissing = initial.missingBundles;
68518
- source = join57(temp, app);
68980
+ source = join58(temp, app);
68519
68981
  }
68520
68982
  const chromeArg = str6(flags.chrome) ?? "preview";
68521
68983
  if (chromeArg !== "preview" && chromeArg !== "app") {
@@ -68551,7 +69013,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68551
69013
  if (!recordKit) return;
68552
69014
  startedAt = (/* @__PURE__ */ new Date()).toISOString();
68553
69015
  const failure = record2(served2, { ok: true, at: startedAt });
68554
- if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join57(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
69016
+ if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join58(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
68555
69017
  await refreshBoard();
68556
69018
  };
68557
69019
  const retract = async () => {
@@ -68582,7 +69044,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68582
69044
  }
68583
69045
  const watchKit = watch2 ? await Promise.resolve().then(() => (init_run_watch(), run_watch_exports)) : null;
68584
69046
  const bus = watchKit ? new watchKit.ReloadBus() : void 0;
68585
- if (watch2) served = mkdtempSync4(join57(tmpdir5(), "himi-run-served-"));
69047
+ if (watch2) served = mkdtempSync4(join58(tmpdir5(), "himi-run-served-"));
68586
69048
  const running = await runPackage2(source, {
68587
69049
  port,
68588
69050
  chrome: chromeArg,
@@ -68594,15 +69056,15 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68594
69056
  ...recordKit ? { extraRoutes: (request) => recordKit.boardFile(dir, new URL(request.url).pathname) } : {},
68595
69057
  ...watchKit && served && bus ? { workDir: served, screenEvents: bus } : {}
68596
69058
  });
68597
- let watcher;
69059
+ let watcher2;
68598
69060
  let rebuild;
68599
69061
  let stopping = false;
68600
69062
  try {
68601
69063
  await announce(running);
68602
69064
  if (watchKit && bus) {
68603
69065
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
68604
- const live2 = join57(running.releaseRoot, running.app);
68605
- const manifestOf = (tree) => JSON.parse(readFileSync72(join57(tree, "release-manifest.json"), "utf8"));
69066
+ const live2 = join58(running.releaseRoot, running.app);
69067
+ const manifestOf = (tree) => JSON.parse(readFileSync73(join58(tree, "release-manifest.json"), "utf8"));
68606
69068
  let current = manifestOf(live2);
68607
69069
  const failed2 = async (message) => {
68608
69070
  if (stopping) return;
@@ -68613,7 +69075,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68613
69075
  };
68614
69076
  rebuild = watchKit.serialize(async () => {
68615
69077
  const t0 = Date.now();
68616
- const next = mkdtempSync4(join57(tmpdir5(), "himi-run-build-"));
69078
+ const next = mkdtempSync4(join58(tmpdir5(), "himi-run-build-"));
68617
69079
  try {
68618
69080
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
68619
69081
  if (bres.compileErrors.length) {
@@ -68630,14 +69092,14 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68630
69092
  return;
68631
69093
  }
68632
69094
  if (stopping) return;
68633
- const fresh = manifestOf(join57(next, running.app));
69095
+ const fresh = manifestOf(join58(next, running.app));
68634
69096
  const diff = watchKit.diffReleases(current, fresh);
68635
69097
  const retired = `${live2}.retired-${Date.now()}`;
68636
69098
  renameSync6(live2, retired);
68637
69099
  try {
68638
- renameSync6(join57(next, running.app), live2);
69100
+ renameSync6(join58(next, running.app), live2);
68639
69101
  } catch {
68640
- cpSync3(join57(next, running.app), live2, { recursive: true });
69102
+ cpSync3(join58(next, running.app), live2, { recursive: true });
68641
69103
  }
68642
69104
  rmSync14(retired, { recursive: true, force: true });
68643
69105
  current = fresh;
@@ -68652,7 +69114,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68652
69114
  rmSync14(next, { recursive: true, force: true });
68653
69115
  }
68654
69116
  });
68655
- watcher = watchKit.watchContent(dir, () => rebuild?.());
69117
+ watcher2 = watchKit.watchContent(dir, () => rebuild?.());
68656
69118
  }
68657
69119
  } catch (e) {
68658
69120
  await retract().catch(() => {
@@ -68674,7 +69136,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
68674
69136
  await new Promise((done) => {
68675
69137
  const stop = async () => {
68676
69138
  stopping = true;
68677
- watcher?.close();
69139
+ watcher2?.close();
68678
69140
  try {
68679
69141
  await rebuild?.stop();
68680
69142
  await retract();
@@ -69235,7 +69697,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
69235
69697
  io.error(JSON.stringify({ error: `patch ${kind} needs --file`, usage: PATCH_USAGE }));
69236
69698
  return 2;
69237
69699
  }
69238
- if (!existsSync60(file)) {
69700
+ if (!existsSync61(file)) {
69239
69701
  io.error(JSON.stringify({ error: `no such file: ${file}` }));
69240
69702
  return 2;
69241
69703
  }
@@ -69291,7 +69753,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
69291
69753
  lintNote: "`himi patch` sends a raw file. Nothing here built or linted your app \u2014 `himi push --only <selector>` is the same edit with the full lint gate in front of it."
69292
69754
  };
69293
69755
  if (kind === "tokens" || kind === "app-config") {
69294
- const body = readFileSync72(file, "utf8");
69756
+ const body = readFileSync73(file, "utf8");
69295
69757
  const patch2 = kind === "tokens" ? { tokens: body } : { appConfig: body };
69296
69758
  return runReleasePatch({
69297
69759
  ...common,
@@ -69306,7 +69768,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
69306
69768
  io.error(JSON.stringify({ error: "patch screen needs a <screenId>", usage: PATCH_USAGE }));
69307
69769
  return 2;
69308
69770
  }
69309
- const descriptor = readFileSync72(file, "utf8");
69771
+ const descriptor = readFileSync73(file, "utf8");
69310
69772
  try {
69311
69773
  JSON.parse(descriptor);
69312
69774
  } catch (e) {
@@ -69346,7 +69808,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
69346
69808
  extra: { kind, screen: id, file: { path: file, bytes: Buffer.byteLength(descriptor, "utf8") } }
69347
69809
  });
69348
69810
  }
69349
- const bytes2 = readFileSync72(file);
69811
+ const bytes2 = readFileSync73(file);
69350
69812
  const sha = createHash27("sha256").update(bytes2).digest("hex");
69351
69813
  const forScreens = (str6(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
69352
69814
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
@@ -69490,7 +69952,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
69490
69952
  }
69491
69953
  let def;
69492
69954
  try {
69493
- const parsed = JSON.parse(arg.startsWith("@") ? readFileSync72(arg.slice(1), "utf8") : arg);
69955
+ const parsed = JSON.parse(arg.startsWith("@") ? readFileSync73(arg.slice(1), "utf8") : arg);
69494
69956
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
69495
69957
  io.error(JSON.stringify({ error: "invalid_definition", problems: [`--definition must be a JSON object, got ${Array.isArray(parsed) ? "an array" : parsed === null ? "null" : typeof parsed}`] }));
69496
69958
  return 2;
@@ -69954,7 +70416,7 @@ ${label2}`);
69954
70416
  const platform = str6(flags.platform)?.split(",").map((p) => p.trim()).filter(Boolean);
69955
70417
  {
69956
70418
  const distDir = contentDir(flags);
69957
- if (existsSync60(join57(distDir, "himalaya.content.json"))) {
70419
+ if (existsSync61(join58(distDir, "himalaya.content.json"))) {
69958
70420
  const issues = await iconIssues(distDir, flags.strict === true, { requireDeclared: true, requireVerified: true });
69959
70421
  const errors = issues.filter((i) => i.severity === "error");
69960
70422
  for (const i of issues.filter((i2) => i2.severity === "warning")) {
@@ -70223,7 +70685,7 @@ ${label2}`);
70223
70685
  const dpxPath = dpxSessionTokenPath();
70224
70686
  let dpx;
70225
70687
  try {
70226
- dpx = readFileSync72(dpxPath, "utf8").trim();
70688
+ dpx = readFileSync73(dpxPath, "utf8").trim();
70227
70689
  } catch {
70228
70690
  io.error(JSON.stringify({ error: `no dpx session at ${dpxPath} \u2014 run \`dpx auth login\` then \`dpx auth token > ${dpxPath}\` (dpx 2.x keeps its own state in ~/.dpx/auth.json and does NOT write the legacy session file), or omit --dpx` }));
70229
70691
  return 1;
@@ -70625,7 +71087,7 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
70625
71087
  const path2 = dpxSessionTokenPath();
70626
71088
  let raw;
70627
71089
  try {
70628
- raw = readFileSync72(path2, "utf8");
71090
+ raw = readFileSync73(path2, "utf8");
70629
71091
  } catch {
70630
71092
  io.error(JSON.stringify({ error: `no DPX BO session at ${path2}` }));
70631
71093
  return 1;