@syntrologie/runtime-sdk 2.8.0-canary.7 → 2.8.0-canary.71

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 (62) hide show
  1. package/CAPABILITIES.md +299 -167
  2. package/README.md +2 -0
  3. package/dist/actions/schema.d.ts +783 -783
  4. package/dist/actions/schema.js +3 -3
  5. package/dist/actions/types.d.ts +1 -1
  6. package/dist/actions/validation-core.d.ts +24 -0
  7. package/dist/actions/validation-rules.d.ts +74 -0
  8. package/dist/actions/validation.d.ts +5 -11
  9. package/dist/bootstrap-init.d.ts +33 -0
  10. package/dist/bootstrap-runtime.d.ts +7 -0
  11. package/dist/bootstrap-types.d.ts +90 -0
  12. package/dist/bootstrap.d.ts +19 -83
  13. package/dist/{chunk-R5DNAIRI.js → chunk-77TNZ66J.js} +4 -4
  14. package/dist/{chunk-R5DNAIRI.js.map → chunk-77TNZ66J.js.map} +2 -2
  15. package/dist/{chunk-XDYJ64IN.js → chunk-IR6UOR63.js} +4 -4
  16. package/dist/chunk-IR6UOR63.js.map +7 -0
  17. package/dist/{chunk-GWF5BTST.js → chunk-WX32GVSP.js} +1855 -783
  18. package/dist/chunk-WX32GVSP.js.map +7 -0
  19. package/dist/chunk-YLLWLUQX.js +241 -0
  20. package/dist/chunk-YLLWLUQX.js.map +7 -0
  21. package/dist/components/ShadowCanvasOverlay.d.ts +1 -2
  22. package/dist/components/TileIcon.d.ts +2 -2
  23. package/dist/components/emojiToIcon.d.ts +24 -0
  24. package/dist/config/schema.d.ts +147 -136
  25. package/dist/config/schema.js +2 -2
  26. package/dist/decisions/schema.d.ts +47 -47
  27. package/dist/decisions/schema.js +1 -1
  28. package/dist/events/EventBus.d.ts +27 -1
  29. package/dist/events/history.d.ts +9 -0
  30. package/dist/events/index.d.ts +3 -0
  31. package/dist/events/normalizers/posthog.d.ts +4 -50
  32. package/dist/events/types.d.ts +30 -23
  33. package/dist/events/validation.d.ts +7 -0
  34. package/dist/fetchers/experimentsFetcher.d.ts +3 -3
  35. package/dist/index.d.ts +0 -2
  36. package/dist/index.js +1529 -212
  37. package/dist/index.js.map +4 -4
  38. package/dist/overlays/runtime/overlay/overlay-runner.d.ts +4 -0
  39. package/dist/overlays/runtime/overlay/overlay-state.d.ts +21 -0
  40. package/dist/overlays/types.d.ts +3 -1
  41. package/dist/react.js +6 -4
  42. package/dist/react.js.map +2 -2
  43. package/dist/smart-canvas.esm.js +115 -65
  44. package/dist/smart-canvas.esm.js.map +4 -4
  45. package/dist/smart-canvas.js +5901 -3064
  46. package/dist/smart-canvas.js.map +4 -4
  47. package/dist/smart-canvas.min.js +115 -65
  48. package/dist/smart-canvas.min.js.map +4 -4
  49. package/dist/telemetry/InterventionTracker.d.ts +23 -0
  50. package/dist/telemetry/adapters/posthog.d.ts +30 -4
  51. package/dist/telemetry/index.d.ts +1 -0
  52. package/dist/test/setup.d.ts +1 -0
  53. package/dist/token.d.ts +2 -0
  54. package/dist/version.d.ts +1 -1
  55. package/package.json +23 -28
  56. package/schema/canvas-config.schema.json +1974 -10925
  57. package/scripts/syntroReactPlugin.mjs +3 -0
  58. package/scripts/validate-config.mjs +42 -0
  59. package/dist/chunk-BU4Z6PD7.js +0 -218
  60. package/dist/chunk-BU4Z6PD7.js.map +0 -7
  61. package/dist/chunk-GWF5BTST.js.map +0 -7
  62. package/dist/chunk-XDYJ64IN.js.map +0 -7
@@ -3,7 +3,7 @@ import {
3
3
  __privateGet,
4
4
  __privateSet,
5
5
  __publicField
6
- } from "./chunk-BU4Z6PD7.js";
6
+ } from "./chunk-YLLWLUQX.js";
7
7
 
8
8
  // ../adaptives/adaptive-content/dist/reconciliation-guard.js
