@syntrologie/runtime-sdk 2.8.0-canary.9 → 2.8.0-canary.90

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 (64) hide show
  1. package/CAPABILITIES.md +291 -181
  2. package/README.md +3 -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 +3 -3
  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/anchor/AnchorResolver.d.ts +1 -0
  10. package/dist/bootstrap-init.d.ts +33 -0
  11. package/dist/bootstrap-runtime.d.ts +7 -0
  12. package/dist/bootstrap-types.d.ts +101 -0
  13. package/dist/bootstrap.d.ts +19 -83
  14. package/dist/{chunk-R5DNAIRI.js → chunk-77TNZ66J.js} +4 -4
  15. package/dist/{chunk-R5DNAIRI.js.map → chunk-77TNZ66J.js.map} +2 -2
  16. package/dist/{chunk-XDYJ64IN.js → chunk-IR6UOR63.js} +4 -4
  17. package/dist/chunk-IR6UOR63.js.map +7 -0
  18. package/dist/{chunk-UURRUKSW.js → chunk-TOBT6CVT.js} +1917 -725
  19. package/dist/chunk-TOBT6CVT.js.map +7 -0
  20. package/dist/chunk-YLLWLUQX.js +241 -0
  21. package/dist/chunk-YLLWLUQX.js.map +7 -0
  22. package/dist/components/ShadowCanvasOverlay.d.ts +1 -2
  23. package/dist/components/TileIcon.d.ts +2 -2
  24. package/dist/components/emojiToIcon.d.ts +24 -0
  25. package/dist/config/schema.d.ts +147 -136
  26. package/dist/config/schema.js +2 -2
  27. package/dist/decisions/schema.d.ts +47 -47
  28. package/dist/decisions/schema.js +1 -1
  29. package/dist/events/EventBus.d.ts +27 -1
  30. package/dist/events/history.d.ts +9 -0
  31. package/dist/events/index.d.ts +3 -0
  32. package/dist/events/normalizers/posthog.d.ts +4 -50
  33. package/dist/events/types.d.ts +30 -23
  34. package/dist/events/validation.d.ts +7 -0
  35. package/dist/fetchers/experimentsFetcher.d.ts +3 -3
  36. package/dist/fetchers/mergeConfigs.d.ts +7 -7
  37. package/dist/index.d.ts +0 -2
  38. package/dist/index.js +1198 -2035
  39. package/dist/index.js.map +4 -4
  40. package/dist/overlays/runtime/overlay/overlay-runner.d.ts +4 -0
  41. package/dist/overlays/runtime/overlay/overlay-state.d.ts +21 -0
  42. package/dist/overlays/types.d.ts +3 -1
  43. package/dist/react.js +6 -4
  44. package/dist/react.js.map +2 -2
  45. package/dist/smart-canvas.esm.js +104 -109
  46. package/dist/smart-canvas.esm.js.map +4 -4
  47. package/dist/smart-canvas.js +6277 -5463
  48. package/dist/smart-canvas.js.map +4 -4
  49. package/dist/smart-canvas.min.js +104 -109
  50. package/dist/smart-canvas.min.js.map +4 -4
  51. package/dist/telemetry/InterventionTracker.d.ts +23 -0
  52. package/dist/telemetry/adapters/posthog.d.ts +30 -4
  53. package/dist/telemetry/index.d.ts +1 -0
  54. package/dist/test/setup.d.ts +1 -0
  55. package/dist/token.d.ts +2 -0
  56. package/dist/version.d.ts +1 -1
  57. package/package.json +23 -28
  58. package/schema/canvas-config.schema.json +2369 -11366
  59. package/scripts/syntroReactPlugin.mjs +3 -0
  60. package/scripts/validate-config.mjs +42 -0
  61. package/dist/chunk-BU4Z6PD7.js +0 -218
  62. package/dist/chunk-BU4Z6PD7.js.map +0 -7
  63. package/dist/chunk-UURRUKSW.js.map +0 -7
  64. 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],
@@ -1642,8 +1681,14 @@ function showHighlight(anchorEl, overlayRoot, opts) {
1642
1681
  scrim.style.pointerEvents = "none";
1643
1682
  scrim.style.opacity = "0";
1644
1683
  setTimeout(() => {
1645
- scrim.remove();
1646
- ring.remove();
1684
+ try {
1685
+ scrim.remove();
1686
+ } catch {
1687
+ }
1688
+ try {
1689
+ ring.remove();
1690
+ } catch {
1691
+ }
1647
1692
  }, 220);
1648
1693
  }
1649
1694
  };
@@ -1671,7 +1716,16 @@ var ALLOWED_TAGS2 = /* @__PURE__ */ new Set([
1671
1716
  "sup",
1672
1717
  "sub",
1673
1718
  "a",
1674
- "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"
1675
1729
  ]);
