@syntrologie/runtime-sdk 2.8.0-canary.4 → 2.8.0-canary.41

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 (45) hide show
  1. package/CAPABILITIES.md +44 -0
  2. package/dist/actions/schema.d.ts +1 -1
  3. package/dist/actions/schema.js +1 -1
  4. package/dist/actions/validation-core.d.ts +24 -0
  5. package/dist/actions/validation-rules.d.ts +74 -0
  6. package/dist/actions/validation.d.ts +5 -11
  7. package/dist/bootstrap-init.d.ts +33 -0
  8. package/dist/bootstrap-runtime.d.ts +7 -0
  9. package/dist/bootstrap-types.d.ts +90 -0
  10. package/dist/bootstrap.d.ts +17 -83
  11. package/dist/{chunk-R5DNAIRI.js → chunk-TN5BLBPU.js} +1 -1
  12. package/dist/{chunk-R5DNAIRI.js.map → chunk-TN5BLBPU.js.map} +1 -1
  13. package/dist/{chunk-5GTCL2IH.js → chunk-X2XEU6KD.js} +1506 -599
  14. package/dist/chunk-X2XEU6KD.js.map +7 -0
  15. package/dist/components/TileIcon.d.ts +2 -2
  16. package/dist/components/emojiToIcon.d.ts +24 -0
  17. package/dist/events/EventBus.d.ts +27 -1
  18. package/dist/events/history.d.ts +9 -0
  19. package/dist/events/index.d.ts +3 -0
  20. package/dist/events/normalizers/posthog.d.ts +4 -50
  21. package/dist/events/types.d.ts +30 -23
  22. package/dist/events/validation.d.ts +7 -0
  23. package/dist/index.d.ts +0 -2
  24. package/dist/index.js +1523 -182
  25. package/dist/index.js.map +4 -4
  26. package/dist/overlays/runtime/overlay/overlay-runner.d.ts +4 -0
  27. package/dist/overlays/runtime/overlay/overlay-state.d.ts +21 -0
  28. package/dist/overlays/types.d.ts +3 -1
  29. package/dist/react.js +4 -2
  30. package/dist/react.js.map +2 -2
  31. package/dist/smart-canvas.esm.js +123 -54
  32. package/dist/smart-canvas.esm.js.map +4 -4
  33. package/dist/smart-canvas.js +5650 -2964
  34. package/dist/smart-canvas.js.map +4 -4
  35. package/dist/smart-canvas.min.js +123 -54
  36. package/dist/smart-canvas.min.js.map +4 -4
  37. package/dist/telemetry/adapters/posthog.d.ts +30 -4
  38. package/dist/test/setup.d.ts +1 -0
  39. package/dist/token.d.ts +2 -0
  40. package/dist/version.d.ts +1 -1
  41. package/package.json +23 -28
  42. package/schema/canvas-config.schema.json +100 -2
  43. package/scripts/syntroReactPlugin.mjs +3 -0
  44. package/scripts/validate-config.mjs +42 -0
  45. package/dist/chunk-5GTCL2IH.js.map +0 -7