9
9
  function guardAgainstReconciliation(container, anchor, reinsertFn, opts) {
@@ -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;
@@ -1369,7 +1408,7 @@ var alert = {
1369
1408
  var tag = {
1370
1409
  content: slateGrey[10],
1371
1410
  border: slateGrey[4],
1372
- background: "#1c2124"
1411
+ background: slateGrey[3]
1373
1412
  };
1374
1413
  var menu = {
1375
1414
  backgroundDefault: slateGrey[2],
@@ -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";
@@ -2221,7 +2293,7 @@ function extractWorkflowsFromActive(activeActions) {
2221
2293
  const workflows = /* @__PURE__ */ new Map();
2222
2294
  for (const entry of activeActions) {
2223
2295
  const action = entry.action;
2224
- if (action.kind === "core:tour" && action.workflow && action.tourId) {
2296
+ if (action.kind === "overlays:tour" && action.workflow && action.tourId) {
2225
2297
  const meta = action.workflow;
2226
2298
  const rawSteps = action.steps || [];
2227
2299
  const steps = rawSteps.map((s) => {
@@ -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
  }
@@ -2729,7 +2816,7 @@ var executors2 = [
2729
2816
  { kind: "overlays:badge", executor: executeBadge },
2730
2817
  { kind: "overlays:tooltip", executor: executeTooltip },
2731
2818
  { kind: "overlays:modal", executor: executeModal },
2732
- { kind: "core:tour", executor: executeTour },
2819
+ { kind: "overlays:tour", executor: executeTour },
2733
2820
  { kind: "overlays:celebrate", executor: executeCelebrate }
2734
2821
  ];
2735
2822
  var runtime2 = {
@@ -2849,7 +2936,15 @@ var CORE_ACTION_KINDS = /* @__PURE__ */ new Set([
2849
2936
  "core:wait",
2850
2937
  "core:sequence",
2851
2938
  "core:parallel",
2852
- "core:tour"
2939
+ "overlays:tour"
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"
2853
2948
  ]);
2854
2949
  var NAMESPACE_TO_APP_ID = {
2855
2950
  faq: "adaptive-faq",
@@ -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.7";
3459
+ var SDK_VERSION = "2.8.0-canary.71";
3362
3460
 
3363
3461
  // src/types.ts
3364
3462
  var SDK_SCHEMA_VERSION = "2.0";
@@ -3381,7 +3479,8 @@ function resolveVariantConfigs(client, keys, _strategy = "first-match") {
3381
3479
  ...variant.configVersion && { configVersion: variant.configVersion },
3382
3480
  ...variant.canvasTitle && { canvasTitle: variant.canvasTitle },
3383
3481
  ...variant.theme && { theme: variant.theme },
3384
- ...variant.launcher && { launcher: variant.launcher }
3482
+ ...variant.launcher && { launcher: variant.launcher },
3483
+ ...variant.meta && { meta: variant.meta }
3385
3484
  };
3386
3485
  }
3387
3486
  }
@@ -3509,12 +3608,14 @@ function getCachedConfig(sdkVersion) {
3509
3608
  var resolveConfigUri = ({
3510
3609
  configUri,
3511
3610
  experiments,
3512
- featureKey = "smart-canvas-config-uri"
3611
+ featureKey
3513
3612
  }) => {
3514
3613
  var _a2;
3515
3614
  if (configUri) return configUri;
3516
- const fromFeature = (_a2 = experiments == null ? void 0 : experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, featureKey, null);
3517
- if (fromFeature) return fromFeature;
3615
+ if (experiments && featureKey) {
3616
+ const fromFeature = (_a2 = experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, featureKey, null);
3617
+ if (fromFeature) return fromFeature;
3618
+ }
3518
3619
  return void 0;
3519
3620
  };
3520
3621
  function getVariantFlagKeys(experiments, manifestKey, variantFlagPrefix) {
@@ -3540,7 +3641,7 @@ var createCanvasConfigFetcher = ({
3540
3641
  experiments,
3541
3642
  featureKey,
3542
3643
  credentials,
3543
- configFeatureKey = "smart-canvas-config",
3644
+ configFeatureKey,
3544
3645
  manifestKey,
3545
3646
  variantFlagPrefix,
3546
3647
  sdkVersion
@@ -3641,7 +3742,8 @@ function registerFromTriggerWhen(triggerWhen, accumulator) {
3641
3742
  if (cond.type === "event_count" && cond.key) {
3642
3743
  const counter = cond.counter;
3643
3744
  const predicate = counter ? buildPredicate(counter) : () => true;
3644
- accumulator.register(cond.key, predicate);
3745
+ const needsTimestamps = typeof cond.withinMs === "number";
3746
+ accumulator.register(cond.key, predicate, needsTimestamps);
3645
3747
  }
3646
3748
  }
3647
3749
  }
@@ -3734,133 +3836,743 @@ function useShadowRoot() {
3734
3836
  return ctx;
3735
3837
  }
3736
3838
 
3737
- // src/events/types.ts
3839
+ // ../event-processor/dist/types.js
3840
+ var EVENT_SCHEMA_VERSION = "1.0.0";
3738
3841
  var StandardEvents = {
3739
- // UI events (from PostHog autocapture)
3740
3842
  UI_CLICK: "ui.click",
3741
3843
  UI_SCROLL: "ui.scroll",
3742
3844
  UI_INPUT: "ui.input",
3743
3845
  UI_CHANGE: "ui.change",
3744
3846
  UI_SUBMIT: "ui.submit",
3745
- // Navigation events
3746
3847
  NAV_PAGE_VIEW: "nav.page_view",
3747
3848
  NAV_PAGE_LEAVE: "nav.page_leave",
3748
- // Canvas events
3749
- CANVAS_OPENED: "canvas.opened",
3750
- CANVAS_CLOSED: "canvas.closed",
3751
- TILE_VIEWED: "tile.viewed",
3752
- TILE_EXPANDED: "tile.expanded",
3753
- TILE_COLLAPSED: "tile.collapsed",
3754
- TILE_ACTION: "tile.action",
3755
- // Overlay/tour events
3756
- OVERLAY_STARTED: "overlay.started",
3757
- OVERLAY_COMPLETED: "overlay.completed",
3758
- OVERLAY_DISMISSED: "overlay.dismissed",
3759
- OVERLAY_STEP_VIEWED: "overlay.step_viewed",
3760
- // Derived behavioral signals (Phase 3)
3761
- BEHAVIOR_RAGE_CLICK: "behavior.rage_click",
3762
- BEHAVIOR_HESITATION: "behavior.hesitation",
3763
- BEHAVIOR_CONFUSION: "behavior.confusion",
3764
- // Action events
3765
- ACTION_APPLIED: "action.applied",
3766
- ACTION_REVERTED: "action.reverted",
3767
- ACTION_FAILED: "action.failed",
3768
- ACTION_CTA_CLICKED: "action.cta_clicked",
3769
- // Notification events
3770
- NOTIFICATION_SHOWN: "notification.shown",
3771
- NOTIFICATION_CLICKED: "notification.clicked",
3772
- NOTIFICATION_DISMISSED: "notification.dismissed",
3773
- NOTIFICATION_DEEP_LINK: "notification.deep_link",
3774
- // Surface events
3775
- SURFACE_MOUNTED: "surface.mounted",
3776
- SURFACE_UNMOUNTED: "surface.unmounted"
3849
+ UI_HESITATE: "ui.hesitate",
3850
+ UI_RAGE_CLICK: "ui.rage_click",
3851
+ UI_SCROLL_THRASH: "ui.scroll_thrash",
3852
+ UI_FOCUS_BOUNCE: "ui.focus_bounce",
3853
+ UI_IDLE: "ui.idle",
3854
+ UI_HOVER: "ui.hover"
3855
+ };
3856
+ var RRWebSource = {
3857
+ Mutation: 0,
3858
+ MouseMove: 1,
3859
+ MouseInteraction: 2,
3860
+ Scroll: 3,
3861
+ ViewportResize: 4,
3862
+ Input: 5,
3863
+ TouchMove: 6,
3864
+ MediaInteraction: 7,
3865
+ Drag: 12
3866
+ };
3867
+ var RRWebMouseInteraction = {
3868
+ MouseUp: 0,
3869
+ MouseDown: 1,
3870
+ Click: 2,
3871
+ ContextMenu: 3,
3872
+ DblClick: 4,
3873
+ Focus: 5,
3874
+ Blur: 6,
3875
+ TouchStart: 7,
3876
+ TouchEnd: 9
3877
+ };
3878
+ var DEFAULT_DETECTOR_CONFIG = {
3879
+ hesitationMs: 3e3,
3880
+ hesitationRadiusPx: 10,
3881
+ rageClickCount: 3,
3882
+ rageClickWindowMs: 1e3,
3883
+ rageClickRadiusPx: 30,
3884
+ scrollThrashReversals: 3,
3885
+ scrollThrashWindowMs: 2e3,
3886
+ focusBounceMaxInputs: 0,
3887
+ idleMs: 5e3,
3888
+ hoverSampleMs: 100
3777
3889
  };
3778
- var EVENT_SCHEMA_VERSION = "1.0.0";
3779
3890
 
3780
- // src/events/normalizers/canvas.ts
3781
- function createCanvasEvent(name, props) {
3782
- return {
3783
- ts: Date.now(),
3784
- name,
3785
- source: "canvas",
3786
- props,
3787
- schemaVersion: EVENT_SCHEMA_VERSION
3788
- };
3789
- }
3790
- function canvasOpened(surface) {
3791
- return createCanvasEvent(StandardEvents.CANVAS_OPENED, { surface });
3792
- }
3793
- function canvasClosed(surface) {
3794
- return createCanvasEvent(StandardEvents.CANVAS_CLOSED, { surface });
3795
- }
3796
- function tileViewed(tileId, surface) {
3797
- return createCanvasEvent(StandardEvents.TILE_VIEWED, { tileId, surface });
3798
- }
3799
- function tileExpanded(tileId, surface) {
3800
- return createCanvasEvent(StandardEvents.TILE_EXPANDED, { tileId, surface });
3801
- }
3802
- function tileCollapsed(tileId, surface) {
3803
- return createCanvasEvent(StandardEvents.TILE_COLLAPSED, { tileId, surface });
3891
+ // ../event-processor/dist/normalizers/posthog.js
3892
+ var POSTHOG_EVENT_MAP = {
3893
+ // NOTE: $autocapture is intentionally NOT in this map.
3894
+ // It's handled below in getEventName() with $event_type refinement
3895
+ // so that change/submit events aren't all mapped to ui.click.
3896
+ $click: StandardEvents.UI_CLICK,
3897
+ $scroll: StandardEvents.UI_SCROLL,
3898
+ $input: StandardEvents.UI_INPUT,
3899
+ $change: StandardEvents.UI_CHANGE,
3900
+ $submit: StandardEvents.UI_SUBMIT,
3901
+ // Navigation events
3902
+ $pageview: StandardEvents.NAV_PAGE_VIEW,
3903
+ $pageleave: StandardEvents.NAV_PAGE_LEAVE,
3904
+ // Session events
3905
+ $session_start: "session.start",
3906
+ // Identify events
3907
+ $identify: "user.identify"
3908
+ };
3909
+ function getEventName(phEvent) {
3910
+ var _a2, _b;
3911
+ const eventName = phEvent.event;
3912
+ if (typeof eventName !== "string") {
3913
+ return "posthog.unknown";
3914
+ }
3915
+ if (POSTHOG_EVENT_MAP[eventName]) {
3916
+ return POSTHOG_EVENT_MAP[eventName];
3917
+ }
3918
+ if (eventName === "$autocapture") {
3919
+ const tagName = (_a2 = phEvent.properties) == null ? void 0 : _a2.$tag_name;
3920
+ const eventType = (_b = phEvent.properties) == null ? void 0 : _b.$event_type;
3921
+ if (eventType === "submit")
3922
+ return StandardEvents.UI_SUBMIT;
3923
+ if (eventType === "change")
3924
+ return StandardEvents.UI_CHANGE;
3925
+ if (tagName === "input" || tagName === "textarea")
3926
+ return StandardEvents.UI_INPUT;
3927
+ return StandardEvents.UI_CLICK;
3928
+ }
3929
+ if (!eventName.startsWith("$")) {
3930
+ return `posthog.${eventName}`;
3931
+ }
3932
+ return eventName.replace("$", "posthog.");
3804
3933
  }
3805
- function tileAction(tileId, actionId, surface) {
3806
- return createCanvasEvent(StandardEvents.TILE_ACTION, {
3807
- tileId,
3808
- actionId,
3809
- surface
3934
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["a", "button", "input", "select", "textarea"]);
3935
+ function parseElementsChain(chain) {
3936
+ if (!chain)
3937
+ return void 0;
3938
+ return chain.split(";").map((segment) => {
3939
+ const el = {};
3940
+ const colonIdx = segment.indexOf(":");
3941
+ const tagPart = colonIdx >= 0 ? segment.slice(0, colonIdx) : segment;
3942
+ const attrPart = colonIdx >= 0 ? segment.slice(colonIdx + 1) : "";
3943
+ const dotIdx = tagPart.indexOf(".");
3944
+ if (dotIdx >= 0) {
3945
+ el.tag_name = tagPart.slice(0, dotIdx);
3946
+ el.attr__class = tagPart.slice(dotIdx + 1).replace(/\./g, " ");
3947
+ } else {
3948
+ el.tag_name = tagPart;
3949
+ }
3950
+ const attrRegex = /([\w$]+)="([^"]*)"/g;
3951
+ let match;
3952
+ while ((match = attrRegex.exec(attrPart)) !== null) {
3953
+ const [, key, value] = match;
3954
+ if (key === "nth-child" || key === "nth-of-type")
3955
+ continue;
3956
+ el[key] = value;
3957
+ }
3958
+ if (el.text) {
3959
+ el.$el_text = el.text;
3960
+ delete el.text;
3961
+ }
3962
+ return el;
3810
3963
  });
3811
3964
  }
3812
- function overlayStarted(recipeId, recipeName) {
3813
- return createCanvasEvent(StandardEvents.OVERLAY_STARTED, {
3814
- recipeId,
3815
- recipeName
3816
- });
3965
+ function resolveInteractiveTag(elements, directTag) {
3966
+ if (directTag && INTERACTIVE_TAGS.has(directTag))
3967
+ return directTag;
3968
+ if (!elements)
3969
+ return directTag;
3970
+ for (const el of elements) {
3971
+ const tag2 = el.tag_name;
3972
+ if (tag2 && INTERACTIVE_TAGS.has(tag2))
3973
+ return tag2;
3974
+ }
3975
+ return directTag;
3817
3976
  }
3818
- function overlayCompleted(recipeId, recipeName) {
3819
- return createCanvasEvent(StandardEvents.OVERLAY_COMPLETED, {
3820
- recipeId,
3821
- recipeName
3822
- });
3977
+ function extractProps(phEvent) {
3978
+ var _a2, _b, _c;
3979
+ const props = {};
3980
+ const phProps = phEvent.properties || {};
3981
+ const elements = (_a2 = phProps.$elements) != null ? _a2 : typeof phProps.$elements_chain === "string" ? parseElementsChain(phProps.$elements_chain) : void 0;
3982
+ const directTag = (_c = phProps.$tag_name) != null ? _c : (_b = elements == null ? void 0 : elements[0]) == null ? void 0 : _b.tag_name;
3983
+ const isClickEvent = phEvent.event === "$autocapture" || phEvent.event === "$click";
3984
+ props.tagName = isClickEvent ? resolveInteractiveTag(elements, directTag) : directTag;
3985
+ if (phProps.$el_text)
3986
+ props.elementText = phProps.$el_text;
3987
+ if (elements)
3988
+ props.elements = elements;
3989
+ if (isClickEvent && !elements) {
3990
+ console.warn(`[PostHogNormalizer] $autocapture click has no element chain. PostHog may have changed wire format. Properties: $elements=${!!phProps.$elements}, $elements_chain=${typeof phProps.$elements_chain}`);
3991
+ }
3992
+ if (phProps.$current_url)
3993
+ props.url = phProps.$current_url;
3994
+ if (phProps.$pathname)
3995
+ props.pathname = phProps.$pathname;
3996
+ if (phProps.$host)
3997
+ props.host = phProps.$host;
3998
+ if (phProps.$viewport_width)
3999
+ props.viewportWidth = phProps.$viewport_width;
4000
+ if (phProps.$viewport_height)
4001
+ props.viewportHeight = phProps.$viewport_height;
4002
+ if (phProps.$session_id)
4003
+ props.sessionId = phProps.$session_id;
4004
+ if (phProps.$scroll_depth)
4005
+ props.scrollDepth = phProps.$scroll_depth;
4006
+ if (phProps.$scroll_percentage)
4007
+ props.scrollPercentage = phProps.$scroll_percentage;
4008
+ props.originalEvent = phEvent.event;
4009
+ return props;
3823
4010
  }
3824
- function overlayDismissed(recipeId, recipeName, stepIndex) {
3825
- return createCanvasEvent(StandardEvents.OVERLAY_DISMISSED, {
3826
- recipeId,
3827
- recipeName,
3828
- stepIndex
3829
- });
4011
+ function normalizePostHogEvent(phEvent) {
4012
+ let ts;
4013
+ if (typeof phEvent.timestamp === "number") {
4014
+ ts = phEvent.timestamp;
4015
+ } else if (typeof phEvent.timestamp === "string") {
4016
+ ts = new Date(phEvent.timestamp).getTime();
4017
+ } else {
4018
+ ts = Date.now();
4019
+ }
4020
+ return {
4021
+ ts,
4022
+ name: getEventName(phEvent),
4023
+ source: "posthog",
4024
+ props: extractProps(phEvent),
4025
+ schemaVersion: EVENT_SCHEMA_VERSION
4026
+ };
3830
4027
  }
3831
- function overlayStepViewed(recipeId, stepIndex, stepTitle) {
3832
- return createCanvasEvent(StandardEvents.OVERLAY_STEP_VIEWED, {
3833
- recipeId,
3834
- stepIndex,
3835
- stepTitle
3836
- });
4028
+ function shouldNormalizeEvent(phEvent) {
4029
+ const eventName = phEvent.event;
4030
+ if (typeof eventName !== "string")
4031
+ return false;
4032
+ const skipEvents = [
4033
+ "$feature_flag_called",
4034
+ "$feature_flags",
4035
+ "$groups",
4036
+ "$groupidentify",
4037
+ "$set",
4038
+ "$set_once",
4039
+ "$unset",
4040
+ "$create_alias",
4041
+ "$capture_metrics",
4042
+ "$performance_event",
4043
+ "$web_vitals",
4044
+ "$exception",
4045
+ "$dead_click",
4046
+ "$heatmap"
4047
+ ];
4048
+ if (skipEvents.includes(eventName)) {
4049
+ return false;
4050
+ }
4051
+ return true;
3837
4052
  }
3838
- function customCanvasEvent(name, props) {
3839
- const eventName = name.startsWith("canvas.") ? name : `canvas.${name}`;
3840
- return createCanvasEvent(eventName, props);
4053
+ function createPostHogNormalizer(publishFn) {
4054
+ return (eventName, properties) => {
4055
+ if (typeof eventName !== "string")
4056
+ return;
4057
+ const phEvent = {
4058
+ event: eventName,
4059
+ properties,
4060
+ timestamp: Date.now()
4061
+ };
4062
+ if (shouldNormalizeEvent(phEvent)) {
4063
+ const normalizedEvent = normalizePostHogEvent(phEvent);
4064
+ publishFn(normalizedEvent);
4065
+ }
4066
+ };
3841
4067
  }
3842
- var CanvasEvents = {
3843
- canvasOpened,
3844
- canvasClosed,
3845
- tileViewed,
3846
- tileExpanded,
3847
- tileCollapsed,
3848
- tileAction,
3849
- overlayStarted,
3850
- overlayCompleted,
3851
- overlayDismissed,
3852
- overlayStepViewed,
3853
- custom: customCanvasEvent
3854
- };
3855
4068
 
3856
- // src/notifications/NotificationToastStack.tsx
3857
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
3858
- var TOAST_STYLES_ID = "syntro-toast-styles";
3859
- var TOAST_CSS = `
3860
- @keyframes syntro-toast-slide-in {
3861
- from { opacity: 0; transform: translateY(12px); }
3862
- to { opacity: 1; transform: translateY(0); }
3863
- }
4069
+ // ../event-processor/dist/detectors/focus-bounce.js
4070
+ var FocusBounceDetector = class {
4071
+ constructor(config, emit2) {
4072
+ this.config = config;
4073
+ this.emit = emit2;
4074
+ this.focused = /* @__PURE__ */ new Map();
4075
+ }
4076
+ ingest(raw) {
4077
+ var _a2, _b;
4078
+ if (raw.type !== 3)
4079
+ return;
4080
+ const ts = raw.timestamp;
4081
+ if (raw.data.source === RRWebSource.MouseInteraction) {
4082
+ const id = (_a2 = raw.data.id) != null ? _a2 : 0;
4083
+ if (raw.data.type === RRWebMouseInteraction.Focus) {
4084
+ this.focused.set(id, { id, focusTs: ts, inputCount: 0 });
4085
+ } else if (raw.data.type === RRWebMouseInteraction.Blur) {
4086
+ const entry = this.focused.get(id);
4087
+ if (entry && entry.inputCount <= this.config.focusBounceMaxInputs) {
4088
+ this.emit({
4089
+ ts,
4090
+ name: StandardEvents.UI_FOCUS_BOUNCE,
4091
+ source: "rrweb",
4092
+ schemaVersion: EVENT_SCHEMA_VERSION,
4093
+ props: {
4094
+ elementId: id,
4095
+ duration_ms: ts - entry.focusTs
4096
+ }
4097
+ });
4098
+ }
4099
+ this.focused.delete(id);
4100
+ }
4101
+ } else if (raw.data.source === RRWebSource.Input) {
4102
+ const id = (_b = raw.data.id) != null ? _b : 0;
4103
+ const entry = this.focused.get(id);
4104
+ if (entry) {
4105
+ entry.inputCount++;
4106
+ }
4107
+ }
4108
+ }
4109
+ };
4110
+
4111
+ // ../event-processor/dist/detectors/hesitation.js
4112
+ var HesitationDetector = class {
4113
+ constructor(config, emit2) {
4114
+ this.config = config;
4115
+ this.emit = emit2;
4116
+ this.anchorX = 0;
4117
+ this.anchorY = 0;
4118
+ this.anchorTs = 0;
4119
+ this.emitted = false;
4120
+ this.hasPosition = false;
4121
+ }
4122
+ ingest(raw) {
4123
+ var _a2;
4124
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4125
+ return;
4126
+ const positions = raw.data.positions;
4127
+ if (!positions || positions.length === 0)
4128
+ return;
4129
+ const last = positions[positions.length - 1];
4130
+ const ts = raw.timestamp + ((_a2 = last.timeOffset) != null ? _a2 : 0);
4131
+ if (!this.hasPosition) {
4132
+ this.anchorX = last.x;
4133
+ this.anchorY = last.y;
4134
+ this.anchorTs = ts;
4135
+ this.hasPosition = true;
4136
+ this.emitted = false;
4137
+ return;
4138
+ }
4139
+ const dx = last.x - this.anchorX;
4140
+ const dy = last.y - this.anchorY;
4141
+ const dist = Math.sqrt(dx * dx + dy * dy);
4142
+ if (dist > this.config.hesitationRadiusPx) {
4143
+ this.anchorX = last.x;
4144
+ this.anchorY = last.y;
4145
+ this.anchorTs = ts;
4146
+ this.emitted = false;
4147
+ }
4148
+ }
4149
+ tick(now) {
4150
+ if (!this.hasPosition || this.emitted)
4151
+ return;
4152
+ const elapsed = now - this.anchorTs;
4153
+ if (elapsed >= this.config.hesitationMs) {
4154
+ this.emit({
4155
+ ts: now,
4156
+ name: StandardEvents.UI_HESITATE,
4157
+ source: "rrweb",
4158
+ schemaVersion: EVENT_SCHEMA_VERSION,
4159
+ props: {
4160
+ x: this.anchorX,
4161
+ y: this.anchorY,
4162
+ duration_ms: elapsed
4163
+ }
4164
+ });
4165
+ this.emitted = true;
4166
+ }
4167
+ }
4168
+ };
4169
+
4170
+ // ../event-processor/dist/detectors/hover.js
4171
+ var HoverTracker = class {
4172
+ constructor(config, emit2, elementResolver) {
4173
+ this.config = config;
4174
+ this.emit = emit2;
4175
+ this.elementResolver = elementResolver;
4176
+ this.currentElement = null;
4177
+ this.hoverStartTs = null;
4178
+ this.lastX = 0;
4179
+ this.lastY = 0;
4180
+ this.lastSampleTs = -Infinity;
4181
+ }
4182
+ ingest(raw) {
4183
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4184
+ return;
4185
+ const positions = raw.data.positions;
4186
+ if (!positions || positions.length === 0)
4187
+ return;
4188
+ const last = positions[positions.length - 1];
4189
+ this.lastX = last.x;
4190
+ this.lastY = last.y;
4191
+ }
4192
+ tick(now) {
4193
+ if (!this.elementResolver)
4194
+ return;
4195
+ if (now - this.lastSampleTs < this.config.hoverSampleMs)
4196
+ return;
4197
+ this.lastSampleTs = now;
4198
+ const newElement = this.elementResolver(this.lastX, this.lastY);
4199
+ const newKey = newElement ? elementKey(newElement) : null;
4200
+ const currentKey = this.currentElement ? elementKey(this.currentElement) : null;
4201
+ if (newKey !== currentKey) {
4202
+ if (this.currentElement && this.hoverStartTs !== null) {
4203
+ this.emit({
4204
+ ts: now,
4205
+ name: StandardEvents.UI_HOVER,
4206
+ source: "rrweb",
4207
+ schemaVersion: EVENT_SCHEMA_VERSION,
4208
+ props: {
4209
+ x: this.lastX,
4210
+ y: this.lastY,
4211
+ duration_ms: now - this.hoverStartTs,
4212
+ element: this.currentElement
4213
+ }
4214
+ });
4215
+ }
4216
+ this.currentElement = newElement;
4217
+ this.hoverStartTs = now;
4218
+ }
4219
+ }
4220
+ };
4221
+ function elementKey(el) {
4222
+ var _a2, _b, _c;
4223
+ return `${(_a2 = el.tag_name) != null ? _a2 : ""}|${(_b = el.attr__id) != null ? _b : ""}|${((_c = el.classes) != null ? _c : []).join(",")}`;
4224
+ }
4225
+
4226
+ // ../event-processor/dist/detectors/idle.js
4227
+ var IdleDetector = class {
4228
+ constructor(config, emit2) {
4229
+ this.config = config;
4230
+ this.emit = emit2;
4231
+ this.lastActivityTs = null;
4232
+ this.emitted = false;
4233
+ }
4234
+ ingest(raw) {
4235
+ if (raw.type !== 3)
4236
+ return;
4237
+ const src = raw.data.source;
4238
+ if (src === RRWebSource.MouseMove || src === RRWebSource.MouseInteraction || src === RRWebSource.Scroll) {
4239
+ this.lastActivityTs = raw.timestamp;
4240
+ this.emitted = false;
4241
+ }
4242
+ }
4243
+ tick(now) {
4244
+ if (this.lastActivityTs === null || this.emitted)
4245
+ return;
4246
+ if (now - this.lastActivityTs >= this.config.idleMs) {
4247
+ this.emit({
4248
+ ts: now,
4249
+ name: StandardEvents.UI_IDLE,
4250
+ source: "rrweb",
4251
+ schemaVersion: EVENT_SCHEMA_VERSION,
4252
+ props: {
4253
+ idle_ms: now - this.lastActivityTs
4254
+ }
4255
+ });
4256
+ this.emitted = true;
4257
+ }
4258
+ }
4259
+ };
4260
+
4261
+ // ../event-processor/dist/detectors/rage-click.js
4262
+ var RageClickDetector = class {
4263
+ constructor(config, emit2) {
4264
+ this.config = config;
4265
+ this.emit = emit2;
4266
+ this.clicks = [];
4267
+ }
4268
+ ingest(raw) {
4269
+ var _a2, _b;
4270
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseInteraction)
4271
+ return;
4272
+ if (raw.data.type !== RRWebMouseInteraction.Click)
4273
+ return;
4274
+ const x = (_a2 = raw.data.x) != null ? _a2 : 0;
4275
+ const y = (_b = raw.data.y) != null ? _b : 0;
4276
+ const ts = raw.timestamp;
4277
+ const cutoff = ts - this.config.rageClickWindowMs;
4278
+ this.clicks = this.clicks.filter((c) => c.ts >= cutoff);
4279
+ this.clicks.push({ ts, x, y });
4280
+ const nearby = this.clicks.filter((c) => {
4281
+ const dx = c.x - x;
4282
+ const dy = c.y - y;
4283
+ return Math.sqrt(dx * dx + dy * dy) <= this.config.rageClickRadiusPx;
4284
+ });
4285
+ if (nearby.length >= this.config.rageClickCount) {
4286
+ this.emit({
4287
+ ts,
4288
+ name: StandardEvents.UI_RAGE_CLICK,
4289
+ source: "rrweb",
4290
+ schemaVersion: EVENT_SCHEMA_VERSION,
4291
+ props: {
4292
+ x,
4293
+ y,
4294
+ clickCount: nearby.length,
4295
+ duration_ms: ts - nearby[0].ts
4296
+ }
4297
+ });
4298
+ this.clicks = [];
4299
+ }
4300
+ }
4301
+ };
4302
+
4303
+ // ../event-processor/dist/detectors/scroll-thrash.js
4304
+ var ScrollThrashDetector = class {
4305
+ constructor(config, emit2) {
4306
+ this.config = config;
4307
+ this.emit = emit2;
4308
+ this.lastY = null;
4309
+ this.lastDirection = null;
4310
+ this.reversals = [];
4311
+ }
4312
+ ingest(raw) {
4313
+ var _a2;
4314
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.Scroll)
4315
+ return;
4316
+ const y = (_a2 = raw.data.y) != null ? _a2 : 0;
4317
+ const ts = raw.timestamp;
4318
+ if (this.lastY !== null) {
4319
+ const direction = y > this.lastY ? "down" : y < this.lastY ? "up" : this.lastDirection;
4320
+ if (direction && direction !== this.lastDirection) {
4321
+ const cutoff = ts - this.config.scrollThrashWindowMs;
4322
+ this.reversals = this.reversals.filter((t) => t > cutoff);
4323
+ this.reversals.push(ts);
4324
+ if (this.reversals.length >= this.config.scrollThrashReversals) {
4325
+ this.emit({
4326
+ ts,
4327
+ name: StandardEvents.UI_SCROLL_THRASH,
4328
+ source: "rrweb",
4329
+ schemaVersion: EVENT_SCHEMA_VERSION,
4330
+ props: {
4331
+ reversals: this.reversals.length,
4332
+ duration_ms: ts - this.reversals[0]
4333
+ }
4334
+ });
4335
+ this.reversals = [];
4336
+ }
4337
+ }
4338
+ this.lastDirection = direction;
4339
+ }
4340
+ this.lastY = y;
4341
+ }
4342
+ };
4343
+
4344
+ // ../event-processor/dist/processor.js
4345
+ function createEventProcessor(options) {
4346
+ const config = { ...DEFAULT_DETECTOR_CONFIG, ...options == null ? void 0 : options.config };
4347
+ const listeners = [];
4348
+ function emit2(event) {
4349
+ for (const cb of listeners)
4350
+ cb(event);
4351
+ }
4352
+ const hesitation = new HesitationDetector(config, emit2);
4353
+ const rageClick = new RageClickDetector(config, emit2);
4354
+ const scrollThrash = new ScrollThrashDetector(config, emit2);
4355
+ const focusBounce = new FocusBounceDetector(config, emit2);
4356
+ const idle = new IdleDetector(config, emit2);
4357
+ const hover = new HoverTracker(config, emit2, options == null ? void 0 : options.elementResolver);
4358
+ const clickAttrBuffer = [];
4359
+ return {
4360
+ ingest(raw) {
4361
+ if (raw.kind === "posthog") {
4362
+ const { kind: _, ...phEvent } = raw;
4363
+ if (shouldNormalizeEvent(phEvent)) {
4364
+ emit2(normalizePostHogEvent(phEvent));
4365
+ }
4366
+ } else if (raw.kind === "rrweb") {
4367
+ hesitation.ingest(raw);
4368
+ rageClick.ingest(raw);
4369
+ scrollThrash.ingest(raw);
4370
+ focusBounce.ingest(raw);
4371
+ idle.ingest(raw);
4372
+ hover.ingest(raw);
4373
+ }
4374
+ },
4375
+ onEvent(callback) {
4376
+ listeners.push(callback);
4377
+ },
4378
+ tick(timestamp) {
4379
+ hesitation.tick(timestamp);
4380
+ idle.tick(timestamp);
4381
+ hover.tick(timestamp);
4382
+ },
4383
+ enrichClickAttributes(timestamp, elements) {
4384
+ clickAttrBuffer.push({ ts: timestamp, elements });
4385
+ const cutoff = timestamp - 500;
4386
+ while (clickAttrBuffer.length > 0 && clickAttrBuffer[0].ts < cutoff) {
4387
+ clickAttrBuffer.shift();
4388
+ }
4389
+ }
4390
+ };
4391
+ }
4392
+
4393
+ // src/events/types.ts
4394
+ var StandardEvents2 = {
4395
+ // UI events (from PostHog autocapture)
4396
+ UI_CLICK: "ui.click",
4397
+ UI_SCROLL: "ui.scroll",
4398
+ UI_INPUT: "ui.input",
4399
+ UI_CHANGE: "ui.change",
4400
+ UI_SUBMIT: "ui.submit",
4401
+ // Navigation events
4402
+ NAV_PAGE_VIEW: "nav.page_view",
4403
+ NAV_PAGE_LEAVE: "nav.page_leave",
4404
+ // Canvas events
4405
+ CANVAS_OPENED: "canvas.opened",
4406
+ CANVAS_CLOSED: "canvas.closed",
4407
+ TILE_VIEWED: "tile.viewed",
4408
+ TILE_EXPANDED: "tile.expanded",
4409
+ TILE_COLLAPSED: "tile.collapsed",
4410
+ TILE_ACTION: "tile.action",
4411
+ // Overlay/tour events
4412
+ OVERLAY_STARTED: "overlay.started",
4413
+ OVERLAY_COMPLETED: "overlay.completed",
4414
+ OVERLAY_DISMISSED: "overlay.dismissed",
4415
+ OVERLAY_STEP_VIEWED: "overlay.step_viewed",
4416
+ // Derived behavioral signals (Phase 3)
4417
+ BEHAVIOR_RAGE_CLICK: "behavior.rage_click",
4418
+ BEHAVIOR_HESITATION: "behavior.hesitation",
4419
+ BEHAVIOR_CONFUSION: "behavior.confusion",
4420
+ // Action events
4421
+ ACTION_APPLIED: "action.applied",
4422
+ ACTION_REVERTED: "action.reverted",
4423
+ ACTION_FAILED: "action.failed",
4424
+ ACTION_CTA_CLICKED: "action.cta_clicked",
4425
+ // Notification events
4426
+ NOTIFICATION_SHOWN: "notification.shown",
4427
+ NOTIFICATION_CLICKED: "notification.clicked",
4428
+ NOTIFICATION_DISMISSED: "notification.dismissed",
4429
+ NOTIFICATION_DEEP_LINK: "notification.deep_link",
4430
+ // Surface events
4431
+ SURFACE_MOUNTED: "surface.mounted",
4432
+ SURFACE_UNMOUNTED: "surface.unmounted"
4433
+ };
4434
+
4435
+ // src/events/normalizers/canvas.ts
4436
+ function createCanvasEvent(name, props) {
4437
+ return {
4438
+ ts: Date.now(),
4439
+ name,
4440
+ source: "canvas",
4441
+ props,
4442
+ schemaVersion: EVENT_SCHEMA_VERSION
4443
+ };
4444
+ }
4445
+ function canvasOpened(surface) {
4446
+ return createCanvasEvent(StandardEvents2.CANVAS_OPENED, { surface });
4447
+ }
4448
+ function canvasClosed(surface) {
4449
+ return createCanvasEvent(StandardEvents2.CANVAS_CLOSED, { surface });
4450
+ }
4451
+ function tileViewed(tileId, surface) {
4452
+ return createCanvasEvent(StandardEvents2.TILE_VIEWED, { tileId, surface });
4453
+ }
4454
+ function tileExpanded(tileId, surface) {
4455
+ return createCanvasEvent(StandardEvents2.TILE_EXPANDED, { tileId, surface });
4456
+ }
4457
+ function tileCollapsed(tileId, surface) {
4458
+ return createCanvasEvent(StandardEvents2.TILE_COLLAPSED, { tileId, surface });
4459
+ }
4460
+ function tileAction(tileId, actionId, surface) {
4461
+ return createCanvasEvent(StandardEvents2.TILE_ACTION, {
4462
+ tileId,
4463
+ actionId,
4464
+ surface
4465
+ });
4466
+ }
4467
+ function overlayStarted(recipeId, recipeName) {
4468
+ return createCanvasEvent(StandardEvents2.OVERLAY_STARTED, {
4469
+ recipeId,
4470
+ recipeName
4471
+ });
4472
+ }
4473
+ function overlayCompleted(recipeId, recipeName) {
4474
+ return createCanvasEvent(StandardEvents2.OVERLAY_COMPLETED, {
4475
+ recipeId,
4476
+ recipeName
4477
+ });
4478
+ }
4479
+ function overlayDismissed(recipeId, recipeName, stepIndex) {
4480
+ return createCanvasEvent(StandardEvents2.OVERLAY_DISMISSED, {
4481
+ recipeId,
4482
+ recipeName,
4483
+ stepIndex
4484
+ });
4485
+ }
4486
+ function overlayStepViewed(recipeId, stepIndex, stepTitle) {
4487
+ return createCanvasEvent(StandardEvents2.OVERLAY_STEP_VIEWED, {
4488
+ recipeId,
4489
+ stepIndex,
4490
+ stepTitle
4491
+ });
4492
+ }
4493
+ function customCanvasEvent(name, props) {
4494
+ const eventName = name.startsWith("canvas.") ? name : `canvas.${name}`;
4495
+ return createCanvasEvent(eventName, props);
4496
+ }
4497
+ var CanvasEvents = {
4498
+ canvasOpened,
4499
+ canvasClosed,
4500
+ tileViewed,
4501
+ tileExpanded,
4502
+ tileCollapsed,
4503
+ tileAction,
4504
+ overlayStarted,
4505
+ overlayCompleted,
4506
+ overlayDismissed,
4507
+ overlayStepViewed,
4508
+ custom: customCanvasEvent
4509
+ };
4510
+
4511
+ // src/components/emojiToIcon.tsx
4512
+ import {
4513
+ AlertTriangle,
4514
+ ArrowRight,
4515
+ Banknote,
4516
+ Bell,
4517
+ BookOpen,
4518
+ CheckCircle,
4519
+ ClipboardList,
4520
+ Compass,
4521
+ FileText,
4522
+ Gamepad2,
4523
+ HelpCircle,
4524
+ Landmark,
4525
+ Layers,
4526
+ Lightbulb,
4527
+ MessageCircle,
4528
+ SkipForward,
4529
+ Sparkles,
4530
+ Timer,
4531
+ Trophy
4532
+ } from "lucide-react";
4533
+ import { jsx as jsx2 } from "react/jsx-runtime";
4534
+ var EMOJI_ICON_MAP = {
4535
+ "\u2753": HelpCircle,
4536
+ "\u{1F9ED}": Compass,
4537
+ "\u{1F4DD}": FileText,
4538
+ "\u{1F3AF}": Layers,
4539
+ "\u{1F3C6}": Trophy,
4540
+ "\u2728": Sparkles,
4541
+ "\u{1F4AC}": MessageCircle,
4542
+ "\u{1F3AE}": Gamepad2,
4543
+ "\u{1F4A1}": Lightbulb,
4544
+ "\u{1F4B0}": Banknote,
4545
+ "\u{1F4CB}": ClipboardList,
4546
+ "\u2705": CheckCircle,
4547
+ "\u26A0\uFE0F": AlertTriangle,
4548
+ "\u{1F4B5}": Banknote,
4549
+ "\u{1F3DB}\uFE0F": Landmark,
4550
+ "\u23ED\uFE0F": SkipForward,
4551
+ "\u27A1\uFE0F": ArrowRight,
4552
+ "\u23F1\uFE0F": Timer,
4553
+ "\u{1F4D6}": BookOpen,
4554
+ "\u{1F514}": Bell
4555
+ };
4556
+ function EmojiIcon({
4557
+ emoji,
4558
+ size = 14,
4559
+ color = "currentColor"
4560
+ }) {
4561
+ const Icon = EMOJI_ICON_MAP[emoji];
4562
+ if (!Icon) {
4563
+ return /* @__PURE__ */ jsx2("span", { children: emoji });
4564
+ }
4565
+ return /* @__PURE__ */ jsx2(Icon, { size, color });
4566
+ }
4567
+
4568
+ // src/notifications/NotificationToastStack.tsx
4569
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
4570
+ var TOAST_STYLES_ID = "syntro-toast-styles";
4571
+ var TOAST_CSS = `
4572
+ @keyframes syntro-toast-slide-in {
4573
+ from { opacity: 0; transform: translateY(12px); }
4574
+ to { opacity: 1; transform: translateY(0); }
4575
+ }
3864
4576
  @keyframes syntro-toast-progress {
3865
4577
  from { width: 100%; }
3866
4578
  to { width: 0%; }
@@ -3891,7 +4603,7 @@ function NotificationToastStack({
3891
4603
  const { shadowRoot } = useShadowRoot();
3892
4604
  ensureToastStyles(shadowRoot);
3893
4605
  if (notifications.length === 0) return null;
3894
- return /* @__PURE__ */ jsx2(
4606
+ return /* @__PURE__ */ jsx3(
3895
4607
  "div",
3896
4608
  {
3897
4609
  "data-testid": "notification-toast-stack",
@@ -3946,7 +4658,7 @@ function NotificationToastStack({
3946
4658
  padding: "10px 12px"
3947
4659
  },
3948
4660
  children: [
3949
- /* @__PURE__ */ jsx2(
4661
+ /* @__PURE__ */ jsx3(
3950
4662
  "div",
3951
4663
  {
3952
4664
  style: {
@@ -3960,11 +4672,11 @@ function NotificationToastStack({
3960
4672
  flexShrink: 0,
3961
4673
  fontSize: "14px"
3962
4674
  },
3963
- children: (_a2 = notif.icon) != null ? _a2 : "\u{1F514}"
4675
+ children: /* @__PURE__ */ jsx3(EmojiIcon, { emoji: (_a2 = notif.icon) != null ? _a2 : "\u{1F514}", size: 14 })
3964
4676
  }
3965
4677
  ),
3966
4678
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
3967
- /* @__PURE__ */ jsx2(
4679
+ /* @__PURE__ */ jsx3(
3968
4680
  "div",
3969
4681
  {
3970
4682
  style: {
@@ -3979,7 +4691,7 @@ function NotificationToastStack({
3979
4691
  children: notif.title
3980
4692
  }
3981
4693
  ),
3982
- notif.body && /* @__PURE__ */ jsx2(
4694
+ notif.body && /* @__PURE__ */ jsx3(
3983
4695
  "div",
3984
4696
  {
3985
4697
  style: {
@@ -3995,7 +4707,7 @@ function NotificationToastStack({
3995
4707
  }
3996
4708
  )
3997
4709
  ] }),
3998
- /* @__PURE__ */ jsx2(
4710
+ /* @__PURE__ */ jsx3(
3999
4711
  "button",
4000
4712
  {
4001
4713
  type: "button",
@@ -4020,7 +4732,7 @@ function NotificationToastStack({
4020
4732
  ]
4021
4733
  }
4022
4734
  ),
4023
- /* @__PURE__ */ jsx2("div", { style: { height: "2px", background: "rgba(0, 0, 0, 0.08)" }, children: /* @__PURE__ */ jsx2(
4735
+ /* @__PURE__ */ jsx3("div", { style: { height: "2px", background: "rgba(0, 0, 0, 0.08)" }, children: /* @__PURE__ */ jsx3(
4024
4736
  "div",
4025
4737
  {
4026
4738
  className: "syntro-toast-progress",
@@ -4098,7 +4810,7 @@ function useNotifications(eventBus, tiles) {
4098
4810
  const timerIds = useRef2(/* @__PURE__ */ new Map());
4099
4811
  const publishDismissed = useCallback2(
4100
4812
  (notif) => {
4101
- eventBus == null ? void 0 : eventBus.publish(StandardEvents.NOTIFICATION_DISMISSED, {
4813
+ eventBus == null ? void 0 : eventBus.publish(StandardEvents2.NOTIFICATION_DISMISSED, {
4102
4814
  notificationId: notif.id,
4103
4815
  tileId: notif.tileId,
4104
4816
  itemId: notif.itemId
@@ -4163,7 +4875,7 @@ function useNotifications(eventBus, tiles) {
4163
4875
  }
4164
4876
  return next;
4165
4877
  });
4166
- eventBus.publish(StandardEvents.NOTIFICATION_SHOWN, {
4878
+ eventBus.publish(StandardEvents2.NOTIFICATION_SHOWN, {
4167
4879
  notificationId: matched.id,
4168
4880
  tileId: matched.tileId,
4169
4881
  itemId: matched.itemId,
@@ -4234,7 +4946,7 @@ function useNotifyWatcher(runtime3, tiles, appRegistry2) {
4234
4946
 
4235
4947
  // src/RuntimeProvider.tsx
4236
4948
  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";
4949
+ import { jsx as jsx4 } from "react/jsx-runtime";
4238
4950
  var RuntimeReactContext = createContext2({
4239
4951
  runtime: null,
4240
4952
  context: null
@@ -4252,7 +4964,7 @@ function RuntimeProvider({ runtime: runtime3, children }) {
4252
4964
  return unsubscribe;
4253
4965
  }, [runtime3]);
4254
4966
  const value = useMemo2(() => ({ runtime: runtime3, context }), [runtime3, context]);
4255
- return /* @__PURE__ */ jsx3(RuntimeReactContext.Provider, { value, children });
4967
+ return /* @__PURE__ */ jsx4(RuntimeReactContext.Provider, { value, children });
4256
4968
  }
4257
4969
  function useRuntime() {
4258
4970
  const { runtime: runtime3 } = useContext2(RuntimeReactContext);
@@ -4326,41 +5038,17 @@ import {
4326
5038
  } from "react";
4327
5039
 
4328
5040
  // 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
- };
5041
+ import { jsx as jsx5 } from "react/jsx-runtime";
4350
5042
  function TileIcon({
4351
5043
  emoji,
4352
5044
  size = 18,
4353
5045
  color = "currentColor"
4354
5046
  }) {
4355
- const Icon = ICON_MAP[emoji];
4356
- if (!Icon) {
4357
- return /* @__PURE__ */ jsx4("span", { children: emoji });
4358
- }
4359
- return /* @__PURE__ */ jsx4(Icon, { size, color });
5047
+ return /* @__PURE__ */ jsx5(EmojiIcon, { emoji, size, color });
4360
5048
  }
4361
5049
 
4362
5050
  // src/components/TileCard.tsx
4363
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
5051
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
4364
5052
  function WidgetMount({ widgetId, props }) {
4365
5053
  var _a2;
4366
5054
  const runtime3 = useRuntime();
@@ -4390,7 +5078,6 @@ function WidgetMount({ widgetId, props }) {
4390
5078
  return () => {
4391
5079
  handle.unmount();
4392
5080
  handleRef.current = null;
4393
- container.remove();
4394
5081
  };
4395
5082
  }, [registry, widgetId, widgetAvailable]);
4396
5083
  const propsJson = JSON.stringify(props);
@@ -4401,7 +5088,7 @@ function WidgetMount({ widgetId, props }) {
4401
5088
  prevPropsJsonRef.current = propsJson;
4402
5089
  (_a3 = handleRef.current) == null ? void 0 : _a3.update(propsRef.current);
4403
5090
  }, [propsJson]);
4404
- if (!registry || !registry.has(widgetId)) {
5091
+ if (!(registry == null ? void 0 : registry.has(widgetId))) {
4405
5092
  return /* @__PURE__ */ jsxs2(
4406
5093
  "div",
4407
5094
  {
@@ -4418,8 +5105,21 @@ function WidgetMount({ widgetId, props }) {
4418
5105
  }
4419
5106
  );
4420
5107
  }
4421
- return /* @__PURE__ */ jsx5("div", { ref: parentRef });
4422
- }
5108
+ return /* @__PURE__ */ jsx6("div", { ref: parentRef });
5109
+ }
5110
+ var INTERACTION_PATTERNS = [
5111
+ ":toggled",
5112
+ ":clicked",
5113
+ ":feedback",
5114
+ ":navigate",
5115
+ ":expanded",
5116
+ ":collapsed",
5117
+ ":dismissed",
5118
+ ":submitted",
5119
+ ":interacted",
5120
+ ":tip_clicked",
5121
+ ":tip_focused"
5122
+ ];
4423
5123
  function TileCard({
4424
5124
  config,
4425
5125
  surface: _surface,
@@ -4428,20 +5128,52 @@ function TileCard({
4428
5128
  }) {
4429
5129
  const { title, subtitle, widget, props, icon } = config;
4430
5130
  const [, setTick] = useState4(0);
5131
+ const articleRef = useRef4(null);
4431
5132
  const runtime3 = useRuntime();
4432
5133
  useEffect5(() => {
4433
5134
  if (runtime3) setTick((t) => t + 1);
4434
5135
  }, [runtime3]);
4435
- const registration = useMemo3(
4436
- () => {
4437
- var _a2, _b;
4438
- return (_b = (_a2 = runtime3 == null ? void 0 : runtime3.widgets) == null ? void 0 : _a2.getRegistration) == null ? void 0 : _b.call(_a2, widget);
4439
- },
4440
- [runtime3 == null ? void 0 : runtime3.widgets, widget]
4441
- );
4442
- const resolvedIcon = useMemo3(() => {
4443
- var _a2, _b;
4444
- if (icon) return icon;
5136
+ useEffect5(() => {
5137
+ var _a2;
5138
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5139
+ if (!articleRef.current || !tracker) return;
5140
+ const observer = new IntersectionObserver(
5141
+ ([entry]) => {
5142
+ var _a3;
5143
+ if (entry.isIntersecting) {
5144
+ tracker.trackSeen(config.id, (_a3 = config.widget) != null ? _a3 : "unknown");
5145
+ observer.disconnect();
5146
+ }
5147
+ },
5148
+ { threshold: 0.5 }
5149
+ );
5150
+ observer.observe(articleRef.current);
5151
+ return () => observer.disconnect();
5152
+ }, [config.id, config.widget]);
5153
+ useEffect5(() => {
5154
+ var _a2;
5155
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5156
+ if (!(runtime3 == null ? void 0 : runtime3.events) || !tracker) return;
5157
+ return runtime3.events.subscribe((event) => {
5158
+ var _a3, _b;
5159
+ if (!INTERACTION_PATTERNS.some((p) => {
5160
+ var _a4;
5161
+ return (_a4 = event.name) == null ? void 0 : _a4.includes(p);
5162
+ })) return;
5163
+ if (((_a3 = event.props) == null ? void 0 : _a3.instanceId) !== config.id) return;
5164
+ tracker.trackInteracted(config.id, (_b = config.widget) != null ? _b : "unknown", event.name);
5165
+ });
5166
+ }, [runtime3 == null ? void 0 : runtime3.events, config.id, config.widget]);
5167
+ const registration = useMemo3(
5168
+ () => {
5169
+ var _a2, _b;
5170
+ return (_b = (_a2 = runtime3 == null ? void 0 : runtime3.widgets) == null ? void 0 : _a2.getRegistration) == null ? void 0 : _b.call(_a2, widget);
5171
+ },
5172
+ [runtime3 == null ? void 0 : runtime3.widgets, widget]
5173
+ );
5174
+ const resolvedIcon = useMemo3(() => {
5175
+ var _a2, _b;
5176
+ if (icon) return icon;
4445
5177
  return (_b = (_a2 = registration == null ? void 0 : registration.metadata) == null ? void 0 : _a2.icon) != null ? _b : "+";
4446
5178
  }, [icon, registration]);
4447
5179
  const resolvedSubtitle = useMemo3(() => {
@@ -4485,15 +5217,16 @@ function TileCard({
4485
5217
  return /* @__PURE__ */ jsxs2(
4486
5218
  "article",
4487
5219
  {
5220
+ ref: articleRef,
4488
5221
  "data-shadow-canvas-id": `tile-${config.id}`,
4489
5222
  style: cardStyle,
4490
5223
  onMouseEnter,
4491
5224
  onMouseLeave,
4492
5225
  children: [
4493
5226
  /* @__PURE__ */ jsxs2("div", { style: headerStyle, children: [
4494
- /* @__PURE__ */ jsx5("div", { style: iconStyle, children: /* @__PURE__ */ jsx5(TileIcon, { emoji: resolvedIcon, size: resolvedSubtitle ? 36 : 24 }) }),
5227
+ /* @__PURE__ */ jsx6("div", { style: iconStyle, children: /* @__PURE__ */ jsx6(TileIcon, { emoji: resolvedIcon, size: resolvedSubtitle ? 36 : 24 }) }),
4495
5228
  /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0 }, children: [
4496
- /* @__PURE__ */ jsx5(
5229
+ /* @__PURE__ */ jsx6(
4497
5230
  "h3",
4498
5231
  {
4499
5232
  style: {
@@ -4508,7 +5241,7 @@ function TileCard({
4508
5241
  children: title != null ? title : widget
4509
5242
  }
4510
5243
  ),
4511
- resolvedSubtitle && /* @__PURE__ */ jsx5(
5244
+ resolvedSubtitle && /* @__PURE__ */ jsx6(
4512
5245
  "p",
4513
5246
  {
4514
5247
  style: {
@@ -4525,14 +5258,14 @@ function TileCard({
4525
5258
  )
4526
5259
  ] })
4527
5260
  ] }),
4528
- /* @__PURE__ */ jsx5(
5261
+ /* @__PURE__ */ jsx6(
4529
5262
  "div",
4530
5263
  {
4531
5264
  style: {
4532
5265
  padding: "var(--sc-tile-body-padding, 0 0.75rem 0.5rem)",
4533
5266
  borderTop: "1px solid rgba(255, 255, 255, 0.06)"
4534
5267
  },
4535
- children: /* @__PURE__ */ jsx5("div", { style: { paddingTop: "var(--sc-tile-gap, 0.25rem)" }, children: /* @__PURE__ */ jsx5(WidgetMount, { widgetId: widget, props: { ...props, instanceId: config.id } }) })
5268
+ children: /* @__PURE__ */ jsx6("div", { style: { paddingTop: "var(--sc-tile-gap, 0.25rem)" }, children: /* @__PURE__ */ jsx6(WidgetMount, { widgetId: widget, props: { ...props, instanceId: config.id } }) })
4536
5269
  }
4537
5270
  )
4538
5271
  ]
@@ -4940,7 +5673,7 @@ function flattenThemeConfig(config) {
4940
5673
 
4941
5674
  // src/theme/ThemeProvider.tsx
4942
5675
  import { createContext as createContext3, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo4 } from "react";
4943
- import { jsx as jsx6 } from "react/jsx-runtime";
5676
+ import { jsx as jsx7 } from "react/jsx-runtime";
4944
5677
  var ThemeContext = createContext3(null);
4945
5678
  function ThemeProvider({
4946
5679
  children,
@@ -4971,7 +5704,7 @@ ${cssRules}
4971
5704
  mode: merged.mode,
4972
5705
  cssVariables
4973
5706
  };
4974
- return /* @__PURE__ */ jsx6(ThemeContext.Provider, { value, children });
5707
+ return /* @__PURE__ */ jsx7(ThemeContext.Provider, { value, children });
4975
5708
  }
4976
5709
  function useTheme() {
4977
5710
  const context = useContext3(ThemeContext);
@@ -4982,7 +5715,7 @@ function useTheme() {
4982
5715
  }
4983
5716
 
4984
5717
  // src/components/ShadowCanvasOverlay.tsx
4985
- import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
5718
+ import { Fragment, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
4986
5719
  var LAUNCHER_STYLES_ID = "syntro-launcher-styles";
4987
5720
  function ensureLauncherStyles(target, css) {
4988
5721
  if (target.querySelector(`#${LAUNCHER_STYLES_ID}`)) return;
@@ -4996,7 +5729,6 @@ function ShadowCanvasOverlay({
4996
5729
  onToggle,
4997
5730
  telemetry,
4998
5731
  launcherLabel: _launcherLabel = "Adaptives",
4999
- launcherIcon,
5000
5732
  launcherAnimate = false,
5001
5733
  launcherAnimationStyle: _launcherAnimationStyle = "pulse",
5002
5734
  notificationCount: _notificationCount,
@@ -5007,7 +5739,7 @@ function ShadowCanvasOverlay({
5007
5739
  canvasTitle,
5008
5740
  displayMode = "standard"
5009
5741
  }) {
5010
- var _a2, _b, _c, _d;
5742
+ var _a2, _b, _c, _d, _e;
5011
5743
  const [mounted, setMounted] = useState5(false);
5012
5744
  const [launcherPos, setLauncherPos] = useState5(null);
5013
5745
  const dragRef = useRef5(null);
@@ -5022,7 +5754,7 @@ function ShadowCanvasOverlay({
5022
5754
  const handleNotificationClick = useCallback4(
5023
5755
  (notif) => {
5024
5756
  if (runtime3) {
5025
- runtime3.events.publish(StandardEvents.NOTIFICATION_CLICKED, {
5757
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_CLICKED, {
5026
5758
  notificationId: notif.id,
5027
5759
  tileId: notif.tileId,
5028
5760
  itemId: notif.itemId
@@ -5032,7 +5764,7 @@ function ShadowCanvasOverlay({
5032
5764
  onToggle();
5033
5765
  }
5034
5766
  if (runtime3 && notif.tileId) {
5035
- runtime3.events.publish(StandardEvents.NOTIFICATION_DEEP_LINK, {
5767
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_DEEP_LINK, {
5036
5768
  tileId: notif.tileId,
5037
5769
  itemId: notif.itemId
5038
5770
  });
@@ -5105,6 +5837,17 @@ function ShadowCanvasOverlay({
5105
5837
  }
5106
5838
  onToggle();
5107
5839
  }, [isOpen, telemetry, runtime3, onToggle]);
5840
+ useEffect7(() => {
5841
+ if (!isOpen) return;
5842
+ const handleOutsideClick = (e) => {
5843
+ const path = e.composedPath();
5844
+ if (containerRef.current && !path.includes(containerRef.current) && launcherRef.current && !path.includes(launcherRef.current)) {
5845
+ toggle2();
5846
+ }
5847
+ };
5848
+ document.addEventListener("mousedown", handleOutsideClick);
5849
+ return () => document.removeEventListener("mousedown", handleOutsideClick);
5850
+ }, [isOpen, toggle2]);
5108
5851
  const onLauncherPointerDown = useCallback4((e) => {
5109
5852
  const rect = e.currentTarget.getBoundingClientRect();
5110
5853
  dragRef.current = {
@@ -5129,7 +5872,7 @@ function ShadowCanvasOverlay({
5129
5872
  }
5130
5873
  }, []);
5131
5874
  const onLauncherPointerUp = useCallback4(
5132
- (_e) => {
5875
+ (_e2) => {
5133
5876
  const drag = dragRef.current;
5134
5877
  dragRef.current = null;
5135
5878
  if (drag && !drag.dragged) {
@@ -5143,6 +5886,7 @@ function ShadowCanvasOverlay({
5143
5886
  const isPush = config.canvas.layout === "push";
5144
5887
  const canvasBorder = (_b = config.canvas.border) != null ? _b : "none";
5145
5888
  const containerRef = useRef5(null);
5889
+ const launcherRef = useRef5(null);
5146
5890
  const zIndex = 2147483600;
5147
5891
  useEffect7(() => {
5148
5892
  var _a3, _b2, _c2, _d2;
@@ -5223,19 +5967,19 @@ function ShadowCanvasOverlay({
5223
5967
  pointerEvents: "none",
5224
5968
  padding: "0"
5225
5969
  };
5226
- const content = /* @__PURE__ */ jsx7(
5970
+ const content = /* @__PURE__ */ jsx8(
5227
5971
  "div",
5228
5972
  {
5229
5973
  "data-shadow-canvas-id": "overlay-root",
5230
5974
  style: {
5231
5975
  position: "fixed",
5232
5976
  inset: 0,
5233
- pointerEvents: isOpen ? "auto" : "none",
5977
+ pointerEvents: "none",
5234
5978
  zIndex
5235
5979
  },
5236
5980
  children: /* @__PURE__ */ jsxs3("div", { style: wrapperStyle, children: [
5237
5981
  /* @__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(
5982
+ isFocused && canvasTitle && /* @__PURE__ */ jsx8("header", { style: { color: "white", padding: "1.5rem 1.5rem 0" }, children: /* @__PURE__ */ jsx8(
5239
5983
  "p",
5240
5984
  {
5241
5985
  style: {
@@ -5248,7 +5992,7 @@ function ShadowCanvasOverlay({
5248
5992
  children: canvasTitle
5249
5993
  }
5250
5994
  ) }),
5251
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, overflowY: "auto", padding: isFocused ? "0" : "1rem" }, children: isLoading ? /* @__PURE__ */ jsx7(
5995
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, overflowY: "auto", padding: isFocused ? "0" : "1rem" }, children: isLoading ? /* @__PURE__ */ jsx8(
5252
5996
  "div",
5253
5997
  {
5254
5998
  style: { color: "var(--sc-overlay-text-color)", padding: isFocused ? "1rem" : "0" },
@@ -5268,7 +6012,7 @@ function ShadowCanvasOverlay({
5268
6012
  }
5269
6013
  ) : isFocused ? (
5270
6014
  /* Focused Mode: Render first tile full size */
5271
- tiles.length > 0 ? /* @__PURE__ */ jsx7(
6015
+ tiles.length > 0 ? /* @__PURE__ */ jsx8(
5272
6016
  TileCard,
5273
6017
  {
5274
6018
  config: tiles[0],
@@ -5279,7 +6023,7 @@ function ShadowCanvasOverlay({
5279
6023
  ) : null
5280
6024
  ) : (
5281
6025
  /* Standard Mode: Stacked cards — widgets always visible */
5282
- /* @__PURE__ */ jsx7(
6026
+ /* @__PURE__ */ jsx8(
5283
6027
  "div",
5284
6028
  {
5285
6029
  style: {
@@ -5288,7 +6032,7 @@ function ShadowCanvasOverlay({
5288
6032
  gap: "0.75rem",
5289
6033
  width: "100%"
5290
6034
  },
5291
- children: tiles.map((tile) => /* @__PURE__ */ jsx7(
6035
+ children: tiles.map((tile) => /* @__PURE__ */ jsx8(
5292
6036
  TileCard,
5293
6037
  {
5294
6038
  config: tile,
@@ -5303,17 +6047,7 @@ function ShadowCanvasOverlay({
5303
6047
  ) }),
5304
6048
  footerSlot
5305
6049
  ] }),
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
- )
6050
+ /* @__PURE__ */ jsx8("div", { style: { flex: "1 1 auto" } })
5317
6051
  ] })
5318
6052
  }
5319
6053
  );
@@ -5331,7 +6065,7 @@ function ShadowCanvasOverlay({
5331
6065
  zIndex: zIndex + 47
5332
6066
  },
5333
6067
  children: [
5334
- /* @__PURE__ */ jsx7(
6068
+ /* @__PURE__ */ jsx8(
5335
6069
  NotificationToastStack,
5336
6070
  {
5337
6071
  notifications,
@@ -5343,6 +6077,7 @@ function ShadowCanvasOverlay({
5343
6077
  /* @__PURE__ */ jsxs3(
5344
6078
  "button",
5345
6079
  {
6080
+ ref: launcherRef,
5346
6081
  type: "button",
5347
6082
  "aria-label": "Toggle shadow canvas",
5348
6083
  className: launcherAnimate && !isOpen ? "syntro-launcher-animate" : void 0,
@@ -5401,14 +6136,14 @@ function ShadowCanvasOverlay({
5401
6136
  focusable: "false",
5402
6137
  style: { transition: "transform 200ms ease" },
5403
6138
  children: [
5404
- /* @__PURE__ */ jsx7("path", { d: "M18 6L6 18" }),
5405
- /* @__PURE__ */ jsx7("path", { d: "M6 6l12 12" })
6139
+ /* @__PURE__ */ jsx8("path", { d: "M18 6L6 18" }),
6140
+ /* @__PURE__ */ jsx8("path", { d: "M6 6l12 12" })
5406
6141
  ]
5407
6142
  }
5408
- ) : launcherIcon ? /* @__PURE__ */ jsx7(
6143
+ ) : ((_e = config.launcher) == null ? void 0 : _e.icon) ? /* @__PURE__ */ jsx8(
5409
6144
  "img",
5410
6145
  {
5411
- src: launcherIcon,
6146
+ src: config.launcher.icon,
5412
6147
  alt: "",
5413
6148
  "aria-hidden": "true",
5414
6149
  style: {
@@ -5433,16 +6168,16 @@ function ShadowCanvasOverlay({
5433
6168
  focusable: "false",
5434
6169
  style: { transition: "transform 200ms ease" },
5435
6170
  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" })
6171
+ /* @__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" }),
6172
+ /* @__PURE__ */ jsx8("path", { d: "M5 3v4" }),
6173
+ /* @__PURE__ */ jsx8("path", { d: "M3 5h4" }),
6174
+ /* @__PURE__ */ jsx8("path", { d: "M19 17v4" }),
6175
+ /* @__PURE__ */ jsx8("path", { d: "M17 19h4" })
5441
6176
  ]
5442
6177
  }
5443
6178
  ),
5444
6179
  !isOpen && notifications.length > 0 && /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", top: -2, right: -2, pointerEvents: "none" }, children: [
5445
- /* @__PURE__ */ jsx7(
6180
+ /* @__PURE__ */ jsx8(
5446
6181
  "span",
5447
6182
  {
5448
6183
  className: "syntro-badge-ping",
@@ -5454,7 +6189,7 @@ function ShadowCanvasOverlay({
5454
6189
  }
5455
6190
  }
5456
6191
  ),
5457
- /* @__PURE__ */ jsx7(
6192
+ /* @__PURE__ */ jsx8(
5458
6193
  "span",
5459
6194
  {
5460
6195
  className: "syntro-badge-glow",
@@ -5465,7 +6200,7 @@ function ShadowCanvasOverlay({
5465
6200
  }
5466
6201
  }
5467
6202
  ),
5468
- /* @__PURE__ */ jsx7(
6203
+ /* @__PURE__ */ jsx8(
5469
6204
  "span",
5470
6205
  {
5471
6206
  className: "syntro-badge-bounce",
@@ -5504,6 +6239,14 @@ var sortTiles = (tiles) => [...tiles].sort((a, b) => {
5504
6239
  var _a2, _b;
5505
6240
  return ((_a2 = b.priority) != null ? _a2 : 0) - ((_b = a.priority) != null ? _b : 0);
5506
6241
  });
6242
+ function fireTriggeredForTiles(tiles) {
6243
+ var _a2, _b;
6244
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
6245
+ if (!tracker) return;
6246
+ for (const tile of tiles) {
6247
+ tracker.trackTriggered(tile.id, (_b = tile.widget) != null ? _b : "unknown");
6248
+ }
6249
+ }
5507
6250
  function useShadowCanvasConfig({
5508
6251
  fetcher,
5509
6252
  experiments,
@@ -5526,6 +6269,7 @@ function useShadowCanvasConfig({
5526
6269
  if (experiments) {
5527
6270
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
5528
6271
  }
6272
+ fireTriggeredForTiles(tiles);
5529
6273
  setState((prev) => ({ ...prev, tiles: sortTiles(tiles) }));
5530
6274
  }, [runtime3, experiments]);
5531
6275
  const load = useCallback5(async () => {
@@ -5543,6 +6287,7 @@ function useShadowCanvasConfig({
5543
6287
  } else if (experiments) {
5544
6288
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
5545
6289
  }
6290
+ fireTriggeredForTiles(tiles);
5546
6291
  debug("SmartCanvas Config", `Tile count after filtering: ${tiles.length}`);
5547
6292
  const newActions = response.actions || [];
5548
6293
  const newActionsJson = JSON.stringify(newActions);
@@ -5600,13 +6345,13 @@ function useShadowCanvasConfig({
5600
6345
 
5601
6346
  // src/SmartCanvasApp.tsx
5602
6347
  import { useEffect as useEffect9, useMemo as useMemo7, useRef as useRef7, useState as useState7 } from "react";
5603
- import { jsx as jsx8 } from "react/jsx-runtime";
6348
+ import { jsx as jsx9 } from "react/jsx-runtime";
5604
6349
  function SmartCanvasApp({
5605
6350
  controller,
5606
6351
  fetcher,
5607
6352
  configUri,
5608
- configUriFeatureKey = "smart-canvas-config-uri",
5609
- configFeatureKey = "smart-canvas-config",
6353
+ configUriFeatureKey,
6354
+ configFeatureKey,
5610
6355
  fetchCredentials = "include",
5611
6356
  pollIntervalMs,
5612
6357
  experiments,
@@ -5623,7 +6368,7 @@ function SmartCanvasApp({
5623
6368
  workspaceTheme
5624
6369
  }) {
5625
6370
  if (runtime3) {
5626
- return /* @__PURE__ */ jsx8(RuntimeProvider, { runtime: runtime3, children: /* @__PURE__ */ jsx8(
6371
+ return /* @__PURE__ */ jsx9(RuntimeProvider, { runtime: runtime3, children: /* @__PURE__ */ jsx9(
5627
6372
  SmartCanvasAppInner,
5628
6373
  {
5629
6374
  controller,
@@ -5648,7 +6393,7 @@ function SmartCanvasApp({
5648
6393
  }
5649
6394
  ) });
5650
6395
  }
5651
- return /* @__PURE__ */ jsx8(
6396
+ return /* @__PURE__ */ jsx9(
5652
6397
  SmartCanvasAppInner,
5653
6398
  {
5654
6399
  controller,
@@ -5676,10 +6421,10 @@ function SmartCanvasAppInner({
5676
6421
  controller,
5677
6422
  fetcher,
5678
6423
  configUri,
5679
- configUriFeatureKey = "smart-canvas-config-uri",
5680
- configFeatureKey = "smart-canvas-config",
6424
+ configUriFeatureKey,
6425
+ configFeatureKey,
5681
6426
  fetchCredentials = "include",
5682
- pollIntervalMs,
6427
+ pollIntervalMs: _pollIntervalMs,
5683
6428
  experiments,
5684
6429
  telemetry,
5685
6430
  runtime: runtime3,
@@ -5693,7 +6438,7 @@ function SmartCanvasAppInner({
5693
6438
  initialBatchHandle,
5694
6439
  workspaceTheme
5695
6440
  }) {
5696
- var _a2, _b, _c, _d, _e, _f, _g;
6441
+ var _a2, _b, _c, _d, _e, _f;
5697
6442
  const [open, setOpen] = useState7(controller.getState().open);
5698
6443
  const pageContext = usePageContext();
5699
6444
  const [localUrl, setLocalUrl] = useState7(
@@ -5803,16 +6548,13 @@ function SmartCanvasAppInner({
5803
6548
  }, [runtime3, controller]);
5804
6549
  const { shadowRoot } = useShadowRoot();
5805
6550
  const themeConfig = configState.theme;
5806
- if (!configState.isLoading && !hasContent) {
5807
- return null;
5808
- }
5809
- return /* @__PURE__ */ jsx8(
6551
+ return /* @__PURE__ */ jsx9(
5810
6552
  ThemeProvider,
5811
6553
  {
5812
6554
  themeConfig,
5813
6555
  workspaceTheme,
5814
6556
  shadowRoot,
5815
- children: /* @__PURE__ */ jsx8(
6557
+ children: !configState.isLoading && !hasContent ? null : /* @__PURE__ */ jsx9(
5816
6558
  ShadowCanvasOverlay,
5817
6559
  {
5818
6560
  tiles: configState.tiles,
@@ -5821,10 +6563,9 @@ function SmartCanvasAppInner({
5821
6563
  canvasTitle: configState.canvasTitle,
5822
6564
  telemetry,
5823
6565
  launcherLabel: launcherLabel != null ? launcherLabel : (_b = configState.launcher) == null ? void 0 : _b.label,
5824
- launcherIcon: (_c = configState.launcher) == null ? void 0 : _c.icon,
5825
- launcherAnimate: (_d = configState.launcher) == null ? void 0 : _d.animate,
5826
- launcherAnimationStyle: (_e = configState.launcher) == null ? void 0 : _e.animationStyle,
5827
- notificationCount: (_g = (_f = configState.launcher) == null ? void 0 : _f.notificationCount) != null ? _g : configState.tiles.length,
6566
+ launcherAnimate: (_c = configState.launcher) == null ? void 0 : _c.animate,
6567
+ launcherAnimationStyle: (_d = configState.launcher) == null ? void 0 : _d.animationStyle,
6568
+ notificationCount: (_f = (_e = configState.launcher) == null ? void 0 : _e.notificationCount) != null ? _f : configState.tiles.length,
5828
6569
  footerSlot,
5829
6570
  isOpen: open,
5830
6571
  onToggle: () => controller.toggle(),
@@ -5837,7 +6578,7 @@ function SmartCanvasAppInner({
5837
6578
 
5838
6579
  // src/SmartCanvasElement.tsx
5839
6580
  import { createRoot as createRoot2 } from "react-dom/client";
5840
- import { jsx as jsx9 } from "react/jsx-runtime";
6581
+ import { jsx as jsx10 } from "react/jsx-runtime";
5841
6582
  var TAG_NAME = "smart-canvas";
5842
6583
  var BASE_CSS = `
5843
6584
  :host {
@@ -5938,13 +6679,13 @@ var SmartCanvasElement = class extends HTMLElement {
5938
6679
  __privateSet(this, _root, createRoot2(__privateGet(this, _mount)));
5939
6680
  }
5940
6681
  __privateGet(this, _root).render(
5941
- /* @__PURE__ */ jsx9(
6682
+ /* @__PURE__ */ jsx10(
5942
6683
  ShadowRootProvider,
5943
6684
  {
5944
6685
  shadowRoot: __privateGet(this, _shadow),
5945
6686
  portalRoot: __privateGet(this, _portalRoot),
5946
6687
  overlayContainer: __privateGet(this, _overlayContainer),
5947
- children: /* @__PURE__ */ jsx9(SmartCanvasApp, { ...__privateGet(this, _lastAppProps), controller: __privateGet(this, _controller), canvasHost: this })
6688
+ children: /* @__PURE__ */ jsx10(SmartCanvasApp, { ...__privateGet(this, _lastAppProps), controller: __privateGet(this, _controller), canvasHost: this })
5948
6689
  }
5949
6690
  )
5950
6691
  );
@@ -6513,7 +7254,7 @@ var createSmartCanvas = async (config = {}) => {
6513
7254
  console.log(
6514
7255
  "[SmartCanvas] Actions to apply:",
6515
7256
  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}"` : ""}`
7257
+ (a, i) => `[${i}] ${a.kind}${a.anchorId ? ` anchor="${a.anchorId.selector}"` : ""}${a.label ? ` "${a.label}"` : ""}`
6517
7258
  ).join(", ")
6518
7259
  );
6519
7260
  }
@@ -6964,17 +7705,18 @@ var PostHogAdapter = class {
6964
7705
  __publicField(this, "client");
6965
7706
  __publicField(this, "featureFlagsCallback");
6966
7707
  __publicField(this, "captureCallback");
6967
- __publicField(this, "consentUnsub");
7708
+ __publicField(this, "rrwebCallback");
6968
7709
  this.client = options.client;
6969
7710
  this.featureFlagsCallback = options.onFeatureFlagsLoaded;
6970
7711
  this.captureCallback = options.onCapture;
7712
+ this.rrwebCallback = options.onRRWebEvent;
6971
7713
  if (!this.client && options.consent && options.requireExplicitConsent && typeof window !== "undefined" && options.apiKey) {
6972
7714
  const consent = options.consent;
6973
7715
  const currentStatus = consent.getStatus();
6974
7716
  if (currentStatus === "granted") {
6975
7717
  this.initPostHog();
6976
7718
  }
6977
- this.consentUnsub = consent.subscribe((status) => {
7719
+ consent.subscribe((status) => {
6978
7720
  if (status === "granted") {
6979
7721
  if (!this.client) {
6980
7722
  this.initPostHog();
@@ -7001,68 +7743,117 @@ var PostHogAdapter = class {
7001
7743
  if (!options.apiKey) return;
7002
7744
  const enableFeatureFlags = (_a2 = options.enableFeatureFlags) != null ? _a2 : true;
7003
7745
  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);
7041
- }
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
- });
7052
- }
7053
- }
7746
+ const initOptions = {
7747
+ api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
7748
+ // Feature flags for segment membership (in_segment_* flags)
7749
+ // When enabled, /decide is called to get segment flags
7750
+ advanced_disable_feature_flags: !enableFeatureFlags,
7751
+ advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
7752
+ // Full-page tracking - all ON by default
7753
+ autocapture: (_c = options.autocapture) != null ? _c : true,
7754
+ capture_pageview: (_d = options.capturePageview) != null ? _d : "history_change",
7755
+ capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
7756
+ disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
7757
+ // CRITICAL: Disable user agent filtering to allow headless Chrome
7758
+ // PostHog blocks "HeadlessChrome" user agents by default as bot detection
7759
+ // This enables session recording in Playwright/crawler sessions
7760
+ opt_out_useragent_filter: true,
7761
+ // Cross-domain iframe recording for embeds
7762
+ session_recording: {
7763
+ recordCrossDomainIFrames: true
7054
7764
  },
7765
+ // Capture performance metrics
7766
+ capture_performance: true,
7767
+ // Enable web vitals
7768
+ enable_recording_console_log: true
7769
+ };
7770
+ const result = posthog.init(
7771
+ options.apiKey,
7772
+ initOptions,
7055
7773
  instanceName
7056
7774
  );
7775
+ if (result) {
7776
+ this.client = result;
7777
+ }
7778
+ if (this.captureCallback && this.client) {
7779
+ this.client.on("eventCaptured", (...args) => {
7780
+ var _a3;
7781
+ const data = args[0];
7782
+ const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
7783
+ const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
7784
+ if (typeof eventName === "string") {
7785
+ (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
7786
+ }
7787
+ });
7788
+ }
7789
+ if (enableFeatureFlags && this.featureFlagsCallback && this.client) {
7790
+ this.client.onFeatureFlags(() => {
7791
+ const allFlags = this.getAllFeatureFlags();
7792
+ if (allFlags && this.featureFlagsCallback) {
7793
+ this.featureFlagsCallback(allFlags);
7794
+ }
7795
+ });
7796
+ const existingFlags = this.getAllFeatureFlags();
7797
+ if (existingFlags && Object.keys(existingFlags).length > 0) {
7798
+ this.featureFlagsCallback(existingFlags);
7799
+ }
7800
+ }
7801
+ if (this.rrwebCallback && this.client) {
7802
+ this.setupRRWebIntercept();
7803
+ }
7804
+ }
7805
+ /**
7806
+ * Set up rrweb event interception on PostHog's session recording.
7807
+ *
7808
+ * PostHog lazy-loads the rrweb recorder. The SessionRecording wrapper has
7809
+ * an `onRRwebEmit` method, but rrweb delivers events directly to the
7810
+ * lazy-loaded recorder instance's `onRRwebEmit`, bypassing the wrapper.
7811
+ * We must find and patch the recorder instance, not the wrapper.
7812
+ *
7813
+ * The recorder instance is stored on a minified property of SessionRecording.
7814
+ * We detect it by looking for an object with both `onRRwebEmit` and `start` methods.
7815
+ */
7816
+ setupRRWebIntercept(retries = 30) {
7817
+ var _a2;
7818
+ const sr = (_a2 = this.client) == null ? void 0 : _a2.sessionRecording;
7819
+ if (!sr) {
7820
+ if (retries > 0) {
7821
+ setTimeout(() => this.setupRRWebIntercept(retries - 1), 500);
7822
+ }
7823
+ return;
7824
+ }
7825
+ let recorder = null;
7826
+ const srRecord = sr;
7827
+ for (const key of Object.getOwnPropertyNames(srRecord)) {
7828
+ const val = srRecord[key];
7829
+ if (val && typeof val === "object" && typeof val.onRRwebEmit === "function" && typeof val.start === "function" && val !== sr) {
7830
+ recorder = val;
7831
+ break;
7832
+ }
7833
+ }
7834
+ if (!recorder) {
7835
+ if (retries > 0) {
7836
+ setTimeout(() => this.setupRRWebIntercept(retries - 1), 500);
7837
+ }
7838
+ return;
7839
+ }
7840
+ const originalEmit = recorder.onRRwebEmit.bind(recorder);
7841
+ recorder.onRRwebEmit = (rawEvent) => {
7842
+ var _a3;
7843
+ (_a3 = this.rrwebCallback) == null ? void 0 : _a3.call(this, { kind: "rrweb", ...rawEvent });
7844
+ originalEmit(rawEvent);
7845
+ };
7846
+ if (typeof window !== "undefined") {
7847
+ window.__RRWEB_INTERCEPT_READY__ = true;
7848
+ }
7057
7849
  }
7058
7850
  /**
7059
7851
  * Get all feature flags from PostHog.
7060
7852
  * Used to extract segment membership flags (in_segment_*).
7061
7853
  */
7062
7854
  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;
7855
+ var _a2, _b;
7856
+ return (_b = (_a2 = this.client) == null ? void 0 : _a2.featureFlags) == null ? void 0 : _b.getFlagVariants();
7066
7857
  }
7067
7858
  /**
7068
7859
  * Get segment membership flags (in_segment_*) from PostHog.
@@ -7143,6 +7934,59 @@ function createPostHogClient(options = {}) {
7143
7934
  return new PostHogAdapter(options);
7144
7935
  }
7145
7936
 
7937
+ // src/telemetry/InterventionTracker.ts
7938
+ var InterventionTracker = class {
7939
+ constructor(telemetry, variantId) {
7940
+ __publicField(this, "telemetry");
7941
+ __publicField(this, "variantId");
7942
+ __publicField(this, "seenSet", /* @__PURE__ */ new Set());
7943
+ __publicField(this, "triggeredSet", /* @__PURE__ */ new Set());
7944
+ this.telemetry = telemetry;
7945
+ this.variantId = variantId;
7946
+ }
7947
+ trackServed(tiles, actions) {
7948
+ var _a2, _b;
7949
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_config_served", {
7950
+ variant_id: this.variantId,
7951
+ tiles,
7952
+ actions
7953
+ });
7954
+ }
7955
+ trackSeen(interventionId, interventionKind) {
7956
+ var _a2, _b;
7957
+ if (this.seenSet.has(interventionId)) return;
7958
+ this.seenSet.add(interventionId);
7959
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_seen", {
7960
+ variant_id: this.variantId,
7961
+ intervention_id: interventionId,
7962
+ intervention_kind: interventionKind
7963
+ });
7964
+ }
7965
+ trackTriggered(interventionId, interventionKind) {
7966
+ var _a2, _b;
7967
+ if (this.triggeredSet.has(interventionId)) return;
7968
+ this.triggeredSet.add(interventionId);
7969
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_triggered", {
7970
+ variant_id: this.variantId,
7971
+ intervention_id: interventionId,
7972
+ intervention_kind: interventionKind
7973
+ });
7974
+ }
7975
+ trackInteracted(interventionId, interventionKind, interactionType) {
7976
+ var _a2, _b;
7977
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_interacted", {
7978
+ variant_id: this.variantId,
7979
+ intervention_id: interventionId,
7980
+ intervention_kind: interventionKind,
7981
+ interaction_type: interactionType
7982
+ });
7983
+ }
7984
+ resetPage() {
7985
+ this.seenSet.clear();
7986
+ this.triggeredSet.clear();
7987
+ }
7988
+ };
7989
+
7146
7990
  // src/actions/executors/core-flow.ts
7147
7991
  var executeSequence = async (action, context) => {
7148
7992
  const handles = [];
@@ -7413,158 +8257,7 @@ function hasExecutor(kind) {
7413
8257
  return executorRegistry.has(kind);
7414
8258
  }
7415
8259
 
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
- }
8260
+ // src/actions/validation-rules.ts
7568
8261
  function validateBadgeAction(action, errors, warnings) {
7569
8262
  if (!action.content || typeof action.content !== "string") {
7570
8263
  errors.push({
@@ -7878,75 +8571,228 @@ function validateModalAction(action, errors, _warnings) {
7878
8571
  }
7879
8572
  }
7880
8573
  }
7881
- function validateTourAction(action, errors, warnings) {
7882
- if (!action.tourId || typeof action.tourId !== "string") {
8574
+ function validateTourAction(action, errors, warnings) {
8575
+ if (!action.tourId || typeof action.tourId !== "string") {
8576
+ errors.push({
8577
+ code: "MISSING_TOUR_ID",
8578
+ message: "Tour action requires 'tourId' property",
8579
+ field: "tourId"
8580
+ });
8581
+ }
8582
+ if (!action.steps || !Array.isArray(action.steps)) {
8583
+ errors.push({
8584
+ code: "MISSING_STEPS",
8585
+ message: "Tour action requires 'steps' array",
8586
+ field: "steps"
8587
+ });
8588
+ return;
8589
+ }
8590
+ if (action.steps.length === 0) {
8591
+ errors.push({
8592
+ code: "EMPTY_STEPS",
8593
+ message: "Tour must have at least one step",
8594
+ field: "steps"
8595
+ });
8596
+ }
8597
+ const stepIds = /* @__PURE__ */ new Set();
8598
+ for (let i = 0; i < action.steps.length; i++) {
8599
+ const step = action.steps[i];
8600
+ if (!step.id || typeof step.id !== "string") {
8601
+ errors.push({
8602
+ code: "MISSING_STEP_ID",
8603
+ message: `Step at index ${i} requires 'id' property`,
8604
+ field: `steps[${i}].id`
8605
+ });
8606
+ } else {
8607
+ if (stepIds.has(step.id)) {
8608
+ errors.push({
8609
+ code: "DUPLICATE_STEP_ID",
8610
+ message: `Duplicate step ID: ${step.id}`,
8611
+ field: `steps[${i}].id`
8612
+ });
8613
+ }
8614
+ stepIds.add(step.id);
8615
+ }
8616
+ if (!step.action) {
8617
+ errors.push({
8618
+ code: "MISSING_STEP_ACTION",
8619
+ message: `Step at index ${i} requires 'action' property`,
8620
+ field: `steps[${i}].action`
8621
+ });
8622
+ } else {
8623
+ const result = validateAction(step.action);
8624
+ for (const error2 of result.errors) {
8625
+ errors.push({
8626
+ ...error2,
8627
+ field: `steps[${i}].action${error2.field ? `.${error2.field}` : ""}`
8628
+ });
8629
+ }
8630
+ for (const warning of result.warnings) {
8631
+ warnings.push(warning);
8632
+ }
8633
+ }
8634
+ if (step.onAction) {
8635
+ for (const [_actionId, targetStepId] of Object.entries(step.onAction)) {
8636
+ if (targetStepId !== "end" && !action.steps.some((s) => s.id === targetStepId)) {
8637
+ warnings.push({
8638
+ code: "UNKNOWN_TARGET_STEP",
8639
+ message: `Step "${step.id}" references unknown target step: ${targetStepId}`,
8640
+ suggestion: `Make sure step "${targetStepId}" exists in the tour`
8641
+ });
8642
+ }
8643
+ }
8644
+ }
8645
+ }
8646
+ }
8647
+
8648
+ // src/actions/validation-core.ts
8649
+ var DANGEROUS_ATTRS = /* @__PURE__ */ new Set([
8650
+ "onclick",
8651
+ "onerror",
8652
+ "onload",
8653
+ "onmouseover",
8654
+ "onfocus",
8655
+ "onblur",
8656
+ "onchange",
8657
+ "onsubmit",
8658
+ "onkeydown",
8659
+ "onkeyup",
8660
+ "onkeypress"
8661
+ ]);
8662
+ var MAX_HTML_LENGTH = 5e4;
8663
+ var MAX_STYLE_COUNT = 50;
8664
+ function validateAction(action) {
8665
+ const errors = [];
8666
+ const warnings = [];
8667
+ if (!action || typeof action !== "object") {
8668
+ errors.push({
8669
+ code: "INVALID_ACTION",
8670
+ message: "Action must be an object"
8671
+ });
8672
+ return { valid: false, errors, warnings };
8673
+ }
8674
+ const { kind } = action;
8675
+ if (!kind || typeof kind !== "string") {
8676
+ errors.push({
8677
+ code: "MISSING_KIND",
8678
+ message: "Action must have a 'kind' property"
8679
+ });
8680
+ return { valid: false, errors, warnings };
8681
+ }
8682
+ if (!hasExecutor(kind) && kind !== "core:mountWidget") {
8683
+ const registered = executorRegistry.list();
8684
+ console.error(
8685
+ `[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.`
8686
+ );
8687
+ errors.push({
8688
+ code: "UNKNOWN_KIND",
8689
+ message: `Unknown action kind: ${kind}`,
8690
+ field: "kind"
8691
+ });
8692
+ return { valid: false, errors, warnings };
8693
+ }
8694
+ switch (kind) {
8695
+ case "overlays:highlight":
8696
+ case "overlays:pulse":
8697
+ case "navigation:scrollTo":
8698
+ validateAnchorAction(action, errors, warnings);
8699
+ break;
8700
+ case "overlays:badge":
8701
+ validateAnchorAction(action, errors, warnings);
8702
+ validateBadgeAction(action, errors, warnings);
8703
+ break;
8704
+ case "overlays:tooltip":
8705
+ validateAnchorAction(action, errors, warnings);
8706
+ validateTooltipAction(action, errors, warnings);
8707
+ break;
8708
+ case "overlays:modal":
8709
+ validateModalAction(action, errors, warnings);
8710
+ break;
8711
+ case "content:insertHtml":
8712
+ validateAnchorAction(action, errors, warnings);
8713
+ validateInsertHtmlAction(action, errors, warnings);
8714
+ break;
8715
+ case "content:setText":
8716
+ validateAnchorAction(action, errors, warnings);
8717
+ validateSetTextAction(action, errors, warnings);
8718
+ break;
8719
+ case "content:setAttr":
8720
+ validateAnchorAction(action, errors, warnings);
8721
+ validateSetAttrAction(action, errors, warnings);
8722
+ break;
8723
+ case "content:addClass":
8724
+ case "content:removeClass":
8725
+ validateAnchorAction(action, errors, warnings);
8726
+ validateClassAction(action, errors, warnings);
8727
+ break;
8728
+ case "content:setStyle":
8729
+ validateAnchorAction(action, errors, warnings);
8730
+ validateSetStyleAction(action, errors, warnings);
8731
+ break;
8732
+ case "core:mountWidget":
8733
+ validateMountWidgetAction(action, errors, warnings);
8734
+ break;
8735
+ case "core:wait":
8736
+ validateWaitAction(action, errors, warnings);
8737
+ break;
8738
+ case "core:sequence":
8739
+ validateSequenceAction(action, errors, warnings);
8740
+ break;
8741
+ case "core:parallel":
8742
+ validateParallelAction(action, errors, warnings);
8743
+ break;
8744
+ case "overlays:tour":
8745
+ validateTourAction(action, errors, warnings);
8746
+ break;
8747
+ case "navigation:navigate":
8748
+ validateNavigateAction(action, errors, warnings);
8749
+ break;
8750
+ }
8751
+ return {
8752
+ valid: errors.length === 0,
8753
+ errors,
8754
+ warnings
8755
+ };
8756
+ }
8757
+ function validateAnchorAction(action, errors, warnings) {
8758
+ const anchorId = action.anchorId;
8759
+ if (!anchorId || typeof anchorId !== "object") {
7883
8760
  errors.push({
7884
- code: "MISSING_TOUR_ID",
7885
- message: "Tour action requires 'tourId' property",
7886
- field: "tourId"
8761
+ code: "MISSING_ANCHOR_ID",
8762
+ message: "Action requires an 'anchorId' object with a 'selector' string",
8763
+ field: "anchorId"
7887
8764
  });
8765
+ return;
7888
8766
  }
7889
- if (!action.steps || !Array.isArray(action.steps)) {
8767
+ if (!anchorId.selector || typeof anchorId.selector !== "string") {
7890
8768
  errors.push({
7891
- code: "MISSING_STEPS",
7892
- message: "Tour action requires 'steps' array",
7893
- field: "steps"
8769
+ code: "MISSING_ANCHOR_SELECTOR",
8770
+ message: "anchorId requires a 'selector' string",
8771
+ field: "anchorId.selector"
8772
+ });
8773
+ } else if (anchorId.selector.length > 200) {
8774
+ warnings.push({
8775
+ code: "LONG_ANCHOR_ID",
8776
+ message: "Anchor selector is unusually long",
8777
+ suggestion: "Consider using a shorter, more descriptive selector"
7894
8778
  });
7895
- return;
7896
8779
  }
7897
- if (action.steps.length === 0) {
8780
+ if (anchorId.route === void 0 || anchorId.route === null) {
7898
8781
  errors.push({
7899
- code: "EMPTY_STEPS",
7900
- message: "Tour must have at least one step",
7901
- field: "steps"
8782
+ code: "MISSING_ANCHOR_ROUTE",
8783
+ message: `anchorId requires a 'route' (string or array of strings). Use "**" for all routes.`,
8784
+ field: "anchorId.route"
7902
8785
  });
7903
- }
7904
- const stepIds = /* @__PURE__ */ new Set();
7905
- for (let i = 0; i < action.steps.length; i++) {
7906
- const step = action.steps[i];
7907
- if (!step.id || typeof step.id !== "string") {
7908
- errors.push({
7909
- code: "MISSING_STEP_ID",
7910
- message: `Step at index ${i} requires 'id' property`,
7911
- field: `steps[${i}].id`
7912
- });
7913
- } else {
7914
- if (stepIds.has(step.id)) {
7915
- errors.push({
7916
- code: "DUPLICATE_STEP_ID",
7917
- message: `Duplicate step ID: ${step.id}`,
7918
- field: `steps[${i}].id`
7919
- });
7920
- }
7921
- stepIds.add(step.id);
7922
- }
7923
- if (!step.action) {
7924
- errors.push({
7925
- code: "MISSING_STEP_ACTION",
7926
- message: `Step at index ${i} requires 'action' property`,
7927
- field: `steps[${i}].action`
7928
- });
7929
- } else {
7930
- const result = validateAction(step.action);
7931
- for (const error2 of result.errors) {
8786
+ } else {
8787
+ const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
8788
+ for (const route of routes) {
8789
+ if (typeof route !== "string") {
7932
8790
  errors.push({
7933
- ...error2,
7934
- field: `steps[${i}].action${error2.field ? `.${error2.field}` : ""}`
8791
+ code: "INVALID_ANCHOR_ROUTE",
8792
+ message: "anchorId.route must be a string or array of strings",
8793
+ field: "anchorId.route"
7935
8794
  });
7936
- }
7937
- for (const warning of result.warnings) {
7938
- warnings.push(warning);
7939
- }
7940
- }
7941
- if (step.onAction) {
7942
- for (const [_actionId, targetStepId] of Object.entries(step.onAction)) {
7943
- if (targetStepId !== "end" && !action.steps.some((s) => s.id === targetStepId)) {
7944
- warnings.push({
7945
- code: "UNKNOWN_TARGET_STEP",
7946
- message: `Step "${step.id}" references unknown target step: ${targetStepId}`,
7947
- suggestion: `Make sure step "${targetStepId}" exists in the tour`
7948
- });
7949
- }
8795
+ break;
7950
8796
  }
7951
8797
  }
7952
8798
  }
@@ -8097,7 +8943,7 @@ function createActionEngine(options) {
8097
8943
  }
8098
8944
  return executor(action, context);
8099
8945
  }
8100
- function subscribeForReeval(id, action, triggerWhen, handle) {
8946
+ function subscribeForReeval(id, action, triggerWhen, _handle) {
8101
8947
  if (!runtime3) return;
8102
8948
  const unsubs = [];
8103
8949
  const onReeval = async () => {
@@ -8241,13 +9087,9 @@ function createActionEngine(options) {
8241
9087
  entry2.state = "reverted";
8242
9088
  publishEvent("action.reverted", { id, kind: action.kind });
8243
9089
  } catch (error2) {
8244
- entry2.state = "failed";
8245
- publishEvent("action.failed", {
8246
- id,
8247
- kind: action.kind,
8248
- error: String(error2)
8249
- });
8250
- throw error2;
9090
+ console.warn(`[ActionEngine] Cleanup error for ${action.kind} (${id}), ignoring:`, error2);
9091
+ entry2.state = "reverted";
9092
+ publishEvent("action.reverted", { id, kind: action.kind });
8251
9093
  } finally {
8252
9094
  activeActions.delete(id);
8253
9095
  }
@@ -8291,7 +9133,7 @@ function createActionEngine(options) {
8291
9133
  errorMessages,
8292
9134
  "\nActions:",
8293
9135
  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}"` : ""}`
9136
+ (a, i) => ` [${i}] ${a.kind} ${a.anchorId ? `anchor="${a.anchorId.selector}"` : ""} ${a.label ? `label="${a.label}"` : ""}`
8295
9137
  ).join("\n")
8296
9138
  );
8297
9139
  throw new Error(`Batch validation failed: ${errorMessages}`);
@@ -8391,6 +9233,7 @@ function createAnchorResolver(opts) {
8391
9233
  function resolve(selector) {
8392
9234
  if (!root) return null;
8393
9235
  try {
9236
+ if (root.matches(selector)) return root;
8394
9237
  return root.querySelector(selector);
8395
9238
  } catch {
8396
9239
  return null;
@@ -8736,7 +9579,7 @@ function createContextManager(options) {
8736
9579
 
8737
9580
  // src/decisions/strategies/rules.ts
8738
9581
  function evaluateCondition(condition, evalContext) {
8739
- var _a2, _b, _c, _d, _e, _f, _g;
9582
+ var _a2, _b, _c, _d, _e;
8740
9583
  const { context, state, events } = evalContext;
8741
9584
  switch (condition.type) {
8742
9585
  case "page_url": {
@@ -8750,8 +9593,7 @@ function evaluateCondition(condition, evalContext) {
8750
9593
  return context.page.routeId === condition.routeId;
8751
9594
  }
8752
9595
  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);
9596
+ const anchor = (_a2 = context.anchors) == null ? void 0 : _a2.find((a) => a.anchorId === condition.anchorId);
8755
9597
  switch (condition.state) {
8756
9598
  case "visible":
8757
9599
  return (anchor == null ? void 0 : anchor.visible) === true;
@@ -8765,7 +9607,7 @@ function evaluateCondition(condition, evalContext) {
8765
9607
  }
8766
9608
  case "event_occurred": {
8767
9609
  if (!events) return false;
8768
- const withinMs = (_d = condition.withinMs) != null ? _d : 6e4;
9610
+ const withinMs = (_b = condition.withinMs) != null ? _b : 6e4;
8769
9611
  return events.hasRecentEvent(condition.eventName, withinMs);
8770
9612
  }
8771
9613
  case "state_equals": {
@@ -8801,17 +9643,17 @@ function evaluateCondition(condition, evalContext) {
8801
9643
  }
8802
9644
  }
8803
9645
  case "dismissed": {
8804
- if (!state) return (_e = condition.inverted) != null ? _e : false;
9646
+ if (!state) return (_c = condition.inverted) != null ? _c : false;
8805
9647
  const isDismissed = state.isDismissed(condition.key);
8806
9648
  return condition.inverted ? !isDismissed : isDismissed;
8807
9649
  }
8808
9650
  case "cooldown_active": {
8809
- if (!state) return (_f = condition.inverted) != null ? _f : false;
9651
+ if (!state) return (_d = condition.inverted) != null ? _d : false;
8810
9652
  const isActive = state.isCooldownActive(condition.key);
8811
9653
  return condition.inverted ? !isActive : isActive;
8812
9654
  }
8813
9655
  case "frequency_limit": {
8814
- if (!state) return (_g = condition.inverted) != null ? _g : false;
9656
+ if (!state) return (_e = condition.inverted) != null ? _e : false;
8815
9657
  const count = state.getFrequencyCount(condition.key);
8816
9658
  const limitReached = count >= condition.limit;
8817
9659
  return condition.inverted ? !limitReached : limitReached;
@@ -9071,6 +9913,66 @@ function createEventAccumulator(options) {
9071
9913
  };
9072
9914
  }
9073
9915
 
9916
+ // src/events/validation.ts
9917
+ var APP_PREFIX = "app:";
9918
+ var RESERVED_PREFIX = "syntro:";
9919
+ var SEGMENT_PATTERN = /^[a-z][a-z0-9_]*$/;
9920
+ function validateEventName(name) {
9921
+ if (!name) {
9922
+ return { valid: false, reason: "Event name cannot be empty" };
9923
+ }
9924
+ if (name.startsWith(RESERVED_PREFIX)) {
9925
+ return { valid: false, reason: '"syntro:" prefix is reserved for internal SDK events' };
9926
+ }
9927
+ if (!name.startsWith(APP_PREFIX)) {
9928
+ return { valid: false, reason: `Custom events must start with "app:" prefix. Got: "${name}"` };
9929
+ }
9930
+ const segments = name.slice(APP_PREFIX.length).split(":");
9931
+ if (segments.length < 2) {
9932
+ return {
9933
+ valid: false,
9934
+ reason: `Event name must have at least 2 segments after "app:" (app:{category}:{action}). Got: "${name}"`
9935
+ };
9936
+ }
9937
+ for (const segment of segments) {
9938
+ if (!SEGMENT_PATTERN.test(segment)) {
9939
+ return {
9940
+ valid: false,
9941
+ reason: `Segment "${segment}" must be lowercase alphanumeric + underscores. Got: "${name}"`
9942
+ };
9943
+ }
9944
+ }
9945
+ return { valid: true };
9946
+ }
9947
+ function isSerializable(value) {
9948
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
9949
+ }
9950
+ function checkDepth(obj, maxDepth, current = 0) {
9951
+ if (current >= maxDepth) return false;
9952
+ for (const value of Object.values(obj)) {
9953
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
9954
+ if (!checkDepth(value, maxDepth, current + 1)) return false;
9955
+ }
9956
+ }
9957
+ return true;
9958
+ }
9959
+ function validateProps(props) {
9960
+ if (props === void 0) return { valid: true };
9961
+ if (props === null || typeof props !== "object" || Array.isArray(props)) {
9962
+ return { valid: false, reason: "Props must be a plain object" };
9963
+ }
9964
+ if (!checkDepth(props, 2)) {
9965
+ return { valid: false, reason: "Props nesting depth exceeds 2 levels" };
9966
+ }
9967
+ const stripped = [];
9968
+ for (const [key, value] of Object.entries(props)) {
9969
+ if (value !== null && typeof value === "object") continue;
9970
+ if (!isSerializable(value)) stripped.push(key);
9971
+ }
9972
+ if (stripped.length > 0) return { valid: true, stripped };
9973
+ return { valid: true };
9974
+ }
9975
+
9074
9976
  // src/events/EventBus.ts
9075
9977
  function matchesFilter(event, filter) {
9076
9978
  if (!filter) return true;
@@ -9102,8 +10004,16 @@ var EventBus = class {
9102
10004
  __publicField(this, "subscriptions", /* @__PURE__ */ new Set());
9103
10005
  __publicField(this, "history", []);
9104
10006
  __publicField(this, "maxHistorySize");
9105
- var _a2;
10007
+ __publicField(this, "debug");
10008
+ __publicField(this, "emitHistory");
10009
+ __publicField(this, "posthogCapture");
10010
+ __publicField(this, "testMode");
10011
+ var _a2, _b, _c, _d, _e;
9106
10012
  this.maxHistorySize = (_a2 = options.maxHistorySize) != null ? _a2 : 100;
10013
+ this.debug = (_b = options.debug) != null ? _b : false;
10014
+ this.emitHistory = (_c = options.history) != null ? _c : null;
10015
+ this.posthogCapture = (_d = options.posthogCapture) != null ? _d : null;
10016
+ this.testMode = (_e = options.testMode) != null ? _e : false;
9107
10017
  }
9108
10018
  /**
9109
10019
  * Subscribe to events matching an optional filter.
@@ -9156,6 +10066,83 @@ var EventBus = class {
9156
10066
  }
9157
10067
  }
9158
10068
  }
10069
+ /**
10070
+ * Emit a validated custom event from the host application.
10071
+ *
10072
+ * Custom events must use the `app:` prefix (e.g. `app:cart:abandoned`).
10073
+ * In debug mode, returns an EmitResult with delivery details.
10074
+ * In production mode, returns undefined.
10075
+ */
10076
+ emit(name, props) {
10077
+ const nameResult = validateEventName(name);
10078
+ if (!nameResult.valid) {
10079
+ console.warn(`[EventBus] emit() rejected: ${nameResult.reason}`);
10080
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
10081
+ }
10082
+ const propsResult = validateProps(props);
10083
+ if (!propsResult.valid) {
10084
+ console.warn(`[EventBus] emit() rejected props: ${propsResult.reason}`);
10085
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
10086
+ }
10087
+ const event = {
10088
+ ts: Date.now(),
10089
+ name,
10090
+ source: "custom",
10091
+ props,
10092
+ schemaVersion: EVENT_SCHEMA_VERSION
10093
+ };
10094
+ this.history.push(event);
10095
+ if (this.history.length > this.maxHistorySize) {
10096
+ this.history.shift();
10097
+ }
10098
+ let listenersNotified = 0;
10099
+ for (const subscription of this.subscriptions) {
10100
+ if (matchesFilter(event, subscription.filter)) {
10101
+ try {
10102
+ subscription.callback(event);
10103
+ listenersNotified++;
10104
+ } catch (err) {
10105
+ console.error("[EventBus] Subscriber error:", err);
10106
+ listenersNotified++;
10107
+ }
10108
+ }
10109
+ }
10110
+ let posthogCaptured = false;
10111
+ if (this.posthogCapture && !this.testMode) {
10112
+ try {
10113
+ this.posthogCapture(name, props);
10114
+ posthogCaptured = true;
10115
+ } catch (err) {
10116
+ console.error("[EventBus] PostHog capture error:", err);
10117
+ }
10118
+ }
10119
+ if (this.emitHistory) {
10120
+ this.emitHistory.record({
10121
+ name,
10122
+ props,
10123
+ source: "custom",
10124
+ timestamp: event.ts,
10125
+ matchedRules: []
10126
+ });
10127
+ }
10128
+ if (this.debug) {
10129
+ console.debug("[EventBus] emit()", { name, props, listenersNotified, posthogCaptured });
10130
+ return {
10131
+ delivered: true,
10132
+ matchedRules: [],
10133
+ posthogCaptured,
10134
+ listenersNotified
10135
+ };
10136
+ }
10137
+ return void 0;
10138
+ }
10139
+ /**
10140
+ * Set the PostHog capture function after construction.
10141
+ * Used by bootstrap to wire PostHog after the EventBus is created.
10142
+ */
10143
+ setPosthogCapture(fn) {
10144
+ this.posthogCapture = fn;
10145
+ }
9159
10146
  /**
9160
10147
  * Get recent events matching an optional filter.
9161
10148
  */
@@ -9206,124 +10193,26 @@ function createEventBus(options = {}) {
9206
10193
  return new EventBus(options);
9207
10194
  }
9208
10195
 
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}`;
10196
+ // src/events/history.ts
10197
+ var EventHistory = class {
10198
+ constructor(maxSize = 100) {
10199
+ __publicField(this, "entries", []);
10200
+ __publicField(this, "maxSize");
10201
+ this.maxSize = maxSize;
9246
10202
  }
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();
10203
+ record(entry) {
10204
+ this.entries.push(entry);
10205
+ if (this.maxSize > 0 && this.entries.length > this.maxSize) {
10206
+ this.entries.shift();
10207
+ }
9280
10208
  }
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;
10209
+ getAll() {
10210
+ return [...this.entries];
9310
10211
  }
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
- };
9326
- }
10212
+ clear() {
10213
+ this.entries = [];
10214
+ }
10215
+ };
9327
10216
 
9328
10217
  // src/navigation/NavigationMonitor.ts
9329
10218
  var NavigationMonitor = class {
@@ -10124,9 +11013,18 @@ function createSurfaces(options) {
10124
11013
  }
10125
11014
  async function unmountEntry(entry) {
10126
11015
  var _a2;
10127
- await playExitAnimation(entry.container, entry.options.animation);
10128
- (_a2 = entry.cleanup) == null ? void 0 : _a2.call(entry);
10129
- entry.container.remove();
11016
+ if (entry.container.isConnected) {
11017
+ await playExitAnimation(entry.container, entry.options.animation);
11018
+ }
11019
+ try {
11020
+ (_a2 = entry.cleanup) == null ? void 0 : _a2.call(entry);
11021
+ } catch (error2) {
11022
+ console.warn("[Surfaces] Cleanup error during unmount, ignoring:", error2);
11023
+ }
11024
+ try {
11025
+ entry.container.remove();
11026
+ } catch {
11027
+ }
10130
11028
  mounts.delete(entry.slot);
10131
11029
  publishEvent("surface.unmounted", {
10132
11030
  slot: entry.slot,
@@ -10309,7 +11207,9 @@ var WidgetRegistry = class {
10309
11207
  var _a2;
10310
11208
  const mounted = this.mountedWidgets.get(mountId);
10311
11209
  if (mounted) {
10312
- (_a2 = mounted.cleanup) == null ? void 0 : _a2.call(mounted);
11210
+ if (container.isConnected) {
11211
+ (_a2 = mounted.cleanup) == null ? void 0 : _a2.call(mounted);
11212
+ }
10313
11213
  this.mountedWidgets.delete(mountId);
10314
11214
  container.removeAttribute("data-widget-mount-id");
10315
11215
  container.removeAttribute("data-widget-id");
@@ -10425,7 +11325,16 @@ function matchesAnchorRoute(anchorId) {
10425
11325
  const pathname = typeof window !== "undefined" ? window.location.pathname : "/";
10426
11326
  const normalizedPath = pathname.replace(/\/$/, "") || "/";
10427
11327
  const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
10428
- return routes.some((pattern) => matchRoutePattern(normalizedPath, pattern));
11328
+ return routes.some((pattern) => {
11329
+ let normalized = pattern;
11330
+ if (/^https?:\/\//.test(normalized)) {
11331
+ try {
11332
+ normalized = new URL(normalized).pathname;
11333
+ } catch {
11334
+ }
11335
+ }
11336
+ return matchRoutePattern(normalizedPath, normalized);
11337
+ });
10429
11338
  }
10430
11339
  function createSmartCanvasRuntime(options = {}) {
10431
11340
  var _a2, _b, _c, _d;
@@ -10597,6 +11506,142 @@ function encodeToken(payload) {
10597
11506
  return TOKEN_PREFIX + base64;
10598
11507
  }
10599
11508
 
11509
+ // src/bootstrap-init.ts
11510
+ function getEnvVar(name) {
11511
+ if (typeof process !== "undefined" && process.env) {
11512
+ return process.env[name];
11513
+ }
11514
+ try {
11515
+ const meta = (0, eval)("import.meta");
11516
+ if (meta == null ? void 0 : meta.env) {
11517
+ return meta.env[name];
11518
+ }
11519
+ } catch {
11520
+ }
11521
+ return void 0;
11522
+ }
11523
+ var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
11524
+ function loadCachedSegmentAttributes() {
11525
+ if (typeof window === "undefined") return {};
11526
+ try {
11527
+ const cached = localStorage.getItem(SEGMENT_CACHE_KEY);
11528
+ if (cached) {
11529
+ const attrs = JSON.parse(cached);
11530
+ debug("Syntro Bootstrap", "Loaded cached segment attributes:", attrs);
11531
+ return attrs;
11532
+ }
11533
+ } catch (err) {
11534
+ warn("Syntro Bootstrap", "Failed to load cached segment attributes:", err);
11535
+ }
11536
+ return {};
11537
+ }
11538
+ function cacheSegmentAttributes(attrs) {
11539
+ if (typeof window === "undefined") return;
11540
+ try {
11541
+ localStorage.setItem(SEGMENT_CACHE_KEY, JSON.stringify(attrs));
11542
+ debug("Syntro Bootstrap", "Cached segment attributes:", attrs);
11543
+ } catch (err) {
11544
+ warn("Syntro Bootstrap", "Failed to cache segment attributes:", err);
11545
+ }
11546
+ }
11547
+ function extractSegmentFlags(allFlags) {
11548
+ if (!allFlags) return {};
11549
+ const segmentFlags = {};
11550
+ for (const [key, value] of Object.entries(allFlags)) {
11551
+ if (key.startsWith("in_segment_")) {
11552
+ segmentFlags[key] = value === true;
11553
+ }
11554
+ }
11555
+ return segmentFlags;
11556
+ }
11557
+ function collectBrowserMetadata() {
11558
+ var _a2;
11559
+ if (typeof window === "undefined") return {};
11560
+ const attrs = {};
11561
+ try {
11562
+ const params = new URLSearchParams(window.location.search);
11563
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
11564
+ const val = params.get(key);
11565
+ if (val) attrs[key] = val;
11566
+ }
11567
+ } catch {
11568
+ }
11569
+ try {
11570
+ if (navigator.language) attrs.browser_language = navigator.language;
11571
+ if ((_a2 = navigator.languages) == null ? void 0 : _a2.length) attrs.browser_languages = [...navigator.languages];
11572
+ } catch {
11573
+ }
11574
+ try {
11575
+ const w = window.innerWidth;
11576
+ attrs.device_type = w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
11577
+ } catch {
11578
+ }
11579
+ try {
11580
+ if (document.referrer) {
11581
+ attrs.referrer = document.referrer;
11582
+ try {
11583
+ attrs.referrer_hostname = new URL(document.referrer).hostname;
11584
+ } catch {
11585
+ }
11586
+ }
11587
+ } catch {
11588
+ }
11589
+ try {
11590
+ const ua = navigator.userAgent;
11591
+ if (ua.includes("Edg/")) attrs.browser = "Edge";
11592
+ else if (ua.includes("OPR/") || ua.includes("Opera")) attrs.browser = "Opera";
11593
+ else if (ua.includes("Chrome/") && !ua.includes("Chromium")) attrs.browser = "Chrome";
11594
+ else if (ua.includes("Safari/") && !ua.includes("Chrome")) attrs.browser = "Safari";
11595
+ else if (ua.includes("Firefox/")) attrs.browser = "Firefox";
11596
+ if (ua.includes("Windows")) attrs.os = "Windows";
11597
+ else if (ua.includes("iPhone") || ua.includes("iPad")) attrs.os = "iOS";
11598
+ else if (ua.includes("Mac OS X") || ua.includes("Macintosh")) attrs.os = "macOS";
11599
+ else if (ua.includes("Android")) attrs.os = "Android";
11600
+ else if (ua.includes("Linux")) attrs.os = "Linux";
11601
+ } catch {
11602
+ }
11603
+ try {
11604
+ attrs.page_url = window.location.href;
11605
+ attrs.page_path = window.location.pathname;
11606
+ attrs.page_host = window.location.hostname;
11607
+ if (window.location.search) attrs.page_query = window.location.search;
11608
+ } catch {
11609
+ }
11610
+ return attrs;
11611
+ }
11612
+ var GEO_CACHE_KEY = "syntro_geo";
11613
+ var GEO_DEFAULT_HOST = "https://geo.syntrologie.com";
11614
+ async function fetchGeo(geoHost) {
11615
+ if (typeof window === "undefined") return {};
11616
+ try {
11617
+ const cached = localStorage.getItem(GEO_CACHE_KEY);
11618
+ if (cached) {
11619
+ const parsed = JSON.parse(cached);
11620
+ debug("Syntro Bootstrap", "Geo: using cached data:", parsed);
11621
+ return parsed;
11622
+ }
11623
+ } catch {
11624
+ }
11625
+ try {
11626
+ const res = await fetch(geoHost, { signal: AbortSignal.timeout(2e3) });
11627
+ if (res.ok) {
11628
+ const geo = await res.json();
11629
+ const cleaned = {};
11630
+ for (const [k, v] of Object.entries(geo)) {
11631
+ if (typeof v === "string" && v) cleaned[k] = v;
11632
+ }
11633
+ try {
11634
+ localStorage.setItem(GEO_CACHE_KEY, JSON.stringify(cleaned));
11635
+ } catch {
11636
+ }
11637
+ debug("Syntro Bootstrap", "Geo: fetched from worker:", cleaned);
11638
+ return cleaned;
11639
+ }
11640
+ } catch {
11641
+ }
11642
+ return {};
11643
+ }
11644
+
10600
11645
  // src/experiments/registry.ts
10601
11646
  var adapters = {
10602
11647
  growthbook: (config) => createGrowthBookClient({
@@ -10703,9 +11748,8 @@ var ExperimentsFetcher = class {
10703
11748
  __publicField(this, "featureKey");
10704
11749
  __publicField(this, "manifestKey");
10705
11750
  __publicField(this, "variantFlagPrefix");
10706
- var _a2;
10707
11751
  this.client = options.client;
10708
- this.featureKey = (_a2 = options.featureKey) != null ? _a2 : "smart-canvas-config";
11752
+ this.featureKey = options.featureKey;
10709
11753
  this.manifestKey = options.manifestKey;
10710
11754
  this.variantFlagPrefix = options.variantFlagPrefix;
10711
11755
  }
@@ -10734,6 +11778,11 @@ var ExperimentsFetcher = class {
10734
11778
  };
10735
11779
  }
10736
11780
  }
11781
+ if (!this.featureKey) {
11782
+ throw new Error(
11783
+ "[SmartCanvas] No featureKey configured and no variant flags found. Ensure at least one config feature flag exists in your experiment platform."
11784
+ );
11785
+ }
10737
11786
  const config = (_b = (_a2 = this.client).getFeatureValue) == null ? void 0 : _b.call(_a2, this.featureKey, null);
10738
11787
  if (!config || typeof config !== "object") {
10739
11788
  throw new Error(
@@ -10845,95 +11894,9 @@ function createTelemetryClient(provider, config) {
10845
11894
  return factory(config);
10846
11895
  }
10847
11896
 
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;
11897
+ // src/bootstrap-runtime.ts
11898
+ async function _initCore(options) {
11899
+ var _a2, _b, _c, _d, _e, _f, _g;
10937
11900
  initLogger();
10938
11901
  debug("Syntro Bootstrap", "====== INIT ======");
10939
11902
  debug("Syntro Bootstrap", "Options:", {
@@ -10998,17 +11961,59 @@ async function init(options) {
10998
11961
  const experimentHost = getEnvVar("NEXT_PUBLIC_SYNTRO_EXPERIMENT_HOST") || getEnvVar("VITE_SYNTRO_EXPERIMENT_HOST") || (payload == null ? void 0 : payload.eh);
10999
11962
  const telemetryHost = getEnvVar("NEXT_PUBLIC_SYNTRO_TELEMETRY_HOST") || getEnvVar("VITE_SYNTRO_TELEMETRY_HOST") || (payload == null ? void 0 : payload.th);
11000
11963
  const editorUrl = getEnvVar("NEXT_PUBLIC_SYNTRO_EDITOR_URL") || getEnvVar("VITE_SYNTRO_EDITOR_URL") || ((_b = options.canvas) == null ? void 0 : _b.editorUrl);
11964
+ const geoHost = (payload == null ? void 0 : payload.g) || getEnvVar("NEXT_PUBLIC_SYNTRO_GEO_HOST") || getEnvVar("VITE_SYNTRO_GEO_HOST") || GEO_DEFAULT_HOST;
11001
11965
  const cachedSegmentAttrs = loadCachedSegmentAttributes();
11002
11966
  const browserMetadata = collectBrowserMetadata();
11003
11967
  const phaseOneAttrs = { ...browserMetadata, ...cachedSegmentAttrs };
11004
11968
  debug("Syntro Bootstrap", "Phase 1: Browser metadata:", browserMetadata);
11005
11969
  debug("Syntro Bootstrap", "Phase 1: Cached segment attributes:", cachedSegmentAttrs);
11970
+ const geoPromise = fetchGeo(geoHost);
11006
11971
  let experiments;
11007
- const events = createEventBus();
11972
+ const isDebugOrTest = options.debug || options.testMode;
11973
+ const events = createEventBus({
11974
+ debug: options.debug,
11975
+ history: isDebugOrTest ? new EventHistory(options.testMode ? 0 : 100) : void 0,
11976
+ testMode: options.testMode
11977
+ });
11008
11978
  console.log("[Syntro Bootstrap] EventBus created");
11009
- const postHogNormalizer = createPostHogNormalizer((event) => {
11010
- events.publishEvent(event);
11979
+ const processor = createEventProcessor({
11980
+ elementResolver: typeof window !== "undefined" ? (x, y) => {
11981
+ const el = document.elementFromPoint(x, y);
11982
+ if (!el) return null;
11983
+ const info = { tag_name: el.tagName.toLowerCase() };
11984
+ if (el.id) info.attr__id = el.id;
11985
+ if (el.className && typeof el.className === "string") {
11986
+ info.classes = el.className.split(" ").filter(Boolean);
11987
+ }
11988
+ for (const attr of el.attributes) {
11989
+ if (attr.name.startsWith("data-")) info[`attr__${attr.name}`] = attr.value;
11990
+ }
11991
+ return info;
11992
+ } : void 0
11011
11993
  });
11994
+ processor.onEvent((event) => events.publishEvent(event));
11995
+ if (typeof window !== "undefined") {
11996
+ setInterval(() => processor.tick(Date.now()), 1e3);
11997
+ document.addEventListener(
11998
+ "click",
11999
+ (e) => {
12000
+ const chain = [];
12001
+ let el = e.target;
12002
+ while (el && el !== document.body) {
12003
+ const info = { tag_name: el.tagName.toLowerCase() };
12004
+ for (const attr of el.attributes) {
12005
+ if (attr.name.startsWith("data-") || attr.name === "id" || attr.name === "class" || attr.name === "aria-label") {
12006
+ info[`attr__${attr.name}`] = attr.value;
12007
+ }
12008
+ }
12009
+ chain.push(info);
12010
+ el = el.parentElement;
12011
+ }
12012
+ processor.enrichClickAttributes(Date.now(), chain);
12013
+ },
12014
+ true
12015
+ );
12016
+ }
11012
12017
  const onFeatureFlagsLoaded = (allFlags) => {
11013
12018
  var _a3, _b2, _c2;
11014
12019
  debug("Syntro Bootstrap", "Phase 2: PostHog feature flags loaded");
@@ -11031,12 +12036,26 @@ async function init(options) {
11031
12036
  // undefined falls back to adapter default
11032
12037
  // Enable PostHog feature flags for segment membership
11033
12038
  enableFeatureFlags: true,
12039
+ // Disable session recording in debug/dev mode (mock telemetry doesn't
12040
+ // support the PostHog recorder extension, causing console errors)
12041
+ sessionRecording: !payload.d,
11034
12042
  // Wire up callback for when flags are loaded (Phase 2)
11035
12043
  onFeatureFlagsLoaded,
11036
- // Wire up event capture to feed into EventBus
11037
- onCapture: postHogNormalizer
12044
+ // Wire up event capture to feed into event processor
12045
+ onCapture: (eventName, properties) => {
12046
+ processor.ingest({ kind: "posthog", event: eventName, properties, timestamp: Date.now() });
12047
+ },
12048
+ // Wire rrweb events for behavioral signal detection
12049
+ onRRWebEvent: (event) => {
12050
+ processor.ingest(event);
12051
+ }
11038
12052
  });
11039
12053
  console.log(`[Syntro Bootstrap] Telemetry client created (${provider}) with EventBus wiring`);
12054
+ const telemetryForCapture = telemetry;
12055
+ events.setPosthogCapture((name, props) => {
12056
+ var _a3;
12057
+ (_a3 = telemetryForCapture.track) == null ? void 0 : _a3.call(telemetryForCapture, name, props);
12058
+ });
11040
12059
  }
11041
12060
  let sessionMetrics;
11042
12061
  if (payload == null ? void 0 : payload.e) {
@@ -11129,11 +12148,17 @@ async function init(options) {
11129
12148
  warn("Syntro Bootstrap", "Failed to load GrowthBook features:", err);
11130
12149
  }
11131
12150
  }
12151
+ const geoData = await geoPromise;
12152
+ if (experiments && Object.keys(geoData).length > 0) {
12153
+ const mergedAttrs = { ...browserMetadata, ...geoData };
12154
+ debug("Syntro Bootstrap", "Merging geo data into GrowthBook attributes:", geoData);
12155
+ (_f = experiments.setAttributes) == null ? void 0 : _f.call(experiments, mergedAttrs);
12156
+ }
11132
12157
  let baseFetcher;
11133
12158
  if (options.fetcher) {
11134
12159
  baseFetcher = options.fetcher;
11135
12160
  } else if (payload == null ? void 0 : payload.f) {
11136
- const configFetcher = createConfigFetcher(payload.f, (_f = payload.o) != null ? _f : {});
12161
+ const configFetcher = createConfigFetcher(payload.f, (_g = payload.o) != null ? _g : {});
11137
12162
  baseFetcher = async () => {
11138
12163
  var _a3;
11139
12164
  const result = await configFetcher.fetch();
@@ -11147,19 +12172,39 @@ async function init(options) {
11147
12172
  }
11148
12173
  const warnedAppFailures = /* @__PURE__ */ new Set();
11149
12174
  const appLoadingFetcher = baseFetcher ? async () => {
11150
- var _a3, _b2, _c2, _d2, _e2, _f2, _g;
12175
+ var _a3, _b2, _c2, _d2, _e2, _f2, _g2, _h, _i, _j, _k, _l;
11151
12176
  const config = await baseFetcher();
12177
+ const tileCount = (_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0;
12178
+ const actionCount = (_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0;
12179
+ const variantId = (_e2 = config.meta) == null ? void 0 : _e2.variant_id;
12180
+ if (tileCount > 0 || actionCount > 0) {
12181
+ if (!variantId) {
12182
+ console.warn(
12183
+ "[Syntro] Config has content but no meta.variant_id \u2014 intervention tracking disabled"
12184
+ );
12185
+ }
12186
+ }
12187
+ if (telemetry && variantId) {
12188
+ const tracker = new InterventionTracker(telemetry, variantId);
12189
+ tracker.trackServed(tileCount, actionCount);
12190
+ if (typeof window !== "undefined") {
12191
+ window.SynOS.interventionTracker = tracker;
12192
+ }
12193
+ runtime3.navigation.subscribe(() => {
12194
+ tracker.resetPage();
12195
+ });
12196
+ }
11152
12197
  console.log(
11153
12198
  "[Syntro Bootstrap] Config fetched:",
11154
- `tiles=${(_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0},`,
11155
- `actions=${(_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0},`,
11156
- `theme=${(_f2 = (_e2 = config.theme) == null ? void 0 : _e2.name) != null ? _f2 : "none"}`
12199
+ `tiles=${(_g2 = (_f2 = config.tiles) == null ? void 0 : _f2.length) != null ? _g2 : 0},`,
12200
+ `actions=${(_i = (_h = config.actions) == null ? void 0 : _h.length) != null ? _i : 0},`,
12201
+ `theme=${(_k = (_j = config.theme) == null ? void 0 : _j.name) != null ? _k : "none"}`
11157
12202
  );
11158
- if (((_g = config.actions) == null ? void 0 : _g.length) > 0) {
12203
+ if (((_l = config.actions) == null ? void 0 : _l.length) > 0) {
11159
12204
  console.log(
11160
12205
  "[Syntro Bootstrap] Actions in config:",
11161
12206
  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}"` : ""}`
12207
+ (a, i) => `[${i}] ${a.kind}${a.anchorId ? ` anchor="${a.anchorId.selector}"` : ""}${a.label ? ` "${a.label}"` : ""}`
11163
12208
  ).join(", ")
11164
12209
  );
11165
12210
  }
@@ -11218,10 +12263,33 @@ async function init(options) {
11218
12263
  });
11219
12264
  return { canvas, runtime: runtime3, experiments, telemetry, sessionMetrics, appLoader };
11220
12265
  }
12266
+
12267
+ // src/bootstrap.ts
12268
+ async function init(options) {
12269
+ var _a2;
12270
+ try {
12271
+ return await _initCore(options);
12272
+ } catch (err) {
12273
+ const message = err instanceof Error ? err.message : String(err);
12274
+ console.warn("[Syntrologie] SDK initialization failed:", message);
12275
+ if (typeof document !== "undefined") {
12276
+ (_a2 = document.getElementById("syntrologie-anti-flicker")) == null ? void 0 : _a2.remove();
12277
+ }
12278
+ return void 0;
12279
+ }
12280
+ }
12281
+ function emit(eventName, props = {}) {
12282
+ var _a2, _b;
12283
+ if (typeof window === "undefined") return;
12284
+ const runtime3 = (_a2 = window.SynOS) == null ? void 0 : _a2.runtime;
12285
+ if (!((_b = runtime3 == null ? void 0 : runtime3.events) == null ? void 0 : _b.publish)) return;
12286
+ runtime3.events.publish({ name: eventName, source: "custom", props });
12287
+ }
11221
12288
  var Syntro = {
11222
12289
  init,
11223
12290
  encodeToken,
11224
- decodeToken
12291
+ decodeToken,
12292
+ events: { emit }
11225
12293
  };
11226
12294
  if (typeof window !== "undefined") {
11227
12295
  window.Syntro = Syntro;
@@ -11252,8 +12320,11 @@ export {
11252
12320
  createSmartCanvasController,
11253
12321
  ShadowRootProvider,
11254
12322
  useShadowRoot,
11255
- StandardEvents,
11256
12323
  EVENT_SCHEMA_VERSION,
12324
+ normalizePostHogEvent,
12325
+ shouldNormalizeEvent,
12326
+ createPostHogNormalizer,
12327
+ StandardEvents2 as StandardEvents,
11257
12328
  CanvasEvents,
11258
12329
  NotificationToastStack,
11259
12330
  MAX_VISIBLE_TOASTS,
@@ -11284,6 +12355,7 @@ export {
11284
12355
  createSessionMetricTracker,
11285
12356
  createNoopClient,
11286
12357
  createPostHogClient,
12358
+ InterventionTracker,
11287
12359
  ExecutorRegistry,
11288
12360
  executorRegistry,
11289
12361
  getExecutor,
@@ -11302,11 +12374,11 @@ export {
11302
12374
  evaluateSync,
11303
12375
  createDecisionEngine,
11304
12376
  createEventAccumulator,
12377
+ validateEventName,
12378
+ validateProps,
11305
12379
  EventBus,
11306
12380
  createEventBus,
11307
- normalizePostHogEvent,
11308
- shouldNormalizeEvent,
11309
- createPostHogNormalizer,
12381
+ EventHistory,
11310
12382
  NavigationMonitor,
11311
12383
  StateStore,
11312
12384
  createStateStore,
@@ -11330,4 +12402,4 @@ export {
11330
12402
  encodeToken,
11331
12403
  Syntro
11332
12404
  };
11333
- //# sourceMappingURL=chunk-GWF5BTST.js.map
12405
+ //# sourceMappingURL=chunk-WX32GVSP.js.map