1676
1730
  function sanitizeHtml2(html) {
1677
1731
  var _a2;
@@ -1880,8 +1934,14 @@ var executeModal = async (action, context) => {
1880
1934
  modal2.style.transform = "translate(-50%, -50%) scale(0.95)";
1881
1935
  scrimEl.style.opacity = "0";
1882
1936
  setTimeout(() => {
1883
- modal2.remove();
1884
- scrimEl.remove();
1937
+ try {
1938
+ modal2.remove();
1939
+ } catch {
1940
+ }
1941
+ try {
1942
+ scrimEl.remove();
1943
+ } catch {
1944
+ }
1885
1945
  }, 200);
1886
1946
  context.publishEvent("action.modal_dismissed", {
1887
1947
  actionClicked
@@ -1928,10 +1988,12 @@ function getAnchorReference(anchorEl) {
1928
1988
  }
1929
1989
  function showTooltip(anchorEl, overlayRoot, opts) {
1930
1990
  var _a2;
1931
- const rect = anchorEl.getBoundingClientRect();
1932
- const isLargeElement = rect.width > window.innerWidth * 0.8 || rect.height > window.innerHeight * 0.8;
1933
- if (!isLargeElement) {
1934
- 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
+ }
1935
1997
  }
1936
1998
  const div = document.createElement("div");
1937
1999
  div.className = "syntro-tooltip";
@@ -2141,7 +2203,12 @@ function showTooltip(anchorEl, overlayRoot, opts) {
2141
2203
  }
2142
2204
  div.style.pointerEvents = "none";
2143
2205
  div.style.opacity = "0";
2144
- setTimeout(() => div.remove(), 200);
2206
+ setTimeout(() => {
2207
+ try {
2208
+ div.remove();
2209
+ } catch {
2210
+ }
2211
+ }, 200);
2145
2212
  }
2146
2213
  };
2147
2214
  return handle;
@@ -2174,7 +2241,7 @@ function WorkflowTracker({ workflows, expanded, onStepClick, onDismiss }) {
2174
2241
  }
2175
2242
 
2176
2243
  // ../adaptives/adaptive-overlays/dist/WorkflowWidget.js
2177
- function showWorkflowToast(container, notification) {
2244
+ function showWorkflowToast(notification) {
2178
2245
  const toast = document.createElement("div");
2179
2246
  toast.setAttribute("data-testid", "workflow-toast");
2180
2247
  toast.className = "se-fixed se-bottom-4 se-right-4 se-z-50";
@@ -2208,7 +2275,7 @@ function showWorkflowToast(container, notification) {
2208
2275
  bodyEl.textContent = notification.body;
2209
2276
  toast.appendChild(bodyEl);
2210
2277
  }
2211
- container.appendChild(toast);
2278
+ document.body.appendChild(toast);
2212
2279
  let removeTimer;
2213
2280
  const fadeTimer = setTimeout(() => {
2214
2281
  toast.style.opacity = "0";
@@ -2226,7 +2293,7 @@ function extractWorkflowsFromActive(activeActions) {
2226
2293
  const workflows = /* @__PURE__ */ new Map();
2227
2294
  for (const entry of activeActions) {
2228
2295
  const action = entry.action;
2229
- if (action.kind === "core:tour" && action.workflow && action.tourId) {
2296
+ if (action.kind === "overlays:tour" && action.workflow && action.tourId) {
2230
2297
  const meta = action.workflow;
2231
2298
  const rawSteps = action.steps || [];
2232
2299
  const steps = rawSteps.map((s) => {
@@ -2307,10 +2374,10 @@ function WorkflowWidgetInner({ runtime: runtime3 }) {
2307
2374
  return newEntries.length > 0 ? [...prev, ...newEntries] : prev;
2308
2375
  });
2309
2376
  for (const [tourId, { meta }] of tourWorkflows) {
2310
- 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]) {
2311
2378
  notifiedRef.current.add(tourId);
2312
2379
  (_c = stateNs == null ? void 0 : stateNs.set) == null ? void 0 : _c.call(stateNs, "notified", [...notifiedRef.current]);
2313
- const cleanup = showWorkflowToast(containerRef.current, meta.notification);
2380
+ const cleanup = showWorkflowToast(meta.notification);
2314
2381
  toastCleanupsRef.current.push(cleanup);
2315
2382
  }
2316
2383
  }
@@ -2343,8 +2410,8 @@ function WorkflowWidgetInner({ runtime: runtime3 }) {
2343
2410
  notifiedRef.current.add(tourId);
2344
2411
  (_c = stateNs == null ? void 0 : stateNs.set) == null ? void 0 : _c.call(stateNs, "notified", [...notifiedRef.current]);
2345
2412
  const workflow = currentWorkflows.get(tourId);
2346
- if ((workflow == null ? void 0 : workflow.meta.notification) && containerRef.current) {
2347
- const cleanup = showWorkflowToast(containerRef.current, workflow.meta.notification);
2413
+ if (workflow == null ? void 0 : workflow.meta.notification) {
2414
+ const cleanup = showWorkflowToast(workflow.meta.notification);
2348
2415
  toastCleanupsRef.current.push(cleanup);
2349
2416
  }
2350
2417
  }
@@ -2488,6 +2555,7 @@ var executeHighlight = async (action, context) => {
2488
2555
  cleanup: () => {
2489
2556
  handle.destroy();
2490
2557
  anchorEl.removeAttribute("data-syntro-highlight");
2558
+ anchorEl.removeAttribute("data-syntro-highlight-dismissed");
2491
2559
  }
2492
2560
  };
2493
2561
  };
@@ -2577,6 +2645,8 @@ var executePulse = async (action, context) => {
2577
2645
  return {
2578
2646
  cleanup: () => {
2579
2647
  clearTimeout(timeoutId);
2648
+ if (!anchorEl.isConnected)
2649
+ return;
2580
2650
  anchorEl.style.animation = originalAnimation;
2581
2651
  anchorEl.removeAttribute("data-syntro-pulse");
2582
2652
  }
@@ -2646,7 +2716,12 @@ var executeBadge = async (action, context) => {
2646
2716
  });
2647
2717
  return {
2648
2718
  cleanup: () => {
2649
- badge2.remove();
2719
+ try {
2720
+ badge2.remove();
2721
+ } catch {
2722
+ }
2723
+ if (!anchorEl.isConnected)
2724
+ return;
2650
2725
  if (originalPosition !== void 0) {
2651
2726
  anchorEl.style.position = originalPosition;
2652
2727
  }
@@ -2741,7 +2816,7 @@ var executors2 = [
2741
2816
  { kind: "overlays:badge", executor: executeBadge },
2742
2817
  { kind: "overlays:tooltip", executor: executeTooltip },
2743
2818
  { kind: "overlays:modal", executor: executeModal },
2744
- { kind: "core:tour", executor: executeTour },
2819
+ { kind: "overlays:tour", executor: executeTour },
2745
2820
  { kind: "overlays:celebrate", executor: executeCelebrate }
2746
2821
  ];
2747
2822
  var runtime2 = {
@@ -2861,7 +2936,15 @@ var CORE_ACTION_KINDS = /* @__PURE__ */ new Set([
2861
2936
  "core:wait",
2862
2937
  "core:sequence",
2863
2938
  "core:parallel",
2864
- "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"
2865
2948
  ]);
2866
2949
  var NAMESPACE_TO_APP_ID = {
2867
2950
  faq: "adaptive-faq",
@@ -3015,6 +3098,9 @@ function createAppLoader(options) {
3015
3098
  }
3016
3099
  }
3017
3100
  }
3101
+ for (const bundled of BUNDLED_APP_IDS) {
3102
+ appIds.delete(bundled);
3103
+ }
3018
3104
  return Array.from(appIds);
3019
3105
  }
3020
3106
  async function loadAppsForConfig(config) {
@@ -3370,14 +3456,20 @@ function getAntiFlickerSnippet(config = {}) {
3370
3456
  }
3371
3457
 
3372
3458
  // src/version.ts
3373
- var SDK_VERSION = "2.8.0-canary.9";
3459
+ var SDK_VERSION = "2.8.0-canary.90";
3374
3460
 
3375
3461
  // src/types.ts
3376
3462
  var SDK_SCHEMA_VERSION = "2.0";
3377
3463
 
3378
3464
  // src/fetchers/mergeConfigs.ts
3379
- function resolveVariantConfigs(client, keys, _strategy = "first-match") {
3380
- var _a2, _b, _c, _d;
3465
+ function resolveVariantConfigs(client, keys, strategy = "merge") {
3466
+ if (strategy === "first-match") {
3467
+ return resolveFirstMatch(client, keys);
3468
+ }
3469
+ return resolveMerge(client, keys);
3470
+ }
3471
+ function resolveFirstMatch(client, keys) {
3472
+ var _a2;
3381
3473
  for (const key of keys) {
3382
3474
  const value = (_a2 = client.getFeatureValue) == null ? void 0 : _a2.call(client, key, null);
3383
3475
  if (!value || typeof value !== "object") continue;
@@ -3385,20 +3477,60 @@ function resolveVariantConfigs(client, keys, _strategy = "first-match") {
3385
3477
  const hasTiles = variant.tiles && variant.tiles.length > 0;
3386
3478
  const hasActions = variant.actions && variant.actions.length > 0;
3387
3479
  if (hasTiles || hasActions) {
3388
- return {
3389
- tiles: (_b = variant.tiles) != null ? _b : [],
3390
- actions: (_c = variant.actions) != null ? _c : [],
3391
- fetchedAt: (_d = variant.fetchedAt) != null ? _d : (/* @__PURE__ */ new Date()).toISOString(),
3392
- ...variant.schemaVersion && { schemaVersion: variant.schemaVersion },
3393
- ...variant.configVersion && { configVersion: variant.configVersion },
3394
- ...variant.canvasTitle && { canvasTitle: variant.canvasTitle },
3395
- ...variant.theme && { theme: variant.theme },
3396
- ...variant.launcher && { launcher: variant.launcher }
3397
- };
3480
+ return buildConfig([variant]);
3398
3481
  }
3399
3482
  }
3400
3483
  return null;
3401
3484
  }
3485
+ function resolveMerge(client, keys) {
3486
+ var _a2;
3487
+ const variants = [];
3488
+ for (const key of keys) {
3489
+ const value = (_a2 = client.getFeatureValue) == null ? void 0 : _a2.call(client, key, null);
3490
+ if (!value || typeof value !== "object") continue;
3491
+ const variant = value;
3492
+ const hasTiles = variant.tiles && variant.tiles.length > 0;
3493
+ const hasActions = variant.actions && variant.actions.length > 0;
3494
+ if (hasTiles || hasActions) {
3495
+ variants.push(variant);
3496
+ }
3497
+ }
3498
+ if (variants.length === 0) return null;
3499
+ return buildConfig(variants);
3500
+ }
3501
+ function buildConfig(variants) {
3502
+ const allTiles = [];
3503
+ const allActions = [];
3504
+ let fetchedAt;
3505
+ let schemaVersion;
3506
+ let configVersion;
3507
+ let canvasTitle;
3508
+ let theme;
3509
+ let launcher;
3510
+ let meta;
3511
+ for (const variant of variants) {
3512
+ if (variant.tiles) allTiles.push(...variant.tiles);
3513
+ if (variant.actions) allActions.push(...variant.actions);
3514
+ fetchedAt != null ? fetchedAt : fetchedAt = variant.fetchedAt;
3515
+ schemaVersion != null ? schemaVersion : schemaVersion = variant.schemaVersion;
3516
+ configVersion != null ? configVersion : configVersion = variant.configVersion;
3517
+ canvasTitle != null ? canvasTitle : canvasTitle = variant.canvasTitle;
3518
+ theme != null ? theme : theme = variant.theme;
3519
+ launcher != null ? launcher : launcher = variant.launcher;
3520
+ meta != null ? meta : meta = variant.meta;
3521
+ }
3522
+ return {
3523
+ tiles: allTiles,
3524
+ actions: allActions,
3525
+ fetchedAt: fetchedAt != null ? fetchedAt : (/* @__PURE__ */ new Date()).toISOString(),
3526
+ ...schemaVersion && { schemaVersion },
3527
+ ...configVersion && { configVersion },
3528
+ ...canvasTitle && { canvasTitle },
3529
+ ...theme && { theme },
3530
+ ...launcher && { launcher },
3531
+ ...meta && { meta }
3532
+ };
3533
+ }
3402
3534
 
3403
3535
  // src/logger.ts
3404
3536
  var debugEnabled = false;
@@ -3521,12 +3653,14 @@ function getCachedConfig(sdkVersion) {
3521
3653
  var resolveConfigUri = ({
3522
3654
  configUri,
3523
3655
  experiments,
3524
- featureKey = "smart-canvas-config-uri"
3656
+ featureKey
3525
3657
  }) => {
3526
3658
  var _a2;
3527
3659
  if (configUri) return configUri;
3528
- const fromFeature = (_a2 = experiments == null ? void 0 : experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, featureKey, null);
3529
- if (fromFeature) return fromFeature;
3660
+ if (experiments && featureKey) {
3661
+ const fromFeature = (_a2 = experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, featureKey, null);
3662
+ if (fromFeature) return fromFeature;
3663
+ }
3530
3664
  return void 0;
3531
3665
  };
3532
3666
  function getVariantFlagKeys(experiments, manifestKey, variantFlagPrefix) {
@@ -3552,7 +3686,7 @@ var createCanvasConfigFetcher = ({
3552
3686
  experiments,
3553
3687
  featureKey,
3554
3688
  credentials,
3555
- configFeatureKey = "smart-canvas-config",
3689
+ configFeatureKey,
3556
3690
  manifestKey,
3557
3691
  variantFlagPrefix,
3558
3692
  sdkVersion
@@ -3653,7 +3787,8 @@ function registerFromTriggerWhen(triggerWhen, accumulator) {
3653
3787
  if (cond.type === "event_count" && cond.key) {
3654
3788
  const counter = cond.counter;
3655
3789
  const predicate = counter ? buildPredicate(counter) : () => true;
3656
- accumulator.register(cond.key, predicate);
3790
+ const needsTimestamps = typeof cond.withinMs === "number";
3791
+ accumulator.register(cond.key, predicate, needsTimestamps);
3657
3792
  }
3658
3793
  }
3659
3794
  }
@@ -3746,127 +3881,737 @@ function useShadowRoot() {
3746
3881
  return ctx;
3747
3882
  }
3748
3883
 
3749
- // src/events/types.ts
3884
+ // ../event-processor/dist/types.js
3885
+ var EVENT_SCHEMA_VERSION = "1.0.0";
3750
3886
  var StandardEvents = {
3751
- // UI events (from PostHog autocapture)
3752
3887
  UI_CLICK: "ui.click",
3753
3888
  UI_SCROLL: "ui.scroll",
3754
3889
  UI_INPUT: "ui.input",
3755
3890
  UI_CHANGE: "ui.change",
3756
3891
  UI_SUBMIT: "ui.submit",
3757
- // Navigation events
3758
3892
  NAV_PAGE_VIEW: "nav.page_view",
3759
3893
  NAV_PAGE_LEAVE: "nav.page_leave",
3760
- // Canvas events
3761
- CANVAS_OPENED: "canvas.opened",
3762
- CANVAS_CLOSED: "canvas.closed",
3763
- TILE_VIEWED: "tile.viewed",
3764
- TILE_EXPANDED: "tile.expanded",
3765
- TILE_COLLAPSED: "tile.collapsed",
3766
- TILE_ACTION: "tile.action",
3767
- // Overlay/tour events
3768
- OVERLAY_STARTED: "overlay.started",
3769
- OVERLAY_COMPLETED: "overlay.completed",
3770
- OVERLAY_DISMISSED: "overlay.dismissed",
3771
- OVERLAY_STEP_VIEWED: "overlay.step_viewed",
3772
- // Derived behavioral signals (Phase 3)
3773
- BEHAVIOR_RAGE_CLICK: "behavior.rage_click",
3774
- BEHAVIOR_HESITATION: "behavior.hesitation",
3775
- BEHAVIOR_CONFUSION: "behavior.confusion",
3776
- // Action events
3777
- ACTION_APPLIED: "action.applied",
3778
- ACTION_REVERTED: "action.reverted",
3779
- ACTION_FAILED: "action.failed",
3780
- ACTION_CTA_CLICKED: "action.cta_clicked",
3781
- // Notification events
3782
- NOTIFICATION_SHOWN: "notification.shown",
3783
- NOTIFICATION_CLICKED: "notification.clicked",
3784
- NOTIFICATION_DISMISSED: "notification.dismissed",
3785
- NOTIFICATION_DEEP_LINK: "notification.deep_link",
3786
- // Surface events
3787
- SURFACE_MOUNTED: "surface.mounted",
3788
- SURFACE_UNMOUNTED: "surface.unmounted"
3894
+ UI_HESITATE: "ui.hesitate",
3895
+ UI_RAGE_CLICK: "ui.rage_click",
3896
+ UI_SCROLL_THRASH: "ui.scroll_thrash",
3897
+ UI_FOCUS_BOUNCE: "ui.focus_bounce",
3898
+ UI_IDLE: "ui.idle",
3899
+ UI_HOVER: "ui.hover"
3900
+ };
3901
+ var RRWebSource = {
3902
+ Mutation: 0,
3903
+ MouseMove: 1,
3904
+ MouseInteraction: 2,
3905
+ Scroll: 3,
3906
+ ViewportResize: 4,
3907
+ Input: 5,
3908
+ TouchMove: 6,
3909
+ MediaInteraction: 7,
3910
+ Drag: 12
3911
+ };
3912
+ var RRWebMouseInteraction = {
3913
+ MouseUp: 0,
3914
+ MouseDown: 1,
3915
+ Click: 2,
3916
+ ContextMenu: 3,
3917
+ DblClick: 4,
3918
+ Focus: 5,
3919
+ Blur: 6,
3920
+ TouchStart: 7,
3921
+ TouchEnd: 9
3922
+ };
3923
+ var DEFAULT_DETECTOR_CONFIG = {
3924
+ hesitationMs: 3e3,
3925
+ hesitationRadiusPx: 10,
3926
+ rageClickCount: 3,
3927
+ rageClickWindowMs: 1e3,
3928
+ rageClickRadiusPx: 30,
3929
+ scrollThrashReversals: 3,
3930
+ scrollThrashWindowMs: 2e3,
3931
+ focusBounceMaxInputs: 0,
3932
+ idleMs: 5e3,
3933
+ hoverSampleMs: 100
3789
3934
  };
3790
- var EVENT_SCHEMA_VERSION = "1.0.0";
3791
3935
 
3792
- // src/events/normalizers/canvas.ts
3793
- function createCanvasEvent(name, props) {
3794
- return {
3795
- ts: Date.now(),
3796
- name,
3797
- source: "canvas",
3798
- props,
3799
- schemaVersion: EVENT_SCHEMA_VERSION
3800
- };
3801
- }
3802
- function canvasOpened(surface) {
3803
- return createCanvasEvent(StandardEvents.CANVAS_OPENED, { surface });
3804
- }
3805
- function canvasClosed(surface) {
3806
- return createCanvasEvent(StandardEvents.CANVAS_CLOSED, { surface });
3807
- }
3808
- function tileViewed(tileId, surface) {
3809
- return createCanvasEvent(StandardEvents.TILE_VIEWED, { tileId, surface });
3810
- }
3811
- function tileExpanded(tileId, surface) {
3812
- return createCanvasEvent(StandardEvents.TILE_EXPANDED, { tileId, surface });
3813
- }
3814
- function tileCollapsed(tileId, surface) {
3815
- return createCanvasEvent(StandardEvents.TILE_COLLAPSED, { tileId, surface });
3936
+ // ../event-processor/dist/normalizers/posthog.js
3937
+ var POSTHOG_EVENT_MAP = {
3938
+ // NOTE: $autocapture is intentionally NOT in this map.
3939
+ // It's handled below in getEventName() with $event_type refinement
3940
+ // so that change/submit events aren't all mapped to ui.click.
3941
+ $click: StandardEvents.UI_CLICK,
3942
+ $scroll: StandardEvents.UI_SCROLL,
3943
+ $input: StandardEvents.UI_INPUT,
3944
+ $change: StandardEvents.UI_CHANGE,
3945
+ $submit: StandardEvents.UI_SUBMIT,
3946
+ // Navigation events
3947
+ $pageview: StandardEvents.NAV_PAGE_VIEW,
3948
+ $pageleave: StandardEvents.NAV_PAGE_LEAVE,
3949
+ // Session events
3950
+ $session_start: "session.start",
3951
+ // Identify events
3952
+ $identify: "user.identify"
3953
+ };
3954
+ function getEventName(phEvent) {
3955
+ var _a2, _b;
3956
+ const eventName = phEvent.event;
3957
+ if (typeof eventName !== "string") {
3958
+ return "posthog.unknown";
3959
+ }
3960
+ if (POSTHOG_EVENT_MAP[eventName]) {
3961
+ return POSTHOG_EVENT_MAP[eventName];
3962
+ }
3963
+ if (eventName === "$autocapture") {
3964
+ const tagName = (_a2 = phEvent.properties) == null ? void 0 : _a2.$tag_name;
3965
+ const eventType = (_b = phEvent.properties) == null ? void 0 : _b.$event_type;
3966
+ if (eventType === "submit")
3967
+ return StandardEvents.UI_SUBMIT;
3968
+ if (eventType === "change")
3969
+ return StandardEvents.UI_CHANGE;
3970
+ if (tagName === "input" || tagName === "textarea")
3971
+ return StandardEvents.UI_INPUT;
3972
+ return StandardEvents.UI_CLICK;
3973
+ }
3974
+ if (!eventName.startsWith("$")) {
3975
+ return `posthog.${eventName}`;
3976
+ }
3977
+ return eventName.replace("$", "posthog.");
3816
3978
  }
3817
- function tileAction(tileId, actionId, surface) {
3818
- return createCanvasEvent(StandardEvents.TILE_ACTION, {
3819
- tileId,
3820
- actionId,
3821
- surface
3979
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["a", "button", "input", "select", "textarea"]);
3980
+ function parseElementsChain(chain) {
3981
+ if (!chain)
3982
+ return void 0;
3983
+ return chain.split(";").map((segment) => {
3984
+ const el = {};
3985
+ const colonIdx = segment.indexOf(":");
3986
+ const tagPart = colonIdx >= 0 ? segment.slice(0, colonIdx) : segment;
3987
+ const attrPart = colonIdx >= 0 ? segment.slice(colonIdx + 1) : "";
3988
+ const dotIdx = tagPart.indexOf(".");
3989
+ if (dotIdx >= 0) {
3990
+ el.tag_name = tagPart.slice(0, dotIdx);
3991
+ el.attr__class = tagPart.slice(dotIdx + 1).replace(/\./g, " ");
3992
+ } else {
3993
+ el.tag_name = tagPart;
3994
+ }
3995
+ const attrRegex = /([\w$]+)="([^"]*)"/g;
3996
+ let match;
3997
+ while ((match = attrRegex.exec(attrPart)) !== null) {
3998
+ const [, key, value] = match;
3999
+ if (key === "nth-child" || key === "nth-of-type")
4000
+ continue;
4001
+ el[key] = value;
4002
+ }
4003
+ if (el.text) {
4004
+ el.$el_text = el.text;
4005
+ delete el.text;
4006
+ }
4007
+ return el;
3822
4008
  });
3823
4009
  }
3824
- function overlayStarted(recipeId, recipeName) {
3825
- return createCanvasEvent(StandardEvents.OVERLAY_STARTED, {
3826
- recipeId,
3827
- recipeName
3828
- });
4010
+ function resolveInteractiveTag(elements, directTag) {
4011
+ if (directTag && INTERACTIVE_TAGS.has(directTag))
4012
+ return directTag;
4013
+ if (!elements)
4014
+ return directTag;
4015
+ for (const el of elements) {
4016
+ const tag2 = el.tag_name;
4017
+ if (tag2 && INTERACTIVE_TAGS.has(tag2))
4018
+ return tag2;
4019
+ }
4020
+ return directTag;
3829
4021
  }
3830
- function overlayCompleted(recipeId, recipeName) {
3831
- return createCanvasEvent(StandardEvents.OVERLAY_COMPLETED, {
3832
- recipeId,
3833
- recipeName
3834
- });
4022
+ function extractProps(phEvent) {
4023
+ var _a2, _b, _c;
4024
+ const props = {};
4025
+ const phProps = phEvent.properties || {};
4026
+ const elements = (_a2 = phProps.$elements) != null ? _a2 : typeof phProps.$elements_chain === "string" ? parseElementsChain(phProps.$elements_chain) : void 0;
4027
+ const directTag = (_c = phProps.$tag_name) != null ? _c : (_b = elements == null ? void 0 : elements[0]) == null ? void 0 : _b.tag_name;
4028
+ const isClickEvent = phEvent.event === "$autocapture" || phEvent.event === "$click";
4029
+ props.tagName = isClickEvent ? resolveInteractiveTag(elements, directTag) : directTag;
4030
+ if (phProps.$el_text)
4031
+ props.elementText = phProps.$el_text;
4032
+ if (elements)
4033
+ props.elements = elements;
4034
+ if (isClickEvent && !elements) {
4035
+ 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}`);
4036
+ }
4037
+ if (phProps.$current_url)
4038
+ props.url = phProps.$current_url;
4039
+ if (phProps.$pathname)
4040
+ props.pathname = phProps.$pathname;
4041
+ if (phProps.$host)
4042
+ props.host = phProps.$host;
4043
+ if (phProps.$viewport_width)
4044
+ props.viewportWidth = phProps.$viewport_width;
4045
+ if (phProps.$viewport_height)
4046
+ props.viewportHeight = phProps.$viewport_height;
4047
+ if (phProps.$session_id)
4048
+ props.sessionId = phProps.$session_id;
4049
+ if (phProps.$scroll_depth)
4050
+ props.scrollDepth = phProps.$scroll_depth;
4051
+ if (phProps.$scroll_percentage)
4052
+ props.scrollPercentage = phProps.$scroll_percentage;
4053
+ props.originalEvent = phEvent.event;
4054
+ return props;
3835
4055
  }
3836
- function overlayDismissed(recipeId, recipeName, stepIndex) {
3837
- return createCanvasEvent(StandardEvents.OVERLAY_DISMISSED, {
3838
- recipeId,
3839
- recipeName,
3840
- stepIndex
3841
- });
4056
+ function normalizePostHogEvent(phEvent) {
4057
+ let ts;
4058
+ if (typeof phEvent.timestamp === "number") {
4059
+ ts = phEvent.timestamp;
4060
+ } else if (typeof phEvent.timestamp === "string") {
4061
+ ts = new Date(phEvent.timestamp).getTime();
4062
+ } else {
4063
+ ts = Date.now();
4064
+ }
4065
+ return {
4066
+ ts,
4067
+ name: getEventName(phEvent),
4068
+ source: "posthog",
4069
+ props: extractProps(phEvent),
4070
+ schemaVersion: EVENT_SCHEMA_VERSION
4071
+ };
3842
4072
  }
3843
- function overlayStepViewed(recipeId, stepIndex, stepTitle) {
3844
- return createCanvasEvent(StandardEvents.OVERLAY_STEP_VIEWED, {
3845
- recipeId,
3846
- stepIndex,
3847
- stepTitle
3848
- });
4073
+ function shouldNormalizeEvent(phEvent) {
4074
+ const eventName = phEvent.event;
4075
+ if (typeof eventName !== "string")
4076
+ return false;
4077
+ const skipEvents = [
4078
+ "$feature_flag_called",
4079
+ "$feature_flags",
4080
+ "$groups",
4081
+ "$groupidentify",
4082
+ "$set",
4083
+ "$set_once",
4084
+ "$unset",
4085
+ "$create_alias",
4086
+ "$capture_metrics",
4087
+ "$performance_event",
4088
+ "$web_vitals",
4089
+ "$exception",
4090
+ "$dead_click",
4091
+ "$heatmap"
4092
+ ];
4093
+ if (skipEvents.includes(eventName)) {
4094
+ return false;
4095
+ }
4096
+ return true;
3849
4097
  }
3850
- function customCanvasEvent(name, props) {
3851
- const eventName = name.startsWith("canvas.") ? name : `canvas.${name}`;
3852
- return createCanvasEvent(eventName, props);
4098
+ function createPostHogNormalizer(publishFn) {
4099
+ return (eventName, properties) => {
4100
+ if (typeof eventName !== "string")
4101
+ return;
4102
+ const phEvent = {
4103
+ event: eventName,
4104
+ properties,
4105
+ timestamp: Date.now()
4106
+ };
4107
+ if (shouldNormalizeEvent(phEvent)) {
4108
+ const normalizedEvent = normalizePostHogEvent(phEvent);
4109
+ publishFn(normalizedEvent);
4110
+ }
4111
+ };
3853
4112
  }
3854
- var CanvasEvents = {
3855
- canvasOpened,
3856
- canvasClosed,
3857
- tileViewed,
3858
- tileExpanded,
3859
- tileCollapsed,
3860
- tileAction,
3861
- overlayStarted,
3862
- overlayCompleted,
4113
+
4114
+ // ../event-processor/dist/detectors/focus-bounce.js
4115
+ var FocusBounceDetector = class {
4116
+ constructor(config, emit2) {
4117
+ this.config = config;
4118
+ this.emit = emit2;
4119
+ this.focused = /* @__PURE__ */ new Map();
4120
+ }
4121
+ ingest(raw) {
4122
+ var _a2, _b;
4123
+ if (raw.type !== 3)
4124
+ return;
4125
+ const ts = raw.timestamp;
4126
+ if (raw.data.source === RRWebSource.MouseInteraction) {
4127
+ const id = (_a2 = raw.data.id) != null ? _a2 : 0;
4128
+ if (raw.data.type === RRWebMouseInteraction.Focus) {
4129
+ this.focused.set(id, { id, focusTs: ts, inputCount: 0 });
4130
+ } else if (raw.data.type === RRWebMouseInteraction.Blur) {
4131
+ const entry = this.focused.get(id);
4132
+ if (entry && entry.inputCount <= this.config.focusBounceMaxInputs) {
4133
+ this.emit({
4134
+ ts,
4135
+ name: StandardEvents.UI_FOCUS_BOUNCE,
4136
+ source: "rrweb",
4137
+ schemaVersion: EVENT_SCHEMA_VERSION,
4138
+ props: {
4139
+ elementId: id,
4140
+ duration_ms: ts - entry.focusTs
4141
+ }
4142
+ });
4143
+ }
4144
+ this.focused.delete(id);
4145
+ }
4146
+ } else if (raw.data.source === RRWebSource.Input) {
4147
+ const id = (_b = raw.data.id) != null ? _b : 0;
4148
+ const entry = this.focused.get(id);
4149
+ if (entry) {
4150
+ entry.inputCount++;
4151
+ }
4152
+ }
4153
+ }
4154
+ };
4155
+
4156
+ // ../event-processor/dist/detectors/hesitation.js
4157
+ var HesitationDetector = class {
4158
+ constructor(config, emit2) {
4159
+ this.config = config;
4160
+ this.emit = emit2;
4161
+ this.anchorX = 0;
4162
+ this.anchorY = 0;
4163
+ this.anchorTs = 0;
4164
+ this.emitted = false;
4165
+ this.hasPosition = false;
4166
+ }
4167
+ ingest(raw) {
4168
+ var _a2;
4169
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4170
+ return;
4171
+ const positions = raw.data.positions;
4172
+ if (!positions || positions.length === 0)
4173
+ return;
4174
+ const last = positions[positions.length - 1];
4175
+ const ts = raw.timestamp + ((_a2 = last.timeOffset) != null ? _a2 : 0);
4176
+ if (!this.hasPosition) {
4177
+ this.anchorX = last.x;
4178
+ this.anchorY = last.y;
4179
+ this.anchorTs = ts;
4180
+ this.hasPosition = true;
4181
+ this.emitted = false;
4182
+ return;
4183
+ }
4184
+ const dx = last.x - this.anchorX;
4185
+ const dy = last.y - this.anchorY;
4186
+ const dist = Math.sqrt(dx * dx + dy * dy);
4187
+ if (dist > this.config.hesitationRadiusPx) {
4188
+ this.anchorX = last.x;
4189
+ this.anchorY = last.y;
4190
+ this.anchorTs = ts;
4191
+ this.emitted = false;
4192
+ }
4193
+ }
4194
+ tick(now) {
4195
+ if (!this.hasPosition || this.emitted)
4196
+ return;
4197
+ const elapsed = now - this.anchorTs;
4198
+ if (elapsed >= this.config.hesitationMs) {
4199
+ this.emit({
4200
+ ts: now,
4201
+ name: StandardEvents.UI_HESITATE,
4202
+ source: "rrweb",
4203
+ schemaVersion: EVENT_SCHEMA_VERSION,
4204
+ props: {
4205
+ x: this.anchorX,
4206
+ y: this.anchorY,
4207
+ duration_ms: elapsed
4208
+ }
4209
+ });
4210
+ this.emitted = true;
4211
+ }
4212
+ }
4213
+ };
4214
+
4215
+ // ../event-processor/dist/detectors/hover.js
4216
+ var HoverTracker = class {
4217
+ constructor(config, emit2, elementResolver) {
4218
+ this.config = config;
4219
+ this.emit = emit2;
4220
+ this.elementResolver = elementResolver;
4221
+ this.currentElement = null;
4222
+ this.hoverStartTs = null;
4223
+ this.lastX = 0;
4224
+ this.lastY = 0;
4225
+ this.lastSampleTs = -Infinity;
4226
+ }
4227
+ ingest(raw) {
4228
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4229
+ return;
4230
+ const positions = raw.data.positions;
4231
+ if (!positions || positions.length === 0)
4232
+ return;
4233
+ const last = positions[positions.length - 1];
4234
+ this.lastX = last.x;
4235
+ this.lastY = last.y;
4236
+ }
4237
+ tick(now) {
4238
+ if (!this.elementResolver)
4239
+ return;
4240
+ if (now - this.lastSampleTs < this.config.hoverSampleMs)
4241
+ return;
4242
+ this.lastSampleTs = now;
4243
+ const newElement = this.elementResolver(this.lastX, this.lastY);
4244
+ const newKey = newElement ? elementKey(newElement) : null;
4245
+ const currentKey = this.currentElement ? elementKey(this.currentElement) : null;
4246
+ if (newKey !== currentKey) {
4247
+ if (this.currentElement && this.hoverStartTs !== null) {
4248
+ this.emit({
4249
+ ts: now,
4250
+ name: StandardEvents.UI_HOVER,
4251
+ source: "rrweb",
4252
+ schemaVersion: EVENT_SCHEMA_VERSION,
4253
+ props: {
4254
+ x: this.lastX,
4255
+ y: this.lastY,
4256
+ duration_ms: now - this.hoverStartTs,
4257
+ element: this.currentElement
4258
+ }
4259
+ });
4260
+ }
4261
+ this.currentElement = newElement;
4262
+ this.hoverStartTs = now;
4263
+ }
4264
+ }
4265
+ };
4266
+ function elementKey(el) {
4267
+ var _a2, _b, _c;
4268
+ return `${(_a2 = el.tag_name) != null ? _a2 : ""}|${(_b = el.attr__id) != null ? _b : ""}|${((_c = el.classes) != null ? _c : []).join(",")}`;
4269
+ }
4270
+
4271
+ // ../event-processor/dist/detectors/idle.js
4272
+ var IdleDetector = class {
4273
+ constructor(config, emit2) {
4274
+ this.config = config;
4275
+ this.emit = emit2;
4276
+ this.lastActivityTs = null;
4277
+ this.emitted = false;
4278
+ }
4279
+ ingest(raw) {
4280
+ if (raw.type !== 3)
4281
+ return;
4282
+ const src = raw.data.source;
4283
+ if (src === RRWebSource.MouseMove || src === RRWebSource.MouseInteraction || src === RRWebSource.Scroll) {
4284
+ this.lastActivityTs = raw.timestamp;
4285
+ this.emitted = false;
4286
+ }
4287
+ }
4288
+ tick(now) {
4289
+ if (this.lastActivityTs === null || this.emitted)
4290
+ return;
4291
+ if (now - this.lastActivityTs >= this.config.idleMs) {
4292
+ this.emit({
4293
+ ts: now,
4294
+ name: StandardEvents.UI_IDLE,
4295
+ source: "rrweb",
4296
+ schemaVersion: EVENT_SCHEMA_VERSION,
4297
+ props: {
4298
+ idle_ms: now - this.lastActivityTs
4299
+ }
4300
+ });
4301
+ this.emitted = true;
4302
+ }
4303
+ }
4304
+ };
4305
+
4306
+ // ../event-processor/dist/detectors/rage-click.js
4307
+ var RageClickDetector = class {
4308
+ constructor(config, emit2) {
4309
+ this.config = config;
4310
+ this.emit = emit2;
4311
+ this.clicks = [];
4312
+ }
4313
+ ingest(raw) {
4314
+ var _a2, _b;
4315
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseInteraction)
4316
+ return;
4317
+ if (raw.data.type !== RRWebMouseInteraction.Click)
4318
+ return;
4319
+ const x = (_a2 = raw.data.x) != null ? _a2 : 0;
4320
+ const y = (_b = raw.data.y) != null ? _b : 0;
4321
+ const ts = raw.timestamp;
4322
+ const cutoff = ts - this.config.rageClickWindowMs;
4323
+ this.clicks = this.clicks.filter((c) => c.ts >= cutoff);
4324
+ this.clicks.push({ ts, x, y });
4325
+ const nearby = this.clicks.filter((c) => {
4326
+ const dx = c.x - x;
4327
+ const dy = c.y - y;
4328
+ return Math.sqrt(dx * dx + dy * dy) <= this.config.rageClickRadiusPx;
4329
+ });
4330
+ if (nearby.length >= this.config.rageClickCount) {
4331
+ this.emit({
4332
+ ts,
4333
+ name: StandardEvents.UI_RAGE_CLICK,
4334
+ source: "rrweb",
4335
+ schemaVersion: EVENT_SCHEMA_VERSION,
4336
+ props: {
4337
+ x,
4338
+ y,
4339
+ clickCount: nearby.length,
4340
+ duration_ms: ts - nearby[0].ts
4341
+ }
4342
+ });
4343
+ this.clicks = [];
4344
+ }
4345
+ }
4346
+ };
4347
+
4348
+ // ../event-processor/dist/detectors/scroll-thrash.js
4349
+ var ScrollThrashDetector = class {
4350
+ constructor(config, emit2) {
4351
+ this.config = config;
4352
+ this.emit = emit2;
4353
+ this.lastY = null;
4354
+ this.lastDirection = null;
4355
+ this.reversals = [];
4356
+ }
4357
+ ingest(raw) {
4358
+ var _a2;
4359
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.Scroll)
4360
+ return;
4361
+ const y = (_a2 = raw.data.y) != null ? _a2 : 0;
4362
+ const ts = raw.timestamp;
4363
+ if (this.lastY !== null) {
4364
+ const direction = y > this.lastY ? "down" : y < this.lastY ? "up" : this.lastDirection;
4365
+ if (direction && direction !== this.lastDirection) {
4366
+ const cutoff = ts - this.config.scrollThrashWindowMs;
4367
+ this.reversals = this.reversals.filter((t) => t > cutoff);
4368
+ this.reversals.push(ts);
4369
+ if (this.reversals.length >= this.config.scrollThrashReversals) {
4370
+ this.emit({
4371
+ ts,
4372
+ name: StandardEvents.UI_SCROLL_THRASH,
4373
+ source: "rrweb",
4374
+ schemaVersion: EVENT_SCHEMA_VERSION,
4375
+ props: {
4376
+ reversals: this.reversals.length,
4377
+ duration_ms: ts - this.reversals[0]
4378
+ }
4379
+ });
4380
+ this.reversals = [];
4381
+ }
4382
+ }
4383
+ this.lastDirection = direction;
4384
+ }
4385
+ this.lastY = y;
4386
+ }
4387
+ };
4388
+
4389
+ // ../event-processor/dist/processor.js
4390
+ function createEventProcessor(options) {
4391
+ const config = { ...DEFAULT_DETECTOR_CONFIG, ...options == null ? void 0 : options.config };
4392
+ const listeners = [];
4393
+ function emit2(event) {
4394
+ for (const cb of listeners)
4395
+ cb(event);
4396
+ }
4397
+ const hesitation = new HesitationDetector(config, emit2);
4398
+ const rageClick = new RageClickDetector(config, emit2);
4399
+ const scrollThrash = new ScrollThrashDetector(config, emit2);
4400
+ const focusBounce = new FocusBounceDetector(config, emit2);
4401
+ const idle = new IdleDetector(config, emit2);
4402
+ const hover = new HoverTracker(config, emit2, options == null ? void 0 : options.elementResolver);
4403
+ const clickAttrBuffer = [];
4404
+ return {
4405
+ ingest(raw) {
4406
+ if (raw.kind === "posthog") {
4407
+ const { kind: _, ...phEvent } = raw;
4408
+ if (shouldNormalizeEvent(phEvent)) {
4409
+ emit2(normalizePostHogEvent(phEvent));
4410
+ }
4411
+ } else if (raw.kind === "rrweb") {
4412
+ hesitation.ingest(raw);
4413
+ rageClick.ingest(raw);
4414
+ scrollThrash.ingest(raw);
4415
+ focusBounce.ingest(raw);
4416
+ idle.ingest(raw);
4417
+ hover.ingest(raw);
4418
+ }
4419
+ },
4420
+ onEvent(callback) {
4421
+ listeners.push(callback);
4422
+ },
4423
+ tick(timestamp) {
4424
+ hesitation.tick(timestamp);
4425
+ idle.tick(timestamp);
4426
+ hover.tick(timestamp);
4427
+ },
4428
+ enrichClickAttributes(timestamp, elements) {
4429
+ clickAttrBuffer.push({ ts: timestamp, elements });
4430
+ const cutoff = timestamp - 500;
4431
+ while (clickAttrBuffer.length > 0 && clickAttrBuffer[0].ts < cutoff) {
4432
+ clickAttrBuffer.shift();
4433
+ }
4434
+ }
4435
+ };
4436
+ }
4437
+
4438
+ // src/events/types.ts
4439
+ var StandardEvents2 = {
4440
+ // UI events (from PostHog autocapture)
4441
+ UI_CLICK: "ui.click",
4442
+ UI_SCROLL: "ui.scroll",
4443
+ UI_INPUT: "ui.input",
4444
+ UI_CHANGE: "ui.change",
4445
+ UI_SUBMIT: "ui.submit",
4446
+ // Navigation events
4447
+ NAV_PAGE_VIEW: "nav.page_view",
4448
+ NAV_PAGE_LEAVE: "nav.page_leave",
4449
+ // Canvas events
4450
+ CANVAS_OPENED: "canvas.opened",
4451
+ CANVAS_CLOSED: "canvas.closed",
4452
+ TILE_VIEWED: "tile.viewed",
4453
+ TILE_EXPANDED: "tile.expanded",
4454
+ TILE_COLLAPSED: "tile.collapsed",
4455
+ TILE_ACTION: "tile.action",
4456
+ // Overlay/tour events
4457
+ OVERLAY_STARTED: "overlay.started",
4458
+ OVERLAY_COMPLETED: "overlay.completed",
4459
+ OVERLAY_DISMISSED: "overlay.dismissed",
4460
+ OVERLAY_STEP_VIEWED: "overlay.step_viewed",
4461
+ // Derived behavioral signals (Phase 3)
4462
+ BEHAVIOR_RAGE_CLICK: "behavior.rage_click",
4463
+ BEHAVIOR_HESITATION: "behavior.hesitation",
4464
+ BEHAVIOR_CONFUSION: "behavior.confusion",
4465
+ // Action events
4466
+ ACTION_APPLIED: "action.applied",
4467
+ ACTION_REVERTED: "action.reverted",
4468
+ ACTION_FAILED: "action.failed",
4469
+ ACTION_CTA_CLICKED: "action.cta_clicked",
4470
+ // Notification events
4471
+ NOTIFICATION_SHOWN: "notification.shown",
4472
+ NOTIFICATION_CLICKED: "notification.clicked",
4473
+ NOTIFICATION_DISMISSED: "notification.dismissed",
4474
+ NOTIFICATION_DEEP_LINK: "notification.deep_link",
4475
+ // Surface events
4476
+ SURFACE_MOUNTED: "surface.mounted",
4477
+ SURFACE_UNMOUNTED: "surface.unmounted"
4478
+ };
4479
+
4480
+ // src/events/normalizers/canvas.ts
4481
+ function createCanvasEvent(name, props) {
4482
+ return {
4483
+ ts: Date.now(),
4484
+ name,
4485
+ source: "canvas",
4486
+ props,
4487
+ schemaVersion: EVENT_SCHEMA_VERSION
4488
+ };
4489
+ }
4490
+ function canvasOpened(surface) {
4491
+ return createCanvasEvent(StandardEvents2.CANVAS_OPENED, { surface });
4492
+ }
4493
+ function canvasClosed(surface) {
4494
+ return createCanvasEvent(StandardEvents2.CANVAS_CLOSED, { surface });
4495
+ }
4496
+ function tileViewed(tileId, surface) {
4497
+ return createCanvasEvent(StandardEvents2.TILE_VIEWED, { tileId, surface });
4498
+ }
4499
+ function tileExpanded(tileId, surface) {
4500
+ return createCanvasEvent(StandardEvents2.TILE_EXPANDED, { tileId, surface });
4501
+ }
4502
+ function tileCollapsed(tileId, surface) {
4503
+ return createCanvasEvent(StandardEvents2.TILE_COLLAPSED, { tileId, surface });
4504
+ }
4505
+ function tileAction(tileId, actionId, surface) {
4506
+ return createCanvasEvent(StandardEvents2.TILE_ACTION, {
4507
+ tileId,
4508
+ actionId,
4509
+ surface
4510
+ });
4511
+ }
4512
+ function overlayStarted(recipeId, recipeName) {
4513
+ return createCanvasEvent(StandardEvents2.OVERLAY_STARTED, {
4514
+ recipeId,
4515
+ recipeName
4516
+ });
4517
+ }
4518
+ function overlayCompleted(recipeId, recipeName) {
4519
+ return createCanvasEvent(StandardEvents2.OVERLAY_COMPLETED, {
4520
+ recipeId,
4521
+ recipeName
4522
+ });
4523
+ }
4524
+ function overlayDismissed(recipeId, recipeName, stepIndex) {
4525
+ return createCanvasEvent(StandardEvents2.OVERLAY_DISMISSED, {
4526
+ recipeId,
4527
+ recipeName,
4528
+ stepIndex
4529
+ });
4530
+ }
4531
+ function overlayStepViewed(recipeId, stepIndex, stepTitle) {
4532
+ return createCanvasEvent(StandardEvents2.OVERLAY_STEP_VIEWED, {
4533
+ recipeId,
4534
+ stepIndex,
4535
+ stepTitle
4536
+ });
4537
+ }
4538
+ function customCanvasEvent(name, props) {
4539
+ const eventName = name.startsWith("canvas.") ? name : `canvas.${name}`;
4540
+ return createCanvasEvent(eventName, props);
4541
+ }
4542
+ var CanvasEvents = {
4543
+ canvasOpened,
4544
+ canvasClosed,
4545
+ tileViewed,
4546
+ tileExpanded,
4547
+ tileCollapsed,
4548
+ tileAction,
4549
+ overlayStarted,
4550
+ overlayCompleted,
3863
4551
  overlayDismissed,
3864
4552
  overlayStepViewed,
3865
4553
  custom: customCanvasEvent
3866
4554
  };
3867
4555
 
4556
+ // src/components/emojiToIcon.tsx
4557
+ import {
4558
+ AlertTriangle,
4559
+ ArrowRight,
4560
+ Banknote,
4561
+ Bell,
4562
+ BookOpen,
4563
+ CheckCircle,
4564
+ ClipboardList,
4565
+ Compass,
4566
+ FileText,
4567
+ Gamepad2,
4568
+ HelpCircle,
4569
+ Landmark,
4570
+ Layers,
4571
+ Lightbulb,
4572
+ MessageCircle,
4573
+ SkipForward,
4574
+ Sparkles,
4575
+ Timer,
4576
+ Trophy
4577
+ } from "lucide-react";
4578
+ import { jsx as jsx2 } from "react/jsx-runtime";
4579
+ var EMOJI_ICON_MAP = {
4580
+ "\u2753": HelpCircle,
4581
+ "\u{1F9ED}": Compass,
4582
+ "\u{1F4DD}": FileText,
4583
+ "\u{1F3AF}": Layers,
4584
+ "\u{1F3C6}": Trophy,
4585
+ "\u2728": Sparkles,
4586
+ "\u{1F4AC}": MessageCircle,
4587
+ "\u{1F3AE}": Gamepad2,
4588
+ "\u{1F4A1}": Lightbulb,
4589
+ "\u{1F4B0}": Banknote,
4590
+ "\u{1F4CB}": ClipboardList,
4591
+ "\u2705": CheckCircle,
4592
+ "\u26A0\uFE0F": AlertTriangle,
4593
+ "\u{1F4B5}": Banknote,
4594
+ "\u{1F3DB}\uFE0F": Landmark,
4595
+ "\u23ED\uFE0F": SkipForward,
4596
+ "\u27A1\uFE0F": ArrowRight,
4597
+ "\u23F1\uFE0F": Timer,
4598
+ "\u{1F4D6}": BookOpen,
4599
+ "\u{1F514}": Bell
4600
+ };
4601
+ function EmojiIcon({
4602
+ emoji,
4603
+ size = 14,
4604
+ color = "currentColor"
4605
+ }) {
4606
+ const Icon = EMOJI_ICON_MAP[emoji];
4607
+ if (!Icon) {
4608
+ return /* @__PURE__ */ jsx2("span", { children: emoji });
4609
+ }
4610
+ return /* @__PURE__ */ jsx2(Icon, { size, color });
4611
+ }
4612
+
3868
4613
  // src/notifications/NotificationToastStack.tsx
3869
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
4614
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
3870
4615
  var TOAST_STYLES_ID = "syntro-toast-styles";
3871
4616
  var TOAST_CSS = `
3872
4617
  @keyframes syntro-toast-slide-in {
@@ -3903,7 +4648,7 @@ function NotificationToastStack({
3903
4648
  const { shadowRoot } = useShadowRoot();
3904
4649
  ensureToastStyles(shadowRoot);
3905
4650
  if (notifications.length === 0) return null;
3906
- return /* @__PURE__ */ jsx2(
4651
+ return /* @__PURE__ */ jsx3(
3907
4652
  "div",
3908
4653
  {
3909
4654
  "data-testid": "notification-toast-stack",
@@ -3958,7 +4703,7 @@ function NotificationToastStack({
3958
4703
  padding: "10px 12px"
3959
4704
  },
3960
4705
  children: [
3961
- /* @__PURE__ */ jsx2(
4706
+ /* @__PURE__ */ jsx3(
3962
4707
  "div",
3963
4708
  {
3964
4709
  style: {
@@ -3972,11 +4717,11 @@ function NotificationToastStack({
3972
4717
  flexShrink: 0,
3973
4718
  fontSize: "14px"
3974
4719
  },
3975
- children: (_a2 = notif.icon) != null ? _a2 : "\u{1F514}"
4720
+ children: /* @__PURE__ */ jsx3(EmojiIcon, { emoji: (_a2 = notif.icon) != null ? _a2 : "\u{1F514}", size: 14 })
3976
4721
  }
3977
4722
  ),
3978
4723
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
3979
- /* @__PURE__ */ jsx2(
4724
+ /* @__PURE__ */ jsx3(
3980
4725
  "div",
3981
4726
  {
3982
4727
  style: {
@@ -3991,7 +4736,7 @@ function NotificationToastStack({
3991
4736
  children: notif.title
3992
4737
  }
3993
4738
  ),
3994
- notif.body && /* @__PURE__ */ jsx2(
4739
+ notif.body && /* @__PURE__ */ jsx3(
3995
4740
  "div",
3996
4741
  {
3997
4742
  style: {
@@ -4007,7 +4752,7 @@ function NotificationToastStack({
4007
4752
  }
4008
4753
  )
4009
4754
  ] }),
4010
- /* @__PURE__ */ jsx2(
4755
+ /* @__PURE__ */ jsx3(
4011
4756
  "button",
4012
4757
  {
4013
4758
  type: "button",
@@ -4032,7 +4777,7 @@ function NotificationToastStack({
4032
4777
  ]
4033
4778
  }
4034
4779
  ),
4035
- /* @__PURE__ */ jsx2("div", { style: { height: "2px", background: "rgba(0, 0, 0, 0.08)" }, children: /* @__PURE__ */ jsx2(
4780
+ /* @__PURE__ */ jsx3("div", { style: { height: "2px", background: "rgba(0, 0, 0, 0.08)" }, children: /* @__PURE__ */ jsx3(
4036
4781
  "div",
4037
4782
  {
4038
4783
  className: "syntro-toast-progress",
@@ -4110,7 +4855,7 @@ function useNotifications(eventBus, tiles) {
4110
4855
  const timerIds = useRef2(/* @__PURE__ */ new Map());
4111
4856
  const publishDismissed = useCallback2(
4112
4857
  (notif) => {
4113
- eventBus == null ? void 0 : eventBus.publish(StandardEvents.NOTIFICATION_DISMISSED, {
4858
+ eventBus == null ? void 0 : eventBus.publish(StandardEvents2.NOTIFICATION_DISMISSED, {
4114
4859
  notificationId: notif.id,
4115
4860
  tileId: notif.tileId,
4116
4861
  itemId: notif.itemId
@@ -4175,7 +4920,7 @@ function useNotifications(eventBus, tiles) {
4175
4920
  }
4176
4921
  return next;
4177
4922
  });
4178
- eventBus.publish(StandardEvents.NOTIFICATION_SHOWN, {
4923
+ eventBus.publish(StandardEvents2.NOTIFICATION_SHOWN, {
4179
4924
  notificationId: matched.id,
4180
4925
  tileId: matched.tileId,
4181
4926
  itemId: matched.itemId,
@@ -4246,7 +4991,7 @@ function useNotifyWatcher(runtime3, tiles, appRegistry2) {
4246
4991
 
4247
4992
  // src/RuntimeProvider.tsx
4248
4993
  import { createContext as createContext2, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo2, useState as useState3 } from "react";
4249
- import { jsx as jsx3 } from "react/jsx-runtime";
4994
+ import { jsx as jsx4 } from "react/jsx-runtime";
4250
4995
  var RuntimeReactContext = createContext2({
4251
4996
  runtime: null,
4252
4997
  context: null
@@ -4264,7 +5009,7 @@ function RuntimeProvider({ runtime: runtime3, children }) {
4264
5009
  return unsubscribe;
4265
5010
  }, [runtime3]);
4266
5011
  const value = useMemo2(() => ({ runtime: runtime3, context }), [runtime3, context]);
4267
- return /* @__PURE__ */ jsx3(RuntimeReactContext.Provider, { value, children });
5012
+ return /* @__PURE__ */ jsx4(RuntimeReactContext.Provider, { value, children });
4268
5013
  }
4269
5014
  function useRuntime() {
4270
5015
  const { runtime: runtime3 } = useContext2(RuntimeReactContext);
@@ -4338,41 +5083,17 @@ import {
4338
5083
  } from "react";
4339
5084
 
4340
5085
  // src/components/TileIcon.tsx
4341
- import {
4342
- Compass,
4343
- FileText,
4344
- Gamepad2,
4345
- HelpCircle,
4346
- Layers,
4347
- MessageCircle,
4348
- Sparkles,
4349
- Trophy
4350
- } from "lucide-react";
4351
- import { jsx as jsx4 } from "react/jsx-runtime";
4352
- var ICON_MAP = {
4353
- "\u2753": HelpCircle,
4354
- "\u{1F9ED}": Compass,
4355
- "\u{1F4DD}": FileText,
4356
- "\u{1F3AF}": Layers,
4357
- "\u{1F3C6}": Trophy,
4358
- "\u2728": Sparkles,
4359
- "\u{1F4AC}": MessageCircle,
4360
- "\u{1F3AE}": Gamepad2
4361
- };
5086
+ import { jsx as jsx5 } from "react/jsx-runtime";
4362
5087
  function TileIcon({
4363
5088
  emoji,
4364
5089
  size = 18,
4365
5090
  color = "currentColor"
4366
5091
  }) {
4367
- const Icon = ICON_MAP[emoji];
4368
- if (!Icon) {
4369
- return /* @__PURE__ */ jsx4("span", { children: emoji });
4370
- }
4371
- return /* @__PURE__ */ jsx4(Icon, { size, color });
5092
+ return /* @__PURE__ */ jsx5(EmojiIcon, { emoji, size, color });
4372
5093
  }
4373
5094
 
4374
5095
  // src/components/TileCard.tsx
4375
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
5096
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
4376
5097
  function WidgetMount({ widgetId, props }) {
4377
5098
  var _a2;
4378
5099
  const runtime3 = useRuntime();
@@ -4402,7 +5123,6 @@ function WidgetMount({ widgetId, props }) {
4402
5123
  return () => {
4403
5124
  handle.unmount();
4404
5125
  handleRef.current = null;
4405
- container.remove();
4406
5126
  };
4407
5127
  }, [registry, widgetId, widgetAvailable]);
4408
5128
  const propsJson = JSON.stringify(props);
@@ -4413,7 +5133,7 @@ function WidgetMount({ widgetId, props }) {
4413
5133
  prevPropsJsonRef.current = propsJson;
4414
5134
  (_a3 = handleRef.current) == null ? void 0 : _a3.update(propsRef.current);
4415
5135
  }, [propsJson]);
4416
- if (!registry || !registry.has(widgetId)) {
5136
+ if (!(registry == null ? void 0 : registry.has(widgetId))) {
4417
5137
  return /* @__PURE__ */ jsxs2(
4418
5138
  "div",
4419
5139
  {
@@ -4430,8 +5150,21 @@ function WidgetMount({ widgetId, props }) {
4430
5150
  }
4431
5151
  );
4432
5152
  }
4433
- return /* @__PURE__ */ jsx5("div", { ref: parentRef });
4434
- }
5153
+ return /* @__PURE__ */ jsx6("div", { ref: parentRef });
5154
+ }
5155
+ var INTERACTION_PATTERNS = [
5156
+ ":toggled",
5157
+ ":clicked",
5158
+ ":feedback",
5159
+ ":navigate",
5160
+ ":expanded",
5161
+ ":collapsed",
5162
+ ":dismissed",
5163
+ ":submitted",
5164
+ ":interacted",
5165
+ ":tip_clicked",
5166
+ ":tip_focused"
5167
+ ];
4435
5168
  function TileCard({
4436
5169
  config,
4437
5170
  surface: _surface,
@@ -4440,10 +5173,42 @@ function TileCard({
4440
5173
  }) {
4441
5174
  const { title, subtitle, widget, props, icon } = config;
4442
5175
  const [, setTick] = useState4(0);
5176
+ const articleRef = useRef4(null);
4443
5177
  const runtime3 = useRuntime();
4444
5178
  useEffect5(() => {
4445
5179
  if (runtime3) setTick((t) => t + 1);
4446
5180
  }, [runtime3]);
5181
+ useEffect5(() => {
5182
+ var _a2;
5183
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5184
+ if (!articleRef.current || !tracker) return;
5185
+ const observer = new IntersectionObserver(
5186
+ ([entry]) => {
5187
+ var _a3;
5188
+ if (entry.isIntersecting) {
5189
+ tracker.trackSeen(config.id, (_a3 = config.widget) != null ? _a3 : "unknown");
5190
+ observer.disconnect();
5191
+ }
5192
+ },
5193
+ { threshold: 0.5 }
5194
+ );
5195
+ observer.observe(articleRef.current);
5196
+ return () => observer.disconnect();
5197
+ }, [config.id, config.widget]);
5198
+ useEffect5(() => {
5199
+ var _a2;
5200
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5201
+ if (!(runtime3 == null ? void 0 : runtime3.events) || !tracker) return;
5202
+ return runtime3.events.subscribe((event) => {
5203
+ var _a3, _b;
5204
+ if (!INTERACTION_PATTERNS.some((p) => {
5205
+ var _a4;
5206
+ return (_a4 = event.name) == null ? void 0 : _a4.includes(p);
5207
+ })) return;
5208
+ if (((_a3 = event.props) == null ? void 0 : _a3.instanceId) !== config.id) return;
5209
+ tracker.trackInteracted(config.id, (_b = config.widget) != null ? _b : "unknown", event.name);
5210
+ });
5211
+ }, [runtime3 == null ? void 0 : runtime3.events, config.id, config.widget]);
4447
5212
  const registration = useMemo3(
4448
5213
  () => {
4449
5214
  var _a2, _b;
@@ -4497,15 +5262,16 @@ function TileCard({
4497
5262
  return /* @__PURE__ */ jsxs2(
4498
5263
  "article",
4499
5264
  {
5265
+ ref: articleRef,
4500
5266
  "data-shadow-canvas-id": `tile-${config.id}`,
4501
5267
  style: cardStyle,
4502
5268
  onMouseEnter,
4503
5269
  onMouseLeave,
4504
5270
  children: [
4505
5271
  /* @__PURE__ */ jsxs2("div", { style: headerStyle, children: [
4506
- /* @__PURE__ */ jsx5("div", { style: iconStyle, children: /* @__PURE__ */ jsx5(TileIcon, { emoji: resolvedIcon, size: resolvedSubtitle ? 36 : 24 }) }),
5272
+ /* @__PURE__ */ jsx6("div", { style: iconStyle, children: /* @__PURE__ */ jsx6(TileIcon, { emoji: resolvedIcon, size: resolvedSubtitle ? 36 : 24 }) }),
4507
5273
  /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0 }, children: [
4508
- /* @__PURE__ */ jsx5(
5274
+ /* @__PURE__ */ jsx6(
4509
5275
  "h3",
4510
5276
  {
4511
5277
  style: {
@@ -4520,7 +5286,7 @@ function TileCard({
4520
5286
  children: title != null ? title : widget
4521
5287
  }
4522
5288
  ),
4523
- resolvedSubtitle && /* @__PURE__ */ jsx5(
5289
+ resolvedSubtitle && /* @__PURE__ */ jsx6(
4524
5290
  "p",
4525
5291
  {
4526
5292
  style: {
@@ -4537,14 +5303,14 @@ function TileCard({
4537
5303
  )
4538
5304
  ] })
4539
5305
  ] }),
4540
- /* @__PURE__ */ jsx5(
5306
+ /* @__PURE__ */ jsx6(
4541
5307
  "div",
4542
5308
  {
4543
5309
  style: {
4544
5310
  padding: "var(--sc-tile-body-padding, 0 0.75rem 0.5rem)",
4545
5311
  borderTop: "1px solid rgba(255, 255, 255, 0.06)"
4546
5312
  },
4547
- children: /* @__PURE__ */ jsx5("div", { style: { paddingTop: "var(--sc-tile-gap, 0.25rem)" }, children: /* @__PURE__ */ jsx5(WidgetMount, { widgetId: widget, props: { ...props, instanceId: config.id } }) })
5313
+ children: /* @__PURE__ */ jsx6("div", { style: { paddingTop: "var(--sc-tile-gap, 0.25rem)" }, children: /* @__PURE__ */ jsx6(WidgetMount, { widgetId: widget, props: { ...props, instanceId: config.id } }) })
4548
5314
  }
4549
5315
  )
4550
5316
  ]
@@ -4952,7 +5718,7 @@ function flattenThemeConfig(config) {
4952
5718
 
4953
5719
  // src/theme/ThemeProvider.tsx
4954
5720
  import { createContext as createContext3, useContext as useContext3, useEffect as useEffect6, useMemo as useMemo4 } from "react";
4955
- import { jsx as jsx6 } from "react/jsx-runtime";
5721
+ import { jsx as jsx7 } from "react/jsx-runtime";
4956
5722
  var ThemeContext = createContext3(null);
4957
5723
  function ThemeProvider({
4958
5724
  children,
@@ -4983,7 +5749,7 @@ ${cssRules}
4983
5749
  mode: merged.mode,
4984
5750
  cssVariables
4985
5751
  };
4986
- return /* @__PURE__ */ jsx6(ThemeContext.Provider, { value, children });
5752
+ return /* @__PURE__ */ jsx7(ThemeContext.Provider, { value, children });
4987
5753
  }
4988
5754
  function useTheme() {
4989
5755
  const context = useContext3(ThemeContext);
@@ -4994,7 +5760,7 @@ function useTheme() {
4994
5760
  }
4995
5761
 
4996
5762
  // src/components/ShadowCanvasOverlay.tsx
4997
- import { Fragment, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
5763
+ import { Fragment, jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
4998
5764
  var LAUNCHER_STYLES_ID = "syntro-launcher-styles";
4999
5765
  function ensureLauncherStyles(target, css) {
5000
5766
  if (target.querySelector(`#${LAUNCHER_STYLES_ID}`)) return;
@@ -5008,7 +5774,6 @@ function ShadowCanvasOverlay({
5008
5774
  onToggle,
5009
5775
  telemetry,
5010
5776
  launcherLabel: _launcherLabel = "Adaptives",
5011
- launcherIcon,
5012
5777
  launcherAnimate = false,
5013
5778
  launcherAnimationStyle: _launcherAnimationStyle = "pulse",
5014
5779
  notificationCount: _notificationCount,
@@ -5019,7 +5784,7 @@ function ShadowCanvasOverlay({
5019
5784
  canvasTitle,
5020
5785
  displayMode = "standard"
5021
5786
  }) {
5022
- var _a2, _b, _c, _d;
5787
+ var _a2, _b, _c, _d, _e;
5023
5788
  const [mounted, setMounted] = useState5(false);
5024
5789
  const [launcherPos, setLauncherPos] = useState5(null);
5025
5790
  const dragRef = useRef5(null);
@@ -5034,7 +5799,7 @@ function ShadowCanvasOverlay({
5034
5799
  const handleNotificationClick = useCallback4(
5035
5800
  (notif) => {
5036
5801
  if (runtime3) {
5037
- runtime3.events.publish(StandardEvents.NOTIFICATION_CLICKED, {
5802
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_CLICKED, {
5038
5803
  notificationId: notif.id,
5039
5804
  tileId: notif.tileId,
5040
5805
  itemId: notif.itemId
@@ -5044,7 +5809,7 @@ function ShadowCanvasOverlay({
5044
5809
  onToggle();
5045
5810
  }
5046
5811
  if (runtime3 && notif.tileId) {
5047
- runtime3.events.publish(StandardEvents.NOTIFICATION_DEEP_LINK, {
5812
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_DEEP_LINK, {
5048
5813
  tileId: notif.tileId,
5049
5814
  itemId: notif.itemId
5050
5815
  });
@@ -5117,6 +5882,17 @@ function ShadowCanvasOverlay({
5117
5882
  }
5118
5883
  onToggle();
5119
5884
  }, [isOpen, telemetry, runtime3, onToggle]);
5885
+ useEffect7(() => {
5886
+ if (!isOpen) return;
5887
+ const handleOutsideClick = (e) => {
5888
+ const path = e.composedPath();
5889
+ if (containerRef.current && !path.includes(containerRef.current) && launcherRef.current && !path.includes(launcherRef.current)) {
5890
+ toggle2();
5891
+ }
5892
+ };
5893
+ document.addEventListener("mousedown", handleOutsideClick);
5894
+ return () => document.removeEventListener("mousedown", handleOutsideClick);
5895
+ }, [isOpen, toggle2]);
5120
5896
  const onLauncherPointerDown = useCallback4((e) => {
5121
5897
  const rect = e.currentTarget.getBoundingClientRect();
5122
5898
  dragRef.current = {
@@ -5141,7 +5917,7 @@ function ShadowCanvasOverlay({
5141
5917
  }
5142
5918
  }, []);
5143
5919
  const onLauncherPointerUp = useCallback4(
5144
- (_e) => {
5920
+ (_e2) => {
5145
5921
  const drag = dragRef.current;
5146
5922
  dragRef.current = null;
5147
5923
  if (drag && !drag.dragged) {
@@ -5155,6 +5931,7 @@ function ShadowCanvasOverlay({
5155
5931
  const isPush = config.canvas.layout === "push";
5156
5932
  const canvasBorder = (_b = config.canvas.border) != null ? _b : "none";
5157
5933
  const containerRef = useRef5(null);
5934
+ const launcherRef = useRef5(null);
5158
5935
  const zIndex = 2147483600;
5159
5936
  useEffect7(() => {
5160
5937
  var _a3, _b2, _c2, _d2;
@@ -5235,19 +6012,19 @@ function ShadowCanvasOverlay({
5235
6012
  pointerEvents: "none",
5236
6013
  padding: "0"
5237
6014
  };
5238
- const content = /* @__PURE__ */ jsx7(
6015
+ const content = /* @__PURE__ */ jsx8(
5239
6016
  "div",
5240
6017
  {
5241
6018
  "data-shadow-canvas-id": "overlay-root",
5242
6019
  style: {
5243
6020
  position: "fixed",
5244
6021
  inset: 0,
5245
- pointerEvents: isOpen ? "auto" : "none",
6022
+ pointerEvents: "none",
5246
6023
  zIndex
5247
6024
  },
5248
6025
  children: /* @__PURE__ */ jsxs3("div", { style: wrapperStyle, children: [
5249
6026
  /* @__PURE__ */ jsxs3("div", { ref: containerRef, "data-shadow-canvas-id": "overlay-container", style: containerStyle, children: [
5250
- isFocused && canvasTitle && /* @__PURE__ */ jsx7("header", { style: { color: "white", padding: "1.5rem 1.5rem 0" }, children: /* @__PURE__ */ jsx7(
6027
+ isFocused && canvasTitle && /* @__PURE__ */ jsx8("header", { style: { color: "white", padding: "1.5rem 1.5rem 0" }, children: /* @__PURE__ */ jsx8(
5251
6028
  "p",
5252
6029
  {
5253
6030
  style: {
@@ -5260,7 +6037,7 @@ function ShadowCanvasOverlay({
5260
6037
  children: canvasTitle
5261
6038
  }
5262
6039
  ) }),
5263
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, overflowY: "auto", padding: isFocused ? "0" : "1rem" }, children: isLoading ? /* @__PURE__ */ jsx7(
6040
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, overflowY: "auto", padding: isFocused ? "0" : "1rem" }, children: isLoading ? /* @__PURE__ */ jsx8(
5264
6041
  "div",
5265
6042
  {
5266
6043
  style: { color: "var(--sc-overlay-text-color)", padding: isFocused ? "1rem" : "0" },
@@ -5280,7 +6057,7 @@ function ShadowCanvasOverlay({
5280
6057
  }
5281
6058
  ) : isFocused ? (
5282
6059
  /* Focused Mode: Render first tile full size */
5283
- tiles.length > 0 ? /* @__PURE__ */ jsx7(
6060
+ tiles.length > 0 ? /* @__PURE__ */ jsx8(
5284
6061
  TileCard,
5285
6062
  {
5286
6063
  config: tiles[0],
@@ -5291,7 +6068,7 @@ function ShadowCanvasOverlay({
5291
6068
  ) : null
5292
6069
  ) : (
5293
6070
  /* Standard Mode: Stacked cards — widgets always visible */
5294
- /* @__PURE__ */ jsx7(
6071
+ /* @__PURE__ */ jsx8(
5295
6072
  "div",
5296
6073
  {
5297
6074
  style: {
@@ -5300,7 +6077,7 @@ function ShadowCanvasOverlay({
5300
6077
  gap: "0.75rem",
5301
6078
  width: "100%"
5302
6079
  },
5303
- children: tiles.map((tile) => /* @__PURE__ */ jsx7(
6080
+ children: tiles.map((tile) => /* @__PURE__ */ jsx8(
5304
6081
  TileCard,
5305
6082
  {
5306
6083
  config: tile,
@@ -5315,17 +6092,7 @@ function ShadowCanvasOverlay({
5315
6092
  ) }),
5316
6093
  footerSlot
5317
6094
  ] }),
5318
- /* @__PURE__ */ jsx7(
5319
- "div",
5320
- {
5321
- onClick: toggle2,
5322
- style: {
5323
- flex: "1 1 auto",
5324
- pointerEvents: isOpen ? "auto" : "none",
5325
- cursor: "default"
5326
- }
5327
- }
5328
- )
6095
+ /* @__PURE__ */ jsx8("div", { style: { flex: "1 1 auto" } })
5329
6096
  ] })
5330
6097
  }
5331
6098
  );
@@ -5343,7 +6110,7 @@ function ShadowCanvasOverlay({
5343
6110
  zIndex: zIndex + 47
5344
6111
  },
5345
6112
  children: [
5346
- /* @__PURE__ */ jsx7(
6113
+ /* @__PURE__ */ jsx8(
5347
6114
  NotificationToastStack,
5348
6115
  {
5349
6116
  notifications,
@@ -5355,6 +6122,7 @@ function ShadowCanvasOverlay({
5355
6122
  /* @__PURE__ */ jsxs3(
5356
6123
  "button",
5357
6124
  {
6125
+ ref: launcherRef,
5358
6126
  type: "button",
5359
6127
  "aria-label": "Toggle shadow canvas",
5360
6128
  className: launcherAnimate && !isOpen ? "syntro-launcher-animate" : void 0,
@@ -5413,14 +6181,14 @@ function ShadowCanvasOverlay({
5413
6181
  focusable: "false",
5414
6182
  style: { transition: "transform 200ms ease" },
5415
6183
  children: [
5416
- /* @__PURE__ */ jsx7("path", { d: "M18 6L6 18" }),
5417
- /* @__PURE__ */ jsx7("path", { d: "M6 6l12 12" })
6184
+ /* @__PURE__ */ jsx8("path", { d: "M18 6L6 18" }),
6185
+ /* @__PURE__ */ jsx8("path", { d: "M6 6l12 12" })
5418
6186
  ]
5419
6187
  }
5420
- ) : launcherIcon ? /* @__PURE__ */ jsx7(
6188
+ ) : ((_e = config.launcher) == null ? void 0 : _e.icon) ? /* @__PURE__ */ jsx8(
5421
6189
  "img",
5422
6190
  {
5423
- src: launcherIcon,
6191
+ src: config.launcher.icon,
5424
6192
  alt: "",
5425
6193
  "aria-hidden": "true",
5426
6194
  style: {
@@ -5445,16 +6213,16 @@ function ShadowCanvasOverlay({
5445
6213
  focusable: "false",
5446
6214
  style: { transition: "transform 200ms ease" },
5447
6215
  children: [
5448
- /* @__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" }),
5449
- /* @__PURE__ */ jsx7("path", { d: "M5 3v4" }),
5450
- /* @__PURE__ */ jsx7("path", { d: "M3 5h4" }),
5451
- /* @__PURE__ */ jsx7("path", { d: "M19 17v4" }),
5452
- /* @__PURE__ */ jsx7("path", { d: "M17 19h4" })
6216
+ /* @__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" }),
6217
+ /* @__PURE__ */ jsx8("path", { d: "M5 3v4" }),
6218
+ /* @__PURE__ */ jsx8("path", { d: "M3 5h4" }),
6219
+ /* @__PURE__ */ jsx8("path", { d: "M19 17v4" }),
6220
+ /* @__PURE__ */ jsx8("path", { d: "M17 19h4" })
5453
6221
  ]
5454
6222
  }
5455
6223
  ),
5456
6224
  !isOpen && notifications.length > 0 && /* @__PURE__ */ jsxs3("div", { style: { position: "absolute", top: -2, right: -2, pointerEvents: "none" }, children: [
5457
- /* @__PURE__ */ jsx7(
6225
+ /* @__PURE__ */ jsx8(
5458
6226
  "span",
5459
6227
  {
5460
6228
  className: "syntro-badge-ping",
@@ -5466,7 +6234,7 @@ function ShadowCanvasOverlay({
5466
6234
  }
5467
6235
  }
5468
6236
  ),
5469
- /* @__PURE__ */ jsx7(
6237
+ /* @__PURE__ */ jsx8(
5470
6238
  "span",
5471
6239
  {
5472
6240
  className: "syntro-badge-glow",
@@ -5477,7 +6245,7 @@ function ShadowCanvasOverlay({
5477
6245
  }
5478
6246
  }
5479
6247
  ),
5480
- /* @__PURE__ */ jsx7(
6248
+ /* @__PURE__ */ jsx8(
5481
6249
  "span",
5482
6250
  {
5483
6251
  className: "syntro-badge-bounce",
@@ -5516,6 +6284,14 @@ var sortTiles = (tiles) => [...tiles].sort((a, b) => {
5516
6284
  var _a2, _b;
5517
6285
  return ((_a2 = b.priority) != null ? _a2 : 0) - ((_b = a.priority) != null ? _b : 0);
5518
6286
  });
6287
+ function fireTriggeredForTiles(tiles) {
6288
+ var _a2, _b;
6289
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
6290
+ if (!tracker) return;
6291
+ for (const tile of tiles) {
6292
+ tracker.trackTriggered(tile.id, (_b = tile.widget) != null ? _b : "unknown");
6293
+ }
6294
+ }
5519
6295
  function useShadowCanvasConfig({
5520
6296
  fetcher,
5521
6297
  experiments,
@@ -5538,6 +6314,7 @@ function useShadowCanvasConfig({
5538
6314
  if (experiments) {
5539
6315
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
5540
6316
  }
6317
+ fireTriggeredForTiles(tiles);
5541
6318
  setState((prev) => ({ ...prev, tiles: sortTiles(tiles) }));
5542
6319
  }, [runtime3, experiments]);
5543
6320
  const load = useCallback5(async () => {
@@ -5555,6 +6332,7 @@ function useShadowCanvasConfig({
5555
6332
  } else if (experiments) {
5556
6333
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
5557
6334
  }
6335
+ fireTriggeredForTiles(tiles);
5558
6336
  debug("SmartCanvas Config", `Tile count after filtering: ${tiles.length}`);
5559
6337
  const newActions = response.actions || [];
5560
6338
  const newActionsJson = JSON.stringify(newActions);
@@ -5612,13 +6390,13 @@ function useShadowCanvasConfig({
5612
6390
 
5613
6391
  // src/SmartCanvasApp.tsx
5614
6392
  import { useEffect as useEffect9, useMemo as useMemo7, useRef as useRef7, useState as useState7 } from "react";
5615
- import { jsx as jsx8 } from "react/jsx-runtime";
6393
+ import { jsx as jsx9 } from "react/jsx-runtime";
5616
6394
  function SmartCanvasApp({
5617
6395
  controller,
5618
6396
  fetcher,
5619
6397
  configUri,
5620
- configUriFeatureKey = "smart-canvas-config-uri",
5621
- configFeatureKey = "smart-canvas-config",
6398
+ configUriFeatureKey,
6399
+ configFeatureKey,
5622
6400
  fetchCredentials = "include",
5623
6401
  pollIntervalMs,
5624
6402
  experiments,
@@ -5635,7 +6413,7 @@ function SmartCanvasApp({
5635
6413
  workspaceTheme
5636
6414
  }) {
5637
6415
  if (runtime3) {
5638
- return /* @__PURE__ */ jsx8(RuntimeProvider, { runtime: runtime3, children: /* @__PURE__ */ jsx8(
6416
+ return /* @__PURE__ */ jsx9(RuntimeProvider, { runtime: runtime3, children: /* @__PURE__ */ jsx9(
5639
6417
  SmartCanvasAppInner,
5640
6418
  {
5641
6419
  controller,
@@ -5660,7 +6438,7 @@ function SmartCanvasApp({
5660
6438
  }
5661
6439
  ) });
5662
6440
  }
5663
- return /* @__PURE__ */ jsx8(
6441
+ return /* @__PURE__ */ jsx9(
5664
6442
  SmartCanvasAppInner,
5665
6443
  {
5666
6444
  controller,
@@ -5688,10 +6466,10 @@ function SmartCanvasAppInner({
5688
6466
  controller,
5689
6467
  fetcher,
5690
6468
  configUri,
5691
- configUriFeatureKey = "smart-canvas-config-uri",
5692
- configFeatureKey = "smart-canvas-config",
6469
+ configUriFeatureKey,
6470
+ configFeatureKey,
5693
6471
  fetchCredentials = "include",
5694
- pollIntervalMs,
6472
+ pollIntervalMs: _pollIntervalMs,
5695
6473
  experiments,
5696
6474
  telemetry,
5697
6475
  runtime: runtime3,
@@ -5705,7 +6483,7 @@ function SmartCanvasAppInner({
5705
6483
  initialBatchHandle,
5706
6484
  workspaceTheme
5707
6485
  }) {
5708
- var _a2, _b, _c, _d, _e, _f, _g;
6486
+ var _a2, _b, _c, _d, _e, _f;
5709
6487
  const [open, setOpen] = useState7(controller.getState().open);
5710
6488
  const pageContext = usePageContext();
5711
6489
  const [localUrl, setLocalUrl] = useState7(
@@ -5718,21 +6496,9 @@ function SmartCanvasAppInner({
5718
6496
  const updateUrl = () => setLocalUrl(window.location.href);
5719
6497
  window.addEventListener("popstate", updateUrl);
5720
6498
  window.addEventListener("hashchange", updateUrl);
5721
- const originalPushState = history.pushState;
5722
- const originalReplaceState = history.replaceState;
5723
- history.pushState = function(...args) {
5724
- originalPushState.apply(this, args);
5725
- queueMicrotask(updateUrl);
5726
- };
5727
- history.replaceState = function(...args) {
5728
- originalReplaceState.apply(this, args);
5729
- queueMicrotask(updateUrl);
5730
- };
5731
6499
  return () => {
5732
6500
  window.removeEventListener("popstate", updateUrl);
5733
6501
  window.removeEventListener("hashchange", updateUrl);
5734
- history.pushState = originalPushState;
5735
- history.replaceState = originalReplaceState;
5736
6502
  };
5737
6503
  }, [runtime3]);
5738
6504
  const derivedFetcher = useMemo7(() => {
@@ -5761,6 +6527,7 @@ function SmartCanvasAppInner({
5761
6527
  const batchHandleRef = useRef7(initialBatchHandle != null ? initialBatchHandle : null);
5762
6528
  const adoptedInitialRef = useRef7(!!initialBatchHandle);
5763
6529
  const runVersionRef = useRef7(0);
6530
+ const pendingRevertRef = useRef7(null);
5764
6531
  useEffect9(() => {
5765
6532
  if (!(runtime3 == null ? void 0 : runtime3.actions)) return;
5766
6533
  if (adoptedInitialRef.current) {
@@ -5772,6 +6539,14 @@ function SmartCanvasAppInner({
5772
6539
  const version = ++runVersionRef.current;
5773
6540
  const stale = () => version !== runVersionRef.current;
5774
6541
  const run = async () => {
6542
+ if (pendingRevertRef.current) {
6543
+ try {
6544
+ await pendingRevertRef.current;
6545
+ } catch (err) {
6546
+ console.error("[SmartCanvasApp] Pending revert failed:", err);
6547
+ }
6548
+ pendingRevertRef.current = null;
6549
+ }
5775
6550
  if (batchHandleRef.current) {
5776
6551
  try {
5777
6552
  await batchHandleRef.current.revertAll();
@@ -5799,7 +6574,7 @@ function SmartCanvasAppInner({
5799
6574
  run();
5800
6575
  return () => {
5801
6576
  if (batchHandleRef.current) {
5802
- batchHandleRef.current.revertAll().catch((err) => {
6577
+ pendingRevertRef.current = batchHandleRef.current.revertAll().catch((err) => {
5803
6578
  console.error("[SmartCanvasApp] Failed to revert actions on cleanup:", err);
5804
6579
  });
5805
6580
  batchHandleRef.current = null;
@@ -5815,13 +6590,13 @@ function SmartCanvasAppInner({
5815
6590
  }, [runtime3, controller]);
5816
6591
  const { shadowRoot } = useShadowRoot();
5817
6592
  const themeConfig = configState.theme;
5818
- return /* @__PURE__ */ jsx8(
6593
+ return /* @__PURE__ */ jsx9(
5819
6594
  ThemeProvider,
5820
6595
  {
5821
6596
  themeConfig,
5822
6597
  workspaceTheme,
5823
6598
  shadowRoot,
5824
- children: !configState.isLoading && !hasContent ? null : /* @__PURE__ */ jsx8(
6599
+ children: !configState.isLoading && !hasContent ? null : /* @__PURE__ */ jsx9(
5825
6600
  ShadowCanvasOverlay,
5826
6601
  {
5827
6602
  tiles: configState.tiles,
@@ -5830,10 +6605,9 @@ function SmartCanvasAppInner({
5830
6605
  canvasTitle: configState.canvasTitle,
5831
6606
  telemetry,
5832
6607
  launcherLabel: launcherLabel != null ? launcherLabel : (_b = configState.launcher) == null ? void 0 : _b.label,
5833
- launcherIcon: (_c = configState.launcher) == null ? void 0 : _c.icon,
5834
- launcherAnimate: (_d = configState.launcher) == null ? void 0 : _d.animate,
5835
- launcherAnimationStyle: (_e = configState.launcher) == null ? void 0 : _e.animationStyle,
5836
- notificationCount: (_g = (_f = configState.launcher) == null ? void 0 : _f.notificationCount) != null ? _g : configState.tiles.length,
6608
+ launcherAnimate: (_c = configState.launcher) == null ? void 0 : _c.animate,
6609
+ launcherAnimationStyle: (_d = configState.launcher) == null ? void 0 : _d.animationStyle,
6610
+ notificationCount: (_f = (_e = configState.launcher) == null ? void 0 : _e.notificationCount) != null ? _f : configState.tiles.length,
5837
6611
  footerSlot,
5838
6612
  isOpen: open,
5839
6613
  onToggle: () => controller.toggle(),
@@ -5846,7 +6620,7 @@ function SmartCanvasAppInner({
5846
6620
 
5847
6621
  // src/SmartCanvasElement.tsx
5848
6622
  import { createRoot as createRoot2 } from "react-dom/client";
5849
- import { jsx as jsx9 } from "react/jsx-runtime";
6623
+ import { jsx as jsx10 } from "react/jsx-runtime";
5850
6624
  var TAG_NAME = "smart-canvas";
5851
6625
  var BASE_CSS = `
5852
6626
  :host {
@@ -5947,13 +6721,13 @@ var SmartCanvasElement = class extends HTMLElement {
5947
6721
  __privateSet(this, _root, createRoot2(__privateGet(this, _mount)));
5948
6722
  }
5949
6723
  __privateGet(this, _root).render(
5950
- /* @__PURE__ */ jsx9(
6724
+ /* @__PURE__ */ jsx10(
5951
6725
  ShadowRootProvider,
5952
6726
  {
5953
6727
  shadowRoot: __privateGet(this, _shadow),
5954
6728
  portalRoot: __privateGet(this, _portalRoot),
5955
6729
  overlayContainer: __privateGet(this, _overlayContainer),
5956
- children: /* @__PURE__ */ jsx9(SmartCanvasApp, { ...__privateGet(this, _lastAppProps), controller: __privateGet(this, _controller), canvasHost: this })
6730
+ children: /* @__PURE__ */ jsx10(SmartCanvasApp, { ...__privateGet(this, _lastAppProps), controller: __privateGet(this, _controller), canvasHost: this })
5957
6731
  }
5958
6732
  )
5959
6733
  );
@@ -6973,17 +7747,18 @@ var PostHogAdapter = class {
6973
7747
  __publicField(this, "client");
6974
7748
  __publicField(this, "featureFlagsCallback");
6975
7749
  __publicField(this, "captureCallback");
6976
- __publicField(this, "consentUnsub");
7750
+ __publicField(this, "rrwebCallback");
6977
7751
  this.client = options.client;
6978
7752
  this.featureFlagsCallback = options.onFeatureFlagsLoaded;
6979
7753
  this.captureCallback = options.onCapture;
7754
+ this.rrwebCallback = options.onRRWebEvent;
6980
7755
  if (!this.client && options.consent && options.requireExplicitConsent && typeof window !== "undefined" && options.apiKey) {
6981
7756
  const consent = options.consent;
6982
7757
  const currentStatus = consent.getStatus();
6983
7758
  if (currentStatus === "granted") {
6984
7759
  this.initPostHog();
6985
7760
  }
6986
- this.consentUnsub = consent.subscribe((status) => {
7761
+ consent.subscribe((status) => {
6987
7762
  if (status === "granted") {
6988
7763
  if (!this.client) {
6989
7764
  this.initPostHog();
@@ -7010,68 +7785,119 @@ var PostHogAdapter = class {
7010
7785
  if (!options.apiKey) return;
7011
7786
  const enableFeatureFlags = (_a2 = options.enableFeatureFlags) != null ? _a2 : true;
7012
7787
  const instanceName = `syntro_${options.apiKey.slice(-6) || "sdk"}`;
7013
- this.client = posthog.init(
7014
- options.apiKey,
7015
- {
7016
- api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
7017
- // Feature flags for segment membership (in_segment_* flags)
7018
- // When enabled, /decide is called to get segment flags
7019
- advanced_disable_feature_flags: !enableFeatureFlags,
7020
- advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
7021
- // Full-page tracking - all ON by default
7022
- autocapture: (_c = options.autocapture) != null ? _c : true,
7023
- capture_pageview: (_d = options.capturePageview) != null ? _d : true,
7024
- capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
7025
- disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
7026
- // CRITICAL: Disable user agent filtering to allow headless Chrome
7027
- // PostHog blocks "HeadlessChrome" user agents by default as bot detection
7028
- // This enables session recording in Playwright/crawler sessions
7029
- opt_out_useragent_filter: true,
7030
- // Cross-domain iframe recording for embeds
7031
- session_recording: {
7032
- recordCrossDomainIFrames: true
7033
- },
7034
- // Capture performance metrics
7035
- capture_performance: true,
7036
- // Enable web vitals
7037
- enable_recording_console_log: true,
7038
- // Bootstrap callback for when flags are loaded
7039
- loaded: (ph) => {
7040
- if (enableFeatureFlags && this.featureFlagsCallback) {
7041
- ph.onFeatureFlags(() => {
7042
- const allFlags = this.getAllFeatureFlags();
7043
- if (allFlags && this.featureFlagsCallback) {
7044
- this.featureFlagsCallback(allFlags);
7045
- }
7046
- });
7047
- const existingFlags = this.getAllFeatureFlags();
7048
- if (existingFlags && Object.keys(existingFlags).length > 0) {
7049
- this.featureFlagsCallback(existingFlags);
7050
- }
7051
- }
7052
- if (this.captureCallback) {
7053
- ph.on("eventCaptured", (data) => {
7054
- var _a3;
7055
- const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
7056
- const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
7057
- if (typeof eventName === "string") {
7058
- (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
7059
- }
7060
- });
7061
- }
7062
- }
7788
+ const baseHost = (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com";
7789
+ const apiHost = `${baseHost.replace(/\/$/, "")}/t/${options.apiKey}`;
7790
+ const initOptions = {
7791
+ api_host: apiHost,
7792
+ // Feature flags for segment membership (in_segment_* flags)
7793
+ // When enabled, /decide is called to get segment flags
7794
+ advanced_disable_feature_flags: !enableFeatureFlags,
7795
+ advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
7796
+ // Full-page tracking - all ON by default
7797
+ autocapture: (_c = options.autocapture) != null ? _c : true,
7798
+ capture_pageview: (_d = options.capturePageview) != null ? _d : "history_change",
7799
+ capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
7800
+ disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
7801
+ // CRITICAL: Disable user agent filtering to allow headless Chrome
7802
+ // PostHog blocks "HeadlessChrome" user agents by default as bot detection
7803
+ // This enables session recording in Playwright/crawler sessions
7804
+ opt_out_useragent_filter: true,
7805
+ // Cross-domain iframe recording for embeds
7806
+ session_recording: {
7807
+ recordCrossDomainIFrames: true
7063
7808
  },
7809
+ // Capture performance metrics
7810
+ capture_performance: true,
7811
+ // Enable web vitals
7812
+ enable_recording_console_log: true
7813
+ };
7814
+ const result = posthog.init(
7815
+ options.apiKey,
7816
+ initOptions,
7064
7817
  instanceName
7065
7818
  );
7819
+ if (result) {
7820
+ this.client = result;
7821
+ }
7822
+ if (this.captureCallback && this.client) {
7823
+ this.client.on("eventCaptured", (...args) => {
7824
+ var _a3;
7825
+ const data = args[0];
7826
+ const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
7827
+ const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
7828
+ if (typeof eventName === "string") {
7829
+ (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
7830
+ }
7831
+ });
7832
+ }
7833
+ if (enableFeatureFlags && this.featureFlagsCallback && this.client) {
7834
+ this.client.onFeatureFlags(() => {
7835
+ const allFlags = this.getAllFeatureFlags();
7836
+ if (allFlags && this.featureFlagsCallback) {
7837
+ this.featureFlagsCallback(allFlags);
7838
+ }
7839
+ });
7840
+ const existingFlags = this.getAllFeatureFlags();
7841
+ if (existingFlags && Object.keys(existingFlags).length > 0) {
7842
+ this.featureFlagsCallback(existingFlags);
7843
+ }
7844
+ }
7845
+ if (this.rrwebCallback && this.client) {
7846
+ this.setupRRWebIntercept();
7847
+ }
7848
+ }
7849
+ /**
7850
+ * Set up rrweb event interception on PostHog's session recording.
7851
+ *
7852
+ * PostHog lazy-loads the rrweb recorder. The SessionRecording wrapper has
7853
+ * an `onRRwebEmit` method, but rrweb delivers events directly to the
7854
+ * lazy-loaded recorder instance's `onRRwebEmit`, bypassing the wrapper.
7855
+ * We must find and patch the recorder instance, not the wrapper.
7856
+ *
7857
+ * The recorder instance is stored on a minified property of SessionRecording.
7858
+ * We detect it by looking for an object with both `onRRwebEmit` and `start` methods.
7859
+ */
7860
+ setupRRWebIntercept(retries = 30) {
7861
+ var _a2;
7862
+ const sr = (_a2 = this.client) == null ? void 0 : _a2.sessionRecording;
7863
+ if (!sr) {
7864
+ if (retries > 0) {
7865
+ setTimeout(() => this.setupRRWebIntercept(retries - 1), 500);
7866
+ }
7867
+ return;
7868
+ }
7869
+ let recorder = null;
7870
+ const srRecord = sr;
7871
+ for (const key of Object.getOwnPropertyNames(srRecord)) {
7872
+ const val = srRecord[key];
7873
+ if (val && typeof val === "object" && typeof val.onRRwebEmit === "function" && typeof val.start === "function" && val !== sr) {
7874
+ recorder = val;
7875
+ break;
7876
+ }
7877
+ }
7878
+ if (!recorder) {
7879
+ if (retries > 0) {
7880
+ setTimeout(() => this.setupRRWebIntercept(retries - 1), 500);
7881
+ }
7882
+ return;
7883
+ }
7884
+ const originalEmit = recorder.onRRwebEmit.bind(recorder);
7885
+ recorder.onRRwebEmit = (rawEvent) => {
7886
+ var _a3;
7887
+ (_a3 = this.rrwebCallback) == null ? void 0 : _a3.call(this, { kind: "rrweb", ...rawEvent });
7888
+ originalEmit(rawEvent);
7889
+ };
7890
+ if (typeof window !== "undefined") {
7891
+ window.__RRWEB_INTERCEPT_READY__ = true;
7892
+ }
7066
7893
  }
7067
7894
  /**
7068
7895
  * Get all feature flags from PostHog.
7069
7896
  * Used to extract segment membership flags (in_segment_*).
7070
7897
  */
7071
7898
  getAllFeatureFlags() {
7072
- var _a2, _b, _c;
7073
- const flags = (_c = (_b = (_a2 = this.client) == null ? void 0 : _a2.featureFlags) == null ? void 0 : _b.getFlagVariants) == null ? void 0 : _c.call(_b);
7074
- return flags;
7899
+ var _a2, _b;
7900
+ return (_b = (_a2 = this.client) == null ? void 0 : _a2.featureFlags) == null ? void 0 : _b.getFlagVariants();
7075
7901
  }
7076
7902
  /**
7077
7903
  * Get segment membership flags (in_segment_*) from PostHog.
@@ -7152,6 +7978,59 @@ function createPostHogClient(options = {}) {
7152
7978
  return new PostHogAdapter(options);
7153
7979
  }
7154
7980
 
7981
+ // src/telemetry/InterventionTracker.ts
7982
+ var InterventionTracker = class {
7983
+ constructor(telemetry, variantId) {
7984
+ __publicField(this, "telemetry");
7985
+ __publicField(this, "variantId");
7986
+ __publicField(this, "seenSet", /* @__PURE__ */ new Set());
7987
+ __publicField(this, "triggeredSet", /* @__PURE__ */ new Set());
7988
+ this.telemetry = telemetry;
7989
+ this.variantId = variantId;
7990
+ }
7991
+ trackServed(tiles, actions) {
7992
+ var _a2, _b;
7993
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_config_served", {
7994
+ variant_id: this.variantId,
7995
+ tiles,
7996
+ actions
7997
+ });
7998
+ }
7999
+ trackSeen(interventionId, interventionKind) {
8000
+ var _a2, _b;
8001
+ if (this.seenSet.has(interventionId)) return;
8002
+ this.seenSet.add(interventionId);
8003
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_seen", {
8004
+ variant_id: this.variantId,
8005
+ intervention_id: interventionId,
8006
+ intervention_kind: interventionKind
8007
+ });
8008
+ }
8009
+ trackTriggered(interventionId, interventionKind) {
8010
+ var _a2, _b;
8011
+ if (this.triggeredSet.has(interventionId)) return;
8012
+ this.triggeredSet.add(interventionId);
8013
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_triggered", {
8014
+ variant_id: this.variantId,
8015
+ intervention_id: interventionId,
8016
+ intervention_kind: interventionKind
8017
+ });
8018
+ }
8019
+ trackInteracted(interventionId, interventionKind, interactionType) {
8020
+ var _a2, _b;
8021
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_interacted", {
8022
+ variant_id: this.variantId,
8023
+ intervention_id: interventionId,
8024
+ intervention_kind: interventionKind,
8025
+ interaction_type: interactionType
8026
+ });
8027
+ }
8028
+ resetPage() {
8029
+ this.seenSet.clear();
8030
+ this.triggeredSet.clear();
8031
+ }
8032
+ };
8033
+
7155
8034
  // src/actions/executors/core-flow.ts
7156
8035
  var executeSequence = async (action, context) => {
7157
8036
  const handles = [];
@@ -7422,158 +8301,7 @@ function hasExecutor(kind) {
7422
8301
  return executorRegistry.has(kind);
7423
8302
  }
7424
8303
 
7425
- // src/actions/validation.ts
7426
- var DANGEROUS_ATTRS = /* @__PURE__ */ new Set([
7427
- "onclick",
7428
- "onerror",
7429
- "onload",
7430
- "onmouseover",
7431
- "onfocus",
7432
- "onblur",
7433
- "onchange",
7434
- "onsubmit",
7435
- "onkeydown",
7436
- "onkeyup",
7437
- "onkeypress"
7438
- ]);
7439
- var MAX_HTML_LENGTH = 5e4;
7440
- var MAX_STYLE_COUNT = 50;
7441
- function validateAction(action) {
7442
- const errors = [];
7443
- const warnings = [];
7444
- if (!action || typeof action !== "object") {
7445
- errors.push({
7446
- code: "INVALID_ACTION",
7447
- message: "Action must be an object"
7448
- });
7449
- return { valid: false, errors, warnings };
7450
- }
7451
- const { kind } = action;
7452
- if (!kind || typeof kind !== "string") {
7453
- errors.push({
7454
- code: "MISSING_KIND",
7455
- message: "Action must have a 'kind' property"
7456
- });
7457
- return { valid: false, errors, warnings };
7458
- }
7459
- if (!hasExecutor(kind) && kind !== "core:mountWidget") {
7460
- const registered = executorRegistry.list();
7461
- console.error(
7462
- `[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.`
7463
- );
7464
- errors.push({
7465
- code: "UNKNOWN_KIND",
7466
- message: `Unknown action kind: ${kind}`,
7467
- field: "kind"
7468
- });
7469
- return { valid: false, errors, warnings };
7470
- }
7471
- switch (kind) {
7472
- case "overlays:highlight":
7473
- case "overlays:pulse":
7474
- case "navigation:scrollTo":
7475
- validateAnchorAction(action, errors, warnings);
7476
- break;
7477
- case "overlays:badge":
7478
- validateAnchorAction(action, errors, warnings);
7479
- validateBadgeAction(action, errors, warnings);
7480
- break;
7481
- case "overlays:tooltip":
7482
- validateAnchorAction(action, errors, warnings);
7483
- validateTooltipAction(action, errors, warnings);
7484
- break;
7485
- case "overlays:modal":
7486
- validateModalAction(action, errors, warnings);
7487
- break;
7488
- case "content:insertHtml":
7489
- validateAnchorAction(action, errors, warnings);
7490
- validateInsertHtmlAction(action, errors, warnings);
7491
- break;
7492
- case "content:setText":
7493
- validateAnchorAction(action, errors, warnings);
7494
- validateSetTextAction(action, errors, warnings);
7495
- break;
7496
- case "content:setAttr":
7497
- validateAnchorAction(action, errors, warnings);
7498
- validateSetAttrAction(action, errors, warnings);
7499
- break;
7500
- case "content:addClass":
7501
- case "content:removeClass":
7502
- validateAnchorAction(action, errors, warnings);
7503
- validateClassAction(action, errors, warnings);
7504
- break;
7505
- case "content:setStyle":
7506
- validateAnchorAction(action, errors, warnings);
7507
- validateSetStyleAction(action, errors, warnings);
7508
- break;
7509
- case "core:mountWidget":
7510
- validateMountWidgetAction(action, errors, warnings);
7511
- break;
7512
- case "core:wait":
7513
- validateWaitAction(action, errors, warnings);
7514
- break;
7515
- case "core:sequence":
7516
- validateSequenceAction(action, errors, warnings);
7517
- break;
7518
- case "core:parallel":
7519
- validateParallelAction(action, errors, warnings);
7520
- break;
7521
- case "core:tour":
7522
- validateTourAction(action, errors, warnings);
7523
- break;
7524
- case "navigation:navigate":
7525
- validateNavigateAction(action, errors, warnings);
7526
- break;
7527
- }
7528
- return {
7529
- valid: errors.length === 0,
7530
- errors,
7531
- warnings
7532
- };
7533
- }
7534
- function validateAnchorAction(action, errors, warnings) {
7535
- const anchorId = action.anchorId;
7536
- if (!anchorId || typeof anchorId !== "object") {
7537
- errors.push({
7538
- code: "MISSING_ANCHOR_ID",
7539
- message: "Action requires an 'anchorId' object with a 'selector' string",
7540
- field: "anchorId"
7541
- });
7542
- return;
7543
- }
7544
- if (!anchorId.selector || typeof anchorId.selector !== "string") {
7545
- errors.push({
7546
- code: "MISSING_ANCHOR_SELECTOR",
7547
- message: "anchorId requires a 'selector' string",
7548
- field: "anchorId.selector"
7549
- });
7550
- } else if (anchorId.selector.length > 200) {
7551
- warnings.push({
7552
- code: "LONG_ANCHOR_ID",
7553
- message: "Anchor selector is unusually long",
7554
- suggestion: "Consider using a shorter, more descriptive selector"
7555
- });
7556
- }
7557
- if (anchorId.route === void 0 || anchorId.route === null) {
7558
- errors.push({
7559
- code: "MISSING_ANCHOR_ROUTE",
7560
- message: `anchorId requires a 'route' (string or array of strings). Use "**" for all routes.`,
7561
- field: "anchorId.route"
7562
- });
7563
- } else {
7564
- const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
7565
- for (const route of routes) {
7566
- if (typeof route !== "string") {
7567
- errors.push({
7568
- code: "INVALID_ANCHOR_ROUTE",
7569
- message: "anchorId.route must be a string or array of strings",
7570
- field: "anchorId.route"
7571
- });
7572
- break;
7573
- }
7574
- }
7575
- }
7576
- }
8304
+ // src/actions/validation-rules.ts
7577
8305
  function validateBadgeAction(action, errors, warnings) {
7578
8306
  if (!action.content || typeof action.content !== "string") {
7579
8307
  errors.push({
@@ -7960,19 +8688,172 @@ function validateTourAction(action, errors, warnings) {
7960
8688
  }
7961
8689
  }
7962
8690
  }
7963
- function validateActions(actions) {
7964
- var _a2;
8691
+
8692
+ // src/actions/validation-core.ts
8693
+ var DANGEROUS_ATTRS = /* @__PURE__ */ new Set([
8694
+ "onclick",
8695
+ "onerror",
8696
+ "onload",
8697
+ "onmouseover",
8698
+ "onfocus",
8699
+ "onblur",
8700
+ "onchange",
8701
+ "onsubmit",
8702
+ "onkeydown",
8703
+ "onkeyup",
8704
+ "onkeypress"
8705
+ ]);
8706
+ var MAX_HTML_LENGTH = 5e4;
8707
+ var MAX_STYLE_COUNT = 50;
8708
+ function validateAction(action) {
7965
8709
  const errors = [];
7966
8710
  const warnings = [];
7967
- if (!Array.isArray(actions)) {
7968
- console.error("[ActionValidation] validateActions called with non-array:", typeof actions);
8711
+ if (!action || typeof action !== "object") {
7969
8712
  errors.push({
7970
- code: "INVALID_ACTIONS",
7971
- message: "Actions must be an array"
8713
+ code: "INVALID_ACTION",
8714
+ message: "Action must be an object"
7972
8715
  });
7973
8716
  return { valid: false, errors, warnings };
7974
8717
  }
7975
- for (let i = 0; i < actions.length; i++) {
8718
+ const { kind } = action;
8719
+ if (!kind || typeof kind !== "string") {
8720
+ errors.push({
8721
+ code: "MISSING_KIND",
8722
+ message: "Action must have a 'kind' property"
8723
+ });
8724
+ return { valid: false, errors, warnings };
8725
+ }
8726
+ if (!hasExecutor(kind) && kind !== "core:mountWidget") {
8727
+ const registered = executorRegistry.list();
8728
+ console.error(
8729
+ `[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.`
8730
+ );
8731
+ errors.push({
8732
+ code: "UNKNOWN_KIND",
8733
+ message: `Unknown action kind: ${kind}`,
8734
+ field: "kind"
8735
+ });
8736
+ return { valid: false, errors, warnings };
8737
+ }
8738
+ switch (kind) {
8739
+ case "overlays:highlight":
8740
+ case "overlays:pulse":
8741
+ case "navigation:scrollTo":
8742
+ validateAnchorAction(action, errors, warnings);
8743
+ break;
8744
+ case "overlays:badge":
8745
+ validateAnchorAction(action, errors, warnings);
8746
+ validateBadgeAction(action, errors, warnings);
8747
+ break;
8748
+ case "overlays:tooltip":
8749
+ validateAnchorAction(action, errors, warnings);
8750
+ validateTooltipAction(action, errors, warnings);
8751
+ break;
8752
+ case "overlays:modal":
8753
+ validateModalAction(action, errors, warnings);
8754
+ break;
8755
+ case "content:insertHtml":
8756
+ validateAnchorAction(action, errors, warnings);
8757
+ validateInsertHtmlAction(action, errors, warnings);
8758
+ break;
8759
+ case "content:setText":
8760
+ validateAnchorAction(action, errors, warnings);
8761
+ validateSetTextAction(action, errors, warnings);
8762
+ break;
8763
+ case "content:setAttr":
8764
+ validateAnchorAction(action, errors, warnings);
8765
+ validateSetAttrAction(action, errors, warnings);
8766
+ break;
8767
+ case "content:addClass":
8768
+ case "content:removeClass":
8769
+ validateAnchorAction(action, errors, warnings);
8770
+ validateClassAction(action, errors, warnings);
8771
+ break;
8772
+ case "content:setStyle":
8773
+ validateAnchorAction(action, errors, warnings);
8774
+ validateSetStyleAction(action, errors, warnings);
8775
+ break;
8776
+ case "core:mountWidget":
8777
+ validateMountWidgetAction(action, errors, warnings);
8778
+ break;
8779
+ case "core:wait":
8780
+ validateWaitAction(action, errors, warnings);
8781
+ break;
8782
+ case "core:sequence":
8783
+ validateSequenceAction(action, errors, warnings);
8784
+ break;
8785
+ case "core:parallel":
8786
+ validateParallelAction(action, errors, warnings);
8787
+ break;
8788
+ case "overlays:tour":
8789
+ validateTourAction(action, errors, warnings);
8790
+ break;
8791
+ case "navigation:navigate":
8792
+ validateNavigateAction(action, errors, warnings);
8793
+ break;
8794
+ }
8795
+ return {
8796
+ valid: errors.length === 0,
8797
+ errors,
8798
+ warnings
8799
+ };
8800
+ }
8801
+ function validateAnchorAction(action, errors, warnings) {
8802
+ const anchorId = action.anchorId;
8803
+ if (!anchorId || typeof anchorId !== "object") {
8804
+ errors.push({
8805
+ code: "MISSING_ANCHOR_ID",
8806
+ message: "Action requires an 'anchorId' object with a 'selector' string",
8807
+ field: "anchorId"
8808
+ });
8809
+ return;
8810
+ }
8811
+ if (!anchorId.selector || typeof anchorId.selector !== "string") {
8812
+ errors.push({
8813
+ code: "MISSING_ANCHOR_SELECTOR",
8814
+ message: "anchorId requires a 'selector' string",
8815
+ field: "anchorId.selector"
8816
+ });
8817
+ } else if (anchorId.selector.length > 200) {
8818
+ warnings.push({
8819
+ code: "LONG_ANCHOR_ID",
8820
+ message: "Anchor selector is unusually long",
8821
+ suggestion: "Consider using a shorter, more descriptive selector"
8822
+ });
8823
+ }
8824
+ if (anchorId.route === void 0 || anchorId.route === null) {
8825
+ errors.push({
8826
+ code: "MISSING_ANCHOR_ROUTE",
8827
+ message: `anchorId requires a 'route' (string or array of strings). Use "**" for all routes.`,
8828
+ field: "anchorId.route"
8829
+ });
8830
+ } else {
8831
+ const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
8832
+ for (const route of routes) {
8833
+ if (typeof route !== "string") {
8834
+ errors.push({
8835
+ code: "INVALID_ANCHOR_ROUTE",
8836
+ message: "anchorId.route must be a string or array of strings",
8837
+ field: "anchorId.route"
8838
+ });
8839
+ break;
8840
+ }
8841
+ }
8842
+ }
8843
+ }
8844
+ function validateActions(actions) {
8845
+ var _a2;
8846
+ const errors = [];
8847
+ const warnings = [];
8848
+ if (!Array.isArray(actions)) {
8849
+ console.error("[ActionValidation] validateActions called with non-array:", typeof actions);
8850
+ errors.push({
8851
+ code: "INVALID_ACTIONS",
8852
+ message: "Actions must be an array"
8853
+ });
8854
+ return { valid: false, errors, warnings };
8855
+ }
8856
+ for (let i = 0; i < actions.length; i++) {
7976
8857
  const result = validateAction(actions[i]);
7977
8858
  if (!result.valid) {
7978
8859
  const action = actions[i];
@@ -8106,7 +8987,7 @@ function createActionEngine(options) {
8106
8987
  }
8107
8988
  return executor(action, context);
8108
8989
  }
8109
- function subscribeForReeval(id, action, triggerWhen, handle) {
8990
+ function subscribeForReeval(id, action, triggerWhen, _handle) {
8110
8991
  if (!runtime3) return;
8111
8992
  const unsubs = [];
8112
8993
  const onReeval = async () => {
@@ -8250,13 +9131,9 @@ function createActionEngine(options) {
8250
9131
  entry2.state = "reverted";
8251
9132
  publishEvent("action.reverted", { id, kind: action.kind });
8252
9133
  } catch (error2) {
8253
- entry2.state = "failed";
8254
- publishEvent("action.failed", {
8255
- id,
8256
- kind: action.kind,
8257
- error: String(error2)
8258
- });
8259
- throw error2;
9134
+ console.warn(`[ActionEngine] Cleanup error for ${action.kind} (${id}), ignoring:`, error2);
9135
+ entry2.state = "reverted";
9136
+ publishEvent("action.reverted", { id, kind: action.kind });
8260
9137
  } finally {
8261
9138
  activeActions.delete(id);
8262
9139
  }
@@ -8309,15 +9186,30 @@ function createActionEngine(options) {
8309
9186
  const handles = [];
8310
9187
  const appliedHandles = [];
8311
9188
  try {
8312
- for (const action of actions) {
8313
- const handle = await apply(action);
8314
- handles.push(handle);
8315
- appliedHandles.push(handle);
9189
+ const results = await Promise.allSettled(actions.map((action) => apply(action)));
9190
+ const errors = [];
9191
+ for (const result of results) {
9192
+ if (result.status === "fulfilled") {
9193
+ handles.push(result.value);
9194
+ appliedHandles.push(result.value);
9195
+ } else {
9196
+ errors.push(
9197
+ result.reason instanceof Error ? result.reason : new Error(String(result.reason))
9198
+ );
9199
+ }
9200
+ }
9201
+ if (errors.length > 0 && appliedHandles.length === 0) {
9202
+ throw errors[0];
9203
+ }
9204
+ if (errors.length > 0) {
9205
+ console.warn(
9206
+ `[ActionEngine] ${errors.length}/${actions.length} action(s) failed in batch:`,
9207
+ errors.map((e) => e.message).join("; ")
9208
+ );
8316
9209
  }
8317
9210
  } catch (error2) {
8318
9211
  console.error(
8319
- `[ActionEngine] Batch apply FAILED at action ${appliedHandles.length + 1}/${actions.length}.`,
8320
- `Successfully applied: ${appliedHandles.length}. Rolling back...`,
9212
+ `[ActionEngine] Batch apply FAILED. Successfully applied: ${appliedHandles.length}. Rolling back...`,
8321
9213
  error2
8322
9214
  );
8323
9215
  for (const handle of appliedHandles) {
@@ -8358,7 +9250,7 @@ function createActionEngine(options) {
8358
9250
  state: entry.state
8359
9251
  }));
8360
9252
  }
8361
- function destroy() {
9253
+ async function destroy() {
8362
9254
  for (const unsubs of conditionalUnsubs.values()) {
8363
9255
  for (const unsub of unsubs) {
8364
9256
  try {
@@ -8368,16 +9260,27 @@ function createActionEngine(options) {
8368
9260
  }
8369
9261
  }
8370
9262
  conditionalUnsubs.clear();
9263
+ const cleanupPromises = [];
8371
9264
  for (const entry of activeActions.values()) {
8372
9265
  if (entry.state === "applied") {
8373
9266
  try {
8374
- entry.cleanup();
9267
+ const result = entry.cleanup();
9268
+ if (result && typeof result.then === "function") {
9269
+ cleanupPromises.push(
9270
+ result.catch((error2) => {
9271
+ console.error(`[ActionEngine] Error during async cleanup:`, error2);
9272
+ })
9273
+ );
9274
+ }
8375
9275
  } catch (error2) {
8376
9276
  console.error(`[ActionEngine] Error during cleanup:`, error2);
8377
9277
  }
8378
9278
  }
8379
9279
  }
8380
9280
  activeActions.clear();
9281
+ if (cleanupPromises.length > 0) {
9282
+ await Promise.all(cleanupPromises);
9283
+ }
8381
9284
  }
8382
9285
  return {
8383
9286
  apply,
@@ -8400,6 +9303,7 @@ function createAnchorResolver(opts) {
8400
9303
  function resolve(selector) {
8401
9304
  if (!root) return null;
8402
9305
  try {
9306
+ if (root.matches(selector)) return root;
8403
9307
  return root.querySelector(selector);
8404
9308
  } catch {
8405
9309
  return null;
@@ -9079,6 +9983,66 @@ function createEventAccumulator(options) {
9079
9983
  };
9080
9984
  }
9081
9985
 
9986
+ // src/events/validation.ts
9987
+ var APP_PREFIX = "app:";
9988
+ var RESERVED_PREFIX = "syntro:";
9989
+ var SEGMENT_PATTERN = /^[a-z][a-z0-9_]*$/;
9990
+ function validateEventName(name) {
9991
+ if (!name) {
9992
+ return { valid: false, reason: "Event name cannot be empty" };
9993
+ }
9994
+ if (name.startsWith(RESERVED_PREFIX)) {
9995
+ return { valid: false, reason: '"syntro:" prefix is reserved for internal SDK events' };
9996
+ }
9997
+ if (!name.startsWith(APP_PREFIX)) {
9998
+ return { valid: false, reason: `Custom events must start with "app:" prefix. Got: "${name}"` };
9999
+ }
10000
+ const segments = name.slice(APP_PREFIX.length).split(":");
10001
+ if (segments.length < 2) {
10002
+ return {
10003
+ valid: false,
10004
+ reason: `Event name must have at least 2 segments after "app:" (app:{category}:{action}). Got: "${name}"`
10005
+ };
10006
+ }
10007
+ for (const segment of segments) {
10008
+ if (!SEGMENT_PATTERN.test(segment)) {
10009
+ return {
10010
+ valid: false,
10011
+ reason: `Segment "${segment}" must be lowercase alphanumeric + underscores. Got: "${name}"`
10012
+ };
10013
+ }
10014
+ }
10015
+ return { valid: true };
10016
+ }
10017
+ function isSerializable(value) {
10018
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
10019
+ }
10020
+ function checkDepth(obj, maxDepth, current = 0) {
10021
+ if (current >= maxDepth) return false;
10022
+ for (const value of Object.values(obj)) {
10023
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
10024
+ if (!checkDepth(value, maxDepth, current + 1)) return false;
10025
+ }
10026
+ }
10027
+ return true;
10028
+ }
10029
+ function validateProps(props) {
10030
+ if (props === void 0) return { valid: true };
10031
+ if (props === null || typeof props !== "object" || Array.isArray(props)) {
10032
+ return { valid: false, reason: "Props must be a plain object" };
10033
+ }
10034
+ if (!checkDepth(props, 2)) {
10035
+ return { valid: false, reason: "Props nesting depth exceeds 2 levels" };
10036
+ }
10037
+ const stripped = [];
10038
+ for (const [key, value] of Object.entries(props)) {
10039
+ if (value !== null && typeof value === "object") continue;
10040
+ if (!isSerializable(value)) stripped.push(key);
10041
+ }
10042
+ if (stripped.length > 0) return { valid: true, stripped };
10043
+ return { valid: true };
10044
+ }
10045
+
9082
10046
  // src/events/EventBus.ts
9083
10047
  function matchesFilter(event, filter) {
9084
10048
  if (!filter) return true;
@@ -9110,8 +10074,16 @@ var EventBus = class {
9110
10074
  __publicField(this, "subscriptions", /* @__PURE__ */ new Set());
9111
10075
  __publicField(this, "history", []);
9112
10076
  __publicField(this, "maxHistorySize");
9113
- var _a2;
10077
+ __publicField(this, "debug");
10078
+ __publicField(this, "emitHistory");
10079
+ __publicField(this, "posthogCapture");
10080
+ __publicField(this, "testMode");
10081
+ var _a2, _b, _c, _d, _e;
9114
10082
  this.maxHistorySize = (_a2 = options.maxHistorySize) != null ? _a2 : 100;
10083
+ this.debug = (_b = options.debug) != null ? _b : false;
10084
+ this.emitHistory = (_c = options.history) != null ? _c : null;
10085
+ this.posthogCapture = (_d = options.posthogCapture) != null ? _d : null;
10086
+ this.testMode = (_e = options.testMode) != null ? _e : false;
9115
10087
  }
9116
10088
  /**
9117
10089
  * Subscribe to events matching an optional filter.
@@ -9164,6 +10136,83 @@ var EventBus = class {
9164
10136
  }
9165
10137
  }
9166
10138
  }
10139
+ /**
10140
+ * Emit a validated custom event from the host application.
10141
+ *
10142
+ * Custom events must use the `app:` prefix (e.g. `app:cart:abandoned`).
10143
+ * In debug mode, returns an EmitResult with delivery details.
10144
+ * In production mode, returns undefined.
10145
+ */
10146
+ emit(name, props) {
10147
+ const nameResult = validateEventName(name);
10148
+ if (!nameResult.valid) {
10149
+ console.warn(`[EventBus] emit() rejected: ${nameResult.reason}`);
10150
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
10151
+ }
10152
+ const propsResult = validateProps(props);
10153
+ if (!propsResult.valid) {
10154
+ console.warn(`[EventBus] emit() rejected props: ${propsResult.reason}`);
10155
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
10156
+ }
10157
+ const event = {
10158
+ ts: Date.now(),
10159
+ name,
10160
+ source: "custom",
10161
+ props,
10162
+ schemaVersion: EVENT_SCHEMA_VERSION
10163
+ };
10164
+ this.history.push(event);
10165
+ if (this.history.length > this.maxHistorySize) {
10166
+ this.history.shift();
10167
+ }
10168
+ let listenersNotified = 0;
10169
+ for (const subscription of this.subscriptions) {
10170
+ if (matchesFilter(event, subscription.filter)) {
10171
+ try {
10172
+ subscription.callback(event);
10173
+ listenersNotified++;
10174
+ } catch (err) {
10175
+ console.error("[EventBus] Subscriber error:", err);
10176
+ listenersNotified++;
10177
+ }
10178
+ }
10179
+ }
10180
+ let posthogCaptured = false;
10181
+ if (this.posthogCapture && !this.testMode) {
10182
+ try {
10183
+ this.posthogCapture(name, props);
10184
+ posthogCaptured = true;
10185
+ } catch (err) {
10186
+ console.error("[EventBus] PostHog capture error:", err);
10187
+ }
10188
+ }
10189
+ if (this.emitHistory) {
10190
+ this.emitHistory.record({
10191
+ name,
10192
+ props,
10193
+ source: "custom",
10194
+ timestamp: event.ts,
10195
+ matchedRules: []
10196
+ });
10197
+ }
10198
+ if (this.debug) {
10199
+ console.debug("[EventBus] emit()", { name, props, listenersNotified, posthogCaptured });
10200
+ return {
10201
+ delivered: true,
10202
+ matchedRules: [],
10203
+ posthogCaptured,
10204
+ listenersNotified
10205
+ };
10206
+ }
10207
+ return void 0;
10208
+ }
10209
+ /**
10210
+ * Set the PostHog capture function after construction.
10211
+ * Used by bootstrap to wire PostHog after the EventBus is created.
10212
+ */
10213
+ setPosthogCapture(fn) {
10214
+ this.posthogCapture = fn;
10215
+ }
9167
10216
  /**
9168
10217
  * Get recent events matching an optional filter.
9169
10218
  */
@@ -9214,124 +10263,26 @@ function createEventBus(options = {}) {
9214
10263
  return new EventBus(options);
9215
10264
  }
9216
10265
 
9217
- // src/events/normalizers/posthog.ts
9218
- var POSTHOG_EVENT_MAP = {
9219
- // Autocapture events
9220
- $autocapture: "ui.click",
9221
- // Default autocapture is usually clicks
9222
- $click: StandardEvents.UI_CLICK,
9223
- $scroll: StandardEvents.UI_SCROLL,
9224
- $input: StandardEvents.UI_INPUT,
9225
- $change: StandardEvents.UI_CHANGE,
9226
- $submit: StandardEvents.UI_SUBMIT,
9227
- // Navigation events
9228
- $pageview: StandardEvents.NAV_PAGE_VIEW,
9229
- $pageleave: StandardEvents.NAV_PAGE_LEAVE,
9230
- // Session events
9231
- $session_start: "session.start",
9232
- // Identify events
9233
- $identify: "user.identify"
9234
- };
9235
- function getEventName(phEvent) {
9236
- var _a2, _b;
9237
- const eventName = phEvent.event;
9238
- if (typeof eventName !== "string") {
9239
- return "posthog.unknown";
10266
+ // src/events/history.ts
10267
+ var EventHistory = class {
10268
+ constructor(maxSize = 100) {
10269
+ __publicField(this, "entries", []);
10270
+ __publicField(this, "maxSize");
10271
+ this.maxSize = maxSize;
9240
10272
  }
9241
- if (POSTHOG_EVENT_MAP[eventName]) {
9242
- return POSTHOG_EVENT_MAP[eventName];
10273
+ record(entry) {
10274
+ this.entries.push(entry);
10275
+ if (this.maxSize > 0 && this.entries.length > this.maxSize) {
10276
+ this.entries.shift();
10277
+ }
9243
10278
  }
9244
- if (eventName === "$autocapture") {
9245
- const tagName = (_a2 = phEvent.properties) == null ? void 0 : _a2.$tag_name;
9246
- const eventType = (_b = phEvent.properties) == null ? void 0 : _b.$event_type;
9247
- if (eventType === "submit") return StandardEvents.UI_SUBMIT;
9248
- if (eventType === "change") return StandardEvents.UI_CHANGE;
9249
- if (tagName === "input" || tagName === "textarea") return StandardEvents.UI_INPUT;
9250
- return StandardEvents.UI_CLICK;
10279
+ getAll() {
10280
+ return [...this.entries];
9251
10281
  }
9252
- if (!eventName.startsWith("$")) {
9253
- return `posthog.${eventName}`;
10282
+ clear() {
10283
+ this.entries = [];
9254
10284
  }
9255
- return eventName.replace("$", "posthog.");
9256
- }
9257
- function extractProps(phEvent) {
9258
- var _a2;
9259
- const props = {};
9260
- const phProps = phEvent.properties || {};
9261
- const elements = phProps.$elements;
9262
- if (phProps.$tag_name) {
9263
- props.tagName = phProps.$tag_name;
9264
- } else if ((_a2 = elements == null ? void 0 : elements[0]) == null ? void 0 : _a2.tag_name) {
9265
- props.tagName = elements[0].tag_name;
9266
- }
9267
- if (phProps.$el_text) props.elementText = phProps.$el_text;
9268
- if (elements) props.elements = elements;
9269
- if (phProps.$current_url) props.url = phProps.$current_url;
9270
- if (phProps.$pathname) props.pathname = phProps.$pathname;
9271
- if (phProps.$host) props.host = phProps.$host;
9272
- if (phProps.$viewport_width) props.viewportWidth = phProps.$viewport_width;
9273
- if (phProps.$viewport_height) props.viewportHeight = phProps.$viewport_height;
9274
- if (phProps.$session_id) props.sessionId = phProps.$session_id;
9275
- if (phProps.$scroll_depth) props.scrollDepth = phProps.$scroll_depth;
9276
- if (phProps.$scroll_percentage) props.scrollPercentage = phProps.$scroll_percentage;
9277
- props.originalEvent = phEvent.event;
9278
- return props;
9279
- }
9280
- function normalizePostHogEvent(phEvent) {
9281
- let ts;
9282
- if (typeof phEvent.timestamp === "number") {
9283
- ts = phEvent.timestamp;
9284
- } else if (typeof phEvent.timestamp === "string") {
9285
- ts = new Date(phEvent.timestamp).getTime();
9286
- } else {
9287
- ts = Date.now();
9288
- }
9289
- return {
9290
- ts,
9291
- name: getEventName(phEvent),
9292
- source: "posthog",
9293
- props: extractProps(phEvent),
9294
- schemaVersion: EVENT_SCHEMA_VERSION
9295
- };
9296
- }
9297
- function shouldNormalizeEvent(phEvent) {
9298
- const eventName = phEvent.event;
9299
- if (typeof eventName !== "string") return false;
9300
- const skipEvents = [
9301
- "$feature_flag_called",
9302
- "$feature_flags",
9303
- "$groups",
9304
- "$groupidentify",
9305
- "$set",
9306
- "$set_once",
9307
- "$unset",
9308
- "$create_alias",
9309
- "$capture_metrics",
9310
- "$performance_event",
9311
- "$web_vitals",
9312
- "$exception",
9313
- "$dead_click",
9314
- "$heatmap"
9315
- ];
9316
- if (skipEvents.includes(eventName)) {
9317
- return false;
9318
- }
9319
- return true;
9320
- }
9321
- function createPostHogNormalizer(publishFn) {
9322
- return (eventName, properties) => {
9323
- if (typeof eventName !== "string") return;
9324
- const phEvent = {
9325
- event: eventName,
9326
- properties,
9327
- timestamp: Date.now()
9328
- };
9329
- if (shouldNormalizeEvent(phEvent)) {
9330
- const normalizedEvent = normalizePostHogEvent(phEvent);
9331
- publishFn(normalizedEvent);
9332
- }
9333
- };
9334
- }
10285
+ };
9335
10286
 
9336
10287
  // src/navigation/NavigationMonitor.ts
9337
10288
  var NavigationMonitor = class {
@@ -10132,9 +11083,18 @@ function createSurfaces(options) {
10132
11083
  }
10133
11084
  async function unmountEntry(entry) {
10134
11085
  var _a2;
10135
- await playExitAnimation(entry.container, entry.options.animation);
10136
- (_a2 = entry.cleanup) == null ? void 0 : _a2.call(entry);
10137
- entry.container.remove();
11086
+ if (entry.container.isConnected) {
11087
+ await playExitAnimation(entry.container, entry.options.animation);
11088
+ }
11089
+ try {
11090
+ (_a2 = entry.cleanup) == null ? void 0 : _a2.call(entry);
11091
+ } catch (error2) {
11092
+ console.warn("[Surfaces] Cleanup error during unmount, ignoring:", error2);
11093
+ }
11094
+ try {
11095
+ entry.container.remove();
11096
+ } catch {
11097
+ }
10138
11098
  mounts.delete(entry.slot);
10139
11099
  publishEvent("surface.unmounted", {
10140
11100
  slot: entry.slot,
@@ -10317,7 +11277,9 @@ var WidgetRegistry = class {
10317
11277
  var _a2;
10318
11278
  const mounted = this.mountedWidgets.get(mountId);
10319
11279
  if (mounted) {
10320
- (_a2 = mounted.cleanup) == null ? void 0 : _a2.call(mounted);
11280
+ if (container.isConnected) {
11281
+ (_a2 = mounted.cleanup) == null ? void 0 : _a2.call(mounted);
11282
+ }
10321
11283
  this.mountedWidgets.delete(mountId);
10322
11284
  container.removeAttribute("data-widget-mount-id");
10323
11285
  container.removeAttribute("data-widget-id");
@@ -10433,7 +11395,16 @@ function matchesAnchorRoute(anchorId) {
10433
11395
  const pathname = typeof window !== "undefined" ? window.location.pathname : "/";
10434
11396
  const normalizedPath = pathname.replace(/\/$/, "") || "/";
10435
11397
  const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
10436
- return routes.some((pattern) => matchRoutePattern(normalizedPath, pattern));
11398
+ return routes.some((pattern) => {
11399
+ let normalized = pattern;
11400
+ if (/^https?:\/\//.test(normalized)) {
11401
+ try {
11402
+ normalized = new URL(normalized).pathname;
11403
+ } catch {
11404
+ }
11405
+ }
11406
+ return matchRoutePattern(normalizedPath, normalized);
11407
+ });
10437
11408
  }
10438
11409
  function createSmartCanvasRuntime(options = {}) {
10439
11410
  var _a2, _b, _c, _d;
@@ -10605,6 +11576,155 @@ function encodeToken(payload) {
10605
11576
  return TOKEN_PREFIX + base64;
10606
11577
  }
10607
11578
 
11579
+ // src/bootstrap-init.ts
11580
+ function getEnvVar(name) {
11581
+ if (typeof process !== "undefined" && process.env) {
11582
+ return process.env[name];
11583
+ }
11584
+ try {
11585
+ const meta = (0, eval)("import.meta");
11586
+ if (meta == null ? void 0 : meta.env) {
11587
+ return meta.env[name];
11588
+ }
11589
+ } catch {
11590
+ }
11591
+ return void 0;
11592
+ }
11593
+ var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
11594
+ function loadCachedSegmentAttributes() {
11595
+ if (typeof window === "undefined") return {};
11596
+ try {
11597
+ const cached = localStorage.getItem(SEGMENT_CACHE_KEY);
11598
+ if (cached) {
11599
+ const attrs = JSON.parse(cached);
11600
+ debug("Syntro Bootstrap", "Loaded cached segment attributes:", attrs);
11601
+ return attrs;
11602
+ }
11603
+ } catch (err) {
11604
+ warn("Syntro Bootstrap", "Failed to load cached segment attributes:", err);
11605
+ }
11606
+ return {};
11607
+ }
11608
+ function cacheSegmentAttributes(attrs) {
11609
+ if (typeof window === "undefined") return;
11610
+ try {
11611
+ localStorage.setItem(SEGMENT_CACHE_KEY, JSON.stringify(attrs));
11612
+ debug("Syntro Bootstrap", "Cached segment attributes:", attrs);
11613
+ } catch (err) {
11614
+ warn("Syntro Bootstrap", "Failed to cache segment attributes:", err);
11615
+ }
11616
+ }
11617
+ function extractSegmentFlags(allFlags) {
11618
+ if (!allFlags) return {};
11619
+ const segmentFlags = {};
11620
+ for (const [key, value] of Object.entries(allFlags)) {
11621
+ if (key.startsWith("in_segment_")) {
11622
+ segmentFlags[key] = value === true;
11623
+ }
11624
+ }
11625
+ return segmentFlags;
11626
+ }
11627
+ function collectBrowserMetadata() {
11628
+ var _a2, _b;
11629
+ if (typeof window === "undefined") return {};
11630
+ const attrs = {};
11631
+ try {
11632
+ const params = new URLSearchParams(window.location.search);
11633
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
11634
+ const val = params.get(key);
11635
+ if (val) attrs[key] = val;
11636
+ }
11637
+ } catch {
11638
+ }
11639
+ try {
11640
+ if (navigator.language) attrs.browser_language = navigator.language;
11641
+ if ((_a2 = navigator.languages) == null ? void 0 : _a2.length) attrs.browser_languages = [...navigator.languages];
11642
+ } catch {
11643
+ }
11644
+ try {
11645
+ const w = window.innerWidth;
11646
+ attrs.device_type = w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
11647
+ } catch {
11648
+ }
11649
+ try {
11650
+ if (document.referrer) {
11651
+ attrs.referrer = document.referrer;
11652
+ try {
11653
+ attrs.referrer_hostname = new URL(document.referrer).hostname;
11654
+ } catch {
11655
+ }
11656
+ }
11657
+ } catch {
11658
+ }
11659
+ try {
11660
+ const ua = navigator.userAgent;
11661
+ if (ua.includes("Edg/")) attrs.browser = "Edge";
11662
+ else if (ua.includes("OPR/") || ua.includes("Opera")) attrs.browser = "Opera";
11663
+ else if (ua.includes("Chrome/") && !ua.includes("Chromium")) attrs.browser = "Chrome";
11664
+ else if (ua.includes("Safari/") && !ua.includes("Chrome")) attrs.browser = "Safari";
11665
+ else if (ua.includes("Firefox/")) attrs.browser = "Firefox";
11666
+ if (ua.includes("Windows")) attrs.os = "Windows";
11667
+ else if (ua.includes("iPhone") || ua.includes("iPad")) attrs.os = "iOS";
11668
+ else if (ua.includes("Mac OS X") || ua.includes("Macintosh")) attrs.os = "macOS";
11669
+ else if (ua.includes("Android")) attrs.os = "Android";
11670
+ else if (ua.includes("Linux")) attrs.os = "Linux";
11671
+ } catch {
11672
+ }
11673
+ try {
11674
+ attrs.page_url = window.location.href;
11675
+ attrs.page_path = window.location.pathname;
11676
+ attrs.page_host = window.location.hostname;
11677
+ if (window.location.search) attrs.page_query = window.location.search;
11678
+ } catch {
11679
+ }
11680
+ try {
11681
+ const tg = (_b = window.Telegram) == null ? void 0 : _b.WebApp;
11682
+ if (tg) {
11683
+ attrs.surface_type = "telegram";
11684
+ attrs.surface_host = tg.platform || "unknown";
11685
+ } else {
11686
+ attrs.surface_type = "web";
11687
+ attrs.surface_host = "web";
11688
+ }
11689
+ } catch {
11690
+ attrs.surface_type = "web";
11691
+ attrs.surface_host = "web";
11692
+ }
11693
+ return attrs;
11694
+ }
11695
+ var GEO_CACHE_KEY = "syntro_geo";
11696
+ var GEO_DEFAULT_HOST = "https://geo.syntrologie.com";
11697
+ async function fetchGeo(geoHost) {
11698
+ if (typeof window === "undefined") return {};
11699
+ try {
11700
+ const cached = localStorage.getItem(GEO_CACHE_KEY);
11701
+ if (cached) {
11702
+ const parsed = JSON.parse(cached);
11703
+ debug("Syntro Bootstrap", "Geo: using cached data:", parsed);
11704
+ return parsed;
11705
+ }
11706
+ } catch {
11707
+ }
11708
+ try {
11709
+ const res = await fetch(geoHost, { signal: AbortSignal.timeout(2e3) });
11710
+ if (res.ok) {
11711
+ const geo = await res.json();
11712
+ const cleaned = {};
11713
+ for (const [k, v] of Object.entries(geo)) {
11714
+ if (typeof v === "string" && v) cleaned[k] = v;
11715
+ }
11716
+ try {
11717
+ localStorage.setItem(GEO_CACHE_KEY, JSON.stringify(cleaned));
11718
+ } catch {
11719
+ }
11720
+ debug("Syntro Bootstrap", "Geo: fetched from worker:", cleaned);
11721
+ return cleaned;
11722
+ }
11723
+ } catch {
11724
+ }
11725
+ return {};
11726
+ }
11727
+
10608
11728
  // src/experiments/registry.ts
10609
11729
  var adapters = {
10610
11730
  growthbook: (config) => createGrowthBookClient({
@@ -10711,9 +11831,8 @@ var ExperimentsFetcher = class {
10711
11831
  __publicField(this, "featureKey");
10712
11832
  __publicField(this, "manifestKey");
10713
11833
  __publicField(this, "variantFlagPrefix");
10714
- var _a2;
10715
11834
  this.client = options.client;
10716
- this.featureKey = (_a2 = options.featureKey) != null ? _a2 : "smart-canvas-config";
11835
+ this.featureKey = options.featureKey;
10717
11836
  this.manifestKey = options.manifestKey;
10718
11837
  this.variantFlagPrefix = options.variantFlagPrefix;
10719
11838
  }
@@ -10742,6 +11861,11 @@ var ExperimentsFetcher = class {
10742
11861
  };
10743
11862
  }
10744
11863
  }
11864
+ if (!this.featureKey) {
11865
+ throw new Error(
11866
+ "[SmartCanvas] No featureKey configured and no variant flags found. Ensure at least one config feature flag exists in your experiment platform."
11867
+ );
11868
+ }
10745
11869
  const config = (_b = (_a2 = this.client).getFeatureValue) == null ? void 0 : _b.call(_a2, this.featureKey, null);
10746
11870
  if (!config || typeof config !== "object") {
10747
11871
  throw new Error(
@@ -10853,95 +11977,9 @@ function createTelemetryClient(provider, config) {
10853
11977
  return factory(config);
10854
11978
  }
10855
11979
 
10856
- // src/bootstrap.ts
10857
- function getEnvVar(name) {
10858
- if (typeof process !== "undefined" && process.env) {
10859
- return process.env[name];
10860
- }
10861
- try {
10862
- const meta = (0, eval)("import.meta");
10863
- if (meta == null ? void 0 : meta.env) {
10864
- return meta.env[name];
10865
- }
10866
- } catch {
10867
- }
10868
- return void 0;
10869
- }
10870
- var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
10871
- function loadCachedSegmentAttributes() {
10872
- if (typeof window === "undefined") return {};
10873
- try {
10874
- const cached = localStorage.getItem(SEGMENT_CACHE_KEY);
10875
- if (cached) {
10876
- const attrs = JSON.parse(cached);
10877
- debug("Syntro Bootstrap", "Loaded cached segment attributes:", attrs);
10878
- return attrs;
10879
- }
10880
- } catch (err) {
10881
- warn("Syntro Bootstrap", "Failed to load cached segment attributes:", err);
10882
- }
10883
- return {};
10884
- }
10885
- function cacheSegmentAttributes(attrs) {
10886
- if (typeof window === "undefined") return;
10887
- try {
10888
- localStorage.setItem(SEGMENT_CACHE_KEY, JSON.stringify(attrs));
10889
- debug("Syntro Bootstrap", "Cached segment attributes:", attrs);
10890
- } catch (err) {
10891
- warn("Syntro Bootstrap", "Failed to cache segment attributes:", err);
10892
- }
10893
- }
10894
- function extractSegmentFlags(allFlags) {
10895
- if (!allFlags) return {};
10896
- const segmentFlags = {};
10897
- for (const [key, value] of Object.entries(allFlags)) {
10898
- if (key.startsWith("in_segment_")) {
10899
- segmentFlags[key] = value === true;
10900
- }
10901
- }
10902
- return segmentFlags;
10903
- }
10904
- function collectBrowserMetadata() {
10905
- var _a2;
10906
- if (typeof window === "undefined") return {};
10907
- const attrs = {};
10908
- try {
10909
- const params = new URLSearchParams(window.location.search);
10910
- for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
10911
- const val = params.get(key);
10912
- if (val) attrs[key] = val;
10913
- }
10914
- } catch {
10915
- }
10916
- try {
10917
- if (navigator.language) attrs.browser_language = navigator.language;
10918
- if ((_a2 = navigator.languages) == null ? void 0 : _a2.length) attrs.browser_languages = [...navigator.languages];
10919
- } catch {
10920
- }
10921
- try {
10922
- const w = window.innerWidth;
10923
- attrs.device_type = w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
10924
- } catch {
10925
- }
10926
- try {
10927
- if (document.referrer) {
10928
- attrs.referrer = document.referrer;
10929
- try {
10930
- attrs.referrer_hostname = new URL(document.referrer).hostname;
10931
- } catch {
10932
- }
10933
- }
10934
- } catch {
10935
- }
10936
- try {
10937
- attrs.page_url = window.location.href;
10938
- attrs.page_path = window.location.pathname;
10939
- } catch {
10940
- }
10941
- return attrs;
10942
- }
10943
- async function init(options) {
10944
- var _a2, _b, _c, _d, _e, _f;
11980
+ // src/bootstrap-runtime.ts
11981
+ async function _initCore(options) {
11982
+ var _a2, _b, _c, _d, _e, _f, _g, _h, _i, _j;
10945
11983
  initLogger();
10946
11984
  debug("Syntro Bootstrap", "====== INIT ======");
10947
11985
  debug("Syntro Bootstrap", "Options:", {
@@ -11006,17 +12044,95 @@ async function init(options) {
11006
12044
  const experimentHost = getEnvVar("NEXT_PUBLIC_SYNTRO_EXPERIMENT_HOST") || getEnvVar("VITE_SYNTRO_EXPERIMENT_HOST") || (payload == null ? void 0 : payload.eh);
11007
12045
  const telemetryHost = getEnvVar("NEXT_PUBLIC_SYNTRO_TELEMETRY_HOST") || getEnvVar("VITE_SYNTRO_TELEMETRY_HOST") || (payload == null ? void 0 : payload.th);
11008
12046
  const editorUrl = getEnvVar("NEXT_PUBLIC_SYNTRO_EDITOR_URL") || getEnvVar("VITE_SYNTRO_EDITOR_URL") || ((_b = options.canvas) == null ? void 0 : _b.editorUrl);
12047
+ const geoHost = (payload == null ? void 0 : payload.g) || getEnvVar("NEXT_PUBLIC_SYNTRO_GEO_HOST") || getEnvVar("VITE_SYNTRO_GEO_HOST") || GEO_DEFAULT_HOST;
11009
12048
  const cachedSegmentAttrs = loadCachedSegmentAttributes();
11010
12049
  const browserMetadata = collectBrowserMetadata();
11011
- const phaseOneAttrs = { ...browserMetadata, ...cachedSegmentAttrs };
12050
+ const appSignals = (_c = options.appSignals) != null ? _c : {};
12051
+ const phaseOneAttrs = { ...browserMetadata, ...cachedSegmentAttrs, ...appSignals };
11012
12052
  debug("Syntro Bootstrap", "Phase 1: Browser metadata:", browserMetadata);
11013
12053
  debug("Syntro Bootstrap", "Phase 1: Cached segment attributes:", cachedSegmentAttrs);
12054
+ if (Object.keys(appSignals).length > 0) {
12055
+ debug("Syntro Bootstrap", "Phase 1: App signals:", appSignals);
12056
+ }
12057
+ const geoPromise = fetchGeo(geoHost);
12058
+ const mcpPromise = (() => {
12059
+ try {
12060
+ if (typeof window === "undefined" || window.self === window.top) return Promise.resolve(null);
12061
+ } catch {
12062
+ }
12063
+ return new Promise((resolve) => {
12064
+ const timer = setTimeout(() => {
12065
+ window.removeEventListener("message", handler);
12066
+ resolve(null);
12067
+ }, 500);
12068
+ function handler(event) {
12069
+ var _a3;
12070
+ const data = event.data;
12071
+ if ((data == null ? void 0 : data.jsonrpc) === "2.0" && (data == null ? void 0 : data.id) === 1 && ((_a3 = data == null ? void 0 : data.result) == null ? void 0 : _a3.hostInfo)) {
12072
+ clearTimeout(timer);
12073
+ window.removeEventListener("message", handler);
12074
+ resolve(data.result.hostInfo);
12075
+ }
12076
+ }
12077
+ window.addEventListener("message", handler);
12078
+ try {
12079
+ window.parent.postMessage(
12080
+ { jsonrpc: "2.0", id: 1, method: "ui/initialize", params: { capabilities: {} } },
12081
+ "*"
12082
+ );
12083
+ } catch {
12084
+ clearTimeout(timer);
12085
+ window.removeEventListener("message", handler);
12086
+ resolve(null);
12087
+ }
12088
+ });
12089
+ })();
11014
12090
  let experiments;
11015
- const events = createEventBus();
12091
+ const isDebugOrTest = options.debug || options.testMode;
12092
+ const events = createEventBus({
12093
+ debug: options.debug,
12094
+ history: isDebugOrTest ? new EventHistory(options.testMode ? 0 : 100) : void 0,
12095
+ testMode: options.testMode
12096
+ });
11016
12097
  console.log("[Syntro Bootstrap] EventBus created");
11017
- const postHogNormalizer = createPostHogNormalizer((event) => {
11018
- events.publishEvent(event);
12098
+ const processor = createEventProcessor({
12099
+ elementResolver: typeof window !== "undefined" ? (x, y) => {
12100
+ const el = document.elementFromPoint(x, y);
12101
+ if (!el) return null;
12102
+ const info = { tag_name: el.tagName.toLowerCase() };
12103
+ if (el.id) info.attr__id = el.id;
12104
+ if (el.className && typeof el.className === "string") {
12105
+ info.classes = el.className.split(" ").filter(Boolean);
12106
+ }
12107
+ for (const attr of el.attributes) {
12108
+ if (attr.name.startsWith("data-")) info[`attr__${attr.name}`] = attr.value;
12109
+ }
12110
+ return info;
12111
+ } : void 0
11019
12112
  });
12113
+ processor.onEvent((event) => events.publishEvent(event));
12114
+ if (typeof window !== "undefined") {
12115
+ setInterval(() => processor.tick(Date.now()), 1e3);
12116
+ document.addEventListener(
12117
+ "click",
12118
+ (e) => {
12119
+ const chain = [];
12120
+ let el = e.target;
12121
+ while (el && el !== document.body) {
12122
+ const info = { tag_name: el.tagName.toLowerCase() };
12123
+ for (const attr of el.attributes) {
12124
+ if (attr.name.startsWith("data-") || attr.name === "id" || attr.name === "class" || attr.name === "aria-label") {
12125
+ info[`attr__${attr.name}`] = attr.value;
12126
+ }
12127
+ }
12128
+ chain.push(info);
12129
+ el = el.parentElement;
12130
+ }
12131
+ processor.enrichClickAttributes(Date.now(), chain);
12132
+ },
12133
+ true
12134
+ );
12135
+ }
11020
12136
  const onFeatureFlagsLoaded = (allFlags) => {
11021
12137
  var _a3, _b2, _c2;
11022
12138
  debug("Syntro Bootstrap", "Phase 2: PostHog feature flags loaded");
@@ -11025,7 +12141,7 @@ async function init(options) {
11025
12141
  cacheSegmentAttributes(segmentFlags);
11026
12142
  if (experiments) {
11027
12143
  const sessionAttrs = (_b2 = (_a3 = sessionMetrics == null ? void 0 : sessionMetrics.getAll) == null ? void 0 : _a3.call(sessionMetrics)) != null ? _b2 : {};
11028
- const updatedAttrs = { ...browserMetadata, ...sessionAttrs, ...segmentFlags };
12144
+ const updatedAttrs = { ...browserMetadata, ...sessionAttrs, ...segmentFlags, ...appSignals };
11029
12145
  debug("Syntro Bootstrap", "Updating GrowthBook with attributes:", updatedAttrs);
11030
12146
  (_c2 = experiments.setAttributes) == null ? void 0 : _c2.call(experiments, updatedAttrs);
11031
12147
  }
@@ -11039,12 +12155,28 @@ async function init(options) {
11039
12155
  // undefined falls back to adapter default
11040
12156
  // Enable PostHog feature flags for segment membership
11041
12157
  enableFeatureFlags: true,
12158
+ sessionRecording: true,
11042
12159
  // Wire up callback for when flags are loaded (Phase 2)
11043
12160
  onFeatureFlagsLoaded,
11044
- // Wire up event capture to feed into EventBus
11045
- onCapture: postHogNormalizer
12161
+ // Wire up event capture to feed into event processor
12162
+ onCapture: (eventName, properties) => {
12163
+ processor.ingest({ kind: "posthog", event: eventName, properties, timestamp: Date.now() });
12164
+ },
12165
+ // Wire rrweb events for behavioral signal detection
12166
+ onRRWebEvent: (event) => {
12167
+ processor.ingest(event);
12168
+ }
11046
12169
  });
11047
12170
  console.log(`[Syntro Bootstrap] Telemetry client created (${provider}) with EventBus wiring`);
12171
+ if (Object.keys(appSignals).length > 0) {
12172
+ (_d = telemetry.register) == null ? void 0 : _d.call(telemetry, appSignals);
12173
+ debug("Syntro Bootstrap", "Registered app signals as super properties:", appSignals);
12174
+ }
12175
+ const telemetryForCapture = telemetry;
12176
+ events.setPosthogCapture((name, props) => {
12177
+ var _a3;
12178
+ (_a3 = telemetryForCapture.track) == null ? void 0 : _a3.call(telemetryForCapture, name, props);
12179
+ });
11048
12180
  }
11049
12181
  let sessionMetrics;
11050
12182
  if (payload == null ? void 0 : payload.e) {
@@ -11110,7 +12242,7 @@ async function init(options) {
11110
12242
  };
11111
12243
  debug("Syntro Bootstrap", "Global SynOS object exposed");
11112
12244
  }
11113
- const registeredApps = (_e = (_d = (_c = runtime3.apps).list) == null ? void 0 : _d.call(_c)) != null ? _e : [];
12245
+ const registeredApps = (_g = (_f = (_e = runtime3.apps).list) == null ? void 0 : _f.call(_e)) != null ? _g : [];
11114
12246
  console.log(
11115
12247
  `[DIAG] Activation loop: ${registeredApps.length} apps in registry:`,
11116
12248
  registeredApps.map((a) => `${a.manifest.id}(${a.state})`).join(", ")
@@ -11137,11 +12269,24 @@ async function init(options) {
11137
12269
  warn("Syntro Bootstrap", "Failed to load GrowthBook features:", err);
11138
12270
  }
11139
12271
  }
12272
+ const geoData = await geoPromise;
12273
+ if (experiments && Object.keys(geoData).length > 0) {
12274
+ const mergedAttrs = { ...browserMetadata, ...geoData, ...appSignals };
12275
+ debug("Syntro Bootstrap", "Merging geo data into GrowthBook attributes:", geoData);
12276
+ (_h = experiments.setAttributes) == null ? void 0 : _h.call(experiments, mergedAttrs);
12277
+ }
12278
+ const mcpHost = await mcpPromise;
12279
+ if (mcpHost && experiments) {
12280
+ browserMetadata.surface_type = "mcp-app";
12281
+ browserMetadata.surface_host = mcpHost.name;
12282
+ (_i = experiments.setAttributes) == null ? void 0 : _i.call(experiments, { ...browserMetadata, ...geoData });
12283
+ debug("Syntro Bootstrap", "MCP App detected:", mcpHost.name);
12284
+ }
11140
12285
  let baseFetcher;
11141
12286
  if (options.fetcher) {
11142
12287
  baseFetcher = options.fetcher;
11143
12288
  } else if (payload == null ? void 0 : payload.f) {
11144
- const configFetcher = createConfigFetcher(payload.f, (_f = payload.o) != null ? _f : {});
12289
+ const configFetcher = createConfigFetcher(payload.f, (_j = payload.o) != null ? _j : {});
11145
12290
  baseFetcher = async () => {
11146
12291
  var _a3;
11147
12292
  const result = await configFetcher.fetch();
@@ -11155,15 +12300,35 @@ async function init(options) {
11155
12300
  }
11156
12301
  const warnedAppFailures = /* @__PURE__ */ new Set();
11157
12302
  const appLoadingFetcher = baseFetcher ? async () => {
11158
- var _a3, _b2, _c2, _d2, _e2, _f2, _g;
12303
+ var _a3, _b2, _c2, _d2, _e2, _f2, _g2, _h2, _i2, _j2, _k, _l;
11159
12304
  const config = await baseFetcher();
12305
+ const tileCount = (_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0;
12306
+ const actionCount = (_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0;
12307
+ const variantId = (_e2 = config.meta) == null ? void 0 : _e2.variant_id;
12308
+ if (tileCount > 0 || actionCount > 0) {
12309
+ if (!variantId) {
12310
+ console.warn(
12311
+ "[Syntro] Config has content but no meta.variant_id \u2014 intervention tracking disabled"
12312
+ );
12313
+ }
12314
+ }
12315
+ if (telemetry && variantId) {
12316
+ const tracker = new InterventionTracker(telemetry, variantId);
12317
+ tracker.trackServed(tileCount, actionCount);
12318
+ if (typeof window !== "undefined") {
12319
+ window.SynOS.interventionTracker = tracker;
12320
+ }
12321
+ runtime3.navigation.subscribe(() => {
12322
+ tracker.resetPage();
12323
+ });
12324
+ }
11160
12325
  console.log(
11161
12326
  "[Syntro Bootstrap] Config fetched:",
11162
- `tiles=${(_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0},`,
11163
- `actions=${(_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0},`,
11164
- `theme=${(_f2 = (_e2 = config.theme) == null ? void 0 : _e2.name) != null ? _f2 : "none"}`
12327
+ `tiles=${(_g2 = (_f2 = config.tiles) == null ? void 0 : _f2.length) != null ? _g2 : 0},`,
12328
+ `actions=${(_i2 = (_h2 = config.actions) == null ? void 0 : _h2.length) != null ? _i2 : 0},`,
12329
+ `theme=${(_k = (_j2 = config.theme) == null ? void 0 : _j2.name) != null ? _k : "none"}`
11165
12330
  );
11166
- if (((_g = config.actions) == null ? void 0 : _g.length) > 0) {
12331
+ if (((_l = config.actions) == null ? void 0 : _l.length) > 0) {
11167
12332
  console.log(
11168
12333
  "[Syntro Bootstrap] Actions in config:",
11169
12334
  config.actions.map(
@@ -11226,10 +12391,33 @@ async function init(options) {
11226
12391
  });
11227
12392
  return { canvas, runtime: runtime3, experiments, telemetry, sessionMetrics, appLoader };
11228
12393
  }
12394
+
12395
+ // src/bootstrap.ts
12396
+ async function init(options) {
12397
+ var _a2;
12398
+ try {
12399
+ return await _initCore(options);
12400
+ } catch (err) {
12401
+ const message = err instanceof Error ? err.message : String(err);
12402
+ console.warn("[Syntrologie] SDK initialization failed:", message);
12403
+ if (typeof document !== "undefined") {
12404
+ (_a2 = document.getElementById("syntrologie-anti-flicker")) == null ? void 0 : _a2.remove();
12405
+ }
12406
+ return void 0;
12407
+ }
12408
+ }
12409
+ function emit(eventName, props = {}) {
12410
+ var _a2, _b;
12411
+ if (typeof window === "undefined") return;
12412
+ const runtime3 = (_a2 = window.SynOS) == null ? void 0 : _a2.runtime;
12413
+ if (!((_b = runtime3 == null ? void 0 : runtime3.events) == null ? void 0 : _b.publish)) return;
12414
+ runtime3.events.publish({ name: eventName, source: "custom", props });
12415
+ }
11229
12416
  var Syntro = {
11230
12417
  init,
11231
12418
  encodeToken,
11232
- decodeToken
12419
+ decodeToken,
12420
+ events: { emit }
11233
12421
  };
11234
12422
  if (typeof window !== "undefined") {
11235
12423
  window.Syntro = Syntro;
@@ -11260,8 +12448,11 @@ export {
11260
12448
  createSmartCanvasController,
11261
12449
  ShadowRootProvider,
11262
12450
  useShadowRoot,
11263
- StandardEvents,
11264
12451
  EVENT_SCHEMA_VERSION,
12452
+ normalizePostHogEvent,
12453
+ shouldNormalizeEvent,
12454
+ createPostHogNormalizer,
12455
+ StandardEvents2 as StandardEvents,
11265
12456
  CanvasEvents,
11266
12457
  NotificationToastStack,
11267
12458
  MAX_VISIBLE_TOASTS,
@@ -11292,6 +12483,7 @@ export {
11292
12483
  createSessionMetricTracker,
11293
12484
  createNoopClient,
11294
12485
  createPostHogClient,
12486
+ InterventionTracker,
11295
12487
  ExecutorRegistry,
11296
12488
  executorRegistry,
11297
12489
  getExecutor,
@@ -11310,11 +12502,11 @@ export {
11310
12502
  evaluateSync,
11311
12503
  createDecisionEngine,
11312
12504
  createEventAccumulator,
12505
+ validateEventName,
12506
+ validateProps,
11313
12507
  EventBus,
11314
12508
  createEventBus,
11315
- normalizePostHogEvent,
11316
- shouldNormalizeEvent,
11317
- createPostHogNormalizer,
12509
+ EventHistory,
11318
12510
  NavigationMonitor,
11319
12511
  StateStore,
11320
12512
  createStateStore,
@@ -11338,4 +12530,4 @@ export {
11338
12530
  encodeToken,
11339
12531
  Syntro
11340
12532
  };
11341
- //# sourceMappingURL=chunk-UURRUKSW.js.map
12533
+ //# sourceMappingURL=chunk-TOBT6CVT.js.map