@@ -34,6 +34,11 @@ function guardAgainstReconciliation(container, anchor, reinsertFn, opts) {
34
34
  debounceTimer = setTimeout(() => {
35
35
  if (disconnected)
36
36
  return;
37
+ if (!anchor.isConnected) {
38
+ observer.disconnect();
39
+ disconnected = true;
40
+ return;
41
+ }
37
42
  retries++;
38
43
  try {
39
44
  reinsertFn();
@@ -75,7 +80,16 @@ var ALLOWED_TAGS = /* @__PURE__ */ new Set([
75
80
  "sup",
76
81
  "sub",
77
82
  "a",
78
- "button"
83
+ "button",
84
+ // SVG elements (for inline Lucide icons in config HTML)
85
+ "svg",
86
+ "path",
87
+ "circle",
88
+ "line",
89
+ "polyline",
90
+ "polygon",
91
+ "rect",
92
+ "g"
79
93
  ]);
80
94
  function sanitizeHtml(html) {
81
95
  var _a2;
@@ -113,6 +127,12 @@ function sanitizeHtml(html) {
113
127
  }
114
128
  }
115
129
  }
130
+ const svgs = Array.from(root.querySelectorAll("svg"));
131
+ for (const svg of svgs) {
132
+ if (toRemove.some((el) => svg.contains(el) && el.tagName.toLowerCase() === "script")) {
133
+ toRemove.push(svg);
134
+ }
135
+ }
116
136
  for (const el of toRemove) {
117
137
  while (el.firstChild) {
118
138
  (_a2 = el.parentNode) == null ? void 0 : _a2.insertBefore(el.firstChild, el);
@@ -213,15 +233,20 @@ var executeInsertHtml = async (action, context) => {
213
233
  container.removeEventListener("click", deepLinkHandler);
214
234
  }
215
235
  guardCleanup();
216
- if (action.position === "replace" && originalContent !== null) {
217
- const restoredEl = document.createElement(anchorEl.tagName);
218
- restoredEl.innerHTML = originalContent;
219
- Array.from(anchorEl.attributes).forEach((attr) => {
220
- restoredEl.setAttribute(attr.name, attr.value);
221
- });
222
- container.replaceWith(restoredEl);
223
- } else {
224
- container.remove();
236
+ if (!container.isConnected)
237
+ return;
238
+ try {
239
+ if (action.position === "replace" && originalContent !== null) {
240
+ const restoredEl = document.createElement(anchorEl.tagName);
241
+ restoredEl.innerHTML = originalContent;
242
+ Array.from(anchorEl.attributes).forEach((attr) => {
243
+ restoredEl.setAttribute(attr.name, attr.value);
244
+ });
245
+ container.replaceWith(restoredEl);
246
+ } else {
247
+ container.remove();
248
+ }
249
+ } catch {
225
250
  }
226
251
  },
227
252
  updateFn: (changes) => {
@@ -251,6 +276,8 @@ var executeSetText = async (action, context) => {
251
276
  });
252
277
  return {
253
278
  cleanup: () => {
279
+ if (!anchorEl.isConnected)
280
+ return;
254
281
  anchorEl.textContent = originalText;
255
282
  },
256
283
  updateFn: (changes) => {
@@ -292,6 +319,8 @@ var executeSetAttr = async (action, context) => {
292
319
  });
293
320
  return {
294
321
  cleanup: () => {
322
+ if (!anchorEl.isConnected)
323
+ return;
295
324
  if (hadAttribute && originalValue !== null) {
296
325
  anchorEl.setAttribute(action.attr, originalValue);
297
326
  } else {
@@ -325,6 +354,8 @@ var executeAddClass = async (action, context) => {
325
354
  });
326
355
  return {
327
356
  cleanup: () => {
357
+ if (!anchorEl.isConnected)
358
+ return;
328
359
  if (!hadClass) {
329
360
  anchorEl.classList.remove(action.className);
330
361
  }
@@ -351,6 +382,8 @@ var executeRemoveClass = async (action, context) => {
351
382
  });
352
383
  return {
353
384
  cleanup: () => {
385
+ if (!anchorEl.isConnected)
386
+ return;
354
387
  if (hadClass) {
355
388
  anchorEl.classList.add(action.className);
356
389
  }
@@ -383,6 +416,8 @@ var executeSetStyle = async (action, context) => {
383
416
  });
384
417
  return {
385
418
  cleanup: () => {
419
+ if (!anchorEl.isConnected)
420
+ return;
386
421
  for (const [prop, originalValue] of originalStyles) {
387
422
  if (originalValue) {
388
423
  anchorEl.style.setProperty(prop, originalValue);
@@ -726,6 +761,10 @@ var CelebrationEngine = class {
726
761
  this.container = null;
727
762
  }
728
763
  start(container, effect, config) {
764
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
765
+ if (prefersReducedMotion) {
766
+ return;
767
+ }
729
768
  this.container = container;
730
769
  this.effect = effect;
731
770
  this.duration = config.duration;
@@ -1558,10 +1597,10 @@ function showHighlight(anchorEl, overlayRoot, opts) {
1558
1597
  return;
1559
1598
  }
1560
1599
  const rect = anchorEl.getBoundingClientRect();
1561
- const x = Math.max(0, rect.left - padding);
1562
- const y = Math.max(0, rect.top - padding);
1563
- const w = Math.min(window.innerWidth, rect.width + padding * 2);
1564
- const h = Math.min(window.innerHeight, rect.height + padding * 2);
1600
+ const x = rect.left - padding;
1601
+ const y = rect.top - padding;
1602
+ const w = rect.width + padding * 2;
1603
+ const h = rect.height + padding * 2;
1565
1604
  Object.assign(ring.style, {
1566
1605
  left: `${x}px`,
1567
1606
  top: `${y}px`,
@@ -1610,17 +1649,22 @@ function showHighlight(anchorEl, overlayRoot, opts) {
1610
1649
  window.addEventListener("scroll", onScroll, true);
1611
1650
  window.addEventListener("resize", onResize);
1612
1651
  const onKey = (e) => {
1613
- if (e.key === "Escape" && onEsc)
1652
+ var _a3;
1653
+ if (e.key === "Escape" && onEsc) {
1654
+ (_a3 = opts == null ? void 0 : opts.onDismiss) == null ? void 0 : _a3.call(opts);
1614
1655
  handle.destroy();
1656
+ }
1615
1657
  };
1616
1658
  if (onEsc) {
1617
1659
  window.addEventListener("keydown", onKey);
1618
1660
  }
1619
1661
  const onClick = (event) => {
1662
+ var _a3;
1620
1663
  if (blocking) {
1621
1664
  event.preventDefault();
1622
1665
  event.stopPropagation();
1623
1666
  } else if (onClickOutside) {
1667
+ (_a3 = opts == null ? void 0 : opts.onDismiss) == null ? void 0 : _a3.call(opts);
1624
1668
  handle.destroy();
1625
1669
  }
1626
1670
  };
@@ -1637,8 +1681,14 @@ function showHighlight(anchorEl, overlayRoot, opts) {
1637
1681
  scrim.style.pointerEvents = "none";
1638
1682
  scrim.style.opacity = "0";
1639
1683
  setTimeout(() => {
1640
- scrim.remove();
1641
- ring.remove();
1684
+ try {
1685
+ scrim.remove();
1686
+ } catch {
1687
+ }
1688
+ try {
1689
+ ring.remove();
1690
+ } catch {
1691
+ }
1642
1692
  }, 220);
1643
1693
  }
1644
1694
  };
@@ -1666,7 +1716,16 @@ var ALLOWED_TAGS2 = /* @__PURE__ */ new Set([
1666
1716
  "sup",
1667
1717
  "sub",
1668
1718
  "a",
1669
- "button"
1719
+ "button",
1720
+ // SVG elements (for inline Lucide icons in config HTML)
1721
+ "svg",
1722
+ "path",
1723
+ "circle",
1724
+ "line",
1725
+ "polyline",
1726
+ "polygon",
1727
+ "rect",
1728
+ "g"
1670
1729
  ]);
1671
1730
  function sanitizeHtml2(html) {
1672
1731
  var _a2;
@@ -1875,8 +1934,14 @@ var executeModal = async (action, context) => {
1875
1934
  modal2.style.transform = "translate(-50%, -50%) scale(0.95)";
1876
1935
  scrimEl.style.opacity = "0";
1877
1936
  setTimeout(() => {
1878
- modal2.remove();
1879
- scrimEl.remove();
1937
+ try {
1938
+ modal2.remove();
1939
+ } catch {
1940
+ }
1941
+ try {
1942
+ scrimEl.remove();
1943
+ } catch {
1944
+ }
1880
1945
  }, 200);
1881
1946
  context.publishEvent("action.modal_dismissed", {
1882
1947
  actionClicked
@@ -1923,10 +1988,12 @@ function getAnchorReference(anchorEl) {
1923
1988
  }
1924
1989
  function showTooltip(anchorEl, overlayRoot, opts) {
1925
1990
  var _a2;
1926
- const rect = anchorEl.getBoundingClientRect();
1927
- const isLargeElement = rect.width > window.innerWidth * 0.8 || rect.height > window.innerHeight * 0.8;
1928
- if (!isLargeElement) {
1929
- anchorEl.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
1991
+ if (!opts.trigger || opts.trigger === "immediate") {
1992
+ const rect = anchorEl.getBoundingClientRect();
1993
+ const isLargeElement = rect.width > window.innerWidth * 0.8 || rect.height > window.innerHeight * 0.8;
1994
+ if (!isLargeElement) {
1995
+ anchorEl.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
1996
+ }
1930
1997
  }
1931
1998
  const div = document.createElement("div");
1932
1999
  div.className = "syntro-tooltip";
@@ -2136,7 +2203,12 @@ function showTooltip(anchorEl, overlayRoot, opts) {
2136
2203
  }
2137
2204
  div.style.pointerEvents = "none";
2138
2205
  div.style.opacity = "0";
2139
- setTimeout(() => div.remove(), 200);
2206
+ setTimeout(() => {
2207
+ try {
2208
+ div.remove();
2209
+ } catch {
2210
+ }
2211
+ }, 200);
2140
2212
  }
2141
2213
  };
2142
2214
  return handle;
@@ -2169,7 +2241,7 @@ function WorkflowTracker({ workflows, expanded, onStepClick, onDismiss }) {
2169
2241
  }
2170
2242
 
2171
2243
  // ../adaptives/adaptive-overlays/dist/WorkflowWidget.js
2172
- function showWorkflowToast(container, notification) {
2244
+ function showWorkflowToast(notification) {
2173
2245
  const toast = document.createElement("div");
2174
2246
  toast.setAttribute("data-testid", "workflow-toast");
2175
2247
  toast.className = "se-fixed se-bottom-4 se-right-4 se-z-50";
@@ -2203,7 +2275,7 @@ function showWorkflowToast(container, notification) {
2203
2275
  bodyEl.textContent = notification.body;
2204
2276
  toast.appendChild(bodyEl);
2205
2277
  }
2206
- container.appendChild(toast);
2278
+ document.body.appendChild(toast);
2207
2279
  let removeTimer;
2208
2280
  const fadeTimer = setTimeout(() => {
2209
2281
  toast.style.opacity = "0";
@@ -2302,10 +2374,10 @@ function WorkflowWidgetInner({ runtime: runtime3 }) {
2302
2374
  return newEntries.length > 0 ? [...prev, ...newEntries] : prev;
2303
2375
  });
2304
2376
  for (const [tourId, { meta }] of tourWorkflows) {
2305
- if (!notifiedRef.current.has(tourId) && meta.notification && containerRef.current && !dismissed.includes(tourId) && !completed[tourId]) {
2377
+ if (!notifiedRef.current.has(tourId) && meta.notification && !dismissed.includes(tourId) && !completed[tourId]) {
2306
2378
  notifiedRef.current.add(tourId);
2307
2379
  (_c = stateNs == null ? void 0 : stateNs.set) == null ? void 0 : _c.call(stateNs, "notified", [...notifiedRef.current]);
2308
- const cleanup = showWorkflowToast(containerRef.current, meta.notification);
2380
+ const cleanup = showWorkflowToast(meta.notification);
2309
2381
  toastCleanupsRef.current.push(cleanup);
2310
2382
  }
2311
2383
  }
@@ -2338,8 +2410,8 @@ function WorkflowWidgetInner({ runtime: runtime3 }) {
2338
2410
  notifiedRef.current.add(tourId);
2339
2411
  (_c = stateNs == null ? void 0 : stateNs.set) == null ? void 0 : _c.call(stateNs, "notified", [...notifiedRef.current]);
2340
2412
  const workflow = currentWorkflows.get(tourId);
2341
- if ((workflow == null ? void 0 : workflow.meta.notification) && containerRef.current) {
2342
- const cleanup = showWorkflowToast(containerRef.current, workflow.meta.notification);
2413
+ if (workflow == null ? void 0 : workflow.meta.notification) {
2414
+ const cleanup = showWorkflowToast(workflow.meta.notification);
2343
2415
  toastCleanupsRef.current.push(cleanup);
2344
2416
  }
2345
2417
  }
@@ -2443,6 +2515,10 @@ var executeHighlight = async (action, context) => {
2443
2515
  return { cleanup: () => {
2444
2516
  } };
2445
2517
  }
2518
+ if (anchorEl.getAttribute("data-syntro-highlight-dismissed")) {
2519
+ return { cleanup: () => {
2520
+ } };
2521
+ }
2446
2522
  const existing = anchorEl.getAttribute("data-syntro-highlight");
2447
2523
  if (existing) {
2448
2524
  const prev = context.overlayRoot.querySelectorAll(".syntro-spotlight-scrim, .syntro-spotlight-ring");
@@ -2465,7 +2541,10 @@ var executeHighlight = async (action, context) => {
2465
2541
  ringColor,
2466
2542
  blocking: (_i = action.blocking) != null ? _i : false,
2467
2543
  onClickOutside: (_j = action.onClickOutside) != null ? _j : true,
2468
- onEsc: (_k = action.onEsc) != null ? _k : true
2544
+ onEsc: (_k = action.onEsc) != null ? _k : true,
2545
+ onDismiss: () => {
2546
+ anchorEl.setAttribute("data-syntro-highlight-dismissed", "true");
2547
+ }
2469
2548
  });
2470
2549
  context.publishEvent("action.applied", {
2471
2550
  id: context.generateId(),
@@ -2476,6 +2555,7 @@ var executeHighlight = async (action, context) => {
2476
2555
  cleanup: () => {
2477
2556
  handle.destroy();
2478
2557
  anchorEl.removeAttribute("data-syntro-highlight");
2558
+ anchorEl.removeAttribute("data-syntro-highlight-dismissed");
2479
2559
  }
2480
2560
  };
2481
2561
  };
@@ -2565,6 +2645,8 @@ var executePulse = async (action, context) => {
2565
2645
  return {
2566
2646
  cleanup: () => {
2567
2647
  clearTimeout(timeoutId);
2648
+ if (!anchorEl.isConnected)
2649
+ return;
2568
2650
  anchorEl.style.animation = originalAnimation;
2569
2651
  anchorEl.removeAttribute("data-syntro-pulse");
2570
2652
  }
@@ -2634,7 +2716,12 @@ var executeBadge = async (action, context) => {
2634
2716
  });
2635
2717
  return {
2636
2718
  cleanup: () => {
2637
- badge2.remove();
2719
+ try {
2720
+ badge2.remove();
2721
+ } catch {
2722
+ }
2723
+ if (!anchorEl.isConnected)
2724
+ return;
2638
2725
  if (originalPosition !== void 0) {
2639
2726
  anchorEl.style.position = originalPosition;
2640
2727
  }
@@ -2851,6 +2938,14 @@ var CORE_ACTION_KINDS = /* @__PURE__ */ new Set([
2851
2938
  "core:parallel",
2852
2939
  "core:tour"
2853
2940
  ]);
2941
+ var BUNDLED_APP_IDS = /* @__PURE__ */ new Set([
2942
+ "adaptive-chatbot",
2943
+ "adaptive-content",
2944
+ "adaptive-faq",
2945
+ "adaptive-gamification",
2946
+ "adaptive-nav",
2947
+ "adaptive-overlays"
2948
+ ]);
2854
2949
  var NAMESPACE_TO_APP_ID = {
2855
2950
  faq: "adaptive-faq",
2856
2951
  nav: "adaptive-nav",
@@ -3003,6 +3098,9 @@ function createAppLoader(options) {
3003
3098
  }
3004
3099
  }
3005
3100
  }
3101
+ for (const bundled of BUNDLED_APP_IDS) {
3102
+ appIds.delete(bundled);
3103
+ }
3006
3104
  return Array.from(appIds);
3007
3105
  }
3008
3106
  async function loadAppsForConfig(config) {
@@ -3358,7 +3456,7 @@ function getAntiFlickerSnippet(config = {}) {
3358
3456
  }
3359
3457
 
3360
3458
  // src/version.ts
3361
- var SDK_VERSION = "2.8.0-canary.4";
3459
+ var SDK_VERSION = "2.8.0-canary.41";
3362
3460
 
3363
3461
  // src/types.ts
3364
3462
  var SDK_SCHEMA_VERSION = "2.0";
@@ -3641,7 +3739,8 @@ function registerFromTriggerWhen(triggerWhen, accumulator) {
3641
3739
  if (cond.type === "event_count" && cond.key) {
3642
3740
  const counter = cond.counter;
3643
3741
  const predicate = counter ? buildPredicate(counter) : () => true;
3644
- accumulator.register(cond.key, predicate);
3742
+ const needsTimestamps = typeof cond.withinMs === "number";
3743
+ accumulator.register(cond.key, predicate, needsTimestamps);
3645
3744
  }
3646
3745
  }
3647
3746
  }
@@ -3734,8 +3833,529 @@ function useShadowRoot() {
3734
3833
  return ctx;
3735
3834
  }
3736
3835
 
3737
- // src/events/types.ts
3836
+ // ../event-processor/dist/types.js
3837
+ var EVENT_SCHEMA_VERSION = "1.0.0";
3738
3838
  var StandardEvents = {
3839
+ UI_CLICK: "ui.click",
3840
+ UI_SCROLL: "ui.scroll",
3841
+ UI_INPUT: "ui.input",
3842
+ UI_CHANGE: "ui.change",
3843
+ UI_SUBMIT: "ui.submit",
3844
+ NAV_PAGE_VIEW: "nav.page_view",
3845
+ NAV_PAGE_LEAVE: "nav.page_leave",
3846
+ UI_HESITATE: "ui.hesitate",
3847
+ UI_RAGE_CLICK: "ui.rage_click",
3848
+ UI_SCROLL_THRASH: "ui.scroll_thrash",
3849
+ UI_FOCUS_BOUNCE: "ui.focus_bounce",
3850
+ UI_IDLE: "ui.idle",
3851
+ UI_HOVER: "ui.hover"
3852
+ };
3853
+ var RRWebSource = {
3854
+ Mutation: 0,
3855
+ MouseMove: 1,
3856
+ MouseInteraction: 2,
3857
+ Scroll: 3,
3858
+ ViewportResize: 4,
3859
+ Input: 5,
3860
+ TouchMove: 6,
3861
+ MediaInteraction: 7,
3862
+ Drag: 12
3863
+ };
3864
+ var RRWebMouseInteraction = {
3865
+ MouseUp: 0,
3866
+ MouseDown: 1,
3867
+ Click: 2,
3868
+ ContextMenu: 3,
3869
+ DblClick: 4,
3870
+ Focus: 5,
3871
+ Blur: 6,
3872
+ TouchStart: 7,
3873
+ TouchEnd: 9
3874
+ };
3875
+ var DEFAULT_DETECTOR_CONFIG = {
3876
+ hesitationMs: 3e3,
3877
+ hesitationRadiusPx: 10,
3878
+ rageClickCount: 3,
3879
+ rageClickWindowMs: 1e3,
3880
+ rageClickRadiusPx: 30,
3881
+ scrollThrashReversals: 3,
3882
+ scrollThrashWindowMs: 2e3,
3883
+ focusBounceMaxInputs: 0,
3884
+ idleMs: 5e3,
3885
+ hoverSampleMs: 100
3886
+ };
3887
+
3888
+ // ../event-processor/dist/normalizers/posthog.js
3889
+ var POSTHOG_EVENT_MAP = {
3890
+ // NOTE: $autocapture is intentionally NOT in this map.
3891
+ // It's handled below in getEventName() with $event_type refinement
3892
+ // so that change/submit events aren't all mapped to ui.click.
3893
+ $click: StandardEvents.UI_CLICK,
3894
+ $scroll: StandardEvents.UI_SCROLL,
3895
+ $input: StandardEvents.UI_INPUT,
3896
+ $change: StandardEvents.UI_CHANGE,
3897
+ $submit: StandardEvents.UI_SUBMIT,
3898
+ // Navigation events
3899
+ $pageview: StandardEvents.NAV_PAGE_VIEW,
3900
+ $pageleave: StandardEvents.NAV_PAGE_LEAVE,
3901
+ // Session events
3902
+ $session_start: "session.start",
3903
+ // Identify events
3904
+ $identify: "user.identify"
3905
+ };
3906
+ function getEventName(phEvent) {
3907
+ var _a2, _b;
3908
+ const eventName = phEvent.event;
3909
+ if (typeof eventName !== "string") {
3910
+ return "posthog.unknown";
3911
+ }
3912
+ if (POSTHOG_EVENT_MAP[eventName]) {
3913
+ return POSTHOG_EVENT_MAP[eventName];
3914
+ }
3915
+ if (eventName === "$autocapture") {
3916
+ const tagName = (_a2 = phEvent.properties) == null ? void 0 : _a2.$tag_name;
3917
+ const eventType = (_b = phEvent.properties) == null ? void 0 : _b.$event_type;
3918
+ if (eventType === "submit")
3919
+ return StandardEvents.UI_SUBMIT;
3920
+ if (eventType === "change")
3921
+ return StandardEvents.UI_CHANGE;
3922
+ if (tagName === "input" || tagName === "textarea")
3923
+ return StandardEvents.UI_INPUT;
3924
+ return StandardEvents.UI_CLICK;
3925
+ }
3926
+ if (!eventName.startsWith("$")) {
3927
+ return `posthog.${eventName}`;
3928
+ }
3929
+ return eventName.replace("$", "posthog.");
3930
+ }
3931
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["a", "button", "input", "select", "textarea"]);
3932
+ function resolveInteractiveTag(elements, directTag) {
3933
+ if (directTag && INTERACTIVE_TAGS.has(directTag))
3934
+ return directTag;
3935
+ if (!elements)
3936
+ return directTag;
3937
+ for (const el of elements) {
3938
+ const tag2 = el.tag_name;
3939
+ if (tag2 && INTERACTIVE_TAGS.has(tag2))
3940
+ return tag2;
3941
+ }
3942
+ return directTag;
3943
+ }
3944
+ function extractProps(phEvent) {
3945
+ var _a2, _b;
3946
+ const props = {};
3947
+ const phProps = phEvent.properties || {};
3948
+ const elements = phProps.$elements;
3949
+ const directTag = (_b = phProps.$tag_name) != null ? _b : (_a2 = elements == null ? void 0 : elements[0]) == null ? void 0 : _a2.tag_name;
3950
+ const isClickEvent = phEvent.event === "$autocapture" || phEvent.event === "$click";
3951
+ props.tagName = isClickEvent ? resolveInteractiveTag(elements, directTag) : directTag;
3952
+ if (phProps.$el_text)
3953
+ props.elementText = phProps.$el_text;
3954
+ if (elements)
3955
+ props.elements = elements;
3956
+ if (phProps.$current_url)
3957
+ props.url = phProps.$current_url;
3958
+ if (phProps.$pathname)
3959
+ props.pathname = phProps.$pathname;
3960
+ if (phProps.$host)
3961
+ props.host = phProps.$host;
3962
+ if (phProps.$viewport_width)
3963
+ props.viewportWidth = phProps.$viewport_width;
3964
+ if (phProps.$viewport_height)
3965
+ props.viewportHeight = phProps.$viewport_height;
3966
+ if (phProps.$session_id)
3967
+ props.sessionId = phProps.$session_id;
3968
+ if (phProps.$scroll_depth)
3969
+ props.scrollDepth = phProps.$scroll_depth;
3970
+ if (phProps.$scroll_percentage)
3971
+ props.scrollPercentage = phProps.$scroll_percentage;
3972
+ props.originalEvent = phEvent.event;
3973
+ return props;
3974
+ }
3975
+ function normalizePostHogEvent(phEvent) {
3976
+ let ts;
3977
+ if (typeof phEvent.timestamp === "number") {
3978
+ ts = phEvent.timestamp;
3979
+ } else if (typeof phEvent.timestamp === "string") {
3980
+ ts = new Date(phEvent.timestamp).getTime();
3981
+ } else {
3982
+ ts = Date.now();
3983
+ }
3984
+ return {
3985
+ ts,
3986
+ name: getEventName(phEvent),
3987
+ source: "posthog",
3988
+ props: extractProps(phEvent),
3989
+ schemaVersion: EVENT_SCHEMA_VERSION
3990
+ };
3991
+ }
3992
+ function shouldNormalizeEvent(phEvent) {
3993
+ const eventName = phEvent.event;
3994
+ if (typeof eventName !== "string")
3995
+ return false;
3996
+ const skipEvents = [
3997
+ "$feature_flag_called",
3998
+ "$feature_flags",
3999
+ "$groups",
4000
+ "$groupidentify",
4001
+ "$set",
4002
+ "$set_once",
4003
+ "$unset",
4004
+ "$create_alias",
4005
+ "$capture_metrics",
4006
+ "$performance_event",
4007
+ "$web_vitals",
4008
+ "$exception",
4009
+ "$dead_click",
4010
+ "$heatmap"
4011
+ ];
4012
+ if (skipEvents.includes(eventName)) {
4013
+ return false;
4014
+ }
4015
+ return true;
4016
+ }
4017
+ function createPostHogNormalizer(publishFn) {
4018
+ return (eventName, properties) => {
4019
+ if (typeof eventName !== "string")
4020
+ return;
4021
+ const phEvent = {
4022
+ event: eventName,
4023
+ properties,
4024
+ timestamp: Date.now()
4025
+ };
4026
+ if (shouldNormalizeEvent(phEvent)) {
4027
+ const normalizedEvent = normalizePostHogEvent(phEvent);
4028
+ publishFn(normalizedEvent);
4029
+ }
4030
+ };
4031
+ }
4032
+
4033
+ // ../event-processor/dist/detectors/focus-bounce.js
4034
+ var FocusBounceDetector = class {
4035
+ constructor(config, emit2) {
4036
+ this.config = config;
4037
+ this.emit = emit2;
4038
+ this.focused = /* @__PURE__ */ new Map();
4039
+ }
4040
+ ingest(raw) {
4041
+ var _a2, _b;
4042
+ if (raw.type !== 3)
4043
+ return;
4044
+ const ts = raw.timestamp;
4045
+ if (raw.data.source === RRWebSource.MouseInteraction) {
4046
+ const id = (_a2 = raw.data.id) != null ? _a2 : 0;
4047
+ if (raw.data.type === RRWebMouseInteraction.Focus) {
4048
+ this.focused.set(id, { id, focusTs: ts, inputCount: 0 });
4049
+ } else if (raw.data.type === RRWebMouseInteraction.Blur) {
4050
+ const entry = this.focused.get(id);
4051
+ if (entry && entry.inputCount <= this.config.focusBounceMaxInputs) {
4052
+ this.emit({
4053
+ ts,
4054
+ name: StandardEvents.UI_FOCUS_BOUNCE,
4055
+ source: "rrweb",
4056
+ schemaVersion: EVENT_SCHEMA_VERSION,
4057
+ props: {
4058
+ elementId: id,
4059
+ duration_ms: ts - entry.focusTs
4060
+ }
4061
+ });
4062
+ }
4063
+ this.focused.delete(id);
4064
+ }
4065
+ } else if (raw.data.source === RRWebSource.Input) {
4066
+ const id = (_b = raw.data.id) != null ? _b : 0;
4067
+ const entry = this.focused.get(id);
4068
+ if (entry) {
4069
+ entry.inputCount++;
4070
+ }
4071
+ }
4072
+ }
4073
+ };
4074
+
4075
+ // ../event-processor/dist/detectors/hesitation.js
4076
+ var HesitationDetector = class {
4077
+ constructor(config, emit2) {
4078
+ this.config = config;
4079
+ this.emit = emit2;
4080
+ this.anchorX = 0;
4081
+ this.anchorY = 0;
4082
+ this.anchorTs = 0;
4083
+ this.emitted = false;
4084
+ this.hasPosition = false;
4085
+ }
4086
+ ingest(raw) {
4087
+ var _a2;
4088
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4089
+ return;
4090
+ const positions = raw.data.positions;
4091
+ if (!positions || positions.length === 0)
4092
+ return;
4093
+ const last = positions[positions.length - 1];
4094
+ const ts = raw.timestamp + ((_a2 = last.timeOffset) != null ? _a2 : 0);
4095
+ if (!this.hasPosition) {
4096
+ this.anchorX = last.x;
4097
+ this.anchorY = last.y;
4098
+ this.anchorTs = ts;
4099
+ this.hasPosition = true;
4100
+ this.emitted = false;
4101
+ return;
4102
+ }
4103
+ const dx = last.x - this.anchorX;
4104
+ const dy = last.y - this.anchorY;
4105
+ const dist = Math.sqrt(dx * dx + dy * dy);
4106
+ if (dist > this.config.hesitationRadiusPx) {
4107
+ this.anchorX = last.x;
4108
+ this.anchorY = last.y;
4109
+ this.anchorTs = ts;
4110
+ this.emitted = false;
4111
+ }
4112
+ }
4113
+ tick(now) {
4114
+ if (!this.hasPosition || this.emitted)
4115
+ return;
4116
+ const elapsed = now - this.anchorTs;
4117
+ if (elapsed >= this.config.hesitationMs) {
4118
+ this.emit({
4119
+ ts: now,
4120
+ name: StandardEvents.UI_HESITATE,
4121
+ source: "rrweb",
4122
+ schemaVersion: EVENT_SCHEMA_VERSION,
4123
+ props: {
4124
+ x: this.anchorX,
4125
+ y: this.anchorY,
4126
+ duration_ms: elapsed
4127
+ }
4128
+ });
4129
+ this.emitted = true;
4130
+ }
4131
+ }
4132
+ };
4133
+
4134
+ // ../event-processor/dist/detectors/hover.js
4135
+ var HoverTracker = class {
4136
+ constructor(config, emit2, elementResolver) {
4137
+ this.config = config;
4138
+ this.emit = emit2;
4139
+ this.elementResolver = elementResolver;
4140
+ this.currentElement = null;
4141
+ this.hoverStartTs = null;
4142
+ this.lastX = 0;
4143
+ this.lastY = 0;
4144
+ this.lastSampleTs = -Infinity;
4145
+ }
4146
+ ingest(raw) {
4147
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4148
+ return;
4149
+ const positions = raw.data.positions;
4150
+ if (!positions || positions.length === 0)
4151
+ return;
4152
+ const last = positions[positions.length - 1];
4153
+ this.lastX = last.x;
4154
+ this.lastY = last.y;
4155
+ }
4156
+ tick(now) {
4157
+ if (!this.elementResolver)
4158
+ return;
4159
+ if (now - this.lastSampleTs < this.config.hoverSampleMs)
4160
+ return;
4161
+ this.lastSampleTs = now;
4162
+ const newElement = this.elementResolver(this.lastX, this.lastY);
4163
+ const newKey = newElement ? elementKey(newElement) : null;
4164
+ const currentKey = this.currentElement ? elementKey(this.currentElement) : null;
4165
+ if (newKey !== currentKey) {
4166
+ if (this.currentElement && this.hoverStartTs !== null) {
4167
+ this.emit({
4168
+ ts: now,
4169
+ name: StandardEvents.UI_HOVER,
4170
+ source: "rrweb",
4171
+ schemaVersion: EVENT_SCHEMA_VERSION,
4172
+ props: {
4173
+ x: this.lastX,
4174
+ y: this.lastY,
4175
+ duration_ms: now - this.hoverStartTs,
4176
+ element: this.currentElement
4177
+ }
4178
+ });
4179
+ }
4180
+ this.currentElement = newElement;
4181
+ this.hoverStartTs = now;
4182
+ }
4183
+ }
4184
+ };
4185
+ function elementKey(el) {
4186
+ var _a2, _b, _c;
4187
+ return `${(_a2 = el.tag_name) != null ? _a2 : ""}|${(_b = el.attr__id) != null ? _b : ""}|${((_c = el.classes) != null ? _c : []).join(",")}`;
4188
+ }
4189
+
4190
+ // ../event-processor/dist/detectors/idle.js
4191
+ var IdleDetector = class {
4192
+ constructor(config, emit2) {
4193
+ this.config = config;
4194
+ this.emit = emit2;
4195
+ this.lastActivityTs = null;
4196
+ this.emitted = false;
4197
+ }
4198
+ ingest(raw) {
4199
+ if (raw.type !== 3)
4200
+ return;
4201
+ const src = raw.data.source;
4202
+ if (src === RRWebSource.MouseMove || src === RRWebSource.MouseInteraction || src === RRWebSource.Scroll) {
4203
+ this.lastActivityTs = raw.timestamp;
4204
+ this.emitted = false;
4205
+ }
4206
+ }
4207
+ tick(now) {
4208
+ if (this.lastActivityTs === null || this.emitted)
4209
+ return;
4210
+ if (now - this.lastActivityTs >= this.config.idleMs) {
4211
+ this.emit({
4212
+ ts: now,
4213
+ name: StandardEvents.UI_IDLE,
4214
+ source: "rrweb",
4215
+ schemaVersion: EVENT_SCHEMA_VERSION,
4216
+ props: {
4217
+ idle_ms: now - this.lastActivityTs
4218
+ }
4219
+ });
4220
+ this.emitted = true;
4221
+ }
4222
+ }
4223
+ };
4224
+
4225
+ // ../event-processor/dist/detectors/rage-click.js
4226
+ var RageClickDetector = class {
4227
+ constructor(config, emit2) {
4228
+ this.config = config;
4229
+ this.emit = emit2;
4230
+ this.clicks = [];
4231
+ }
4232
+ ingest(raw) {
4233
+ var _a2, _b;
4234
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseInteraction)
4235
+ return;
4236
+ if (raw.data.type !== RRWebMouseInteraction.Click)
4237
+ return;
4238
+ const x = (_a2 = raw.data.x) != null ? _a2 : 0;
4239
+ const y = (_b = raw.data.y) != null ? _b : 0;
4240
+ const ts = raw.timestamp;
4241
+ const cutoff = ts - this.config.rageClickWindowMs;
4242
+ this.clicks = this.clicks.filter((c) => c.ts >= cutoff);
4243
+ this.clicks.push({ ts, x, y });
4244
+ const nearby = this.clicks.filter((c) => {
4245
+ const dx = c.x - x;
4246
+ const dy = c.y - y;
4247
+ return Math.sqrt(dx * dx + dy * dy) <= this.config.rageClickRadiusPx;
4248
+ });
4249
+ if (nearby.length >= this.config.rageClickCount) {
4250
+ this.emit({
4251
+ ts,
4252
+ name: StandardEvents.UI_RAGE_CLICK,
4253
+ source: "rrweb",
4254
+ schemaVersion: EVENT_SCHEMA_VERSION,
4255
+ props: {
4256
+ x,
4257
+ y,
4258
+ clickCount: nearby.length,
4259
+ duration_ms: ts - nearby[0].ts
4260
+ }
4261
+ });
4262
+ this.clicks = [];
4263
+ }
4264
+ }
4265
+ };
4266
+
4267
+ // ../event-processor/dist/detectors/scroll-thrash.js
4268
+ var ScrollThrashDetector = class {
4269
+ constructor(config, emit2) {
4270
+ this.config = config;
4271
+ this.emit = emit2;
4272
+ this.lastY = null;
4273
+ this.lastDirection = null;
4274
+ this.reversals = [];
4275
+ }
4276
+ ingest(raw) {
4277
+ var _a2;
4278
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.Scroll)
4279
+ return;
4280
+ const y = (_a2 = raw.data.y) != null ? _a2 : 0;
4281
+ const ts = raw.timestamp;
4282
+ if (this.lastY !== null) {
4283
+ const direction = y > this.lastY ? "down" : y < this.lastY ? "up" : this.lastDirection;
4284
+ if (direction && direction !== this.lastDirection) {
4285
+ const cutoff = ts - this.config.scrollThrashWindowMs;
4286
+ this.reversals = this.reversals.filter((t) => t > cutoff);
4287
+ this.reversals.push(ts);
4288
+ if (this.reversals.length >= this.config.scrollThrashReversals) {
4289
+ this.emit({
4290
+ ts,
4291
+ name: StandardEvents.UI_SCROLL_THRASH,
4292
+ source: "rrweb",
4293
+ schemaVersion: EVENT_SCHEMA_VERSION,
4294
+ props: {
4295
+ reversals: this.reversals.length,
4296
+ duration_ms: ts - this.reversals[0]
4297
+ }
4298
+ });
4299
+ this.reversals = [];
4300
+ }
4301
+ }
4302
+ this.lastDirection = direction;
4303
+ }
4304
+ this.lastY = y;
4305
+ }
4306
+ };
4307
+
4308
+ // ../event-processor/dist/processor.js
4309
+ function createEventProcessor(options) {
4310
+ const config = { ...DEFAULT_DETECTOR_CONFIG, ...options == null ? void 0 : options.config };
4311
+ const listeners = [];
4312
+ function emit2(event) {
4313
+ for (const cb of listeners)
4314
+ cb(event);
4315
+ }
4316
+ const hesitation = new HesitationDetector(config, emit2);
4317
+ const rageClick = new RageClickDetector(config, emit2);
4318
+ const scrollThrash = new ScrollThrashDetector(config, emit2);
4319
+ const focusBounce = new FocusBounceDetector(config, emit2);
4320
+ const idle = new IdleDetector(config, emit2);
4321
+ const hover = new HoverTracker(config, emit2, options == null ? void 0 : options.elementResolver);
4322
+ const clickAttrBuffer = [];
4323
+ return {
4324
+ ingest(raw) {
4325
+ if (raw.kind === "posthog") {
4326
+ const { kind: _, ...phEvent } = raw;
4327
+ if (shouldNormalizeEvent(phEvent)) {
4328
+ emit2(normalizePostHogEvent(phEvent));
4329
+ }
4330
+ } else if (raw.kind === "rrweb") {
4331
+ hesitation.ingest(raw);
4332
+ rageClick.ingest(raw);
4333
+ scrollThrash.ingest(raw);
4334
+ focusBounce.ingest(raw);
4335
+ idle.ingest(raw);
4336
+ hover.ingest(raw);
4337
+ }
4338
+ },
4339
+ onEvent(callback) {
4340
+ listeners.push(callback);
4341
+ },
4342
+ tick(timestamp) {
4343
+ hesitation.tick(timestamp);
4344
+ idle.tick(timestamp);
4345
+ hover.tick(timestamp);
4346
+ },
4347
+ enrichClickAttributes(timestamp, elements) {
4348
+ clickAttrBuffer.push({ ts: timestamp, elements });
4349
+ const cutoff = timestamp - 500;
4350
+ while (clickAttrBuffer.length > 0 && clickAttrBuffer[0].ts < cutoff) {
4351
+ clickAttrBuffer.shift();
4352
+ }
4353
+ }
4354
+ };
4355
+ }
4356
+
4357
+ // src/events/types.ts
4358
+ var StandardEvents2 = {
3739
4359
  // UI events (from PostHog autocapture)
3740
4360
  UI_CLICK: "ui.click",
3741
4361
  UI_SCROLL: "ui.scroll",
@@ -3775,7 +4395,6 @@ var StandardEvents = {
3775
4395
  SURFACE_MOUNTED: "surface.mounted",
3776
4396
  SURFACE_UNMOUNTED: "surface.unmounted"
3777
4397
  };
3778
- var EVENT_SCHEMA_VERSION = "1.0.0";
3779
4398
 
3780
4399
  // src/events/normalizers/canvas.ts
3781
4400
  function createCanvasEvent(name, props) {
@@ -3788,48 +4407,48 @@ function createCanvasEvent(name, props) {
3788
4407
  };
3789
4408
  }
3790
4409
  function canvasOpened(surface) {
3791
- return createCanvasEvent(StandardEvents.CANVAS_OPENED, { surface });
4410
+ return createCanvasEvent(StandardEvents2.CANVAS_OPENED, { surface });
3792
4411
  }
3793
4412
  function canvasClosed(surface) {
3794
- return createCanvasEvent(StandardEvents.CANVAS_CLOSED, { surface });
4413
+ return createCanvasEvent(StandardEvents2.CANVAS_CLOSED, { surface });
3795
4414
  }
3796
4415
  function tileViewed(tileId, surface) {
3797
- return createCanvasEvent(StandardEvents.TILE_VIEWED, { tileId, surface });
4416
+ return createCanvasEvent(StandardEvents2.TILE_VIEWED, { tileId, surface });
3798
4417
  }
3799
4418
  function tileExpanded(tileId, surface) {
3800
- return createCanvasEvent(StandardEvents.TILE_EXPANDED, { tileId, surface });
4419
+ return createCanvasEvent(StandardEvents2.TILE_EXPANDED, { tileId, surface });
3801
4420
  }
3802
4421
  function tileCollapsed(tileId, surface) {
3803
- return createCanvasEvent(StandardEvents.TILE_COLLAPSED, { tileId, surface });
4422
+ return createCanvasEvent(StandardEvents2.TILE_COLLAPSED, { tileId, surface });
3804
4423
  }
3805
4424
  function tileAction(tileId, actionId, surface) {
3806
- return createCanvasEvent(StandardEvents.TILE_ACTION, {
4425
+ return createCanvasEvent(StandardEvents2.TILE_ACTION, {
3807
4426
  tileId,
3808
4427
  actionId,
3809
4428
  surface
3810
4429
  });
3811
4430
  }
3812
4431
  function overlayStarted(recipeId, recipeName) {
3813
- return createCanvasEvent(StandardEvents.OVERLAY_STARTED, {
4432
+ return createCanvasEvent(StandardEvents2.OVERLAY_STARTED, {
3814
4433
  recipeId,
3815
4434
  recipeName
3816
4435
  });
3817
4436
  }
3818
4437
  function overlayCompleted(recipeId, recipeName) {
3819
- return createCanvasEvent(StandardEvents.OVERLAY_COMPLETED, {
4438
+ return createCanvasEvent(StandardEvents2.OVERLAY_COMPLETED, {
3820
4439
  recipeId,
3821
4440
  recipeName
3822
4441
  });
3823
4442
  }
3824
4443
  function overlayDismissed(recipeId, recipeName, stepIndex) {
3825
- return createCanvasEvent(StandardEvents.OVERLAY_DISMISSED, {
4444
+ return createCanvasEvent(StandardEvents2.OVERLAY_DISMISSED, {
3826
4445
  recipeId,
3827
4446
  recipeName,
3828
4447
  stepIndex
3829
4448
  });
3830
4449
  }
3831
4450
  function overlayStepViewed(recipeId, stepIndex, stepTitle) {
3832
- return createCanvasEvent(StandardEvents.OVERLAY_STEP_VIEWED, {
4451
+ return createCanvasEvent(StandardEvents2.OVERLAY_STEP_VIEWED, {
3833
4452
  recipeId,
3834
4453
  stepIndex,
3835
4454
  stepTitle
@@ -3853,8 +4472,65 @@ var CanvasEvents = {
3853
4472
  custom: customCanvasEvent
3854
4473
  };
3855
4474
 
3856
- // src/notifications/NotificationToastStack.tsx
3857
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
4475
+ // src/components/emojiToIcon.tsx
4476
+ import {
4477
+ AlertTriangle,
4478
+ ArrowRight,
4479
+ Banknote,
4480
+ Bell,
4481
+ BookOpen,
4482
+ CheckCircle,
4483
+ ClipboardList,
4484
+ Compass,
4485
+ FileText,
4486
+ Gamepad2,
4487
+ HelpCircle,
4488
+ Landmark,
4489
+ Layers,
4490
+ Lightbulb,
4491
+ MessageCircle,
4492
+ SkipForward,
4493
+ Sparkles,
4494
+ Timer,
4495
+ Trophy
4496
+ } from "lucide-react";
4497
+ import { jsx as jsx2 } from "react/jsx-runtime";
4498
+ var EMOJI_ICON_MAP = {
4499
+ "\u2753": HelpCircle,
4500
+ "\u{1F9ED}": Compass,
4501
+ "\u{1F4DD}": FileText,
4502
+ "\u{1F3AF}": Layers,
4503
+ "\u{1F3C6}": Trophy,
4504
+ "\u2728": Sparkles,
4505
+ "\u{1F4AC}": MessageCircle,
4506
+ "\u{1F3AE}": Gamepad2,
4507
+ "\u{1F4A1}": Lightbulb,
4508
+ "\u{1F4B0}": Banknote,
4509
+ "\u{1F4CB}": ClipboardList,
4510
+ "\u2705": CheckCircle,
4511
+ "\u26A0\uFE0F": AlertTriangle,
4512
+ "\u{1F4B5}": Banknote,
4513
+ "\u{1F3DB}\uFE0F": Landmark,
4514
+ "\u23ED\uFE0F": SkipForward,
4515
+ "\u27A1\uFE0F": ArrowRight,
4516
+ "\u23F1\uFE0F": Timer,
4517
+ "\u{1F4D6}": BookOpen,
4518
+ "\u{1F514}": Bell
4519
+ };
4520
+ function EmojiIcon({
4521
+ emoji,
4522
+ size = 14,
4523
+ color = "currentColor"
4524
+ }) {
4525
+ const Icon = EMOJI_ICON_MAP[emoji];
4526
+ if (!Icon) {
4527
+ return /* @__PURE__ */ jsx2("span", { children: emoji });
4528
+ }
4529
+ return /* @__PURE__ */ jsx2(Icon, { size, color });
4530
+ }
4531
+
4532
+ // src/notifications/NotificationToastStack.tsx
4533
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
3858
4534
  var TOAST_STYLES_ID = "syntro-toast-styles";
3859
4535
  var TOAST_CSS = `
3860
4536
  @keyframes syntro-toast-slide-in {
@@ -3891,7 +4567,7 @@ function NotificationToastStack({
3891
4567
  const { shadowRoot } = useShadowRoot();
3892
4568
  ensureToastStyles(shadowRoot);
3893
4569
  if (notifications.length === 0) return null;
3894
- return /* @__PURE__ */ jsx2(
4570
+ return /* @__PURE__ */ jsx3(
3895
4571
  "div",
3896
4572
  {
3897
4573
  "data-testid": "notification-toast-stack",
@@ -3946,7 +4622,7 @@ function NotificationToastStack({
3946
4622
  padding: "10px 12px"
3947
4623
  },
3948
4624
  children: [
3949
- /* @__PURE__ */ jsx2(
4625
+ /* @__PURE__ */ jsx3(
3950
4626
  "div",
3951
4627
  {
3952
4628
  style: {
@@ -3960,11 +4636,11 @@ function NotificationToastStack({
3960
4636
  flexShrink: 0,
3961
4637
  fontSize: "14px"
3962
4638
  },
3963
- children: (_a2 = notif.icon) != null ? _a2 : "\u{1F514}"
4639
+ children: /* @__PURE__ */ jsx3(EmojiIcon, { emoji: (_a2 = notif.icon) != null ? _a2 : "\u{1F514}", size: 14 })
3964
4640
  }
3965
4641
  ),
3966
4642
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
3967
- /* @__PURE__ */ jsx2(
4643
+ /* @__PURE__ */ jsx3(
3968
4644
  "div",
3969
4645
  {
3970
4646
  style: {
@@ -3979,7 +4655,7 @@ function NotificationToastStack({
3979
4655
  children: notif.title
3980
4656
  }
3981
4657
  ),
3982
- notif.body && /* @__PURE__ */ jsx2(
4658
+ notif.body && /* @__PURE__ */ jsx3(
3983
4659
  "div",
3984
4660
  {
3985
4661
  style: {
@@ -3995,7 +4671,7 @@ function NotificationToastStack({
3995
4671
  }
3996
4672
  )
3997
4673
  ] }),
3998
- /* @__PURE__ */ jsx2(
4674
+ /* @__PURE__ */ jsx3(
3999
4675
  "button",
4000
4676
  {
4001
4677
  type: "button",
@@ -4020,7 +4696,7 @@ function NotificationToastStack({
4020
4696
  ]
4021
4697
  }
4022
4698
  ),
4023
- /* @__PURE__ */ jsx2("div", { style: { height: "2px", background: "rgba(0, 0, 0, 0.08)" }, children: /* @__PURE__ */ jsx2(
4699
+ /* @__PURE__ */ jsx3("div", { style: { height: "2px", background: "rgba(0, 0, 0, 0.08)" }, children: /* @__PURE__ */ jsx3(
4024
4700
  "div",
4025
4701
  {
4026
4702
  className: "syntro-toast-progress",
@@ -4098,7 +4774,7 @@ function useNotifications(eventBus, tiles) {
4098
4774
  const timerIds = useRef2(/* @__PURE__ */ new Map());
4099
4775
  const publishDismissed = useCallback2(
4100
4776
  (notif) => {
4101
- eventBus == null ? void 0 : eventBus.publish(StandardEvents.NOTIFICATION_DISMISSED, {
4777
+ eventBus == null ? void 0 : eventBus.publish(StandardEvents2.NOTIFICATION_DISMISSED, {
4102
4778
  notificationId: notif.id,
4103
4779
  tileId: notif.tileId,
4104
4780
  itemId: notif.itemId
@@ -4163,7 +4839,7 @@ function useNotifications(eventBus, tiles) {
4163
4839
  }
4164
4840
  return next;
4165
4841
  });
4166
- eventBus.publish(StandardEvents.NOTIFICATION_SHOWN, {
4842
+ eventBus.publish(StandardEvents2.NOTIFICATION_SHOWN, {
4167
4843
  notificationId: matched.id,
4168
4844
  tileId: matched.tileId,
4169
4845
  itemId: matched.itemId,
@@ -4234,7 +4910,7 @@ function useNotifyWatcher(runtime3, tiles, appRegistry2) {
4234
4910
 
4235
4911
  // src/RuntimeProvider.tsx
4236
4912
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo2, useState as useState3 } from "react";
4237
- import { jsx as jsx3 } from "react/jsx-runtime";
4913
+ import { jsx as jsx4 } from "react/jsx-runtime";
4238
4914
  var RuntimeReactContext = createContext2({
4239
4915
  runtime: null,
4240
4916
  context: null
@@ -4252,7 +4928,7 @@ function RuntimeProvider({ runtime: runtime3, children }) {
4252
4928
  return unsubscribe;
4253
4929
  }, [runtime3]);
4254
4930
  const value = useMemo2(() => ({ runtime: runtime3, context }), [runtime3, context]);
4255
- return /* @__PURE__ */ jsx3(RuntimeReactContext.Provider, { value, children });
4931
+ return /* @__PURE__ */ jsx4(RuntimeReactContext.Provider, { value, children });
4256
4932
  }
4257
4933
  function useRuntime() {
4258
4934
  const { runtime: runtime3 } = useContext2(RuntimeReactContext);
@@ -4326,41 +5002,17 @@ import {
4326
5002
  } from "react";
4327
5003
 
4328
5004
  // src/components/TileIcon.tsx
4329
- import {
4330
- Compass,
4331
- FileText,
4332
- Gamepad2,
4333
- HelpCircle,
4334
- Layers,
4335
- MessageCircle,
4336
- Sparkles,
4337
- Trophy
4338
- } from "lucide-react";
4339
- import { jsx as jsx4 } from "react/jsx-runtime";
4340
- var ICON_MAP = {
4341
- "\u2753": HelpCircle,
4342
- "\u{1F9ED}": Compass,
4343
- "\u{1F4DD}": FileText,
4344
- "\u{1F3AF}": Layers,
4345
- "\u{1F3C6}": Trophy,
4346
- "\u2728": Sparkles,
4347
- "\u{1F4AC}": MessageCircle,
4348
- "\u{1F3AE}": Gamepad2
4349
- };
5005
+ import { jsx as jsx5 } from "react/jsx-runtime";
4350
5006
  function TileIcon({
4351
5007
  emoji,
4352
5008
  size = 18,
4353
5009
  color = "currentColor"
4354
5010
  }) {
4355
- const Icon = ICON_MAP[emoji];
4356
- if (!Icon) {
4357
- return /* @__PURE__ */ jsx4("span", { children: emoji });
4358
- }
4359
- return /* @__PURE__ */ jsx4(Icon, { size, color });
5011
+ return /* @__PURE__ */ jsx5(EmojiIcon, { emoji, size, color });
4360
5012
  }
4361
5013
 
4362
5014
  // src/components/TileCard.tsx
4363
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
5015
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
4364
5016
  function WidgetMount({ widgetId, props }) {
4365
5017
  var _a2;
4366
5018
  const runtime3 = useRuntime();
@@ -4390,7 +5042,6 @@ function WidgetMount({ widgetId, props }) {
4390
5042
  return () => {
4391
5043
  handle.unmount();
4392
5044
  handleRef.current = null;
4393
- container.remove();
4394
5045
  };
4395
5046
  }, [registry, widgetId, widgetAvailable]);
4396
5047
  const propsJson = JSON.stringify(props);
@@ -4401,7 +5052,7 @@ function WidgetMount({ widgetId, props }) {
4401
5052
  prevPropsJsonRef.current = propsJson;
4402
5053
  (_a3 = handleRef.current) == null ? void 0 : _a3.update(propsRef.current);
4403
5054
  }, [propsJson]);
4404
- if (!registry || !registry.has(widgetId)) {
5055
+ if (!(registry == null ? void 0 : registry.has(widgetId))) {
4405
5056
  return /* @__PURE__ */ jsxs2(
4406
5057
  "div",
4407
5058
  {
@@ -4418,7 +5069,7 @@ function WidgetMount({ widgetId, props }) {
4418
5069
  }
4419
5070
  );
4420
5071
  }
4421
- return /* @__PURE__ */ jsx5("div", { ref: parentRef });
5072
+ return /* @__PURE__ */ jsx6("div", { ref: parentRef });
4422
5073
  }
4423
5074
  function TileCard({
4424
5075
  config,
@@ -4491,9 +5142,9 @@ function TileCard({
4491
5142
  onMouseLeave,
4492
5143
  children: [
4493
5144
  /* @__PURE__ */ jsxs2("div", { style: headerStyle, children: [
4494
- /* @__PURE__ */ jsx5("div", { style: iconStyle, children: /* @__PURE__ */ jsx5(TileIcon, { emoji: resolvedIcon, size: resolvedSubtitle ? 36 : 24 }) }),
5145
+ /* @__PURE__ */ jsx6("div", { style: iconStyle, children: /* @__PURE__ */ jsx6(TileIcon, { emoji: resolvedIcon, size: resolvedSubtitle ? 36 : 24 }) }),
4495
5146
  /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0 }, children: [
4496
- /* @__PURE__ */ jsx5(
5147
+ /* @__PURE__ */ jsx6(
4497
5148
  "h3",
4498
5149
  {
4499
5150
  style: {
@@ -4508,7 +5159,7 @@ function TileCard({
4508
5159
  children: title != null ? title : widget
4509
5160
  }
4510
5161
  ),
4511
- resolvedSubtitle && /* @__PURE__ */ jsx5(
5162
+ resolvedSubtitle && /* @__PURE__ */ jsx6(
4512
5163
  "p",
4513
5164
  {
4514
5165
  style: {
@@ -4525,14 +5176,14 @@ function TileCard({
4525
5176
  )
4526
5177
  ] })
4527
5178
  ] }),
4528
- /* @__PURE__ */ jsx5(
5179
+ /* @__PURE__ */ jsx6(
4529
5180
  "div",
4530
5181
  {
4531
5182
  style: {
4532
5183
  padding: "var(--sc-tile-body-padding, 0 0.75rem 0.5rem)",
4533
5184
  borderTop: "1px solid rgba(255, 255, 255, 0.06)"
4534
5185
  },
4535
- children: /* @__PURE__ */ jsx5("div", { style: { paddingTop: "var(--sc-tile-gap, 0.25rem)" }, children: /* @__PURE__ */ jsx5(WidgetMount, { widgetId: widget, props: { ...props, instanceId: config.id } }) })
5186
+ children: /* @__PURE__ */ jsx6("div", { style: { paddingTop: "var(--sc-tile-gap, 0.25rem)" }, children: /* @__PURE__ */ jsx6(WidgetMount, { widgetId: widget, props: { ...props, instanceId: config.id } }) })
4536
5187
  }
4537
5188
  )
4538
5189
  ]
@@ -4940,7 +5591,7 @@ function flattenThemeConfig(config) {
4940
5591
 
4941
5592
  // src/theme/ThemeProvider.tsx
4942
5593
  import { createContext as createContext3, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo4 } from "react";
4943
- import { jsx as jsx6 } from "react/jsx-runtime";
5594
+ import { jsx as jsx7 } from "react/jsx-runtime";
4944
5595
  var ThemeContext = createContext3(null);
4945
5596
  function ThemeProvider({
4946
5597
  children,
@@ -4971,7 +5622,7 @@ ${cssRules}
4971
5622
  mode: merged.mode,
4972
5623
  cssVariables
4973
5624
  };
4974
- return /* @__PURE__ */ jsx6(ThemeContext.Provider, { value, children });
5625
+ return /* @__PURE__ */ jsx7(ThemeContext.Provider, { value, children });
4975
5626
  }
4976
5627
  function useTheme() {
4977
5628
  const context = useContext3(ThemeContext);
@@ -4982,7 +5633,7 @@ function useTheme() {
4982
5633
  }
4983
5634
 
4984
5635
  // src/components/ShadowCanvasOverlay.tsx
4985
- import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
5636
+ import { Fragment, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
4986
5637
  var LAUNCHER_STYLES_ID = "syntro-launcher-styles";
4987
5638
  function ensureLauncherStyles(target, css) {
4988
5639
  if (target.querySelector(`#${LAUNCHER_STYLES_ID}`)) return;
@@ -5022,7 +5673,7 @@ function ShadowCanvasOverlay({
5022
5673
  const handleNotificationClick = useCallback4(
5023
5674
  (notif) => {
5024
5675
  if (runtime3) {
5025
- runtime3.events.publish(StandardEvents.NOTIFICATION_CLICKED, {
5676
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_CLICKED, {
5026
5677
  notificationId: notif.id,
5027
5678
  tileId: notif.tileId,
5028
5679
  itemId: notif.itemId
@@ -5032,7 +5683,7 @@ function ShadowCanvasOverlay({
5032
5683
  onToggle();
5033
5684
  }
5034
5685
  if (runtime3 && notif.tileId) {
5035
- runtime3.events.publish(StandardEvents.NOTIFICATION_DEEP_LINK, {
5686
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_DEEP_LINK, {
5036
5687
  tileId: notif.tileId,
5037
5688
  itemId: notif.itemId
5038
5689
  });
@@ -5105,6 +5756,17 @@ function ShadowCanvasOverlay({
5105
5756
  }
5106
5757
  onToggle();
5107
5758
  }, [isOpen, telemetry, runtime3, onToggle]);
5759
+ useEffect7(() => {
5760
+ if (!isOpen) return;
5761
+ const handleOutsideClick = (e) => {
5762
+ const path = e.composedPath();
5763
+ if (containerRef.current && !path.includes(containerRef.current) && launcherRef.current && !path.includes(launcherRef.current)) {
5764
+ toggle2();
5765
+ }
5766
+ };
5767
+ document.addEventListener("mousedown", handleOutsideClick);
5768
+ return () => document.removeEventListener("mousedown", handleOutsideClick);
5769
+ }, [isOpen, toggle2]);
5108
5770
  const onLauncherPointerDown = useCallback4((e) => {
5109
5771
  const rect = e.currentTarget.getBoundingClientRect();
5110
5772
  dragRef.current = {
@@ -5143,6 +5805,7 @@ function ShadowCanvasOverlay({
5143
5805
  const isPush = config.canvas.layout === "push";
5144
5806
  const canvasBorder = (_b = config.canvas.border) != null ? _b : "none";
5145
5807
  const containerRef = useRef5(null);
5808
+ const launcherRef = useRef5(null);
5146
5809
  const zIndex = 2147483600;
5147
5810
  useEffect7(() => {
5148
5811
  var _a3, _b2, _c2, _d2;
@@ -5223,19 +5886,19 @@ function ShadowCanvasOverlay({
5223
5886
  pointerEvents: "none",
5224
5887
  padding: "0"
5225
5888
  };
5226
- const content = /* @__PURE__ */ jsx7(
5889
+ const content = /* @__PURE__ */ jsx8(
5227
5890
  "div",
5228
5891
  {
5229
5892
  "data-shadow-canvas-id": "overlay-root",
5230
5893
  style: {
5231
5894
  position: "fixed",
5232
5895
  inset: 0,
5233
- pointerEvents: isOpen ? "auto" : "none",
5896
+ pointerEvents: "none",
5234
5897
  zIndex
5235
5898
  },
5236
5899
  children: /* @__PURE__ */ jsxs3("div", { style: wrapperStyle, children: [
5237
5900
  /* @__PURE__ */ jsxs3("div", { ref: containerRef, "data-shadow-canvas-id": "overlay-container", style: containerStyle, children: [
5238
- isFocused && canvasTitle && /* @__PURE__ */ jsx7("header", { style: { color: "white", padding: "1.5rem 1.5rem 0" }, children: /* @__PURE__ */ jsx7(
5901
+ isFocused && canvasTitle && /* @__PURE__ */ jsx8("header", { style: { color: "white", padding: "1.5rem 1.5rem 0" }, children: /* @__PURE__ */ jsx8(
5239
5902
  "p",
5240
5903
  {
5241
5904
  style: {
@@ -5248,7 +5911,7 @@ function ShadowCanvasOverlay({
5248
5911
  children: canvasTitle
5249
5912
  }
5250
5913
  ) }),
5251
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, overflowY: "auto", padding: isFocused ? "0" : "1rem" }, children: isLoading ? /* @__PURE__ */ jsx7(
5914
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, overflowY: "auto", padding: isFocused ? "0" : "1rem" }, children: isLoading ? /* @__PURE__ */ jsx8(
5252
5915
  "div",
5253
5916
  {
5254
5917
  style: { color: "var(--sc-overlay-text-color)", padding: isFocused ? "1rem" : "0" },
@@ -5268,7 +5931,7 @@ function ShadowCanvasOverlay({
5268
5931
  }
5269
5932
  ) : isFocused ? (
5270
5933
  /* Focused Mode: Render first tile full size */
5271
- tiles.length > 0 ? /* @__PURE__ */ jsx7(
5934
+ tiles.length > 0 ? /* @__PURE__ */ jsx8(
5272
5935
  TileCard,
5273
5936
  {
5274
5937
  config: tiles[0],
@@ -5279,7 +5942,7 @@ function ShadowCanvasOverlay({
5279
5942
  ) : null
5280
5943
  ) : (
5281
5944
  /* Standard Mode: Stacked cards — widgets always visible */
5282
- /* @__PURE__ */ jsx7(
5945
+ /* @__PURE__ */ jsx8(
5283
5946
  "div",
5284
5947
  {
5285
5948
  style: {
@@ -5288,7 +5951,7 @@ function ShadowCanvasOverlay({
5288
5951
  gap: "0.75rem",
5289
5952
  width: "100%"
5290
5953
  },
5291
- children: tiles.map((tile) => /* @__PURE__ */ jsx7(
5954
+ children: tiles.map((tile) => /* @__PURE__ */ jsx8(
5292
5955
  TileCard,
5293
5956
  {
5294
5957
  config: tile,
@@ -5303,17 +5966,7 @@ function ShadowCanvasOverlay({
5303
5966
  ) }),
5304
5967
  footerSlot
5305
5968
  ] }),
5306
- /* @__PURE__ */ jsx7(
5307
- "div",
5308
- {
5309
- onClick: toggle2,
5310
- style: {
5311
- flex: "1 1 auto",
5312
- pointerEvents: isOpen ? "auto" : "none",
5313
- cursor: "default"
5314
- }
5315
- }
5316
- )
5969
+ /* @__PURE__ */ jsx8("div", { style: { flex: "1 1 auto" } })
5317
5970
  ] })
5318
5971
  }
5319
5972
  );
@@ -5331,7 +5984,7 @@ function ShadowCanvasOverlay({
5331
5984
  zIndex: zIndex + 47
5332
5985
  },
5333
5986
  children: [
5334
- /* @__PURE__ */ jsx7(
5987
+ /* @__PURE__ */ jsx8(
5335
5988
  NotificationToastStack,
5336
5989
  {
5337
5990
  notifications,
@@ -5343,6 +5996,7 @@ function ShadowCanvasOverlay({
5343
5996
  /* @__PURE__ */ jsxs3(
5344
5997
  "button",
5345
5998
  {
5999
+ ref: launcherRef,
5346
6000
  type: "button",
5347
6001
  "aria-label": "Toggle shadow canvas",
5348
6002
  className: launcherAnimate && !isOpen ? "syntro-launcher-animate" : void 0,
@@ -5401,11 +6055,11 @@ function ShadowCanvasOverlay({
5401
6055
  focusable: "false",
5402
6056
  style: { transition: "transform 200ms ease" },
5403
6057
  children: [
5404
- /* @__PURE__ */ jsx7("path", { d: "M18 6L6 18" }),
5405
- /* @__PURE__ */ jsx7("path", { d: "M6 6l12 12" })
6058
+ /* @__PURE__ */ jsx8("path", { d: "M18 6L6 18" }),
6059
+ /* @__PURE__ */ jsx8("path", { d: "M6 6l12 12" })
5406
6060
  ]
5407
6061
  }
5408
- ) : launcherIcon ? /* @__PURE__ */ jsx7(
6062
+ ) : launcherIcon ? /* @__PURE__ */ jsx8(
5409
6063
  "img",
5410
6064
  {
5411
6065
  src: launcherIcon,
@@ -5433,16 +6087,16 @@ function ShadowCanvasOverlay({
5433
6087
  focusable: "false",
5434
6088
  style: { transition: "transform 200ms ease" },
5435
6089
  children: [
5436
- /* @__PURE__ */ jsx7("path", { d: "M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3Z" }),
5437
- /* @__PURE__ */ jsx7("path", { d: "M5 3v4" }),
5438
- /* @__PURE__ */ jsx7("path", { d: "M3 5h4" }),
5439
- /* @__PURE__ */ jsx7("path", { d: "M19 17v4" }),
5440
- /* @__PURE__ */ jsx7("path", { d: "M17 19h4" })
6090
+ /* @__PURE__ */ jsx8("path", { d: "M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3Z" }),
6091
+ /* @__PURE__ */ jsx8("path", { d: "M5 3v4" }),
6092
+ /* @__PURE__ */ jsx8("path", { d: "M3 5h4" }),
6093
+ /* @__PURE__ */ jsx8("path", { d: "M19 17v4" }),
6094
+ /* @__PURE__ */ jsx8("path", { d: "M17 19h4" })
5441
6095
  ]
5442
6096
  }
5443
6097
  ),
5444
6098
  !isOpen && notifications.length > 0 && /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", top: -2, right: -2, pointerEvents: "none" }, children: [
5445
- /* @__PURE__ */ jsx7(
6099
+ /* @__PURE__ */ jsx8(
5446
6100
  "span",
5447
6101
  {
5448
6102
  className: "syntro-badge-ping",
@@ -5454,7 +6108,7 @@ function ShadowCanvasOverlay({
5454
6108
  }
5455
6109
  }
5456
6110
  ),
5457
- /* @__PURE__ */ jsx7(
6111
+ /* @__PURE__ */ jsx8(
5458
6112
  "span",
5459
6113
  {
5460
6114
  className: "syntro-badge-glow",
@@ -5465,7 +6119,7 @@ function ShadowCanvasOverlay({
5465
6119
  }
5466
6120
  }
5467
6121
  ),
5468
- /* @__PURE__ */ jsx7(
6122
+ /* @__PURE__ */ jsx8(
5469
6123
  "span",
5470
6124
  {
5471
6125
  className: "syntro-badge-bounce",
@@ -5600,7 +6254,7 @@ function useShadowCanvasConfig({
5600
6254
 
5601
6255
  // src/SmartCanvasApp.tsx
5602
6256
  import { useEffect as useEffect9, useMemo as useMemo7, useRef as useRef7, useState as useState7 } from "react";
5603
- import { jsx as jsx8 } from "react/jsx-runtime";
6257
+ import { jsx as jsx9 } from "react/jsx-runtime";
5604
6258
  function SmartCanvasApp({
5605
6259
  controller,
5606
6260
  fetcher,
@@ -5623,7 +6277,7 @@ function SmartCanvasApp({
5623
6277
  workspaceTheme
5624
6278
  }) {
5625
6279
  if (runtime3) {
5626
- return /* @__PURE__ */ jsx8(RuntimeProvider, { runtime: runtime3, children: /* @__PURE__ */ jsx8(
6280
+ return /* @__PURE__ */ jsx9(RuntimeProvider, { runtime: runtime3, children: /* @__PURE__ */ jsx9(
5627
6281
  SmartCanvasAppInner,
5628
6282
  {
5629
6283
  controller,
@@ -5648,7 +6302,7 @@ function SmartCanvasApp({
5648
6302
  }
5649
6303
  ) });
5650
6304
  }
5651
- return /* @__PURE__ */ jsx8(
6305
+ return /* @__PURE__ */ jsx9(
5652
6306
  SmartCanvasAppInner,
5653
6307
  {
5654
6308
  controller,
@@ -5679,7 +6333,7 @@ function SmartCanvasAppInner({
5679
6333
  configUriFeatureKey = "smart-canvas-config-uri",
5680
6334
  configFeatureKey = "smart-canvas-config",
5681
6335
  fetchCredentials = "include",
5682
- pollIntervalMs,
6336
+ pollIntervalMs: _pollIntervalMs,
5683
6337
  experiments,
5684
6338
  telemetry,
5685
6339
  runtime: runtime3,
@@ -5803,16 +6457,13 @@ function SmartCanvasAppInner({
5803
6457
  }, [runtime3, controller]);
5804
6458
  const { shadowRoot } = useShadowRoot();
5805
6459
  const themeConfig = configState.theme;
5806
- if (!configState.isLoading && !hasContent) {
5807
- return null;
5808
- }
5809
- return /* @__PURE__ */ jsx8(
6460
+ return /* @__PURE__ */ jsx9(
5810
6461
  ThemeProvider,
5811
6462
  {
5812
6463
  themeConfig,
5813
6464
  workspaceTheme,
5814
6465
  shadowRoot,
5815
- children: /* @__PURE__ */ jsx8(
6466
+ children: !configState.isLoading && !hasContent ? null : /* @__PURE__ */ jsx9(
5816
6467
  ShadowCanvasOverlay,
5817
6468
  {
5818
6469
  tiles: configState.tiles,
@@ -5837,7 +6488,7 @@ function SmartCanvasAppInner({
5837
6488
 
5838
6489
  // src/SmartCanvasElement.tsx
5839
6490
  import { createRoot as createRoot2 } from "react-dom/client";
5840
- import { jsx as jsx9 } from "react/jsx-runtime";
6491
+ import { jsx as jsx10 } from "react/jsx-runtime";
5841
6492
  var TAG_NAME = "smart-canvas";
5842
6493
  var BASE_CSS = `
5843
6494
  :host {
@@ -5938,13 +6589,13 @@ var SmartCanvasElement = class extends HTMLElement {
5938
6589
  __privateSet(this, _root, createRoot2(__privateGet(this, _mount)));
5939
6590
  }
5940
6591
  __privateGet(this, _root).render(
5941
- /* @__PURE__ */ jsx9(
6592
+ /* @__PURE__ */ jsx10(
5942
6593
  ShadowRootProvider,
5943
6594
  {
5944
6595
  shadowRoot: __privateGet(this, _shadow),
5945
6596
  portalRoot: __privateGet(this, _portalRoot),
5946
6597
  overlayContainer: __privateGet(this, _overlayContainer),
5947
- children: /* @__PURE__ */ jsx9(SmartCanvasApp, { ...__privateGet(this, _lastAppProps), controller: __privateGet(this, _controller), canvasHost: this })
6598
+ children: /* @__PURE__ */ jsx10(SmartCanvasApp, { ...__privateGet(this, _lastAppProps), controller: __privateGet(this, _controller), canvasHost: this })
5948
6599
  }
5949
6600
  )
5950
6601
  );
@@ -6513,7 +7164,7 @@ var createSmartCanvas = async (config = {}) => {
6513
7164
  console.log(
6514
7165
  "[SmartCanvas] Actions to apply:",
6515
7166
  canvasConfig.actions.map(
6516
- (a, i) => `[${i}] ${a.kind}${a.anchorId ? ` anchor="${typeof a.anchorId === "string" ? a.anchorId : a.anchorId.selector}"` : ""}${a.label ? ` "${a.label}"` : ""}`
7167
+ (a, i) => `[${i}] ${a.kind}${a.anchorId ? ` anchor="${a.anchorId.selector}"` : ""}${a.label ? ` "${a.label}"` : ""}`
6517
7168
  ).join(", ")
6518
7169
  );
6519
7170
  }
@@ -6964,17 +7615,18 @@ var PostHogAdapter = class {
6964
7615
  __publicField(this, "client");
6965
7616
  __publicField(this, "featureFlagsCallback");
6966
7617
  __publicField(this, "captureCallback");
6967
- __publicField(this, "consentUnsub");
7618
+ __publicField(this, "rrwebCallback");
6968
7619
  this.client = options.client;
6969
7620
  this.featureFlagsCallback = options.onFeatureFlagsLoaded;
6970
7621
  this.captureCallback = options.onCapture;
7622
+ this.rrwebCallback = options.onRRWebEvent;
6971
7623
  if (!this.client && options.consent && options.requireExplicitConsent && typeof window !== "undefined" && options.apiKey) {
6972
7624
  const consent = options.consent;
6973
7625
  const currentStatus = consent.getStatus();
6974
7626
  if (currentStatus === "granted") {
6975
7627
  this.initPostHog();
6976
7628
  }
6977
- this.consentUnsub = consent.subscribe((status) => {
7629
+ consent.subscribe((status) => {
6978
7630
  if (status === "granted") {
6979
7631
  if (!this.client) {
6980
7632
  this.initPostHog();
@@ -7001,68 +7653,120 @@ var PostHogAdapter = class {
7001
7653
  if (!options.apiKey) return;
7002
7654
  const enableFeatureFlags = (_a2 = options.enableFeatureFlags) != null ? _a2 : true;
7003
7655
  const instanceName = `syntro_${options.apiKey.slice(-6) || "sdk"}`;
7004
- this.client = posthog.init(
7005
- options.apiKey,
7006
- {
7007
- api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
7008
- // Feature flags for segment membership (in_segment_* flags)
7009
- // When enabled, /decide is called to get segment flags
7010
- advanced_disable_feature_flags: !enableFeatureFlags,
7011
- advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
7012
- // Full-page tracking - all ON by default
7013
- autocapture: (_c = options.autocapture) != null ? _c : true,
7014
- capture_pageview: (_d = options.capturePageview) != null ? _d : true,
7015
- capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
7016
- disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
7017
- // CRITICAL: Disable user agent filtering to allow headless Chrome
7018
- // PostHog blocks "HeadlessChrome" user agents by default as bot detection
7019
- // This enables session recording in Playwright/crawler sessions
7020
- opt_out_useragent_filter: true,
7021
- // Cross-domain iframe recording for embeds
7022
- session_recording: {
7023
- recordCrossDomainIFrames: true
7024
- },
7025
- // Capture performance metrics
7026
- capture_performance: true,
7027
- // Enable web vitals
7028
- enable_recording_console_log: true,
7029
- // Bootstrap callback for when flags are loaded
7030
- loaded: (ph) => {
7031
- if (enableFeatureFlags && this.featureFlagsCallback) {
7032
- ph.onFeatureFlags(() => {
7033
- const allFlags = this.getAllFeatureFlags();
7034
- if (allFlags && this.featureFlagsCallback) {
7035
- this.featureFlagsCallback(allFlags);
7036
- }
7037
- });
7038
- const existingFlags = this.getAllFeatureFlags();
7039
- if (existingFlags && Object.keys(existingFlags).length > 0) {
7040
- this.featureFlagsCallback(existingFlags);
7656
+ const initOptions = {
7657
+ api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
7658
+ // Feature flags for segment membership (in_segment_* flags)
7659
+ // When enabled, /decide is called to get segment flags
7660
+ advanced_disable_feature_flags: !enableFeatureFlags,
7661
+ advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
7662
+ // Full-page tracking - all ON by default
7663
+ autocapture: (_c = options.autocapture) != null ? _c : true,
7664
+ capture_pageview: (_d = options.capturePageview) != null ? _d : "history_change",
7665
+ capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
7666
+ disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
7667
+ // CRITICAL: Disable user agent filtering to allow headless Chrome
7668
+ // PostHog blocks "HeadlessChrome" user agents by default as bot detection
7669
+ // This enables session recording in Playwright/crawler sessions
7670
+ opt_out_useragent_filter: true,
7671
+ // Cross-domain iframe recording for embeds
7672
+ session_recording: {
7673
+ recordCrossDomainIFrames: true
7674
+ },
7675
+ // Capture performance metrics
7676
+ capture_performance: true,
7677
+ // Enable web vitals
7678
+ enable_recording_console_log: true,
7679
+ // Bootstrap callback for when flags are loaded
7680
+ loaded: (ph) => {
7681
+ if (enableFeatureFlags && this.featureFlagsCallback) {
7682
+ ph.onFeatureFlags(() => {
7683
+ const allFlags = this.getAllFeatureFlags();
7684
+ if (allFlags && this.featureFlagsCallback) {
7685
+ this.featureFlagsCallback(allFlags);
7041
7686
  }
7042
- }
7043
- if (this.captureCallback) {
7044
- ph.on("eventCaptured", (data) => {
7045
- var _a3;
7046
- const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
7047
- const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
7048
- if (typeof eventName === "string") {
7049
- (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
7050
- }
7051
- });
7687
+ });
7688
+ const existingFlags = this.getAllFeatureFlags();
7689
+ if (existingFlags && Object.keys(existingFlags).length > 0) {
7690
+ this.featureFlagsCallback(existingFlags);
7052
7691
  }
7053
7692
  }
7054
- },
7693
+ if (this.captureCallback) {
7694
+ ph.on("eventCaptured", (...args) => {
7695
+ var _a3;
7696
+ const data = args[0];
7697
+ const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
7698
+ const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
7699
+ if (typeof eventName === "string") {
7700
+ (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
7701
+ }
7702
+ });
7703
+ }
7704
+ }
7705
+ };
7706
+ const result = posthog.init(
7707
+ options.apiKey,
7708
+ initOptions,
7055
7709
  instanceName
7056
7710
  );
7711
+ if (result) {
7712
+ this.client = result;
7713
+ }
7714
+ if (this.rrwebCallback && this.client) {
7715
+ this.setupRRWebIntercept();
7716
+ }
7717
+ }
7718
+ /**
7719
+ * Set up rrweb event interception on PostHog's session recording.
7720
+ *
7721
+ * PostHog lazy-loads the rrweb recorder. The SessionRecording wrapper has
7722
+ * an `onRRwebEmit` method, but rrweb delivers events directly to the
7723
+ * lazy-loaded recorder instance's `onRRwebEmit`, bypassing the wrapper.
7724
+ * We must find and patch the recorder instance, not the wrapper.
7725
+ *
7726
+ * The recorder instance is stored on a minified property of SessionRecording.
7727
+ * We detect it by looking for an object with both `onRRwebEmit` and `start` methods.
7728
+ */
7729
+ setupRRWebIntercept(retries = 30) {
7730
+ var _a2;
7731
+ const sr = (_a2 = this.client) == null ? void 0 : _a2.sessionRecording;
7732
+ if (!sr) {
7733
+ if (retries > 0) {
7734
+ setTimeout(() => this.setupRRWebIntercept(retries - 1), 500);
7735
+ }
7736
+ return;
7737
+ }
7738
+ let recorder = null;
7739
+ const srRecord = sr;
7740
+ for (const key of Object.getOwnPropertyNames(srRecord)) {
7741
+ const val = srRecord[key];
7742
+ if (val && typeof val === "object" && typeof val.onRRwebEmit === "function" && typeof val.start === "function" && val !== sr) {
7743
+ recorder = val;
7744
+ break;
7745
+ }
7746
+ }
7747
+ if (!recorder) {
7748
+ if (retries > 0) {
7749
+ setTimeout(() => this.setupRRWebIntercept(retries - 1), 500);
7750
+ }
7751
+ return;
7752
+ }
7753
+ const originalEmit = recorder.onRRwebEmit.bind(recorder);
7754
+ recorder.onRRwebEmit = (rawEvent) => {
7755
+ var _a3;
7756
+ (_a3 = this.rrwebCallback) == null ? void 0 : _a3.call(this, { kind: "rrweb", ...rawEvent });
7757
+ originalEmit(rawEvent);
7758
+ };
7759
+ if (typeof window !== "undefined") {
7760
+ window.__RRWEB_INTERCEPT_READY__ = true;
7761
+ }
7057
7762
  }
7058
7763
  /**
7059
7764
  * Get all feature flags from PostHog.
7060
7765
  * Used to extract segment membership flags (in_segment_*).
7061
7766
  */
7062
7767
  getAllFeatureFlags() {
7063
- var _a2, _b, _c;
7064
- const flags = (_c = (_b = (_a2 = this.client) == null ? void 0 : _a2.featureFlags) == null ? void 0 : _b.getFlagVariants) == null ? void 0 : _c.call(_b);
7065
- return flags;
7768
+ var _a2, _b;
7769
+ return (_b = (_a2 = this.client) == null ? void 0 : _a2.featureFlags) == null ? void 0 : _b.getFlagVariants();
7066
7770
  }
7067
7771
  /**
7068
7772
  * Get segment membership flags (in_segment_*) from PostHog.
@@ -7413,158 +8117,7 @@ function hasExecutor(kind) {
7413
8117
  return executorRegistry.has(kind);
7414
8118
  }
7415
8119
 
7416
- // src/actions/validation.ts
7417
- var DANGEROUS_ATTRS = /* @__PURE__ */ new Set([
7418
- "onclick",
7419
- "onerror",
7420
- "onload",
7421
- "onmouseover",
7422
- "onfocus",
7423
- "onblur",
7424
- "onchange",
7425
- "onsubmit",
7426
- "onkeydown",
7427
- "onkeyup",
7428
- "onkeypress"
7429
- ]);
7430
- var MAX_HTML_LENGTH = 5e4;
7431
- var MAX_STYLE_COUNT = 50;
7432
- function validateAction(action) {
7433
- const errors = [];
7434
- const warnings = [];
7435
- if (!action || typeof action !== "object") {
7436
- errors.push({
7437
- code: "INVALID_ACTION",
7438
- message: "Action must be an object"
7439
- });
7440
- return { valid: false, errors, warnings };
7441
- }
7442
- const { kind } = action;
7443
- if (!kind || typeof kind !== "string") {
7444
- errors.push({
7445
- code: "MISSING_KIND",
7446
- message: "Action must have a 'kind' property"
7447
- });
7448
- return { valid: false, errors, warnings };
7449
- }
7450
- if (!hasExecutor(kind) && kind !== "core:mountWidget") {
7451
- const registered = executorRegistry.list();
7452
- console.error(
7453
- `[ActionValidation] Unknown action kind: "${kind}". Registered kinds (${registered.length}): [${registered.join(", ")}]. This usually means the app that provides "${kind}" hasn't been activated yet, or the executor was never registered in ExecutorRegistry.`
7454
- );
7455
- errors.push({
7456
- code: "UNKNOWN_KIND",
7457
- message: `Unknown action kind: ${kind}`,
7458
- field: "kind"
7459
- });
7460
- return { valid: false, errors, warnings };
7461
- }
7462
- switch (kind) {
7463
- case "overlays:highlight":
7464
- case "overlays:pulse":
7465
- case "navigation:scrollTo":
7466
- validateAnchorAction(action, errors, warnings);
7467
- break;
7468
- case "overlays:badge":
7469
- validateAnchorAction(action, errors, warnings);
7470
- validateBadgeAction(action, errors, warnings);
7471
- break;
7472
- case "overlays:tooltip":
7473
- validateAnchorAction(action, errors, warnings);
7474
- validateTooltipAction(action, errors, warnings);
7475
- break;
7476
- case "overlays:modal":
7477
- validateModalAction(action, errors, warnings);
7478
- break;
7479
- case "content:insertHtml":
7480
- validateAnchorAction(action, errors, warnings);
7481
- validateInsertHtmlAction(action, errors, warnings);
7482
- break;
7483
- case "content:setText":
7484
- validateAnchorAction(action, errors, warnings);
7485
- validateSetTextAction(action, errors, warnings);
7486
- break;
7487
- case "content:setAttr":
7488
- validateAnchorAction(action, errors, warnings);
7489
- validateSetAttrAction(action, errors, warnings);
7490
- break;
7491
- case "content:addClass":
7492
- case "content:removeClass":
7493
- validateAnchorAction(action, errors, warnings);
7494
- validateClassAction(action, errors, warnings);
7495
- break;
7496
- case "content:setStyle":
7497
- validateAnchorAction(action, errors, warnings);
7498
- validateSetStyleAction(action, errors, warnings);
7499
- break;
7500
- case "core:mountWidget":
7501
- validateMountWidgetAction(action, errors, warnings);
7502
- break;
7503
- case "core:wait":
7504
- validateWaitAction(action, errors, warnings);
7505
- break;
7506
- case "core:sequence":
7507
- validateSequenceAction(action, errors, warnings);
7508
- break;
7509
- case "core:parallel":
7510
- validateParallelAction(action, errors, warnings);
7511
- break;
7512
- case "core:tour":
7513
- validateTourAction(action, errors, warnings);
7514
- break;
7515
- case "navigation:navigate":
7516
- validateNavigateAction(action, errors, warnings);
7517
- break;
7518
- }
7519
- return {
7520
- valid: errors.length === 0,
7521
- errors,
7522
- warnings
7523
- };
7524
- }
7525
- function validateAnchorAction(action, errors, warnings) {
7526
- const anchorId = action.anchorId;
7527
- if (!anchorId || typeof anchorId !== "object") {
7528
- errors.push({
7529
- code: "MISSING_ANCHOR_ID",
7530
- message: "Action requires an 'anchorId' object with a 'selector' string",
7531
- field: "anchorId"
7532
- });
7533
- return;
7534
- }
7535
- if (!anchorId.selector || typeof anchorId.selector !== "string") {
7536
- errors.push({
7537
- code: "MISSING_ANCHOR_SELECTOR",
7538
- message: "anchorId requires a 'selector' string",
7539
- field: "anchorId.selector"
7540
- });
7541
- } else if (anchorId.selector.length > 200) {
7542
- warnings.push({
7543
- code: "LONG_ANCHOR_ID",
7544
- message: "Anchor selector is unusually long",
7545
- suggestion: "Consider using a shorter, more descriptive selector"
7546
- });
7547
- }
7548
- if (anchorId.route === void 0 || anchorId.route === null) {
7549
- errors.push({
7550
- code: "MISSING_ANCHOR_ROUTE",
7551
- message: `anchorId requires a 'route' (string or array of strings). Use "**" for all routes.`,
7552
- field: "anchorId.route"
7553
- });
7554
- } else {
7555
- const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
7556
- for (const route of routes) {
7557
- if (typeof route !== "string") {
7558
- errors.push({
7559
- code: "INVALID_ANCHOR_ROUTE",
7560
- message: "anchorId.route must be a string or array of strings",
7561
- field: "anchorId.route"
7562
- });
7563
- break;
7564
- }
7565
- }
7566
- }
7567
- }
8120
+ // src/actions/validation-rules.ts
7568
8121
  function validateBadgeAction(action, errors, warnings) {
7569
8122
  if (!action.content || typeof action.content !== "string") {
7570
8123
  errors.push({
@@ -7951,7 +8504,160 @@ function validateTourAction(action, errors, warnings) {
7951
8504
  }
7952
8505
  }
7953
8506
  }
7954
- function validateActions(actions) {
8507
+
8508
+ // src/actions/validation-core.ts
8509
+ var DANGEROUS_ATTRS = /* @__PURE__ */ new Set([
8510
+ "onclick",
8511
+ "onerror",
8512
+ "onload",
8513
+ "onmouseover",
8514
+ "onfocus",
8515
+ "onblur",
8516
+ "onchange",
8517
+ "onsubmit",
8518
+ "onkeydown",
8519
+ "onkeyup",
8520
+ "onkeypress"
8521
+ ]);
8522
+ var MAX_HTML_LENGTH = 5e4;
8523
+ var MAX_STYLE_COUNT = 50;
8524
+ function validateAction(action) {
8525
+ const errors = [];
8526
+ const warnings = [];
8527
+ if (!action || typeof action !== "object") {
8528
+ errors.push({
8529
+ code: "INVALID_ACTION",
8530
+ message: "Action must be an object"
8531
+ });
8532
+ return { valid: false, errors, warnings };
8533
+ }
8534
+ const { kind } = action;
8535
+ if (!kind || typeof kind !== "string") {
8536
+ errors.push({
8537
+ code: "MISSING_KIND",
8538
+ message: "Action must have a 'kind' property"
8539
+ });
8540
+ return { valid: false, errors, warnings };
8541
+ }
8542
+ if (!hasExecutor(kind) && kind !== "core:mountWidget") {
8543
+ const registered = executorRegistry.list();
8544
+ console.error(
8545
+ `[ActionValidation] Unknown action kind: "${kind}". Registered kinds (${registered.length}): [${registered.join(", ")}]. This usually means the app that provides "${kind}" hasn't been activated yet, or the executor was never registered in ExecutorRegistry.`
8546
+ );
8547
+ errors.push({
8548
+ code: "UNKNOWN_KIND",
8549
+ message: `Unknown action kind: ${kind}`,
8550
+ field: "kind"
8551
+ });
8552
+ return { valid: false, errors, warnings };
8553
+ }
8554
+ switch (kind) {
8555
+ case "overlays:highlight":
8556
+ case "overlays:pulse":
8557
+ case "navigation:scrollTo":
8558
+ validateAnchorAction(action, errors, warnings);
8559
+ break;
8560
+ case "overlays:badge":
8561
+ validateAnchorAction(action, errors, warnings);
8562
+ validateBadgeAction(action, errors, warnings);
8563
+ break;
8564
+ case "overlays:tooltip":
8565
+ validateAnchorAction(action, errors, warnings);
8566
+ validateTooltipAction(action, errors, warnings);
8567
+ break;
8568
+ case "overlays:modal":
8569
+ validateModalAction(action, errors, warnings);
8570
+ break;
8571
+ case "content:insertHtml":
8572
+ validateAnchorAction(action, errors, warnings);
8573
+ validateInsertHtmlAction(action, errors, warnings);
8574
+ break;
8575
+ case "content:setText":
8576
+ validateAnchorAction(action, errors, warnings);
8577
+ validateSetTextAction(action, errors, warnings);
8578
+ break;
8579
+ case "content:setAttr":
8580
+ validateAnchorAction(action, errors, warnings);
8581
+ validateSetAttrAction(action, errors, warnings);
8582
+ break;
8583
+ case "content:addClass":
8584
+ case "content:removeClass":
8585
+ validateAnchorAction(action, errors, warnings);
8586
+ validateClassAction(action, errors, warnings);
8587
+ break;
8588
+ case "content:setStyle":
8589
+ validateAnchorAction(action, errors, warnings);
8590
+ validateSetStyleAction(action, errors, warnings);
8591
+ break;
8592
+ case "core:mountWidget":
8593
+ validateMountWidgetAction(action, errors, warnings);
8594
+ break;
8595
+ case "core:wait":
8596
+ validateWaitAction(action, errors, warnings);
8597
+ break;
8598
+ case "core:sequence":
8599
+ validateSequenceAction(action, errors, warnings);
8600
+ break;
8601
+ case "core:parallel":
8602
+ validateParallelAction(action, errors, warnings);
8603
+ break;
8604
+ case "core:tour":
8605
+ validateTourAction(action, errors, warnings);
8606
+ break;
8607
+ case "navigation:navigate":
8608
+ validateNavigateAction(action, errors, warnings);
8609
+ break;
8610
+ }
8611
+ return {
8612
+ valid: errors.length === 0,
8613
+ errors,
8614
+ warnings
8615
+ };
8616
+ }
8617
+ function validateAnchorAction(action, errors, warnings) {
8618
+ const anchorId = action.anchorId;
8619
+ if (!anchorId || typeof anchorId !== "object") {
8620
+ errors.push({
8621
+ code: "MISSING_ANCHOR_ID",
8622
+ message: "Action requires an 'anchorId' object with a 'selector' string",
8623
+ field: "anchorId"
8624
+ });
8625
+ return;
8626
+ }
8627
+ if (!anchorId.selector || typeof anchorId.selector !== "string") {
8628
+ errors.push({
8629
+ code: "MISSING_ANCHOR_SELECTOR",
8630
+ message: "anchorId requires a 'selector' string",
8631
+ field: "anchorId.selector"
8632
+ });
8633
+ } else if (anchorId.selector.length > 200) {
8634
+ warnings.push({
8635
+ code: "LONG_ANCHOR_ID",
8636
+ message: "Anchor selector is unusually long",
8637
+ suggestion: "Consider using a shorter, more descriptive selector"
8638
+ });
8639
+ }
8640
+ if (anchorId.route === void 0 || anchorId.route === null) {
8641
+ errors.push({
8642
+ code: "MISSING_ANCHOR_ROUTE",
8643
+ message: `anchorId requires a 'route' (string or array of strings). Use "**" for all routes.`,
8644
+ field: "anchorId.route"
8645
+ });
8646
+ } else {
8647
+ const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
8648
+ for (const route of routes) {
8649
+ if (typeof route !== "string") {
8650
+ errors.push({
8651
+ code: "INVALID_ANCHOR_ROUTE",
8652
+ message: "anchorId.route must be a string or array of strings",
8653
+ field: "anchorId.route"
8654
+ });
8655
+ break;
8656
+ }
8657
+ }
8658
+ }
8659
+ }
8660
+ function validateActions(actions) {
7955
8661
  var _a2;
7956
8662
  const errors = [];
7957
8663
  const warnings = [];
@@ -8097,7 +8803,7 @@ function createActionEngine(options) {
8097
8803
  }
8098
8804
  return executor(action, context);
8099
8805
  }
8100
- function subscribeForReeval(id, action, triggerWhen, handle) {
8806
+ function subscribeForReeval(id, action, triggerWhen, _handle) {
8101
8807
  if (!runtime3) return;
8102
8808
  const unsubs = [];
8103
8809
  const onReeval = async () => {
@@ -8241,13 +8947,9 @@ function createActionEngine(options) {
8241
8947
  entry2.state = "reverted";
8242
8948
  publishEvent("action.reverted", { id, kind: action.kind });
8243
8949
  } catch (error2) {
8244
- entry2.state = "failed";
8245
- publishEvent("action.failed", {
8246
- id,
8247
- kind: action.kind,
8248
- error: String(error2)
8249
- });
8250
- throw error2;
8950
+ console.warn(`[ActionEngine] Cleanup error for ${action.kind} (${id}), ignoring:`, error2);
8951
+ entry2.state = "reverted";
8952
+ publishEvent("action.reverted", { id, kind: action.kind });
8251
8953
  } finally {
8252
8954
  activeActions.delete(id);
8253
8955
  }
@@ -8291,7 +8993,7 @@ function createActionEngine(options) {
8291
8993
  errorMessages,
8292
8994
  "\nActions:",
8293
8995
  actions.map(
8294
- (a, i) => ` [${i}] ${a.kind} ${a.anchorId ? `anchor="${typeof a.anchorId === "string" ? a.anchorId : a.anchorId.selector}"` : ""} ${a.label ? `label="${a.label}"` : ""}`
8996
+ (a, i) => ` [${i}] ${a.kind} ${a.anchorId ? `anchor="${a.anchorId.selector}"` : ""} ${a.label ? `label="${a.label}"` : ""}`
8295
8997
  ).join("\n")
8296
8998
  );
8297
8999
  throw new Error(`Batch validation failed: ${errorMessages}`);
@@ -8391,6 +9093,7 @@ function createAnchorResolver(opts) {
8391
9093
  function resolve(selector) {
8392
9094
  if (!root) return null;
8393
9095
  try {
9096
+ if (root.matches(selector)) return root;
8394
9097
  return root.querySelector(selector);
8395
9098
  } catch {
8396
9099
  return null;
@@ -8736,7 +9439,7 @@ function createContextManager(options) {
8736
9439
 
8737
9440
  // src/decisions/strategies/rules.ts
8738
9441
  function evaluateCondition(condition, evalContext) {
8739
- var _a2, _b, _c, _d, _e, _f, _g;
9442
+ var _a2, _b, _c, _d, _e;
8740
9443
  const { context, state, events } = evalContext;
8741
9444
  switch (condition.type) {
8742
9445
  case "page_url": {
@@ -8750,8 +9453,7 @@ function evaluateCondition(condition, evalContext) {
8750
9453
  return context.page.routeId === condition.routeId;
8751
9454
  }
8752
9455
  case "anchor_visible": {
8753
- const condSelector = typeof condition.anchorId === "string" ? condition.anchorId : (_b = (_a2 = condition.anchorId) == null ? void 0 : _a2.selector) != null ? _b : "";
8754
- const anchor = (_c = context.anchors) == null ? void 0 : _c.find((a) => a.anchorId === condSelector);
9456
+ const anchor = (_a2 = context.anchors) == null ? void 0 : _a2.find((a) => a.anchorId === condition.anchorId);
8755
9457
  switch (condition.state) {
8756
9458
  case "visible":
8757
9459
  return (anchor == null ? void 0 : anchor.visible) === true;
@@ -8765,7 +9467,7 @@ function evaluateCondition(condition, evalContext) {
8765
9467
  }
8766
9468
  case "event_occurred": {
8767
9469
  if (!events) return false;
8768
- const withinMs = (_d = condition.withinMs) != null ? _d : 6e4;
9470
+ const withinMs = (_b = condition.withinMs) != null ? _b : 6e4;
8769
9471
  return events.hasRecentEvent(condition.eventName, withinMs);
8770
9472
  }
8771
9473
  case "state_equals": {
@@ -8801,17 +9503,17 @@ function evaluateCondition(condition, evalContext) {
8801
9503
  }
8802
9504
  }
8803
9505
  case "dismissed": {
8804
- if (!state) return (_e = condition.inverted) != null ? _e : false;
9506
+ if (!state) return (_c = condition.inverted) != null ? _c : false;
8805
9507
  const isDismissed = state.isDismissed(condition.key);
8806
9508
  return condition.inverted ? !isDismissed : isDismissed;
8807
9509
  }
8808
9510
  case "cooldown_active": {
8809
- if (!state) return (_f = condition.inverted) != null ? _f : false;
9511
+ if (!state) return (_d = condition.inverted) != null ? _d : false;
8810
9512
  const isActive = state.isCooldownActive(condition.key);
8811
9513
  return condition.inverted ? !isActive : isActive;
8812
9514
  }
8813
9515
  case "frequency_limit": {
8814
- if (!state) return (_g = condition.inverted) != null ? _g : false;
9516
+ if (!state) return (_e = condition.inverted) != null ? _e : false;
8815
9517
  const count = state.getFrequencyCount(condition.key);
8816
9518
  const limitReached = count >= condition.limit;
8817
9519
  return condition.inverted ? !limitReached : limitReached;
@@ -9071,6 +9773,66 @@ function createEventAccumulator(options) {
9071
9773
  };
9072
9774
  }
9073
9775
 
9776
+ // src/events/validation.ts
9777
+ var APP_PREFIX = "app:";
9778
+ var RESERVED_PREFIX = "syntro:";
9779
+ var SEGMENT_PATTERN = /^[a-z][a-z0-9_]*$/;
9780
+ function validateEventName(name) {
9781
+ if (!name) {
9782
+ return { valid: false, reason: "Event name cannot be empty" };
9783
+ }
9784
+ if (name.startsWith(RESERVED_PREFIX)) {
9785
+ return { valid: false, reason: '"syntro:" prefix is reserved for internal SDK events' };
9786
+ }
9787
+ if (!name.startsWith(APP_PREFIX)) {
9788
+ return { valid: false, reason: `Custom events must start with "app:" prefix. Got: "${name}"` };
9789
+ }
9790
+ const segments = name.slice(APP_PREFIX.length).split(":");
9791
+ if (segments.length < 2) {
9792
+ return {
9793
+ valid: false,
9794
+ reason: `Event name must have at least 2 segments after "app:" (app:{category}:{action}). Got: "${name}"`
9795
+ };
9796
+ }
9797
+ for (const segment of segments) {
9798
+ if (!SEGMENT_PATTERN.test(segment)) {
9799
+ return {
9800
+ valid: false,
9801
+ reason: `Segment "${segment}" must be lowercase alphanumeric + underscores. Got: "${name}"`
9802
+ };
9803
+ }
9804
+ }
9805
+ return { valid: true };
9806
+ }
9807
+ function isSerializable(value) {
9808
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
9809
+ }
9810
+ function checkDepth(obj, maxDepth, current = 0) {
9811
+ if (current >= maxDepth) return false;
9812
+ for (const value of Object.values(obj)) {
9813
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
9814
+ if (!checkDepth(value, maxDepth, current + 1)) return false;
9815
+ }
9816
+ }
9817
+ return true;
9818
+ }
9819
+ function validateProps(props) {
9820
+ if (props === void 0) return { valid: true };
9821
+ if (props === null || typeof props !== "object" || Array.isArray(props)) {
9822
+ return { valid: false, reason: "Props must be a plain object" };
9823
+ }
9824
+ if (!checkDepth(props, 2)) {
9825
+ return { valid: false, reason: "Props nesting depth exceeds 2 levels" };
9826
+ }
9827
+ const stripped = [];
9828
+ for (const [key, value] of Object.entries(props)) {
9829
+ if (value !== null && typeof value === "object") continue;
9830
+ if (!isSerializable(value)) stripped.push(key);
9831
+ }
9832
+ if (stripped.length > 0) return { valid: true, stripped };
9833
+ return { valid: true };
9834
+ }
9835
+
9074
9836
  // src/events/EventBus.ts
9075
9837
  function matchesFilter(event, filter) {
9076
9838
  if (!filter) return true;
@@ -9102,8 +9864,16 @@ var EventBus = class {
9102
9864
  __publicField(this, "subscriptions", /* @__PURE__ */ new Set());
9103
9865
  __publicField(this, "history", []);
9104
9866
  __publicField(this, "maxHistorySize");
9105
- var _a2;
9867
+ __publicField(this, "debug");
9868
+ __publicField(this, "emitHistory");
9869
+ __publicField(this, "posthogCapture");
9870
+ __publicField(this, "testMode");
9871
+ var _a2, _b, _c, _d, _e;
9106
9872
  this.maxHistorySize = (_a2 = options.maxHistorySize) != null ? _a2 : 100;
9873
+ this.debug = (_b = options.debug) != null ? _b : false;
9874
+ this.emitHistory = (_c = options.history) != null ? _c : null;
9875
+ this.posthogCapture = (_d = options.posthogCapture) != null ? _d : null;
9876
+ this.testMode = (_e = options.testMode) != null ? _e : false;
9107
9877
  }
9108
9878
  /**
9109
9879
  * Subscribe to events matching an optional filter.
@@ -9156,6 +9926,83 @@ var EventBus = class {
9156
9926
  }
9157
9927
  }
9158
9928
  }
9929
+ /**
9930
+ * Emit a validated custom event from the host application.
9931
+ *
9932
+ * Custom events must use the `app:` prefix (e.g. `app:cart:abandoned`).
9933
+ * In debug mode, returns an EmitResult with delivery details.
9934
+ * In production mode, returns undefined.
9935
+ */
9936
+ emit(name, props) {
9937
+ const nameResult = validateEventName(name);
9938
+ if (!nameResult.valid) {
9939
+ console.warn(`[EventBus] emit() rejected: ${nameResult.reason}`);
9940
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
9941
+ }
9942
+ const propsResult = validateProps(props);
9943
+ if (!propsResult.valid) {
9944
+ console.warn(`[EventBus] emit() rejected props: ${propsResult.reason}`);
9945
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
9946
+ }
9947
+ const event = {
9948
+ ts: Date.now(),
9949
+ name,
9950
+ source: "custom",
9951
+ props,
9952
+ schemaVersion: EVENT_SCHEMA_VERSION
9953
+ };
9954
+ this.history.push(event);
9955
+ if (this.history.length > this.maxHistorySize) {
9956
+ this.history.shift();
9957
+ }
9958
+ let listenersNotified = 0;
9959
+ for (const subscription of this.subscriptions) {
9960
+ if (matchesFilter(event, subscription.filter)) {
9961
+ try {
9962
+ subscription.callback(event);
9963
+ listenersNotified++;
9964
+ } catch (err) {
9965
+ console.error("[EventBus] Subscriber error:", err);
9966
+ listenersNotified++;
9967
+ }
9968
+ }
9969
+ }
9970
+ let posthogCaptured = false;
9971
+ if (this.posthogCapture && !this.testMode) {
9972
+ try {
9973
+ this.posthogCapture(name, props);
9974
+ posthogCaptured = true;
9975
+ } catch (err) {
9976
+ console.error("[EventBus] PostHog capture error:", err);
9977
+ }
9978
+ }
9979
+ if (this.emitHistory) {
9980
+ this.emitHistory.record({
9981
+ name,
9982
+ props,
9983
+ source: "custom",
9984
+ timestamp: event.ts,
9985
+ matchedRules: []
9986
+ });
9987
+ }
9988
+ if (this.debug) {
9989
+ console.debug("[EventBus] emit()", { name, props, listenersNotified, posthogCaptured });
9990
+ return {
9991
+ delivered: true,
9992
+ matchedRules: [],
9993
+ posthogCaptured,
9994
+ listenersNotified
9995
+ };
9996
+ }
9997
+ return void 0;
9998
+ }
9999
+ /**
10000
+ * Set the PostHog capture function after construction.
10001
+ * Used by bootstrap to wire PostHog after the EventBus is created.
10002
+ */
10003
+ setPosthogCapture(fn) {
10004
+ this.posthogCapture = fn;
10005
+ }
9159
10006
  /**
9160
10007
  * Get recent events matching an optional filter.
9161
10008
  */
@@ -9199,132 +10046,34 @@ var EventBus = class {
9199
10046
  * Get the number of current subscribers.
9200
10047
  */
9201
10048
  getSubscriberCount() {
9202
- return this.subscriptions.size;
9203
- }
9204
- };
9205
- function createEventBus(options = {}) {
9206
- return new EventBus(options);
9207
- }
9208
-
9209
- // src/events/normalizers/posthog.ts
9210
- var POSTHOG_EVENT_MAP = {
9211
- // Autocapture events
9212
- $autocapture: "ui.click",
9213
- // Default autocapture is usually clicks
9214
- $click: StandardEvents.UI_CLICK,
9215
- $scroll: StandardEvents.UI_SCROLL,
9216
- $input: StandardEvents.UI_INPUT,
9217
- $change: StandardEvents.UI_CHANGE,
9218
- $submit: StandardEvents.UI_SUBMIT,
9219
- // Navigation events
9220
- $pageview: StandardEvents.NAV_PAGE_VIEW,
9221
- $pageleave: StandardEvents.NAV_PAGE_LEAVE,
9222
- // Session events
9223
- $session_start: "session.start",
9224
- // Identify events
9225
- $identify: "user.identify"
9226
- };
9227
- function getEventName(phEvent) {
9228
- var _a2, _b;
9229
- const eventName = phEvent.event;
9230
- if (typeof eventName !== "string") {
9231
- return "posthog.unknown";
9232
- }
9233
- if (POSTHOG_EVENT_MAP[eventName]) {
9234
- return POSTHOG_EVENT_MAP[eventName];
9235
- }
9236
- if (eventName === "$autocapture") {
9237
- const tagName = (_a2 = phEvent.properties) == null ? void 0 : _a2.$tag_name;
9238
- const eventType = (_b = phEvent.properties) == null ? void 0 : _b.$event_type;
9239
- if (eventType === "submit") return StandardEvents.UI_SUBMIT;
9240
- if (eventType === "change") return StandardEvents.UI_CHANGE;
9241
- if (tagName === "input" || tagName === "textarea") return StandardEvents.UI_INPUT;
9242
- return StandardEvents.UI_CLICK;
9243
- }
9244
- if (!eventName.startsWith("$")) {
9245
- return `posthog.${eventName}`;
9246
- }
9247
- return eventName.replace("$", "posthog.");
9248
- }
9249
- function extractProps(phEvent) {
9250
- var _a2;
9251
- const props = {};
9252
- const phProps = phEvent.properties || {};
9253
- const elements = phProps.$elements;
9254
- if (phProps.$tag_name) {
9255
- props.tagName = phProps.$tag_name;
9256
- } else if ((_a2 = elements == null ? void 0 : elements[0]) == null ? void 0 : _a2.tag_name) {
9257
- props.tagName = elements[0].tag_name;
9258
- }
9259
- if (phProps.$el_text) props.elementText = phProps.$el_text;
9260
- if (elements) props.elements = elements;
9261
- if (phProps.$current_url) props.url = phProps.$current_url;
9262
- if (phProps.$pathname) props.pathname = phProps.$pathname;
9263
- if (phProps.$host) props.host = phProps.$host;
9264
- if (phProps.$viewport_width) props.viewportWidth = phProps.$viewport_width;
9265
- if (phProps.$viewport_height) props.viewportHeight = phProps.$viewport_height;
9266
- if (phProps.$session_id) props.sessionId = phProps.$session_id;
9267
- if (phProps.$scroll_depth) props.scrollDepth = phProps.$scroll_depth;
9268
- if (phProps.$scroll_percentage) props.scrollPercentage = phProps.$scroll_percentage;
9269
- props.originalEvent = phEvent.event;
9270
- return props;
9271
- }
9272
- function normalizePostHogEvent(phEvent) {
9273
- let ts;
9274
- if (typeof phEvent.timestamp === "number") {
9275
- ts = phEvent.timestamp;
9276
- } else if (typeof phEvent.timestamp === "string") {
9277
- ts = new Date(phEvent.timestamp).getTime();
9278
- } else {
9279
- ts = Date.now();
9280
- }
9281
- return {
9282
- ts,
9283
- name: getEventName(phEvent),
9284
- source: "posthog",
9285
- props: extractProps(phEvent),
9286
- schemaVersion: EVENT_SCHEMA_VERSION
9287
- };
9288
- }
9289
- function shouldNormalizeEvent(phEvent) {
9290
- const eventName = phEvent.event;
9291
- if (typeof eventName !== "string") return false;
9292
- const skipEvents = [
9293
- "$feature_flag_called",
9294
- "$feature_flags",
9295
- "$groups",
9296
- "$groupidentify",
9297
- "$set",
9298
- "$set_once",
9299
- "$unset",
9300
- "$create_alias",
9301
- "$capture_metrics",
9302
- "$performance_event",
9303
- "$web_vitals",
9304
- "$exception",
9305
- "$dead_click",
9306
- "$heatmap"
9307
- ];
9308
- if (skipEvents.includes(eventName)) {
9309
- return false;
9310
- }
9311
- return true;
9312
- }
9313
- function createPostHogNormalizer(publishFn) {
9314
- return (eventName, properties) => {
9315
- if (typeof eventName !== "string") return;
9316
- const phEvent = {
9317
- event: eventName,
9318
- properties,
9319
- timestamp: Date.now()
9320
- };
9321
- if (shouldNormalizeEvent(phEvent)) {
9322
- const normalizedEvent = normalizePostHogEvent(phEvent);
9323
- publishFn(normalizedEvent);
9324
- }
9325
- };
10049
+ return this.subscriptions.size;
10050
+ }
10051
+ };
10052
+ function createEventBus(options = {}) {
10053
+ return new EventBus(options);
9326
10054
  }
9327
10055
 
10056
+ // src/events/history.ts
10057
+ var EventHistory = class {
10058
+ constructor(maxSize = 100) {
10059
+ __publicField(this, "entries", []);
10060
+ __publicField(this, "maxSize");
10061
+ this.maxSize = maxSize;
10062
+ }
10063
+ record(entry) {
10064
+ this.entries.push(entry);
10065
+ if (this.maxSize > 0 && this.entries.length > this.maxSize) {
10066
+ this.entries.shift();
10067
+ }
10068
+ }
10069
+ getAll() {
10070
+ return [...this.entries];
10071
+ }
10072
+ clear() {
10073
+ this.entries = [];
10074
+ }
10075
+ };
10076
+
9328
10077
  // src/navigation/NavigationMonitor.ts
9329
10078
  var NavigationMonitor = class {
9330
10079
  constructor() {
@@ -10124,9 +10873,18 @@ function createSurfaces(options) {
10124
10873
  }
10125
10874
  async function unmountEntry(entry) {
10126
10875
  var _a2;
10127
- await playExitAnimation(entry.container, entry.options.animation);
10128
- (_a2 = entry.cleanup) == null ? void 0 : _a2.call(entry);
10129
- entry.container.remove();
10876
+ if (entry.container.isConnected) {
10877
+ await playExitAnimation(entry.container, entry.options.animation);
10878
+ }
10879
+ try {
10880
+ (_a2 = entry.cleanup) == null ? void 0 : _a2.call(entry);
10881
+ } catch (error2) {
10882
+ console.warn("[Surfaces] Cleanup error during unmount, ignoring:", error2);
10883
+ }
10884
+ try {
10885
+ entry.container.remove();
10886
+ } catch {
10887
+ }
10130
10888
  mounts.delete(entry.slot);
10131
10889
  publishEvent("surface.unmounted", {
10132
10890
  slot: entry.slot,
@@ -10309,7 +11067,9 @@ var WidgetRegistry = class {
10309
11067
  var _a2;
10310
11068
  const mounted = this.mountedWidgets.get(mountId);
10311
11069
  if (mounted) {
10312
- (_a2 = mounted.cleanup) == null ? void 0 : _a2.call(mounted);
11070
+ if (container.isConnected) {
11071
+ (_a2 = mounted.cleanup) == null ? void 0 : _a2.call(mounted);
11072
+ }
10313
11073
  this.mountedWidgets.delete(mountId);
10314
11074
  container.removeAttribute("data-widget-mount-id");
10315
11075
  container.removeAttribute("data-widget-id");
@@ -10425,7 +11185,16 @@ function matchesAnchorRoute(anchorId) {
10425
11185
  const pathname = typeof window !== "undefined" ? window.location.pathname : "/";
10426
11186
  const normalizedPath = pathname.replace(/\/$/, "") || "/";
10427
11187
  const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
10428
- return routes.some((pattern) => matchRoutePattern(normalizedPath, pattern));
11188
+ return routes.some((pattern) => {
11189
+ let normalized = pattern;
11190
+ if (/^https?:\/\//.test(normalized)) {
11191
+ try {
11192
+ normalized = new URL(normalized).pathname;
11193
+ } catch {
11194
+ }
11195
+ }
11196
+ return matchRoutePattern(normalizedPath, normalized);
11197
+ });
10429
11198
  }
10430
11199
  function createSmartCanvasRuntime(options = {}) {
10431
11200
  var _a2, _b, _c, _d;
@@ -10597,6 +11366,142 @@ function encodeToken(payload) {
10597
11366
  return TOKEN_PREFIX + base64;
10598
11367
  }
10599
11368
 
11369
+ // src/bootstrap-init.ts
11370
+ function getEnvVar(name) {
11371
+ if (typeof process !== "undefined" && process.env) {
11372
+ return process.env[name];
11373
+ }
11374
+ try {
11375
+ const meta = (0, eval)("import.meta");
11376
+ if (meta == null ? void 0 : meta.env) {
11377
+ return meta.env[name];
11378
+ }
11379
+ } catch {
11380
+ }
11381
+ return void 0;
11382
+ }
11383
+ var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
11384
+ function loadCachedSegmentAttributes() {
11385
+ if (typeof window === "undefined") return {};
11386
+ try {
11387
+ const cached = localStorage.getItem(SEGMENT_CACHE_KEY);
11388
+ if (cached) {
11389
+ const attrs = JSON.parse(cached);
11390
+ debug("Syntro Bootstrap", "Loaded cached segment attributes:", attrs);
11391
+ return attrs;
11392
+ }
11393
+ } catch (err) {
11394
+ warn("Syntro Bootstrap", "Failed to load cached segment attributes:", err);
11395
+ }
11396
+ return {};
11397
+ }
11398
+ function cacheSegmentAttributes(attrs) {
11399
+ if (typeof window === "undefined") return;
11400
+ try {
11401
+ localStorage.setItem(SEGMENT_CACHE_KEY, JSON.stringify(attrs));
11402
+ debug("Syntro Bootstrap", "Cached segment attributes:", attrs);
11403
+ } catch (err) {
11404
+ warn("Syntro Bootstrap", "Failed to cache segment attributes:", err);
11405
+ }
11406
+ }
11407
+ function extractSegmentFlags(allFlags) {
11408
+ if (!allFlags) return {};
11409
+ const segmentFlags = {};
11410
+ for (const [key, value] of Object.entries(allFlags)) {
11411
+ if (key.startsWith("in_segment_")) {
11412
+ segmentFlags[key] = value === true;
11413
+ }
11414
+ }
11415
+ return segmentFlags;
11416
+ }
11417
+ function collectBrowserMetadata() {
11418
+ var _a2;
11419
+ if (typeof window === "undefined") return {};
11420
+ const attrs = {};
11421
+ try {
11422
+ const params = new URLSearchParams(window.location.search);
11423
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
11424
+ const val = params.get(key);
11425
+ if (val) attrs[key] = val;
11426
+ }
11427
+ } catch {
11428
+ }
11429
+ try {
11430
+ if (navigator.language) attrs.browser_language = navigator.language;
11431
+ if ((_a2 = navigator.languages) == null ? void 0 : _a2.length) attrs.browser_languages = [...navigator.languages];
11432
+ } catch {
11433
+ }
11434
+ try {
11435
+ const w = window.innerWidth;
11436
+ attrs.device_type = w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
11437
+ } catch {
11438
+ }
11439
+ try {
11440
+ if (document.referrer) {
11441
+ attrs.referrer = document.referrer;
11442
+ try {
11443
+ attrs.referrer_hostname = new URL(document.referrer).hostname;
11444
+ } catch {
11445
+ }
11446
+ }
11447
+ } catch {
11448
+ }
11449
+ try {
11450
+ const ua = navigator.userAgent;
11451
+ if (ua.includes("Edg/")) attrs.browser = "Edge";
11452
+ else if (ua.includes("OPR/") || ua.includes("Opera")) attrs.browser = "Opera";
11453
+ else if (ua.includes("Chrome/") && !ua.includes("Chromium")) attrs.browser = "Chrome";
11454
+ else if (ua.includes("Safari/") && !ua.includes("Chrome")) attrs.browser = "Safari";
11455
+ else if (ua.includes("Firefox/")) attrs.browser = "Firefox";
11456
+ if (ua.includes("Windows")) attrs.os = "Windows";
11457
+ else if (ua.includes("iPhone") || ua.includes("iPad")) attrs.os = "iOS";
11458
+ else if (ua.includes("Mac OS X") || ua.includes("Macintosh")) attrs.os = "macOS";
11459
+ else if (ua.includes("Android")) attrs.os = "Android";
11460
+ else if (ua.includes("Linux")) attrs.os = "Linux";
11461
+ } catch {
11462
+ }
11463
+ try {
11464
+ attrs.page_url = window.location.href;
11465
+ attrs.page_path = window.location.pathname;
11466
+ attrs.page_host = window.location.hostname;
11467
+ if (window.location.search) attrs.page_query = window.location.search;
11468
+ } catch {
11469
+ }
11470
+ return attrs;
11471
+ }
11472
+ var GEO_CACHE_KEY = "syntro_geo";
11473
+ var GEO_DEFAULT_HOST = "https://geo.syntrologie.com";
11474
+ async function fetchGeo(geoHost) {
11475
+ if (typeof window === "undefined") return {};
11476
+ try {
11477
+ const cached = localStorage.getItem(GEO_CACHE_KEY);
11478
+ if (cached) {
11479
+ const parsed = JSON.parse(cached);
11480
+ debug("Syntro Bootstrap", "Geo: using cached data:", parsed);
11481
+ return parsed;
11482
+ }
11483
+ } catch {
11484
+ }
11485
+ try {
11486
+ const res = await fetch(geoHost, { signal: AbortSignal.timeout(2e3) });
11487
+ if (res.ok) {
11488
+ const geo = await res.json();
11489
+ const cleaned = {};
11490
+ for (const [k, v] of Object.entries(geo)) {
11491
+ if (typeof v === "string" && v) cleaned[k] = v;
11492
+ }
11493
+ try {
11494
+ localStorage.setItem(GEO_CACHE_KEY, JSON.stringify(cleaned));
11495
+ } catch {
11496
+ }
11497
+ debug("Syntro Bootstrap", "Geo: fetched from worker:", cleaned);
11498
+ return cleaned;
11499
+ }
11500
+ } catch {
11501
+ }
11502
+ return {};
11503
+ }
11504
+
10600
11505
  // src/experiments/registry.ts
10601
11506
  var adapters = {
10602
11507
  growthbook: (config) => createGrowthBookClient({
@@ -10845,95 +11750,9 @@ function createTelemetryClient(provider, config) {
10845
11750
  return factory(config);
10846
11751
  }
10847
11752
 
10848
- // src/bootstrap.ts
10849
- function getEnvVar(name) {
10850
- if (typeof process !== "undefined" && process.env) {
10851
- return process.env[name];
10852
- }
10853
- try {
10854
- const meta = (0, eval)("import.meta");
10855
- if (meta == null ? void 0 : meta.env) {
10856
- return meta.env[name];
10857
- }
10858
- } catch {
10859
- }
10860
- return void 0;
10861
- }
10862
- var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
10863
- function loadCachedSegmentAttributes() {
10864
- if (typeof window === "undefined") return {};
10865
- try {
10866
- const cached = localStorage.getItem(SEGMENT_CACHE_KEY);
10867
- if (cached) {
10868
- const attrs = JSON.parse(cached);
10869
- debug("Syntro Bootstrap", "Loaded cached segment attributes:", attrs);
10870
- return attrs;
10871
- }
10872
- } catch (err) {
10873
- warn("Syntro Bootstrap", "Failed to load cached segment attributes:", err);
10874
- }
10875
- return {};
10876
- }
10877
- function cacheSegmentAttributes(attrs) {
10878
- if (typeof window === "undefined") return;
10879
- try {
10880
- localStorage.setItem(SEGMENT_CACHE_KEY, JSON.stringify(attrs));
10881
- debug("Syntro Bootstrap", "Cached segment attributes:", attrs);
10882
- } catch (err) {
10883
- warn("Syntro Bootstrap", "Failed to cache segment attributes:", err);
10884
- }
10885
- }
10886
- function extractSegmentFlags(allFlags) {
10887
- if (!allFlags) return {};
10888
- const segmentFlags = {};
10889
- for (const [key, value] of Object.entries(allFlags)) {
10890
- if (key.startsWith("in_segment_")) {
10891
- segmentFlags[key] = value === true;
10892
- }
10893
- }
10894
- return segmentFlags;
10895
- }
10896
- function collectBrowserMetadata() {
10897
- var _a2;
10898
- if (typeof window === "undefined") return {};
10899
- const attrs = {};
10900
- try {
10901
- const params = new URLSearchParams(window.location.search);
10902
- for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
10903
- const val = params.get(key);
10904
- if (val) attrs[key] = val;
10905
- }
10906
- } catch {
10907
- }
10908
- try {
10909
- if (navigator.language) attrs.browser_language = navigator.language;
10910
- if ((_a2 = navigator.languages) == null ? void 0 : _a2.length) attrs.browser_languages = [...navigator.languages];
10911
- } catch {
10912
- }
10913
- try {
10914
- const w = window.innerWidth;
10915
- attrs.device_type = w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
10916
- } catch {
10917
- }
10918
- try {
10919
- if (document.referrer) {
10920
- attrs.referrer = document.referrer;
10921
- try {
10922
- attrs.referrer_hostname = new URL(document.referrer).hostname;
10923
- } catch {
10924
- }
10925
- }
10926
- } catch {
10927
- }
10928
- try {
10929
- attrs.page_url = window.location.href;
10930
- attrs.page_path = window.location.pathname;
10931
- } catch {
10932
- }
10933
- return attrs;
10934
- }
10935
- async function init(options) {
10936
- var _a2, _b, _c, _d, _e, _f;
11753
+ // src/bootstrap-runtime.ts
11754
+ async function _initCore(options) {
11755
+ var _a2, _b, _c, _d, _e, _f, _g;
10937
11756
  initLogger();
10938
11757
  debug("Syntro Bootstrap", "====== INIT ======");
10939
11758
  debug("Syntro Bootstrap", "Options:", {
@@ -10998,17 +11817,59 @@ async function init(options) {
10998
11817
  const experimentHost = getEnvVar("NEXT_PUBLIC_SYNTRO_EXPERIMENT_HOST") || getEnvVar("VITE_SYNTRO_EXPERIMENT_HOST") || (payload == null ? void 0 : payload.eh);
10999
11818
  const telemetryHost = getEnvVar("NEXT_PUBLIC_SYNTRO_TELEMETRY_HOST") || getEnvVar("VITE_SYNTRO_TELEMETRY_HOST") || (payload == null ? void 0 : payload.th);
11000
11819
  const editorUrl = getEnvVar("NEXT_PUBLIC_SYNTRO_EDITOR_URL") || getEnvVar("VITE_SYNTRO_EDITOR_URL") || ((_b = options.canvas) == null ? void 0 : _b.editorUrl);
11820
+ const geoHost = (payload == null ? void 0 : payload.g) || getEnvVar("NEXT_PUBLIC_SYNTRO_GEO_HOST") || getEnvVar("VITE_SYNTRO_GEO_HOST") || GEO_DEFAULT_HOST;
11001
11821
  const cachedSegmentAttrs = loadCachedSegmentAttributes();
11002
11822
  const browserMetadata = collectBrowserMetadata();
11003
11823
  const phaseOneAttrs = { ...browserMetadata, ...cachedSegmentAttrs };
11004
11824
  debug("Syntro Bootstrap", "Phase 1: Browser metadata:", browserMetadata);
11005
11825
  debug("Syntro Bootstrap", "Phase 1: Cached segment attributes:", cachedSegmentAttrs);
11826
+ const geoPromise = fetchGeo(geoHost);
11006
11827
  let experiments;
11007
- const events = createEventBus();
11828
+ const isDebugOrTest = options.debug || options.testMode;
11829
+ const events = createEventBus({
11830
+ debug: options.debug,
11831
+ history: isDebugOrTest ? new EventHistory(options.testMode ? 0 : 100) : void 0,
11832
+ testMode: options.testMode
11833
+ });
11008
11834
  console.log("[Syntro Bootstrap] EventBus created");
11009
- const postHogNormalizer = createPostHogNormalizer((event) => {
11010
- events.publishEvent(event);
11835
+ const processor = createEventProcessor({
11836
+ elementResolver: typeof window !== "undefined" ? (x, y) => {
11837
+ const el = document.elementFromPoint(x, y);
11838
+ if (!el) return null;
11839
+ const info = { tag_name: el.tagName.toLowerCase() };
11840
+ if (el.id) info.attr__id = el.id;
11841
+ if (el.className && typeof el.className === "string") {
11842
+ info.classes = el.className.split(" ").filter(Boolean);
11843
+ }
11844
+ for (const attr of el.attributes) {
11845
+ if (attr.name.startsWith("data-")) info[`attr__${attr.name}`] = attr.value;
11846
+ }
11847
+ return info;
11848
+ } : void 0
11011
11849
  });
11850
+ processor.onEvent((event) => events.publishEvent(event));
11851
+ if (typeof window !== "undefined") {
11852
+ setInterval(() => processor.tick(Date.now()), 1e3);
11853
+ document.addEventListener(
11854
+ "click",
11855
+ (e) => {
11856
+ const chain = [];
11857
+ let el = e.target;
11858
+ while (el && el !== document.body) {
11859
+ const info = { tag_name: el.tagName.toLowerCase() };
11860
+ for (const attr of el.attributes) {
11861
+ if (attr.name.startsWith("data-") || attr.name === "id" || attr.name === "class" || attr.name === "aria-label") {
11862
+ info[`attr__${attr.name}`] = attr.value;
11863
+ }
11864
+ }
11865
+ chain.push(info);
11866
+ el = el.parentElement;
11867
+ }
11868
+ processor.enrichClickAttributes(Date.now(), chain);
11869
+ },
11870
+ true
11871
+ );
11872
+ }
11012
11873
  const onFeatureFlagsLoaded = (allFlags) => {
11013
11874
  var _a3, _b2, _c2;
11014
11875
  debug("Syntro Bootstrap", "Phase 2: PostHog feature flags loaded");
@@ -11031,12 +11892,26 @@ async function init(options) {
11031
11892
  // undefined falls back to adapter default
11032
11893
  // Enable PostHog feature flags for segment membership
11033
11894
  enableFeatureFlags: true,
11895
+ // Disable session recording in debug/dev mode (mock telemetry doesn't
11896
+ // support the PostHog recorder extension, causing console errors)
11897
+ sessionRecording: !payload.d,
11034
11898
  // Wire up callback for when flags are loaded (Phase 2)
11035
11899
  onFeatureFlagsLoaded,
11036
- // Wire up event capture to feed into EventBus
11037
- onCapture: postHogNormalizer
11900
+ // Wire up event capture to feed into event processor
11901
+ onCapture: (eventName, properties) => {
11902
+ processor.ingest({ kind: "posthog", event: eventName, properties, timestamp: Date.now() });
11903
+ },
11904
+ // Wire rrweb events for behavioral signal detection
11905
+ onRRWebEvent: (event) => {
11906
+ processor.ingest(event);
11907
+ }
11038
11908
  });
11039
11909
  console.log(`[Syntro Bootstrap] Telemetry client created (${provider}) with EventBus wiring`);
11910
+ const telemetryForCapture = telemetry;
11911
+ events.setPosthogCapture((name, props) => {
11912
+ var _a3;
11913
+ (_a3 = telemetryForCapture.track) == null ? void 0 : _a3.call(telemetryForCapture, name, props);
11914
+ });
11040
11915
  }
11041
11916
  let sessionMetrics;
11042
11917
  if (payload == null ? void 0 : payload.e) {
@@ -11129,11 +12004,17 @@ async function init(options) {
11129
12004
  warn("Syntro Bootstrap", "Failed to load GrowthBook features:", err);
11130
12005
  }
11131
12006
  }
12007
+ const geoData = await geoPromise;
12008
+ if (experiments && Object.keys(geoData).length > 0) {
12009
+ const mergedAttrs = { ...browserMetadata, ...geoData };
12010
+ debug("Syntro Bootstrap", "Merging geo data into GrowthBook attributes:", geoData);
12011
+ (_f = experiments.setAttributes) == null ? void 0 : _f.call(experiments, mergedAttrs);
12012
+ }
11132
12013
  let baseFetcher;
11133
12014
  if (options.fetcher) {
11134
12015
  baseFetcher = options.fetcher;
11135
12016
  } else if (payload == null ? void 0 : payload.f) {
11136
- const configFetcher = createConfigFetcher(payload.f, (_f = payload.o) != null ? _f : {});
12017
+ const configFetcher = createConfigFetcher(payload.f, (_g = payload.o) != null ? _g : {});
11137
12018
  baseFetcher = async () => {
11138
12019
  var _a3;
11139
12020
  const result = await configFetcher.fetch();
@@ -11147,7 +12028,7 @@ async function init(options) {
11147
12028
  }
11148
12029
  const warnedAppFailures = /* @__PURE__ */ new Set();
11149
12030
  const appLoadingFetcher = baseFetcher ? async () => {
11150
- var _a3, _b2, _c2, _d2, _e2, _f2, _g;
12031
+ var _a3, _b2, _c2, _d2, _e2, _f2, _g2;
11151
12032
  const config = await baseFetcher();
11152
12033
  console.log(
11153
12034
  "[Syntro Bootstrap] Config fetched:",
@@ -11155,11 +12036,11 @@ async function init(options) {
11155
12036
  `actions=${(_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0},`,
11156
12037
  `theme=${(_f2 = (_e2 = config.theme) == null ? void 0 : _e2.name) != null ? _f2 : "none"}`
11157
12038
  );
11158
- if (((_g = config.actions) == null ? void 0 : _g.length) > 0) {
12039
+ if (((_g2 = config.actions) == null ? void 0 : _g2.length) > 0) {
11159
12040
  console.log(
11160
12041
  "[Syntro Bootstrap] Actions in config:",
11161
12042
  config.actions.map(
11162
- (a, i) => `[${i}] ${a.kind}${a.anchorId ? ` anchor="${typeof a.anchorId === "string" ? a.anchorId : a.anchorId.selector}"` : ""}${a.label ? ` "${a.label}"` : ""}`
12043
+ (a, i) => `[${i}] ${a.kind}${a.anchorId ? ` anchor="${a.anchorId.selector}"` : ""}${a.label ? ` "${a.label}"` : ""}`
11163
12044
  ).join(", ")
11164
12045
  );
11165
12046
  }
@@ -11218,10 +12099,33 @@ async function init(options) {
11218
12099
  });
11219
12100
  return { canvas, runtime: runtime3, experiments, telemetry, sessionMetrics, appLoader };
11220
12101
  }
12102
+
12103
+ // src/bootstrap.ts
12104
+ async function init(options) {
12105
+ var _a2;
12106
+ try {
12107
+ return await _initCore(options);
12108
+ } catch (err) {
12109
+ const message = err instanceof Error ? err.message : String(err);
12110
+ console.warn("[Syntrologie] SDK initialization failed:", message);
12111
+ if (typeof document !== "undefined") {
12112
+ (_a2 = document.getElementById("syntrologie-anti-flicker")) == null ? void 0 : _a2.remove();
12113
+ }
12114
+ return void 0;
12115
+ }
12116
+ }
12117
+ function emit(eventName, props = {}) {
12118
+ var _a2, _b;
12119
+ if (typeof window === "undefined") return;
12120
+ const runtime3 = (_a2 = window.SynOS) == null ? void 0 : _a2.runtime;
12121
+ if (!((_b = runtime3 == null ? void 0 : runtime3.events) == null ? void 0 : _b.publish)) return;
12122
+ runtime3.events.publish({ name: eventName, source: "custom", props });
12123
+ }
11221
12124
  var Syntro = {
11222
12125
  init,
11223
12126
  encodeToken,
11224
- decodeToken
12127
+ decodeToken,
12128
+ events: { emit }
11225
12129
  };
11226
12130
  if (typeof window !== "undefined") {
11227
12131
  window.Syntro = Syntro;
@@ -11252,8 +12156,11 @@ export {
11252
12156
  createSmartCanvasController,
11253
12157
  ShadowRootProvider,
11254
12158
  useShadowRoot,
11255
- StandardEvents,
11256
12159
  EVENT_SCHEMA_VERSION,
12160
+ normalizePostHogEvent,
12161
+ shouldNormalizeEvent,
12162
+ createPostHogNormalizer,
12163
+ StandardEvents2 as StandardEvents,
11257
12164
  CanvasEvents,
11258
12165
  NotificationToastStack,
11259
12166
  MAX_VISIBLE_TOASTS,
@@ -11302,11 +12209,11 @@ export {
11302
12209
  evaluateSync,
11303
12210
  createDecisionEngine,
11304
12211
  createEventAccumulator,
12212
+ validateEventName,
12213
+ validateProps,
11305
12214
  EventBus,
11306
12215
  createEventBus,
11307
- normalizePostHogEvent,
11308
- shouldNormalizeEvent,
11309
- createPostHogNormalizer,
12216
+ EventHistory,
11310
12217
  NavigationMonitor,
11311
12218
  StateStore,
11312
12219
  createStateStore,
@@ -11330,4 +12237,4 @@ export {
11330
12237
  encodeToken,
11331
12238
  Syntro
11332
12239
  };
11333
- //# sourceMappingURL=chunk-5GTCL2IH.js.map
12240
+ //# sourceMappingURL=chunk-X2XEU6KD.js.map