@syntrologie/runtime-sdk 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CAPABILITIES.md +261 -173
  2. package/README.md +2 -0
  3. package/dist/actions/schema.d.ts +7 -7
  4. package/dist/actions/schema.js +3 -4
  5. package/dist/actions/types.d.ts +1 -1
  6. package/dist/actions/validation-core.d.ts +24 -0
  7. package/dist/actions/validation-rules.d.ts +74 -0
  8. package/dist/actions/validation.d.ts +5 -11
  9. package/dist/bootstrap-init.d.ts +33 -0
  10. package/dist/bootstrap-runtime.d.ts +7 -0
  11. package/dist/bootstrap-types.d.ts +90 -0
  12. package/dist/bootstrap.d.ts +19 -83
  13. package/dist/{chunk-Q77NT67W.js → chunk-BU4Z6PD7.js} +16 -1
  14. package/dist/{chunk-Q77NT67W.js.map → chunk-BU4Z6PD7.js.map} +1 -1
  15. package/dist/{chunk-H3FAYTUV.js → chunk-GF364MMB.js} +1343 -393
  16. package/dist/chunk-GF364MMB.js.map +7 -0
  17. package/dist/{chunk-NBFQGKSV.js → chunk-L6RJMBR2.js} +4 -4
  18. package/dist/{chunk-NBFQGKSV.js.map → chunk-L6RJMBR2.js.map} +2 -2
  19. package/dist/{chunk-37TTQRH5.js → chunk-XDYJ64IN.js} +2 -2
  20. package/dist/config/schema.js +2 -3
  21. package/dist/decisions/schema.js +1 -2
  22. package/dist/events/EventBus.d.ts +27 -1
  23. package/dist/events/history.d.ts +9 -0
  24. package/dist/events/index.d.ts +3 -0
  25. package/dist/events/types.d.ts +24 -0
  26. package/dist/events/validation.d.ts +7 -0
  27. package/dist/index.d.ts +0 -2
  28. package/dist/index.js +1133 -2039
  29. package/dist/index.js.map +4 -4
  30. package/dist/overlays/runtime/overlay/overlay-runner.d.ts +4 -0
  31. package/dist/overlays/runtime/overlay/overlay-state.d.ts +21 -0
  32. package/dist/overlays/types.d.ts +3 -1
  33. package/dist/react.js +6 -5
  34. package/dist/react.js.map +2 -2
  35. package/dist/smart-canvas.esm.js +92 -108
  36. package/dist/smart-canvas.esm.js.map +4 -4
  37. package/dist/smart-canvas.js +4763 -4955
  38. package/dist/smart-canvas.js.map +4 -4
  39. package/dist/smart-canvas.min.js +92 -108
  40. package/dist/smart-canvas.min.js.map +4 -4
  41. package/dist/telemetry/InterventionTracker.d.ts +23 -0
  42. package/dist/telemetry/adapters/posthog.d.ts +5 -10
  43. package/dist/telemetry/index.d.ts +1 -0
  44. package/dist/test/setup.d.ts +1 -0
  45. package/dist/token.d.ts +2 -0
  46. package/dist/version.d.ts +1 -1
  47. package/package.json +23 -29
  48. package/schema/canvas-config.schema.json +1 -1
  49. package/scripts/syntroReactPlugin.mjs +3 -0
  50. package/scripts/validate-config.mjs +42 -0
  51. package/dist/chunk-H3FAYTUV.js.map +0 -7
  52. package/dist/chunk-JMHRHAEL.js +0 -18
  53. package/dist/chunk-JMHRHAEL.js.map +0 -7
  54. package/dist/replayMirror-QZ3GQ527.js +0 -32
  55. package/dist/replayMirror-QZ3GQ527.js.map +0 -7
  56. package/dist/telemetry/replayMirror.d.ts +0 -7
  57. /package/dist/{chunk-37TTQRH5.js.map → chunk-XDYJ64IN.js.map} +0 -0
@@ -3,7 +3,7 @@ import {
3
3
  __privateGet,
4
4
  __privateSet,
5
5
  __publicField
6
- } from "./chunk-JMHRHAEL.js";
6
+ } from "./chunk-BU4Z6PD7.js";
7
7
 
8
8
  // ../adaptives/adaptive-content/dist/reconciliation-guard.js
9
9
  function guardAgainstReconciliation(container, anchor, reinsertFn, opts) {
@@ -127,6 +127,12 @@ function sanitizeHtml(html) {
127
127
  }
128
128
  }
129
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
+ }
130
136
  for (const el of toRemove) {
131
137
  while (el.firstChild) {
132
138
  (_a2 = el.parentNode) == null ? void 0 : _a2.insertBefore(el.firstChild, el);
@@ -755,6 +761,10 @@ var CelebrationEngine = class {
755
761
  this.container = null;
756
762
  }
757
763
  start(container, effect, config) {
764
+ const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
765
+ if (prefersReducedMotion) {
766
+ return;
767
+ }
758
768
  this.container = container;
759
769
  this.effect = effect;
760
770
  this.duration = config.duration;
@@ -1398,7 +1408,7 @@ var alert = {
1398
1408
  var tag = {
1399
1409
  content: slateGrey[10],
1400
1410
  border: slateGrey[4],
1401
- background: "#1c2124"
1411
+ background: slateGrey[3]
1402
1412
  };
1403
1413
  var menu = {
1404
1414
  backgroundDefault: slateGrey[2],
@@ -2283,7 +2293,7 @@ function extractWorkflowsFromActive(activeActions) {
2283
2293
  const workflows = /* @__PURE__ */ new Map();
2284
2294
  for (const entry of activeActions) {
2285
2295
  const action = entry.action;
2286
- if (action.kind === "core:tour" && action.workflow && action.tourId) {
2296
+ if (action.kind === "overlays:tour" && action.workflow && action.tourId) {
2287
2297
  const meta = action.workflow;
2288
2298
  const rawSteps = action.steps || [];
2289
2299
  const steps = rawSteps.map((s) => {
@@ -2806,7 +2816,7 @@ var executors2 = [
2806
2816
  { kind: "overlays:badge", executor: executeBadge },
2807
2817
  { kind: "overlays:tooltip", executor: executeTooltip },
2808
2818
  { kind: "overlays:modal", executor: executeModal },
2809
- { kind: "core:tour", executor: executeTour },
2819
+ { kind: "overlays:tour", executor: executeTour },
2810
2820
  { kind: "overlays:celebrate", executor: executeCelebrate }
2811
2821
  ];
2812
2822
  var runtime2 = {
@@ -2926,7 +2936,15 @@ var CORE_ACTION_KINDS = /* @__PURE__ */ new Set([
2926
2936
  "core:wait",
2927
2937
  "core:sequence",
2928
2938
  "core:parallel",
2929
- "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"
2930
2948
  ]);
2931
2949
  var NAMESPACE_TO_APP_ID = {
2932
2950
  faq: "adaptive-faq",
@@ -3080,6 +3098,9 @@ function createAppLoader(options) {
3080
3098
  }
3081
3099
  }
3082
3100
  }
3101
+ for (const bundled of BUNDLED_APP_IDS) {
3102
+ appIds.delete(bundled);
3103
+ }
3083
3104
  return Array.from(appIds);
3084
3105
  }
3085
3106
  async function loadAppsForConfig(config) {
@@ -3435,7 +3456,7 @@ function getAntiFlickerSnippet(config = {}) {
3435
3456
  }
3436
3457
 
3437
3458
  // src/version.ts
3438
- var SDK_VERSION = "2.11.0";
3459
+ var SDK_VERSION = "2.13.0";
3439
3460
 
3440
3461
  // src/types.ts
3441
3462
  var SDK_SCHEMA_VERSION = "2.0";
@@ -3586,12 +3607,14 @@ function getCachedConfig(sdkVersion) {
3586
3607
  var resolveConfigUri = ({
3587
3608
  configUri,
3588
3609
  experiments,
3589
- featureKey = "smart-canvas-config-uri"
3610
+ featureKey
3590
3611
  }) => {
3591
3612
  var _a2;
3592
3613
  if (configUri) return configUri;
3593
- const fromFeature = (_a2 = experiments == null ? void 0 : experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, featureKey, null);
3594
- if (fromFeature) return fromFeature;
3614
+ if (experiments && featureKey) {
3615
+ const fromFeature = (_a2 = experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, featureKey, null);
3616
+ if (fromFeature) return fromFeature;
3617
+ }
3595
3618
  return void 0;
3596
3619
  };
3597
3620
  function getVariantFlagKeys(experiments, manifestKey, variantFlagPrefix) {
@@ -3617,7 +3640,7 @@ var createCanvasConfigFetcher = ({
3617
3640
  experiments,
3618
3641
  featureKey,
3619
3642
  credentials,
3620
- configFeatureKey = "smart-canvas-config",
3643
+ configFeatureKey,
3621
3644
  manifestKey,
3622
3645
  variantFlagPrefix,
3623
3646
  sdkVersion
@@ -3812,9 +3835,529 @@ function useShadowRoot() {
3812
3835
  return ctx;
3813
3836
  }
3814
3837
 
3815
- // src/events/types.ts
3816
- import { EVENT_SCHEMA_VERSION } from "@syntrologie/event-processor";
3838
+ // ../event-processor/dist/types.js
3839
+ var EVENT_SCHEMA_VERSION = "1.0.0";
3817
3840
  var StandardEvents = {
3841
+ UI_CLICK: "ui.click",
3842
+ UI_SCROLL: "ui.scroll",
3843
+ UI_INPUT: "ui.input",
3844
+ UI_CHANGE: "ui.change",
3845
+ UI_SUBMIT: "ui.submit",
3846
+ NAV_PAGE_VIEW: "nav.page_view",
3847
+ NAV_PAGE_LEAVE: "nav.page_leave",
3848
+ UI_HESITATE: "ui.hesitate",
3849
+ UI_RAGE_CLICK: "ui.rage_click",
3850
+ UI_SCROLL_THRASH: "ui.scroll_thrash",
3851
+ UI_FOCUS_BOUNCE: "ui.focus_bounce",
3852
+ UI_IDLE: "ui.idle",
3853
+ UI_HOVER: "ui.hover"
3854
+ };
3855
+ var RRWebSource = {
3856
+ Mutation: 0,
3857
+ MouseMove: 1,
3858
+ MouseInteraction: 2,
3859
+ Scroll: 3,
3860
+ ViewportResize: 4,
3861
+ Input: 5,
3862
+ TouchMove: 6,
3863
+ MediaInteraction: 7,
3864
+ Drag: 12
3865
+ };
3866
+ var RRWebMouseInteraction = {
3867
+ MouseUp: 0,
3868
+ MouseDown: 1,
3869
+ Click: 2,
3870
+ ContextMenu: 3,
3871
+ DblClick: 4,
3872
+ Focus: 5,
3873
+ Blur: 6,
3874
+ TouchStart: 7,
3875
+ TouchEnd: 9
3876
+ };
3877
+ var DEFAULT_DETECTOR_CONFIG = {
3878
+ hesitationMs: 3e3,
3879
+ hesitationRadiusPx: 10,
3880
+ rageClickCount: 3,
3881
+ rageClickWindowMs: 1e3,
3882
+ rageClickRadiusPx: 30,
3883
+ scrollThrashReversals: 3,
3884
+ scrollThrashWindowMs: 2e3,
3885
+ focusBounceMaxInputs: 0,
3886
+ idleMs: 5e3,
3887
+ hoverSampleMs: 100
3888
+ };
3889
+
3890
+ // ../event-processor/dist/normalizers/posthog.js
3891
+ var POSTHOG_EVENT_MAP = {
3892
+ // NOTE: $autocapture is intentionally NOT in this map.
3893
+ // It's handled below in getEventName() with $event_type refinement
3894
+ // so that change/submit events aren't all mapped to ui.click.
3895
+ $click: StandardEvents.UI_CLICK,
3896
+ $scroll: StandardEvents.UI_SCROLL,
3897
+ $input: StandardEvents.UI_INPUT,
3898
+ $change: StandardEvents.UI_CHANGE,
3899
+ $submit: StandardEvents.UI_SUBMIT,
3900
+ // Navigation events
3901
+ $pageview: StandardEvents.NAV_PAGE_VIEW,
3902
+ $pageleave: StandardEvents.NAV_PAGE_LEAVE,
3903
+ // Session events
3904
+ $session_start: "session.start",
3905
+ // Identify events
3906
+ $identify: "user.identify"
3907
+ };
3908
+ function getEventName(phEvent) {
3909
+ var _a2, _b;
3910
+ const eventName = phEvent.event;
3911
+ if (typeof eventName !== "string") {
3912
+ return "posthog.unknown";
3913
+ }
3914
+ if (POSTHOG_EVENT_MAP[eventName]) {
3915
+ return POSTHOG_EVENT_MAP[eventName];
3916
+ }
3917
+ if (eventName === "$autocapture") {
3918
+ const tagName = (_a2 = phEvent.properties) == null ? void 0 : _a2.$tag_name;
3919
+ const eventType = (_b = phEvent.properties) == null ? void 0 : _b.$event_type;
3920
+ if (eventType === "submit")
3921
+ return StandardEvents.UI_SUBMIT;
3922
+ if (eventType === "change")
3923
+ return StandardEvents.UI_CHANGE;
3924
+ if (tagName === "input" || tagName === "textarea")
3925
+ return StandardEvents.UI_INPUT;
3926
+ return StandardEvents.UI_CLICK;
3927
+ }
3928
+ if (!eventName.startsWith("$")) {
3929
+ return `posthog.${eventName}`;
3930
+ }
3931
+ return eventName.replace("$", "posthog.");
3932
+ }
3933
+ var INTERACTIVE_TAGS = /* @__PURE__ */ new Set(["a", "button", "input", "select", "textarea"]);
3934
+ function resolveInteractiveTag(elements, directTag) {
3935
+ if (directTag && INTERACTIVE_TAGS.has(directTag))
3936
+ return directTag;
3937
+ if (!elements)
3938
+ return directTag;
3939
+ for (const el of elements) {
3940
+ const tag2 = el.tag_name;
3941
+ if (tag2 && INTERACTIVE_TAGS.has(tag2))
3942
+ return tag2;
3943
+ }
3944
+ return directTag;
3945
+ }
3946
+ function extractProps(phEvent) {
3947
+ var _a2, _b;
3948
+ const props = {};
3949
+ const phProps = phEvent.properties || {};
3950
+ const elements = phProps.$elements;
3951
+ const directTag = (_b = phProps.$tag_name) != null ? _b : (_a2 = elements == null ? void 0 : elements[0]) == null ? void 0 : _a2.tag_name;
3952
+ const isClickEvent = phEvent.event === "$autocapture" || phEvent.event === "$click";
3953
+ props.tagName = isClickEvent ? resolveInteractiveTag(elements, directTag) : directTag;
3954
+ if (phProps.$el_text)
3955
+ props.elementText = phProps.$el_text;
3956
+ if (elements)
3957
+ props.elements = elements;
3958
+ if (phProps.$current_url)
3959
+ props.url = phProps.$current_url;
3960
+ if (phProps.$pathname)
3961
+ props.pathname = phProps.$pathname;
3962
+ if (phProps.$host)
3963
+ props.host = phProps.$host;
3964
+ if (phProps.$viewport_width)
3965
+ props.viewportWidth = phProps.$viewport_width;
3966
+ if (phProps.$viewport_height)
3967
+ props.viewportHeight = phProps.$viewport_height;
3968
+ if (phProps.$session_id)
3969
+ props.sessionId = phProps.$session_id;
3970
+ if (phProps.$scroll_depth)
3971
+ props.scrollDepth = phProps.$scroll_depth;
3972
+ if (phProps.$scroll_percentage)
3973
+ props.scrollPercentage = phProps.$scroll_percentage;
3974
+ props.originalEvent = phEvent.event;
3975
+ return props;
3976
+ }
3977
+ function normalizePostHogEvent(phEvent) {
3978
+ let ts;
3979
+ if (typeof phEvent.timestamp === "number") {
3980
+ ts = phEvent.timestamp;
3981
+ } else if (typeof phEvent.timestamp === "string") {
3982
+ ts = new Date(phEvent.timestamp).getTime();
3983
+ } else {
3984
+ ts = Date.now();
3985
+ }
3986
+ return {
3987
+ ts,
3988
+ name: getEventName(phEvent),
3989
+ source: "posthog",
3990
+ props: extractProps(phEvent),
3991
+ schemaVersion: EVENT_SCHEMA_VERSION
3992
+ };
3993
+ }
3994
+ function shouldNormalizeEvent(phEvent) {
3995
+ const eventName = phEvent.event;
3996
+ if (typeof eventName !== "string")
3997
+ return false;
3998
+ const skipEvents = [
3999
+ "$feature_flag_called",
4000
+ "$feature_flags",
4001
+ "$groups",
4002
+ "$groupidentify",
4003
+ "$set",
4004
+ "$set_once",
4005
+ "$unset",
4006
+ "$create_alias",
4007
+ "$capture_metrics",
4008
+ "$performance_event",
4009
+ "$web_vitals",
4010
+ "$exception",
4011
+ "$dead_click",
4012
+ "$heatmap"
4013
+ ];
4014
+ if (skipEvents.includes(eventName)) {
4015
+ return false;
4016
+ }
4017
+ return true;
4018
+ }
4019
+ function createPostHogNormalizer(publishFn) {
4020
+ return (eventName, properties) => {
4021
+ if (typeof eventName !== "string")
4022
+ return;
4023
+ const phEvent = {
4024
+ event: eventName,
4025
+ properties,
4026
+ timestamp: Date.now()
4027
+ };
4028
+ if (shouldNormalizeEvent(phEvent)) {
4029
+ const normalizedEvent = normalizePostHogEvent(phEvent);
4030
+ publishFn(normalizedEvent);
4031
+ }
4032
+ };
4033
+ }
4034
+
4035
+ // ../event-processor/dist/detectors/focus-bounce.js
4036
+ var FocusBounceDetector = class {
4037
+ constructor(config, emit2) {
4038
+ this.config = config;
4039
+ this.emit = emit2;
4040
+ this.focused = /* @__PURE__ */ new Map();
4041
+ }
4042
+ ingest(raw) {
4043
+ var _a2, _b;
4044
+ if (raw.type !== 3)
4045
+ return;
4046
+ const ts = raw.timestamp;
4047
+ if (raw.data.source === RRWebSource.MouseInteraction) {
4048
+ const id = (_a2 = raw.data.id) != null ? _a2 : 0;
4049
+ if (raw.data.type === RRWebMouseInteraction.Focus) {
4050
+ this.focused.set(id, { id, focusTs: ts, inputCount: 0 });
4051
+ } else if (raw.data.type === RRWebMouseInteraction.Blur) {
4052
+ const entry = this.focused.get(id);
4053
+ if (entry && entry.inputCount <= this.config.focusBounceMaxInputs) {
4054
+ this.emit({
4055
+ ts,
4056
+ name: StandardEvents.UI_FOCUS_BOUNCE,
4057
+ source: "rrweb",
4058
+ schemaVersion: EVENT_SCHEMA_VERSION,
4059
+ props: {
4060
+ elementId: id,
4061
+ duration_ms: ts - entry.focusTs
4062
+ }
4063
+ });
4064
+ }
4065
+ this.focused.delete(id);
4066
+ }
4067
+ } else if (raw.data.source === RRWebSource.Input) {
4068
+ const id = (_b = raw.data.id) != null ? _b : 0;
4069
+ const entry = this.focused.get(id);
4070
+ if (entry) {
4071
+ entry.inputCount++;
4072
+ }
4073
+ }
4074
+ }
4075
+ };
4076
+
4077
+ // ../event-processor/dist/detectors/hesitation.js
4078
+ var HesitationDetector = class {
4079
+ constructor(config, emit2) {
4080
+ this.config = config;
4081
+ this.emit = emit2;
4082
+ this.anchorX = 0;
4083
+ this.anchorY = 0;
4084
+ this.anchorTs = 0;
4085
+ this.emitted = false;
4086
+ this.hasPosition = false;
4087
+ }
4088
+ ingest(raw) {
4089
+ var _a2;
4090
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4091
+ return;
4092
+ const positions = raw.data.positions;
4093
+ if (!positions || positions.length === 0)
4094
+ return;
4095
+ const last = positions[positions.length - 1];
4096
+ const ts = raw.timestamp + ((_a2 = last.timeOffset) != null ? _a2 : 0);
4097
+ if (!this.hasPosition) {
4098
+ this.anchorX = last.x;
4099
+ this.anchorY = last.y;
4100
+ this.anchorTs = ts;
4101
+ this.hasPosition = true;
4102
+ this.emitted = false;
4103
+ return;
4104
+ }
4105
+ const dx = last.x - this.anchorX;
4106
+ const dy = last.y - this.anchorY;
4107
+ const dist = Math.sqrt(dx * dx + dy * dy);
4108
+ if (dist > this.config.hesitationRadiusPx) {
4109
+ this.anchorX = last.x;
4110
+ this.anchorY = last.y;
4111
+ this.anchorTs = ts;
4112
+ this.emitted = false;
4113
+ }
4114
+ }
4115
+ tick(now) {
4116
+ if (!this.hasPosition || this.emitted)
4117
+ return;
4118
+ const elapsed = now - this.anchorTs;
4119
+ if (elapsed >= this.config.hesitationMs) {
4120
+ this.emit({
4121
+ ts: now,
4122
+ name: StandardEvents.UI_HESITATE,
4123
+ source: "rrweb",
4124
+ schemaVersion: EVENT_SCHEMA_VERSION,
4125
+ props: {
4126
+ x: this.anchorX,
4127
+ y: this.anchorY,
4128
+ duration_ms: elapsed
4129
+ }
4130
+ });
4131
+ this.emitted = true;
4132
+ }
4133
+ }
4134
+ };
4135
+
4136
+ // ../event-processor/dist/detectors/hover.js
4137
+ var HoverTracker = class {
4138
+ constructor(config, emit2, elementResolver) {
4139
+ this.config = config;
4140
+ this.emit = emit2;
4141
+ this.elementResolver = elementResolver;
4142
+ this.currentElement = null;
4143
+ this.hoverStartTs = null;
4144
+ this.lastX = 0;
4145
+ this.lastY = 0;
4146
+ this.lastSampleTs = -Infinity;
4147
+ }
4148
+ ingest(raw) {
4149
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseMove)
4150
+ return;
4151
+ const positions = raw.data.positions;
4152
+ if (!positions || positions.length === 0)
4153
+ return;
4154
+ const last = positions[positions.length - 1];
4155
+ this.lastX = last.x;
4156
+ this.lastY = last.y;
4157
+ }
4158
+ tick(now) {
4159
+ if (!this.elementResolver)
4160
+ return;
4161
+ if (now - this.lastSampleTs < this.config.hoverSampleMs)
4162
+ return;
4163
+ this.lastSampleTs = now;
4164
+ const newElement = this.elementResolver(this.lastX, this.lastY);
4165
+ const newKey = newElement ? elementKey(newElement) : null;
4166
+ const currentKey = this.currentElement ? elementKey(this.currentElement) : null;
4167
+ if (newKey !== currentKey) {
4168
+ if (this.currentElement && this.hoverStartTs !== null) {
4169
+ this.emit({
4170
+ ts: now,
4171
+ name: StandardEvents.UI_HOVER,
4172
+ source: "rrweb",
4173
+ schemaVersion: EVENT_SCHEMA_VERSION,
4174
+ props: {
4175
+ x: this.lastX,
4176
+ y: this.lastY,
4177
+ duration_ms: now - this.hoverStartTs,
4178
+ element: this.currentElement
4179
+ }
4180
+ });
4181
+ }
4182
+ this.currentElement = newElement;
4183
+ this.hoverStartTs = now;
4184
+ }
4185
+ }
4186
+ };
4187
+ function elementKey(el) {
4188
+ var _a2, _b, _c;
4189
+ return `${(_a2 = el.tag_name) != null ? _a2 : ""}|${(_b = el.attr__id) != null ? _b : ""}|${((_c = el.classes) != null ? _c : []).join(",")}`;
4190
+ }
4191
+
4192
+ // ../event-processor/dist/detectors/idle.js
4193
+ var IdleDetector = class {
4194
+ constructor(config, emit2) {
4195
+ this.config = config;
4196
+ this.emit = emit2;
4197
+ this.lastActivityTs = null;
4198
+ this.emitted = false;
4199
+ }
4200
+ ingest(raw) {
4201
+ if (raw.type !== 3)
4202
+ return;
4203
+ const src = raw.data.source;
4204
+ if (src === RRWebSource.MouseMove || src === RRWebSource.MouseInteraction || src === RRWebSource.Scroll) {
4205
+ this.lastActivityTs = raw.timestamp;
4206
+ this.emitted = false;
4207
+ }
4208
+ }
4209
+ tick(now) {
4210
+ if (this.lastActivityTs === null || this.emitted)
4211
+ return;
4212
+ if (now - this.lastActivityTs >= this.config.idleMs) {
4213
+ this.emit({
4214
+ ts: now,
4215
+ name: StandardEvents.UI_IDLE,
4216
+ source: "rrweb",
4217
+ schemaVersion: EVENT_SCHEMA_VERSION,
4218
+ props: {
4219
+ idle_ms: now - this.lastActivityTs
4220
+ }
4221
+ });
4222
+ this.emitted = true;
4223
+ }
4224
+ }
4225
+ };
4226
+
4227
+ // ../event-processor/dist/detectors/rage-click.js
4228
+ var RageClickDetector = class {
4229
+ constructor(config, emit2) {
4230
+ this.config = config;
4231
+ this.emit = emit2;
4232
+ this.clicks = [];
4233
+ }
4234
+ ingest(raw) {
4235
+ var _a2, _b;
4236
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.MouseInteraction)
4237
+ return;
4238
+ if (raw.data.type !== RRWebMouseInteraction.Click)
4239
+ return;
4240
+ const x = (_a2 = raw.data.x) != null ? _a2 : 0;
4241
+ const y = (_b = raw.data.y) != null ? _b : 0;
4242
+ const ts = raw.timestamp;
4243
+ const cutoff = ts - this.config.rageClickWindowMs;
4244
+ this.clicks = this.clicks.filter((c) => c.ts >= cutoff);
4245
+ this.clicks.push({ ts, x, y });
4246
+ const nearby = this.clicks.filter((c) => {
4247
+ const dx = c.x - x;
4248
+ const dy = c.y - y;
4249
+ return Math.sqrt(dx * dx + dy * dy) <= this.config.rageClickRadiusPx;
4250
+ });
4251
+ if (nearby.length >= this.config.rageClickCount) {
4252
+ this.emit({
4253
+ ts,
4254
+ name: StandardEvents.UI_RAGE_CLICK,
4255
+ source: "rrweb",
4256
+ schemaVersion: EVENT_SCHEMA_VERSION,
4257
+ props: {
4258
+ x,
4259
+ y,
4260
+ clickCount: nearby.length,
4261
+ duration_ms: ts - nearby[0].ts
4262
+ }
4263
+ });
4264
+ this.clicks = [];
4265
+ }
4266
+ }
4267
+ };
4268
+
4269
+ // ../event-processor/dist/detectors/scroll-thrash.js
4270
+ var ScrollThrashDetector = class {
4271
+ constructor(config, emit2) {
4272
+ this.config = config;
4273
+ this.emit = emit2;
4274
+ this.lastY = null;
4275
+ this.lastDirection = null;
4276
+ this.reversals = [];
4277
+ }
4278
+ ingest(raw) {
4279
+ var _a2;
4280
+ if (raw.type !== 3 || raw.data.source !== RRWebSource.Scroll)
4281
+ return;
4282
+ const y = (_a2 = raw.data.y) != null ? _a2 : 0;
4283
+ const ts = raw.timestamp;
4284
+ if (this.lastY !== null) {
4285
+ const direction = y > this.lastY ? "down" : y < this.lastY ? "up" : this.lastDirection;
4286
+ if (direction && direction !== this.lastDirection) {
4287
+ const cutoff = ts - this.config.scrollThrashWindowMs;
4288
+ this.reversals = this.reversals.filter((t) => t > cutoff);
4289
+ this.reversals.push(ts);
4290
+ if (this.reversals.length >= this.config.scrollThrashReversals) {
4291
+ this.emit({
4292
+ ts,
4293
+ name: StandardEvents.UI_SCROLL_THRASH,
4294
+ source: "rrweb",
4295
+ schemaVersion: EVENT_SCHEMA_VERSION,
4296
+ props: {
4297
+ reversals: this.reversals.length,
4298
+ duration_ms: ts - this.reversals[0]
4299
+ }
4300
+ });
4301
+ this.reversals = [];
4302
+ }
4303
+ }
4304
+ this.lastDirection = direction;
4305
+ }
4306
+ this.lastY = y;
4307
+ }
4308
+ };
4309
+
4310
+ // ../event-processor/dist/processor.js
4311
+ function createEventProcessor(options) {
4312
+ const config = { ...DEFAULT_DETECTOR_CONFIG, ...options == null ? void 0 : options.config };
4313
+ const listeners = [];
4314
+ function emit2(event) {
4315
+ for (const cb of listeners)
4316
+ cb(event);
4317
+ }
4318
+ const hesitation = new HesitationDetector(config, emit2);
4319
+ const rageClick = new RageClickDetector(config, emit2);
4320
+ const scrollThrash = new ScrollThrashDetector(config, emit2);
4321
+ const focusBounce = new FocusBounceDetector(config, emit2);
4322
+ const idle = new IdleDetector(config, emit2);
4323
+ const hover = new HoverTracker(config, emit2, options == null ? void 0 : options.elementResolver);
4324
+ const clickAttrBuffer = [];
4325
+ return {
4326
+ ingest(raw) {
4327
+ if (raw.kind === "posthog") {
4328
+ const { kind: _, ...phEvent } = raw;
4329
+ if (shouldNormalizeEvent(phEvent)) {
4330
+ emit2(normalizePostHogEvent(phEvent));
4331
+ }
4332
+ } else if (raw.kind === "rrweb") {
4333
+ hesitation.ingest(raw);
4334
+ rageClick.ingest(raw);
4335
+ scrollThrash.ingest(raw);
4336
+ focusBounce.ingest(raw);
4337
+ idle.ingest(raw);
4338
+ hover.ingest(raw);
4339
+ }
4340
+ },
4341
+ onEvent(callback) {
4342
+ listeners.push(callback);
4343
+ },
4344
+ tick(timestamp) {
4345
+ hesitation.tick(timestamp);
4346
+ idle.tick(timestamp);
4347
+ hover.tick(timestamp);
4348
+ },
4349
+ enrichClickAttributes(timestamp, elements) {
4350
+ clickAttrBuffer.push({ ts: timestamp, elements });
4351
+ const cutoff = timestamp - 500;
4352
+ while (clickAttrBuffer.length > 0 && clickAttrBuffer[0].ts < cutoff) {
4353
+ clickAttrBuffer.shift();
4354
+ }
4355
+ }
4356
+ };
4357
+ }
4358
+
4359
+ // src/events/types.ts
4360
+ var StandardEvents2 = {
3818
4361
  // UI events (from PostHog autocapture)
3819
4362
  UI_CLICK: "ui.click",
3820
4363
  UI_SCROLL: "ui.scroll",
@@ -3866,48 +4409,48 @@ function createCanvasEvent(name, props) {
3866
4409
  };
3867
4410
  }
3868
4411
  function canvasOpened(surface) {
3869
- return createCanvasEvent(StandardEvents.CANVAS_OPENED, { surface });
4412
+ return createCanvasEvent(StandardEvents2.CANVAS_OPENED, { surface });
3870
4413
  }
3871
4414
  function canvasClosed(surface) {
3872
- return createCanvasEvent(StandardEvents.CANVAS_CLOSED, { surface });
4415
+ return createCanvasEvent(StandardEvents2.CANVAS_CLOSED, { surface });
3873
4416
  }
3874
4417
  function tileViewed(tileId, surface) {
3875
- return createCanvasEvent(StandardEvents.TILE_VIEWED, { tileId, surface });
4418
+ return createCanvasEvent(StandardEvents2.TILE_VIEWED, { tileId, surface });
3876
4419
  }
3877
4420
  function tileExpanded(tileId, surface) {
3878
- return createCanvasEvent(StandardEvents.TILE_EXPANDED, { tileId, surface });
4421
+ return createCanvasEvent(StandardEvents2.TILE_EXPANDED, { tileId, surface });
3879
4422
  }
3880
4423
  function tileCollapsed(tileId, surface) {
3881
- return createCanvasEvent(StandardEvents.TILE_COLLAPSED, { tileId, surface });
4424
+ return createCanvasEvent(StandardEvents2.TILE_COLLAPSED, { tileId, surface });
3882
4425
  }
3883
4426
  function tileAction(tileId, actionId, surface) {
3884
- return createCanvasEvent(StandardEvents.TILE_ACTION, {
4427
+ return createCanvasEvent(StandardEvents2.TILE_ACTION, {
3885
4428
  tileId,
3886
4429
  actionId,
3887
4430
  surface
3888
4431
  });
3889
4432
  }
3890
4433
  function overlayStarted(recipeId, recipeName) {
3891
- return createCanvasEvent(StandardEvents.OVERLAY_STARTED, {
4434
+ return createCanvasEvent(StandardEvents2.OVERLAY_STARTED, {
3892
4435
  recipeId,
3893
4436
  recipeName
3894
4437
  });
3895
4438
  }
3896
4439
  function overlayCompleted(recipeId, recipeName) {
3897
- return createCanvasEvent(StandardEvents.OVERLAY_COMPLETED, {
4440
+ return createCanvasEvent(StandardEvents2.OVERLAY_COMPLETED, {
3898
4441
  recipeId,
3899
4442
  recipeName
3900
4443
  });
3901
4444
  }
3902
4445
  function overlayDismissed(recipeId, recipeName, stepIndex) {
3903
- return createCanvasEvent(StandardEvents.OVERLAY_DISMISSED, {
4446
+ return createCanvasEvent(StandardEvents2.OVERLAY_DISMISSED, {
3904
4447
  recipeId,
3905
4448
  recipeName,
3906
4449
  stepIndex
3907
4450
  });
3908
4451
  }
3909
4452
  function overlayStepViewed(recipeId, stepIndex, stepTitle) {
3910
- return createCanvasEvent(StandardEvents.OVERLAY_STEP_VIEWED, {
4453
+ return createCanvasEvent(StandardEvents2.OVERLAY_STEP_VIEWED, {
3911
4454
  recipeId,
3912
4455
  stepIndex,
3913
4456
  stepTitle
@@ -4233,7 +4776,7 @@ function useNotifications(eventBus, tiles) {
4233
4776
  const timerIds = useRef2(/* @__PURE__ */ new Map());
4234
4777
  const publishDismissed = useCallback2(
4235
4778
  (notif) => {
4236
- eventBus == null ? void 0 : eventBus.publish(StandardEvents.NOTIFICATION_DISMISSED, {
4779
+ eventBus == null ? void 0 : eventBus.publish(StandardEvents2.NOTIFICATION_DISMISSED, {
4237
4780
  notificationId: notif.id,
4238
4781
  tileId: notif.tileId,
4239
4782
  itemId: notif.itemId
@@ -4298,7 +4841,7 @@ function useNotifications(eventBus, tiles) {
4298
4841
  }
4299
4842
  return next;
4300
4843
  });
4301
- eventBus.publish(StandardEvents.NOTIFICATION_SHOWN, {
4844
+ eventBus.publish(StandardEvents2.NOTIFICATION_SHOWN, {
4302
4845
  notificationId: matched.id,
4303
4846
  tileId: matched.tileId,
4304
4847
  itemId: matched.itemId,
@@ -4511,7 +5054,7 @@ function WidgetMount({ widgetId, props }) {
4511
5054
  prevPropsJsonRef.current = propsJson;
4512
5055
  (_a3 = handleRef.current) == null ? void 0 : _a3.update(propsRef.current);
4513
5056
  }, [propsJson]);
4514
- if (!registry || !registry.has(widgetId)) {
5057
+ if (!(registry == null ? void 0 : registry.has(widgetId))) {
4515
5058
  return /* @__PURE__ */ jsxs2(
4516
5059
  "div",
4517
5060
  {
@@ -4530,6 +5073,19 @@ function WidgetMount({ widgetId, props }) {
4530
5073
  }
4531
5074
  return /* @__PURE__ */ jsx6("div", { ref: parentRef });
4532
5075
  }
5076
+ var INTERACTION_PATTERNS = [
5077
+ ":toggled",
5078
+ ":clicked",
5079
+ ":feedback",
5080
+ ":navigate",
5081
+ ":expanded",
5082
+ ":collapsed",
5083
+ ":dismissed",
5084
+ ":submitted",
5085
+ ":interacted",
5086
+ ":tip_clicked",
5087
+ ":tip_focused"
5088
+ ];
4533
5089
  function TileCard({
4534
5090
  config,
4535
5091
  surface: _surface,
@@ -4538,10 +5094,42 @@ function TileCard({
4538
5094
  }) {
4539
5095
  const { title, subtitle, widget, props, icon } = config;
4540
5096
  const [, setTick] = useState4(0);
5097
+ const articleRef = useRef4(null);
4541
5098
  const runtime3 = useRuntime();
4542
5099
  useEffect5(() => {
4543
5100
  if (runtime3) setTick((t) => t + 1);
4544
5101
  }, [runtime3]);
5102
+ useEffect5(() => {
5103
+ var _a2;
5104
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5105
+ if (!articleRef.current || !tracker) return;
5106
+ const observer = new IntersectionObserver(
5107
+ ([entry]) => {
5108
+ var _a3;
5109
+ if (entry.isIntersecting) {
5110
+ tracker.trackSeen(config.id, (_a3 = config.widget) != null ? _a3 : "unknown");
5111
+ observer.disconnect();
5112
+ }
5113
+ },
5114
+ { threshold: 0.5 }
5115
+ );
5116
+ observer.observe(articleRef.current);
5117
+ return () => observer.disconnect();
5118
+ }, [config.id, config.widget]);
5119
+ useEffect5(() => {
5120
+ var _a2;
5121
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5122
+ if (!(runtime3 == null ? void 0 : runtime3.events) || !tracker) return;
5123
+ return runtime3.events.subscribe((event) => {
5124
+ var _a3, _b;
5125
+ if (!INTERACTION_PATTERNS.some((p) => {
5126
+ var _a4;
5127
+ return (_a4 = event.name) == null ? void 0 : _a4.includes(p);
5128
+ })) return;
5129
+ if (((_a3 = event.props) == null ? void 0 : _a3.instanceId) !== config.id) return;
5130
+ tracker.trackInteracted(config.id, (_b = config.widget) != null ? _b : "unknown", event.name);
5131
+ });
5132
+ }, [runtime3 == null ? void 0 : runtime3.events, config.id, config.widget]);
4545
5133
  const registration = useMemo3(
4546
5134
  () => {
4547
5135
  var _a2, _b;
@@ -4595,6 +5183,7 @@ function TileCard({
4595
5183
  return /* @__PURE__ */ jsxs2(
4596
5184
  "article",
4597
5185
  {
5186
+ ref: articleRef,
4598
5187
  "data-shadow-canvas-id": `tile-${config.id}`,
4599
5188
  style: cardStyle,
4600
5189
  onMouseEnter,
@@ -5132,7 +5721,7 @@ function ShadowCanvasOverlay({
5132
5721
  const handleNotificationClick = useCallback4(
5133
5722
  (notif) => {
5134
5723
  if (runtime3) {
5135
- runtime3.events.publish(StandardEvents.NOTIFICATION_CLICKED, {
5724
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_CLICKED, {
5136
5725
  notificationId: notif.id,
5137
5726
  tileId: notif.tileId,
5138
5727
  itemId: notif.itemId
@@ -5142,7 +5731,7 @@ function ShadowCanvasOverlay({
5142
5731
  onToggle();
5143
5732
  }
5144
5733
  if (runtime3 && notif.tileId) {
5145
- runtime3.events.publish(StandardEvents.NOTIFICATION_DEEP_LINK, {
5734
+ runtime3.events.publish(StandardEvents2.NOTIFICATION_DEEP_LINK, {
5146
5735
  tileId: notif.tileId,
5147
5736
  itemId: notif.itemId
5148
5737
  });
@@ -5215,6 +5804,17 @@ function ShadowCanvasOverlay({
5215
5804
  }
5216
5805
  onToggle();
5217
5806
  }, [isOpen, telemetry, runtime3, onToggle]);
5807
+ useEffect7(() => {
5808
+ if (!isOpen) return;
5809
+ const handleOutsideClick = (e) => {
5810
+ const path = e.composedPath();
5811
+ if (containerRef.current && !path.includes(containerRef.current) && launcherRef.current && !path.includes(launcherRef.current)) {
5812
+ toggle2();
5813
+ }
5814
+ };
5815
+ document.addEventListener("mousedown", handleOutsideClick);
5816
+ return () => document.removeEventListener("mousedown", handleOutsideClick);
5817
+ }, [isOpen, toggle2]);
5218
5818
  const onLauncherPointerDown = useCallback4((e) => {
5219
5819
  const rect = e.currentTarget.getBoundingClientRect();
5220
5820
  dragRef.current = {
@@ -5253,6 +5853,7 @@ function ShadowCanvasOverlay({
5253
5853
  const isPush = config.canvas.layout === "push";
5254
5854
  const canvasBorder = (_b = config.canvas.border) != null ? _b : "none";
5255
5855
  const containerRef = useRef5(null);
5856
+ const launcherRef = useRef5(null);
5256
5857
  const zIndex = 2147483600;
5257
5858
  useEffect7(() => {
5258
5859
  var _a3, _b2, _c2, _d2;
@@ -5340,7 +5941,7 @@ function ShadowCanvasOverlay({
5340
5941
  style: {
5341
5942
  position: "fixed",
5342
5943
  inset: 0,
5343
- pointerEvents: isOpen ? "auto" : "none",
5944
+ pointerEvents: "none",
5344
5945
  zIndex
5345
5946
  },
5346
5947
  children: /* @__PURE__ */ jsxs3("div", { style: wrapperStyle, children: [
@@ -5413,17 +6014,7 @@ function ShadowCanvasOverlay({
5413
6014
  ) }),
5414
6015
  footerSlot
5415
6016
  ] }),
5416
- /* @__PURE__ */ jsx8(
5417
- "div",
5418
- {
5419
- onClick: toggle2,
5420
- style: {
5421
- flex: "1 1 auto",
5422
- pointerEvents: isOpen ? "auto" : "none",
5423
- cursor: "default"
5424
- }
5425
- }
5426
- )
6017
+ /* @__PURE__ */ jsx8("div", { style: { flex: "1 1 auto" } })
5427
6018
  ] })
5428
6019
  }
5429
6020
  );
@@ -5453,6 +6044,7 @@ function ShadowCanvasOverlay({
5453
6044
  /* @__PURE__ */ jsxs3(
5454
6045
  "button",
5455
6046
  {
6047
+ ref: launcherRef,
5456
6048
  type: "button",
5457
6049
  "aria-label": "Toggle shadow canvas",
5458
6050
  className: launcherAnimate && !isOpen ? "syntro-launcher-animate" : void 0,
@@ -5614,6 +6206,14 @@ var sortTiles = (tiles) => [...tiles].sort((a, b) => {
5614
6206
  var _a2, _b;
5615
6207
  return ((_a2 = b.priority) != null ? _a2 : 0) - ((_b = a.priority) != null ? _b : 0);
5616
6208
  });
6209
+ function fireTriggeredForTiles(tiles) {
6210
+ var _a2, _b;
6211
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
6212
+ if (!tracker) return;
6213
+ for (const tile of tiles) {
6214
+ tracker.trackTriggered(tile.id, (_b = tile.widget) != null ? _b : "unknown");
6215
+ }
6216
+ }
5617
6217
  function useShadowCanvasConfig({
5618
6218
  fetcher,
5619
6219
  experiments,
@@ -5636,6 +6236,7 @@ function useShadowCanvasConfig({
5636
6236
  if (experiments) {
5637
6237
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
5638
6238
  }
6239
+ fireTriggeredForTiles(tiles);
5639
6240
  setState((prev) => ({ ...prev, tiles: sortTiles(tiles) }));
5640
6241
  }, [runtime3, experiments]);
5641
6242
  const load = useCallback5(async () => {
@@ -5653,6 +6254,7 @@ function useShadowCanvasConfig({
5653
6254
  } else if (experiments) {
5654
6255
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
5655
6256
  }
6257
+ fireTriggeredForTiles(tiles);
5656
6258
  debug("SmartCanvas Config", `Tile count after filtering: ${tiles.length}`);
5657
6259
  const newActions = response.actions || [];
5658
6260
  const newActionsJson = JSON.stringify(newActions);
@@ -5715,8 +6317,8 @@ function SmartCanvasApp({
5715
6317
  controller,
5716
6318
  fetcher,
5717
6319
  configUri,
5718
- configUriFeatureKey = "smart-canvas-config-uri",
5719
- configFeatureKey = "smart-canvas-config",
6320
+ configUriFeatureKey,
6321
+ configFeatureKey,
5720
6322
  fetchCredentials = "include",
5721
6323
  pollIntervalMs,
5722
6324
  experiments,
@@ -5786,10 +6388,10 @@ function SmartCanvasAppInner({
5786
6388
  controller,
5787
6389
  fetcher,
5788
6390
  configUri,
5789
- configUriFeatureKey = "smart-canvas-config-uri",
5790
- configFeatureKey = "smart-canvas-config",
6391
+ configUriFeatureKey,
6392
+ configFeatureKey,
5791
6393
  fetchCredentials = "include",
5792
- pollIntervalMs,
6394
+ pollIntervalMs: _pollIntervalMs,
5793
6395
  experiments,
5794
6396
  telemetry,
5795
6397
  runtime: runtime3,
@@ -7072,7 +7674,6 @@ var PostHogAdapter = class {
7072
7674
  __publicField(this, "featureFlagsCallback");
7073
7675
  __publicField(this, "captureCallback");
7074
7676
  __publicField(this, "rrwebCallback");
7075
- __publicField(this, "consentUnsub");
7076
7677
  this.client = options.client;
7077
7678
  this.featureFlagsCallback = options.onFeatureFlagsLoaded;
7078
7679
  this.captureCallback = options.onCapture;
@@ -7083,7 +7684,7 @@ var PostHogAdapter = class {
7083
7684
  if (currentStatus === "granted") {
7084
7685
  this.initPostHog();
7085
7686
  }
7086
- this.consentUnsub = consent.subscribe((status) => {
7687
+ consent.subscribe((status) => {
7087
7688
  if (status === "granted") {
7088
7689
  if (!this.client) {
7089
7690
  this.initPostHog();
@@ -7110,71 +7711,67 @@ var PostHogAdapter = class {
7110
7711
  if (!options.apiKey) return;
7111
7712
  const enableFeatureFlags = (_a2 = options.enableFeatureFlags) != null ? _a2 : true;
7112
7713
  const instanceName = `syntro_${options.apiKey.slice(-6) || "sdk"}`;
7113
- this.client = posthog.init(
7114
- options.apiKey,
7115
- {
7116
- api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
7117
- // Feature flags for segment membership (in_segment_* flags)
7118
- // When enabled, /decide is called to get segment flags
7119
- advanced_disable_feature_flags: !enableFeatureFlags,
7120
- advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
7121
- // Full-page tracking - all ON by default
7122
- autocapture: (_c = options.autocapture) != null ? _c : true,
7123
- capture_pageview: (_d = options.capturePageview) != null ? _d : true,
7124
- capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
7125
- disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
7126
- // CRITICAL: Disable user agent filtering to allow headless Chrome
7127
- // PostHog blocks "HeadlessChrome" user agents by default as bot detection
7128
- // This enables session recording in Playwright/crawler sessions
7129
- opt_out_useragent_filter: true,
7130
- // Cross-domain iframe recording for embeds
7131
- session_recording: {
7132
- recordCrossDomainIFrames: true
7133
- },
7134
- // Capture performance metrics
7135
- capture_performance: true,
7136
- // Enable web vitals
7137
- enable_recording_console_log: true,
7138
- // Bootstrap callback for when flags are loaded
7139
- loaded: (ph) => {
7140
- if (enableFeatureFlags && this.featureFlagsCallback) {
7141
- ph.onFeatureFlags(() => {
7142
- const allFlags = this.getAllFeatureFlags();
7143
- if (allFlags && this.featureFlagsCallback) {
7144
- this.featureFlagsCallback(allFlags);
7145
- }
7146
- });
7147
- const existingFlags = this.getAllFeatureFlags();
7148
- if (existingFlags && Object.keys(existingFlags).length > 0) {
7149
- this.featureFlagsCallback(existingFlags);
7714
+ const initOptions = {
7715
+ api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
7716
+ // Feature flags for segment membership (in_segment_* flags)
7717
+ // When enabled, /decide is called to get segment flags
7718
+ advanced_disable_feature_flags: !enableFeatureFlags,
7719
+ advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
7720
+ // Full-page tracking - all ON by default
7721
+ autocapture: (_c = options.autocapture) != null ? _c : true,
7722
+ capture_pageview: (_d = options.capturePageview) != null ? _d : "history_change",
7723
+ capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
7724
+ disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
7725
+ // CRITICAL: Disable user agent filtering to allow headless Chrome
7726
+ // PostHog blocks "HeadlessChrome" user agents by default as bot detection
7727
+ // This enables session recording in Playwright/crawler sessions
7728
+ opt_out_useragent_filter: true,
7729
+ // Cross-domain iframe recording for embeds
7730
+ session_recording: {
7731
+ recordCrossDomainIFrames: true
7732
+ },
7733
+ // Capture performance metrics
7734
+ capture_performance: true,
7735
+ // Enable web vitals
7736
+ enable_recording_console_log: true,
7737
+ // Bootstrap callback for when flags are loaded
7738
+ loaded: (ph) => {
7739
+ if (enableFeatureFlags && this.featureFlagsCallback) {
7740
+ ph.onFeatureFlags(() => {
7741
+ const allFlags = this.getAllFeatureFlags();
7742
+ if (allFlags && this.featureFlagsCallback) {
7743
+ this.featureFlagsCallback(allFlags);
7150
7744
  }
7151
- }
7152
- if (this.captureCallback) {
7153
- ph.on("eventCaptured", (data) => {
7154
- var _a3;
7155
- const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
7156
- const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
7157
- if (typeof eventName === "string") {
7158
- (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
7159
- }
7160
- });
7745
+ });
7746
+ const existingFlags = this.getAllFeatureFlags();
7747
+ if (existingFlags && Object.keys(existingFlags).length > 0) {
7748
+ this.featureFlagsCallback(existingFlags);
7161
7749
  }
7162
7750
  }
7163
- },
7751
+ if (this.captureCallback) {
7752
+ ph.on("eventCaptured", (...args) => {
7753
+ var _a3;
7754
+ const data = args[0];
7755
+ const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
7756
+ const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
7757
+ if (typeof eventName === "string") {
7758
+ (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
7759
+ }
7760
+ });
7761
+ }
7762
+ }
7763
+ };
7764
+ const result = posthog.init(
7765
+ options.apiKey,
7766
+ initOptions,
7164
7767
  instanceName
7165
7768
  );
7769
+ if (result) {
7770
+ this.client = result;
7771
+ }
7166
7772
  if (this.rrwebCallback && this.client) {
7167
7773
  this.setupRRWebIntercept();
7168
7774
  }
7169
- if (options.replayMirrorEndpoint && this.client) {
7170
- import("./replayMirror-QZ3GQ527.js").then(({ setupReplayMirror }) => {
7171
- var _a3;
7172
- setupReplayMirror({
7173
- posthogHost: (_a3 = options.apiHost) != null ? _a3 : "https://telemetry.syntrologie.com",
7174
- mirrorEndpoint: options.replayMirrorEndpoint
7175
- });
7176
- });
7177
- }
7178
7775
  }
7179
7776
  /**
7180
7777
  * Set up rrweb event interception on PostHog's session recording.
@@ -7197,8 +7794,9 @@ var PostHogAdapter = class {
7197
7794
  return;
7198
7795
  }
7199
7796
  let recorder = null;
7200
- for (const key of Object.getOwnPropertyNames(sr)) {
7201
- const val = sr[key];
7797
+ const srRecord = sr;
7798
+ for (const key of Object.getOwnPropertyNames(srRecord)) {
7799
+ const val = srRecord[key];
7202
7800
  if (val && typeof val === "object" && typeof val.onRRwebEmit === "function" && typeof val.start === "function" && val !== sr) {
7203
7801
  recorder = val;
7204
7802
  break;
@@ -7225,9 +7823,8 @@ var PostHogAdapter = class {
7225
7823
  * Used to extract segment membership flags (in_segment_*).
7226
7824
  */
7227
7825
  getAllFeatureFlags() {
7228
- var _a2, _b, _c;
7229
- const flags = (_c = (_b = (_a2 = this.client) == null ? void 0 : _a2.featureFlags) == null ? void 0 : _b.getFlagVariants) == null ? void 0 : _c.call(_b);
7230
- return flags;
7826
+ var _a2, _b;
7827
+ return (_b = (_a2 = this.client) == null ? void 0 : _a2.featureFlags) == null ? void 0 : _b.getFlagVariants();
7231
7828
  }
7232
7829
  /**
7233
7830
  * Get segment membership flags (in_segment_*) from PostHog.
@@ -7308,6 +7905,59 @@ function createPostHogClient(options = {}) {
7308
7905
  return new PostHogAdapter(options);
7309
7906
  }
7310
7907
 
7908
+ // src/telemetry/InterventionTracker.ts
7909
+ var InterventionTracker = class {
7910
+ constructor(telemetry, variantId) {
7911
+ __publicField(this, "telemetry");
7912
+ __publicField(this, "variantId");
7913
+ __publicField(this, "seenSet", /* @__PURE__ */ new Set());
7914
+ __publicField(this, "triggeredSet", /* @__PURE__ */ new Set());
7915
+ this.telemetry = telemetry;
7916
+ this.variantId = variantId;
7917
+ }
7918
+ trackServed(tiles, actions) {
7919
+ var _a2, _b;
7920
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_config_served", {
7921
+ variant_id: this.variantId,
7922
+ tiles,
7923
+ actions
7924
+ });
7925
+ }
7926
+ trackSeen(interventionId, interventionKind) {
7927
+ var _a2, _b;
7928
+ if (this.seenSet.has(interventionId)) return;
7929
+ this.seenSet.add(interventionId);
7930
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_seen", {
7931
+ variant_id: this.variantId,
7932
+ intervention_id: interventionId,
7933
+ intervention_kind: interventionKind
7934
+ });
7935
+ }
7936
+ trackTriggered(interventionId, interventionKind) {
7937
+ var _a2, _b;
7938
+ if (this.triggeredSet.has(interventionId)) return;
7939
+ this.triggeredSet.add(interventionId);
7940
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_triggered", {
7941
+ variant_id: this.variantId,
7942
+ intervention_id: interventionId,
7943
+ intervention_kind: interventionKind
7944
+ });
7945
+ }
7946
+ trackInteracted(interventionId, interventionKind, interactionType) {
7947
+ var _a2, _b;
7948
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_interacted", {
7949
+ variant_id: this.variantId,
7950
+ intervention_id: interventionId,
7951
+ intervention_kind: interventionKind,
7952
+ interaction_type: interactionType
7953
+ });
7954
+ }
7955
+ resetPage() {
7956
+ this.seenSet.clear();
7957
+ this.triggeredSet.clear();
7958
+ }
7959
+ };
7960
+
7311
7961
  // src/actions/executors/core-flow.ts
7312
7962
  var executeSequence = async (action, context) => {
7313
7963
  const handles = [];
@@ -7578,187 +8228,36 @@ function hasExecutor(kind) {
7578
8228
  return executorRegistry.has(kind);
7579
8229
  }
7580
8230
 
7581
- // src/actions/validation.ts
7582
- var DANGEROUS_ATTRS = /* @__PURE__ */ new Set([
7583
- "onclick",
7584
- "onerror",
7585
- "onload",
7586
- "onmouseover",
7587
- "onfocus",
7588
- "onblur",
7589
- "onchange",
7590
- "onsubmit",
7591
- "onkeydown",
7592
- "onkeyup",
7593
- "onkeypress"
7594
- ]);
7595
- var MAX_HTML_LENGTH = 5e4;
7596
- var MAX_STYLE_COUNT = 50;
7597
- function validateAction(action) {
7598
- const errors = [];
7599
- const warnings = [];
7600
- if (!action || typeof action !== "object") {
8231
+ // src/actions/validation-rules.ts
8232
+ function validateBadgeAction(action, errors, warnings) {
8233
+ if (!action.content || typeof action.content !== "string") {
7601
8234
  errors.push({
7602
- code: "INVALID_ACTION",
7603
- message: "Action must be an object"
8235
+ code: "MISSING_CONTENT",
8236
+ message: "Badge action requires 'content' property",
8237
+ field: "content"
8238
+ });
8239
+ } else if (action.content.length > 100) {
8240
+ warnings.push({
8241
+ code: "LONG_BADGE_CONTENT",
8242
+ message: "Badge content is quite long",
8243
+ suggestion: "Keep badge content short (under 100 characters)"
7604
8244
  });
7605
- return { valid: false, errors, warnings };
7606
8245
  }
7607
- const { kind } = action;
7608
- if (!kind || typeof kind !== "string") {
8246
+ }
8247
+ function validateTooltipAction(action, errors, _warnings) {
8248
+ if (!action.content || typeof action.content !== "object") {
7609
8249
  errors.push({
7610
- code: "MISSING_KIND",
7611
- message: "Action must have a 'kind' property"
8250
+ code: "MISSING_CONTENT",
8251
+ message: "Tooltip action requires 'content' object",
8252
+ field: "content"
7612
8253
  });
7613
- return { valid: false, errors, warnings };
8254
+ return;
7614
8255
  }
7615
- if (!hasExecutor(kind) && kind !== "core:mountWidget") {
7616
- const registered = executorRegistry.list();
7617
- console.error(
7618
- `[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.`
7619
- );
8256
+ if (!action.content.body || typeof action.content.body !== "string") {
7620
8257
  errors.push({
7621
- code: "UNKNOWN_KIND",
7622
- message: `Unknown action kind: ${kind}`,
7623
- field: "kind"
7624
- });
7625
- return { valid: false, errors, warnings };
7626
- }
7627
- switch (kind) {
7628
- case "overlays:highlight":
7629
- case "overlays:pulse":
7630
- case "navigation:scrollTo":
7631
- validateAnchorAction(action, errors, warnings);
7632
- break;
7633
- case "overlays:badge":
7634
- validateAnchorAction(action, errors, warnings);
7635
- validateBadgeAction(action, errors, warnings);
7636
- break;
7637
- case "overlays:tooltip":
7638
- validateAnchorAction(action, errors, warnings);
7639
- validateTooltipAction(action, errors, warnings);
7640
- break;
7641
- case "overlays:modal":
7642
- validateModalAction(action, errors, warnings);
7643
- break;
7644
- case "content:insertHtml":
7645
- validateAnchorAction(action, errors, warnings);
7646
- validateInsertHtmlAction(action, errors, warnings);
7647
- break;
7648
- case "content:setText":
7649
- validateAnchorAction(action, errors, warnings);
7650
- validateSetTextAction(action, errors, warnings);
7651
- break;
7652
- case "content:setAttr":
7653
- validateAnchorAction(action, errors, warnings);
7654
- validateSetAttrAction(action, errors, warnings);
7655
- break;
7656
- case "content:addClass":
7657
- case "content:removeClass":
7658
- validateAnchorAction(action, errors, warnings);
7659
- validateClassAction(action, errors, warnings);
7660
- break;
7661
- case "content:setStyle":
7662
- validateAnchorAction(action, errors, warnings);
7663
- validateSetStyleAction(action, errors, warnings);
7664
- break;
7665
- case "core:mountWidget":
7666
- validateMountWidgetAction(action, errors, warnings);
7667
- break;
7668
- case "core:wait":
7669
- validateWaitAction(action, errors, warnings);
7670
- break;
7671
- case "core:sequence":
7672
- validateSequenceAction(action, errors, warnings);
7673
- break;
7674
- case "core:parallel":
7675
- validateParallelAction(action, errors, warnings);
7676
- break;
7677
- case "core:tour":
7678
- validateTourAction(action, errors, warnings);
7679
- break;
7680
- case "navigation:navigate":
7681
- validateNavigateAction(action, errors, warnings);
7682
- break;
7683
- }
7684
- return {
7685
- valid: errors.length === 0,
7686
- errors,
7687
- warnings
7688
- };
7689
- }
7690
- function validateAnchorAction(action, errors, warnings) {
7691
- const anchorId = action.anchorId;
7692
- if (!anchorId || typeof anchorId !== "object") {
7693
- errors.push({
7694
- code: "MISSING_ANCHOR_ID",
7695
- message: "Action requires an 'anchorId' object with a 'selector' string",
7696
- field: "anchorId"
7697
- });
7698
- return;
7699
- }
7700
- if (!anchorId.selector || typeof anchorId.selector !== "string") {
7701
- errors.push({
7702
- code: "MISSING_ANCHOR_SELECTOR",
7703
- message: "anchorId requires a 'selector' string",
7704
- field: "anchorId.selector"
7705
- });
7706
- } else if (anchorId.selector.length > 200) {
7707
- warnings.push({
7708
- code: "LONG_ANCHOR_ID",
7709
- message: "Anchor selector is unusually long",
7710
- suggestion: "Consider using a shorter, more descriptive selector"
7711
- });
7712
- }
7713
- if (anchorId.route === void 0 || anchorId.route === null) {
7714
- errors.push({
7715
- code: "MISSING_ANCHOR_ROUTE",
7716
- message: `anchorId requires a 'route' (string or array of strings). Use "**" for all routes.`,
7717
- field: "anchorId.route"
7718
- });
7719
- } else {
7720
- const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
7721
- for (const route of routes) {
7722
- if (typeof route !== "string") {
7723
- errors.push({
7724
- code: "INVALID_ANCHOR_ROUTE",
7725
- message: "anchorId.route must be a string or array of strings",
7726
- field: "anchorId.route"
7727
- });
7728
- break;
7729
- }
7730
- }
7731
- }
7732
- }
7733
- function validateBadgeAction(action, errors, warnings) {
7734
- if (!action.content || typeof action.content !== "string") {
7735
- errors.push({
7736
- code: "MISSING_CONTENT",
7737
- message: "Badge action requires 'content' property",
7738
- field: "content"
7739
- });
7740
- } else if (action.content.length > 100) {
7741
- warnings.push({
7742
- code: "LONG_BADGE_CONTENT",
7743
- message: "Badge content is quite long",
7744
- suggestion: "Keep badge content short (under 100 characters)"
7745
- });
7746
- }
7747
- }
7748
- function validateTooltipAction(action, errors, _warnings) {
7749
- if (!action.content || typeof action.content !== "object") {
7750
- errors.push({
7751
- code: "MISSING_CONTENT",
7752
- message: "Tooltip action requires 'content' object",
7753
- field: "content"
7754
- });
7755
- return;
7756
- }
7757
- if (!action.content.body || typeof action.content.body !== "string") {
7758
- errors.push({
7759
- code: "MISSING_BODY",
7760
- message: "Tooltip content requires 'body' property",
7761
- field: "content.body"
8258
+ code: "MISSING_BODY",
8259
+ message: "Tooltip content requires 'body' property",
8260
+ field: "content.body"
7762
8261
  });
7763
8262
  }
7764
8263
  }
@@ -8116,6 +8615,159 @@ function validateTourAction(action, errors, warnings) {
8116
8615
  }
8117
8616
  }
8118
8617
  }
8618
+
8619
+ // src/actions/validation-core.ts
8620
+ var DANGEROUS_ATTRS = /* @__PURE__ */ new Set([
8621
+ "onclick",
8622
+ "onerror",
8623
+ "onload",
8624
+ "onmouseover",
8625
+ "onfocus",
8626
+ "onblur",
8627
+ "onchange",
8628
+ "onsubmit",
8629
+ "onkeydown",
8630
+ "onkeyup",
8631
+ "onkeypress"
8632
+ ]);
8633
+ var MAX_HTML_LENGTH = 5e4;
8634
+ var MAX_STYLE_COUNT = 50;
8635
+ function validateAction(action) {
8636
+ const errors = [];
8637
+ const warnings = [];
8638
+ if (!action || typeof action !== "object") {
8639
+ errors.push({
8640
+ code: "INVALID_ACTION",
8641
+ message: "Action must be an object"
8642
+ });
8643
+ return { valid: false, errors, warnings };
8644
+ }
8645
+ const { kind } = action;
8646
+ if (!kind || typeof kind !== "string") {
8647
+ errors.push({
8648
+ code: "MISSING_KIND",
8649
+ message: "Action must have a 'kind' property"
8650
+ });
8651
+ return { valid: false, errors, warnings };
8652
+ }
8653
+ if (!hasExecutor(kind) && kind !== "core:mountWidget") {
8654
+ const registered = executorRegistry.list();
8655
+ console.error(
8656
+ `[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.`
8657
+ );
8658
+ errors.push({
8659
+ code: "UNKNOWN_KIND",
8660
+ message: `Unknown action kind: ${kind}`,
8661
+ field: "kind"
8662
+ });
8663
+ return { valid: false, errors, warnings };
8664
+ }
8665
+ switch (kind) {
8666
+ case "overlays:highlight":
8667
+ case "overlays:pulse":
8668
+ case "navigation:scrollTo":
8669
+ validateAnchorAction(action, errors, warnings);
8670
+ break;
8671
+ case "overlays:badge":
8672
+ validateAnchorAction(action, errors, warnings);
8673
+ validateBadgeAction(action, errors, warnings);
8674
+ break;
8675
+ case "overlays:tooltip":
8676
+ validateAnchorAction(action, errors, warnings);
8677
+ validateTooltipAction(action, errors, warnings);
8678
+ break;
8679
+ case "overlays:modal":
8680
+ validateModalAction(action, errors, warnings);
8681
+ break;
8682
+ case "content:insertHtml":
8683
+ validateAnchorAction(action, errors, warnings);
8684
+ validateInsertHtmlAction(action, errors, warnings);
8685
+ break;
8686
+ case "content:setText":
8687
+ validateAnchorAction(action, errors, warnings);
8688
+ validateSetTextAction(action, errors, warnings);
8689
+ break;
8690
+ case "content:setAttr":
8691
+ validateAnchorAction(action, errors, warnings);
8692
+ validateSetAttrAction(action, errors, warnings);
8693
+ break;
8694
+ case "content:addClass":
8695
+ case "content:removeClass":
8696
+ validateAnchorAction(action, errors, warnings);
8697
+ validateClassAction(action, errors, warnings);
8698
+ break;
8699
+ case "content:setStyle":
8700
+ validateAnchorAction(action, errors, warnings);
8701
+ validateSetStyleAction(action, errors, warnings);
8702
+ break;
8703
+ case "core:mountWidget":
8704
+ validateMountWidgetAction(action, errors, warnings);
8705
+ break;
8706
+ case "core:wait":
8707
+ validateWaitAction(action, errors, warnings);
8708
+ break;
8709
+ case "core:sequence":
8710
+ validateSequenceAction(action, errors, warnings);
8711
+ break;
8712
+ case "core:parallel":
8713
+ validateParallelAction(action, errors, warnings);
8714
+ break;
8715
+ case "overlays:tour":
8716
+ validateTourAction(action, errors, warnings);
8717
+ break;
8718
+ case "navigation:navigate":
8719
+ validateNavigateAction(action, errors, warnings);
8720
+ break;
8721
+ }
8722
+ return {
8723
+ valid: errors.length === 0,
8724
+ errors,
8725
+ warnings
8726
+ };
8727
+ }
8728
+ function validateAnchorAction(action, errors, warnings) {
8729
+ const anchorId = action.anchorId;
8730
+ if (!anchorId || typeof anchorId !== "object") {
8731
+ errors.push({
8732
+ code: "MISSING_ANCHOR_ID",
8733
+ message: "Action requires an 'anchorId' object with a 'selector' string",
8734
+ field: "anchorId"
8735
+ });
8736
+ return;
8737
+ }
8738
+ if (!anchorId.selector || typeof anchorId.selector !== "string") {
8739
+ errors.push({
8740
+ code: "MISSING_ANCHOR_SELECTOR",
8741
+ message: "anchorId requires a 'selector' string",
8742
+ field: "anchorId.selector"
8743
+ });
8744
+ } else if (anchorId.selector.length > 200) {
8745
+ warnings.push({
8746
+ code: "LONG_ANCHOR_ID",
8747
+ message: "Anchor selector is unusually long",
8748
+ suggestion: "Consider using a shorter, more descriptive selector"
8749
+ });
8750
+ }
8751
+ if (anchorId.route === void 0 || anchorId.route === null) {
8752
+ errors.push({
8753
+ code: "MISSING_ANCHOR_ROUTE",
8754
+ message: `anchorId requires a 'route' (string or array of strings). Use "**" for all routes.`,
8755
+ field: "anchorId.route"
8756
+ });
8757
+ } else {
8758
+ const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
8759
+ for (const route of routes) {
8760
+ if (typeof route !== "string") {
8761
+ errors.push({
8762
+ code: "INVALID_ANCHOR_ROUTE",
8763
+ message: "anchorId.route must be a string or array of strings",
8764
+ field: "anchorId.route"
8765
+ });
8766
+ break;
8767
+ }
8768
+ }
8769
+ }
8770
+ }
8119
8771
  function validateActions(actions) {
8120
8772
  var _a2;
8121
8773
  const errors = [];
@@ -8262,7 +8914,7 @@ function createActionEngine(options) {
8262
8914
  }
8263
8915
  return executor(action, context);
8264
8916
  }
8265
- function subscribeForReeval(id, action, triggerWhen, handle) {
8917
+ function subscribeForReeval(id, action, triggerWhen, _handle) {
8266
8918
  if (!runtime3) return;
8267
8919
  const unsubs = [];
8268
8920
  const onReeval = async () => {
@@ -9232,6 +9884,66 @@ function createEventAccumulator(options) {
9232
9884
  };
9233
9885
  }
9234
9886
 
9887
+ // src/events/validation.ts
9888
+ var APP_PREFIX = "app:";
9889
+ var RESERVED_PREFIX = "syntro:";
9890
+ var SEGMENT_PATTERN = /^[a-z][a-z0-9_]*$/;
9891
+ function validateEventName(name) {
9892
+ if (!name) {
9893
+ return { valid: false, reason: "Event name cannot be empty" };
9894
+ }
9895
+ if (name.startsWith(RESERVED_PREFIX)) {
9896
+ return { valid: false, reason: '"syntro:" prefix is reserved for internal SDK events' };
9897
+ }
9898
+ if (!name.startsWith(APP_PREFIX)) {
9899
+ return { valid: false, reason: `Custom events must start with "app:" prefix. Got: "${name}"` };
9900
+ }
9901
+ const segments = name.slice(APP_PREFIX.length).split(":");
9902
+ if (segments.length < 2) {
9903
+ return {
9904
+ valid: false,
9905
+ reason: `Event name must have at least 2 segments after "app:" (app:{category}:{action}). Got: "${name}"`
9906
+ };
9907
+ }
9908
+ for (const segment of segments) {
9909
+ if (!SEGMENT_PATTERN.test(segment)) {
9910
+ return {
9911
+ valid: false,
9912
+ reason: `Segment "${segment}" must be lowercase alphanumeric + underscores. Got: "${name}"`
9913
+ };
9914
+ }
9915
+ }
9916
+ return { valid: true };
9917
+ }
9918
+ function isSerializable(value) {
9919
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null;
9920
+ }
9921
+ function checkDepth(obj, maxDepth, current = 0) {
9922
+ if (current >= maxDepth) return false;
9923
+ for (const value of Object.values(obj)) {
9924
+ if (value !== null && typeof value === "object" && !Array.isArray(value)) {
9925
+ if (!checkDepth(value, maxDepth, current + 1)) return false;
9926
+ }
9927
+ }
9928
+ return true;
9929
+ }
9930
+ function validateProps(props) {
9931
+ if (props === void 0) return { valid: true };
9932
+ if (props === null || typeof props !== "object" || Array.isArray(props)) {
9933
+ return { valid: false, reason: "Props must be a plain object" };
9934
+ }
9935
+ if (!checkDepth(props, 2)) {
9936
+ return { valid: false, reason: "Props nesting depth exceeds 2 levels" };
9937
+ }
9938
+ const stripped = [];
9939
+ for (const [key, value] of Object.entries(props)) {
9940
+ if (value !== null && typeof value === "object") continue;
9941
+ if (!isSerializable(value)) stripped.push(key);
9942
+ }
9943
+ if (stripped.length > 0) return { valid: true, stripped };
9944
+ return { valid: true };
9945
+ }
9946
+
9235
9947
  // src/events/EventBus.ts
9236
9948
  function matchesFilter(event, filter) {
9237
9949
  if (!filter) return true;
@@ -9263,8 +9975,16 @@ var EventBus = class {
9263
9975
  __publicField(this, "subscriptions", /* @__PURE__ */ new Set());
9264
9976
  __publicField(this, "history", []);
9265
9977
  __publicField(this, "maxHistorySize");
9266
- var _a2;
9978
+ __publicField(this, "debug");
9979
+ __publicField(this, "emitHistory");
9980
+ __publicField(this, "posthogCapture");
9981
+ __publicField(this, "testMode");
9982
+ var _a2, _b, _c, _d, _e;
9267
9983
  this.maxHistorySize = (_a2 = options.maxHistorySize) != null ? _a2 : 100;
9984
+ this.debug = (_b = options.debug) != null ? _b : false;
9985
+ this.emitHistory = (_c = options.history) != null ? _c : null;
9986
+ this.posthogCapture = (_d = options.posthogCapture) != null ? _d : null;
9987
+ this.testMode = (_e = options.testMode) != null ? _e : false;
9268
9988
  }
9269
9989
  /**
9270
9990
  * Subscribe to events matching an optional filter.
@@ -9293,29 +10013,106 @@ var EventBus = class {
9293
10013
  const event = {
9294
10014
  ts: Date.now(),
9295
10015
  name,
9296
- source,
10016
+ source,
10017
+ props,
10018
+ schemaVersion: EVENT_SCHEMA_VERSION
10019
+ };
10020
+ this.publishEvent(event);
10021
+ }
10022
+ /**
10023
+ * Publish a pre-constructed NormalizedEvent.
10024
+ */
10025
+ publishEvent(event) {
10026
+ this.history.push(event);
10027
+ if (this.history.length > this.maxHistorySize) {
10028
+ this.history.shift();
10029
+ }
10030
+ for (const subscription of this.subscriptions) {
10031
+ if (matchesFilter(event, subscription.filter)) {
10032
+ try {
10033
+ subscription.callback(event);
10034
+ } catch (err) {
10035
+ console.error("[EventBus] Subscriber error:", err);
10036
+ }
10037
+ }
10038
+ }
10039
+ }
10040
+ /**
10041
+ * Emit a validated custom event from the host application.
10042
+ *
10043
+ * Custom events must use the `app:` prefix (e.g. `app:cart:abandoned`).
10044
+ * In debug mode, returns an EmitResult with delivery details.
10045
+ * In production mode, returns undefined.
10046
+ */
10047
+ emit(name, props) {
10048
+ const nameResult = validateEventName(name);
10049
+ if (!nameResult.valid) {
10050
+ console.warn(`[EventBus] emit() rejected: ${nameResult.reason}`);
10051
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
10052
+ }
10053
+ const propsResult = validateProps(props);
10054
+ if (!propsResult.valid) {
10055
+ console.warn(`[EventBus] emit() rejected props: ${propsResult.reason}`);
10056
+ return this.debug ? { delivered: false, matchedRules: [], posthogCaptured: false, listenersNotified: 0 } : void 0;
10057
+ }
10058
+ const event = {
10059
+ ts: Date.now(),
10060
+ name,
10061
+ source: "custom",
9297
10062
  props,
9298
10063
  schemaVersion: EVENT_SCHEMA_VERSION
9299
10064
  };
9300
- this.publishEvent(event);
9301
- }
9302
- /**
9303
- * Publish a pre-constructed NormalizedEvent.
9304
- */
9305
- publishEvent(event) {
9306
10065
  this.history.push(event);
9307
10066
  if (this.history.length > this.maxHistorySize) {
9308
10067
  this.history.shift();
9309
10068
  }
10069
+ let listenersNotified = 0;
9310
10070
  for (const subscription of this.subscriptions) {
9311
10071
  if (matchesFilter(event, subscription.filter)) {
9312
10072
  try {
9313
10073
  subscription.callback(event);
10074
+ listenersNotified++;
9314
10075
  } catch (err) {
9315
10076
  console.error("[EventBus] Subscriber error:", err);
10077
+ listenersNotified++;
9316
10078
  }
9317
10079
  }
9318
10080
  }
10081
+ let posthogCaptured = false;
10082
+ if (this.posthogCapture && !this.testMode) {
10083
+ try {
10084
+ this.posthogCapture(name, props);
10085
+ posthogCaptured = true;
10086
+ } catch (err) {
10087
+ console.error("[EventBus] PostHog capture error:", err);
10088
+ }
10089
+ }
10090
+ if (this.emitHistory) {
10091
+ this.emitHistory.record({
10092
+ name,
10093
+ props,
10094
+ source: "custom",
10095
+ timestamp: event.ts,
10096
+ matchedRules: []
10097
+ });
10098
+ }
10099
+ if (this.debug) {
10100
+ console.debug("[EventBus] emit()", { name, props, listenersNotified, posthogCaptured });
10101
+ return {
10102
+ delivered: true,
10103
+ matchedRules: [],
10104
+ posthogCaptured,
10105
+ listenersNotified
10106
+ };
10107
+ }
10108
+ return void 0;
10109
+ }
10110
+ /**
10111
+ * Set the PostHog capture function after construction.
10112
+ * Used by bootstrap to wire PostHog after the EventBus is created.
10113
+ */
10114
+ setPosthogCapture(fn) {
10115
+ this.posthogCapture = fn;
9319
10116
  }
9320
10117
  /**
9321
10118
  * Get recent events matching an optional filter.
@@ -9367,6 +10164,27 @@ function createEventBus(options = {}) {
9367
10164
  return new EventBus(options);
9368
10165
  }
9369
10166
 
10167
+ // src/events/history.ts
10168
+ var EventHistory = class {
10169
+ constructor(maxSize = 100) {
10170
+ __publicField(this, "entries", []);
10171
+ __publicField(this, "maxSize");
10172
+ this.maxSize = maxSize;
10173
+ }
10174
+ record(entry) {
10175
+ this.entries.push(entry);
10176
+ if (this.maxSize > 0 && this.entries.length > this.maxSize) {
10177
+ this.entries.shift();
10178
+ }
10179
+ }
10180
+ getAll() {
10181
+ return [...this.entries];
10182
+ }
10183
+ clear() {
10184
+ this.entries = [];
10185
+ }
10186
+ };
10187
+
9370
10188
  // src/navigation/NavigationMonitor.ts
9371
10189
  var NavigationMonitor = class {
9372
10190
  constructor() {
@@ -10478,7 +11296,16 @@ function matchesAnchorRoute(anchorId) {
10478
11296
  const pathname = typeof window !== "undefined" ? window.location.pathname : "/";
10479
11297
  const normalizedPath = pathname.replace(/\/$/, "") || "/";
10480
11298
  const routes = Array.isArray(anchorId.route) ? anchorId.route : [anchorId.route];
10481
- return routes.some((pattern) => matchRoutePattern(normalizedPath, pattern));
11299
+ return routes.some((pattern) => {
11300
+ let normalized = pattern;
11301
+ if (/^https?:\/\//.test(normalized)) {
11302
+ try {
11303
+ normalized = new URL(normalized).pathname;
11304
+ } catch {
11305
+ }
11306
+ }
11307
+ return matchRoutePattern(normalizedPath, normalized);
11308
+ });
10482
11309
  }
10483
11310
  function createSmartCanvasRuntime(options = {}) {
10484
11311
  var _a2, _b, _c, _d;
@@ -10650,8 +11477,141 @@ function encodeToken(payload) {
10650
11477
  return TOKEN_PREFIX + base64;
10651
11478
  }
10652
11479
 
10653
- // src/bootstrap.ts
10654
- import { createEventProcessor } from "@syntrologie/event-processor";
11480
+ // src/bootstrap-init.ts
11481
+ function getEnvVar(name) {
11482
+ if (typeof process !== "undefined" && process.env) {
11483
+ return process.env[name];
11484
+ }
11485
+ try {
11486
+ const meta = (0, eval)("import.meta");
11487
+ if (meta == null ? void 0 : meta.env) {
11488
+ return meta.env[name];
11489
+ }
11490
+ } catch {
11491
+ }
11492
+ return void 0;
11493
+ }
11494
+ var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
11495
+ function loadCachedSegmentAttributes() {
11496
+ if (typeof window === "undefined") return {};
11497
+ try {
11498
+ const cached = localStorage.getItem(SEGMENT_CACHE_KEY);
11499
+ if (cached) {
11500
+ const attrs = JSON.parse(cached);
11501
+ debug("Syntro Bootstrap", "Loaded cached segment attributes:", attrs);
11502
+ return attrs;
11503
+ }
11504
+ } catch (err) {
11505
+ warn("Syntro Bootstrap", "Failed to load cached segment attributes:", err);
11506
+ }
11507
+ return {};
11508
+ }
11509
+ function cacheSegmentAttributes(attrs) {
11510
+ if (typeof window === "undefined") return;
11511
+ try {
11512
+ localStorage.setItem(SEGMENT_CACHE_KEY, JSON.stringify(attrs));
11513
+ debug("Syntro Bootstrap", "Cached segment attributes:", attrs);
11514
+ } catch (err) {
11515
+ warn("Syntro Bootstrap", "Failed to cache segment attributes:", err);
11516
+ }
11517
+ }
11518
+ function extractSegmentFlags(allFlags) {
11519
+ if (!allFlags) return {};
11520
+ const segmentFlags = {};
11521
+ for (const [key, value] of Object.entries(allFlags)) {
11522
+ if (key.startsWith("in_segment_")) {
11523
+ segmentFlags[key] = value === true;
11524
+ }
11525
+ }
11526
+ return segmentFlags;
11527
+ }
11528
+ function collectBrowserMetadata() {
11529
+ var _a2;
11530
+ if (typeof window === "undefined") return {};
11531
+ const attrs = {};
11532
+ try {
11533
+ const params = new URLSearchParams(window.location.search);
11534
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
11535
+ const val = params.get(key);
11536
+ if (val) attrs[key] = val;
11537
+ }
11538
+ } catch {
11539
+ }
11540
+ try {
11541
+ if (navigator.language) attrs.browser_language = navigator.language;
11542
+ if ((_a2 = navigator.languages) == null ? void 0 : _a2.length) attrs.browser_languages = [...navigator.languages];
11543
+ } catch {
11544
+ }
11545
+ try {
11546
+ const w = window.innerWidth;
11547
+ attrs.device_type = w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
11548
+ } catch {
11549
+ }
11550
+ try {
11551
+ if (document.referrer) {
11552
+ attrs.referrer = document.referrer;
11553
+ try {
11554
+ attrs.referrer_hostname = new URL(document.referrer).hostname;
11555
+ } catch {
11556
+ }
11557
+ }
11558
+ } catch {
11559
+ }
11560
+ try {
11561
+ const ua = navigator.userAgent;
11562
+ if (ua.includes("Edg/")) attrs.browser = "Edge";
11563
+ else if (ua.includes("OPR/") || ua.includes("Opera")) attrs.browser = "Opera";
11564
+ else if (ua.includes("Chrome/") && !ua.includes("Chromium")) attrs.browser = "Chrome";
11565
+ else if (ua.includes("Safari/") && !ua.includes("Chrome")) attrs.browser = "Safari";
11566
+ else if (ua.includes("Firefox/")) attrs.browser = "Firefox";
11567
+ if (ua.includes("Windows")) attrs.os = "Windows";
11568
+ else if (ua.includes("iPhone") || ua.includes("iPad")) attrs.os = "iOS";
11569
+ else if (ua.includes("Mac OS X") || ua.includes("Macintosh")) attrs.os = "macOS";
11570
+ else if (ua.includes("Android")) attrs.os = "Android";
11571
+ else if (ua.includes("Linux")) attrs.os = "Linux";
11572
+ } catch {
11573
+ }
11574
+ try {
11575
+ attrs.page_url = window.location.href;
11576
+ attrs.page_path = window.location.pathname;
11577
+ attrs.page_host = window.location.hostname;
11578
+ if (window.location.search) attrs.page_query = window.location.search;
11579
+ } catch {
11580
+ }
11581
+ return attrs;
11582
+ }
11583
+ var GEO_CACHE_KEY = "syntro_geo";
11584
+ var GEO_DEFAULT_HOST = "https://geo.syntrologie.com";
11585
+ async function fetchGeo(geoHost) {
11586
+ if (typeof window === "undefined") return {};
11587
+ try {
11588
+ const cached = localStorage.getItem(GEO_CACHE_KEY);
11589
+ if (cached) {
11590
+ const parsed = JSON.parse(cached);
11591
+ debug("Syntro Bootstrap", "Geo: using cached data:", parsed);
11592
+ return parsed;
11593
+ }
11594
+ } catch {
11595
+ }
11596
+ try {
11597
+ const res = await fetch(geoHost, { signal: AbortSignal.timeout(2e3) });
11598
+ if (res.ok) {
11599
+ const geo = await res.json();
11600
+ const cleaned = {};
11601
+ for (const [k, v] of Object.entries(geo)) {
11602
+ if (typeof v === "string" && v) cleaned[k] = v;
11603
+ }
11604
+ try {
11605
+ localStorage.setItem(GEO_CACHE_KEY, JSON.stringify(cleaned));
11606
+ } catch {
11607
+ }
11608
+ debug("Syntro Bootstrap", "Geo: fetched from worker:", cleaned);
11609
+ return cleaned;
11610
+ }
11611
+ } catch {
11612
+ }
11613
+ return {};
11614
+ }
10655
11615
 
10656
11616
  // src/experiments/registry.ts
10657
11617
  var adapters = {
@@ -10790,6 +11750,11 @@ var ExperimentsFetcher = class {
10790
11750
  };
10791
11751
  }
10792
11752
  }
11753
+ if (!this.featureKey) {
11754
+ throw new Error(
11755
+ "[SmartCanvas] No featureKey configured and no variant flags found. Ensure at least one config feature flag exists in your experiment platform."
11756
+ );
11757
+ }
10793
11758
  const config = (_b = (_a2 = this.client).getFeatureValue) == null ? void 0 : _b.call(_a2, this.featureKey, null);
10794
11759
  if (!config || typeof config !== "object") {
10795
11760
  throw new Error(
@@ -10901,95 +11866,9 @@ function createTelemetryClient(provider, config) {
10901
11866
  return factory(config);
10902
11867
  }
10903
11868
 
10904
- // src/bootstrap.ts
10905
- function getEnvVar(name) {
10906
- if (typeof process !== "undefined" && process.env) {
10907
- return process.env[name];
10908
- }
10909
- try {
10910
- const meta = (0, eval)("import.meta");
10911
- if (meta == null ? void 0 : meta.env) {
10912
- return meta.env[name];
10913
- }
10914
- } catch {
10915
- }
10916
- return void 0;
10917
- }
10918
- var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
10919
- function loadCachedSegmentAttributes() {
10920
- if (typeof window === "undefined") return {};
10921
- try {
10922
- const cached = localStorage.getItem(SEGMENT_CACHE_KEY);
10923
- if (cached) {
10924
- const attrs = JSON.parse(cached);
10925
- debug("Syntro Bootstrap", "Loaded cached segment attributes:", attrs);
10926
- return attrs;
10927
- }
10928
- } catch (err) {
10929
- warn("Syntro Bootstrap", "Failed to load cached segment attributes:", err);
10930
- }
10931
- return {};
10932
- }
10933
- function cacheSegmentAttributes(attrs) {
10934
- if (typeof window === "undefined") return;
10935
- try {
10936
- localStorage.setItem(SEGMENT_CACHE_KEY, JSON.stringify(attrs));
10937
- debug("Syntro Bootstrap", "Cached segment attributes:", attrs);
10938
- } catch (err) {
10939
- warn("Syntro Bootstrap", "Failed to cache segment attributes:", err);
10940
- }
10941
- }
10942
- function extractSegmentFlags(allFlags) {
10943
- if (!allFlags) return {};
10944
- const segmentFlags = {};
10945
- for (const [key, value] of Object.entries(allFlags)) {
10946
- if (key.startsWith("in_segment_")) {
10947
- segmentFlags[key] = value === true;
10948
- }
10949
- }
10950
- return segmentFlags;
10951
- }
10952
- function collectBrowserMetadata() {
10953
- var _a2;
10954
- if (typeof window === "undefined") return {};
10955
- const attrs = {};
10956
- try {
10957
- const params = new URLSearchParams(window.location.search);
10958
- for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term"]) {
10959
- const val = params.get(key);
10960
- if (val) attrs[key] = val;
10961
- }
10962
- } catch {
10963
- }
10964
- try {
10965
- if (navigator.language) attrs.browser_language = navigator.language;
10966
- if ((_a2 = navigator.languages) == null ? void 0 : _a2.length) attrs.browser_languages = [...navigator.languages];
10967
- } catch {
10968
- }
10969
- try {
10970
- const w = window.innerWidth;
10971
- attrs.device_type = w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
10972
- } catch {
10973
- }
10974
- try {
10975
- if (document.referrer) {
10976
- attrs.referrer = document.referrer;
10977
- try {
10978
- attrs.referrer_hostname = new URL(document.referrer).hostname;
10979
- } catch {
10980
- }
10981
- }
10982
- } catch {
10983
- }
10984
- try {
10985
- attrs.page_url = window.location.href;
10986
- attrs.page_path = window.location.pathname;
10987
- } catch {
10988
- }
10989
- return attrs;
10990
- }
10991
- async function init(options) {
10992
- var _a2, _b, _c, _d, _e, _f;
11869
+ // src/bootstrap-runtime.ts
11870
+ async function _initCore(options) {
11871
+ var _a2, _b, _c, _d, _e, _f, _g;
10993
11872
  initLogger();
10994
11873
  debug("Syntro Bootstrap", "====== INIT ======");
10995
11874
  debug("Syntro Bootstrap", "Options:", {
@@ -11054,13 +11933,20 @@ async function init(options) {
11054
11933
  const experimentHost = getEnvVar("NEXT_PUBLIC_SYNTRO_EXPERIMENT_HOST") || getEnvVar("VITE_SYNTRO_EXPERIMENT_HOST") || (payload == null ? void 0 : payload.eh);
11055
11934
  const telemetryHost = getEnvVar("NEXT_PUBLIC_SYNTRO_TELEMETRY_HOST") || getEnvVar("VITE_SYNTRO_TELEMETRY_HOST") || (payload == null ? void 0 : payload.th);
11056
11935
  const editorUrl = getEnvVar("NEXT_PUBLIC_SYNTRO_EDITOR_URL") || getEnvVar("VITE_SYNTRO_EDITOR_URL") || ((_b = options.canvas) == null ? void 0 : _b.editorUrl);
11936
+ const geoHost = (payload == null ? void 0 : payload.g) || getEnvVar("NEXT_PUBLIC_SYNTRO_GEO_HOST") || getEnvVar("VITE_SYNTRO_GEO_HOST") || GEO_DEFAULT_HOST;
11057
11937
  const cachedSegmentAttrs = loadCachedSegmentAttributes();
11058
11938
  const browserMetadata = collectBrowserMetadata();
11059
11939
  const phaseOneAttrs = { ...browserMetadata, ...cachedSegmentAttrs };
11060
11940
  debug("Syntro Bootstrap", "Phase 1: Browser metadata:", browserMetadata);
11061
11941
  debug("Syntro Bootstrap", "Phase 1: Cached segment attributes:", cachedSegmentAttrs);
11942
+ const geoPromise = fetchGeo(geoHost);
11062
11943
  let experiments;
11063
- const events = createEventBus();
11944
+ const isDebugOrTest = options.debug || options.testMode;
11945
+ const events = createEventBus({
11946
+ debug: options.debug,
11947
+ history: isDebugOrTest ? new EventHistory(options.testMode ? 0 : 100) : void 0,
11948
+ testMode: options.testMode
11949
+ });
11064
11950
  console.log("[Syntro Bootstrap] EventBus created");
11065
11951
  const processor = createEventProcessor({
11066
11952
  elementResolver: typeof window !== "undefined" ? (x, y) => {
@@ -11122,6 +12008,9 @@ async function init(options) {
11122
12008
  // undefined falls back to adapter default
11123
12009
  // Enable PostHog feature flags for segment membership
11124
12010
  enableFeatureFlags: true,
12011
+ // Disable session recording in debug/dev mode (mock telemetry doesn't
12012
+ // support the PostHog recorder extension, causing console errors)
12013
+ sessionRecording: !payload.d,
11125
12014
  // Wire up callback for when flags are loaded (Phase 2)
11126
12015
  onFeatureFlagsLoaded,
11127
12016
  // Wire up event capture to feed into event processor
@@ -11134,6 +12023,11 @@ async function init(options) {
11134
12023
  }
11135
12024
  });
11136
12025
  console.log(`[Syntro Bootstrap] Telemetry client created (${provider}) with EventBus wiring`);
12026
+ const telemetryForCapture = telemetry;
12027
+ events.setPosthogCapture((name, props) => {
12028
+ var _a3;
12029
+ (_a3 = telemetryForCapture.track) == null ? void 0 : _a3.call(telemetryForCapture, name, props);
12030
+ });
11137
12031
  }
11138
12032
  let sessionMetrics;
11139
12033
  if (payload == null ? void 0 : payload.e) {
@@ -11226,11 +12120,17 @@ async function init(options) {
11226
12120
  warn("Syntro Bootstrap", "Failed to load GrowthBook features:", err);
11227
12121
  }
11228
12122
  }
12123
+ const geoData = await geoPromise;
12124
+ if (experiments && Object.keys(geoData).length > 0) {
12125
+ const mergedAttrs = { ...browserMetadata, ...geoData };
12126
+ debug("Syntro Bootstrap", "Merging geo data into GrowthBook attributes:", geoData);
12127
+ (_f = experiments.setAttributes) == null ? void 0 : _f.call(experiments, mergedAttrs);
12128
+ }
11229
12129
  let baseFetcher;
11230
12130
  if (options.fetcher) {
11231
12131
  baseFetcher = options.fetcher;
11232
12132
  } else if (payload == null ? void 0 : payload.f) {
11233
- const configFetcher = createConfigFetcher(payload.f, (_f = payload.o) != null ? _f : {});
12133
+ const configFetcher = createConfigFetcher(payload.f, (_g = payload.o) != null ? _g : {});
11234
12134
  baseFetcher = async () => {
11235
12135
  var _a3;
11236
12136
  const result = await configFetcher.fetch();
@@ -11244,15 +12144,35 @@ async function init(options) {
11244
12144
  }
11245
12145
  const warnedAppFailures = /* @__PURE__ */ new Set();
11246
12146
  const appLoadingFetcher = baseFetcher ? async () => {
11247
- var _a3, _b2, _c2, _d2, _e2, _f2, _g;
12147
+ var _a3, _b2, _c2, _d2, _e2, _f2, _g2, _h, _i, _j, _k, _l;
11248
12148
  const config = await baseFetcher();
12149
+ const tileCount = (_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0;
12150
+ const actionCount = (_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0;
12151
+ const variantId = (_e2 = config.meta) == null ? void 0 : _e2.variant_id;
12152
+ if (tileCount > 0 || actionCount > 0) {
12153
+ if (!variantId) {
12154
+ console.warn(
12155
+ "[Syntro] Config has content but no meta.variant_id \u2014 intervention tracking disabled"
12156
+ );
12157
+ }
12158
+ }
12159
+ if (telemetry && variantId) {
12160
+ const tracker = new InterventionTracker(telemetry, variantId);
12161
+ tracker.trackServed(tileCount, actionCount);
12162
+ if (typeof window !== "undefined") {
12163
+ window.SynOS.interventionTracker = tracker;
12164
+ }
12165
+ runtime3.navigation.subscribe(() => {
12166
+ tracker.resetPage();
12167
+ });
12168
+ }
11249
12169
  console.log(
11250
12170
  "[Syntro Bootstrap] Config fetched:",
11251
- `tiles=${(_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0},`,
11252
- `actions=${(_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0},`,
11253
- `theme=${(_f2 = (_e2 = config.theme) == null ? void 0 : _e2.name) != null ? _f2 : "none"}`
12171
+ `tiles=${(_g2 = (_f2 = config.tiles) == null ? void 0 : _f2.length) != null ? _g2 : 0},`,
12172
+ `actions=${(_i = (_h = config.actions) == null ? void 0 : _h.length) != null ? _i : 0},`,
12173
+ `theme=${(_k = (_j = config.theme) == null ? void 0 : _j.name) != null ? _k : "none"}`
11254
12174
  );
11255
- if (((_g = config.actions) == null ? void 0 : _g.length) > 0) {
12175
+ if (((_l = config.actions) == null ? void 0 : _l.length) > 0) {
11256
12176
  console.log(
11257
12177
  "[Syntro Bootstrap] Actions in config:",
11258
12178
  config.actions.map(
@@ -11315,10 +12235,33 @@ async function init(options) {
11315
12235
  });
11316
12236
  return { canvas, runtime: runtime3, experiments, telemetry, sessionMetrics, appLoader };
11317
12237
  }
12238
+
12239
+ // src/bootstrap.ts
12240
+ async function init(options) {
12241
+ var _a2;
12242
+ try {
12243
+ return await _initCore(options);
12244
+ } catch (err) {
12245
+ const message = err instanceof Error ? err.message : String(err);
12246
+ console.warn("[Syntrologie] SDK initialization failed:", message);
12247
+ if (typeof document !== "undefined") {
12248
+ (_a2 = document.getElementById("syntrologie-anti-flicker")) == null ? void 0 : _a2.remove();
12249
+ }
12250
+ return void 0;
12251
+ }
12252
+ }
12253
+ function emit(eventName, props = {}) {
12254
+ var _a2, _b;
12255
+ if (typeof window === "undefined") return;
12256
+ const runtime3 = (_a2 = window.SynOS) == null ? void 0 : _a2.runtime;
12257
+ if (!((_b = runtime3 == null ? void 0 : runtime3.events) == null ? void 0 : _b.publish)) return;
12258
+ runtime3.events.publish({ name: eventName, source: "custom", props });
12259
+ }
11318
12260
  var Syntro = {
11319
12261
  init,
11320
12262
  encodeToken,
11321
- decodeToken
12263
+ decodeToken,
12264
+ events: { emit }
11322
12265
  };
11323
12266
  if (typeof window !== "undefined") {
11324
12267
  window.Syntro = Syntro;
@@ -11349,8 +12292,11 @@ export {
11349
12292
  createSmartCanvasController,
11350
12293
  ShadowRootProvider,
11351
12294
  useShadowRoot,
11352
- StandardEvents,
11353
12295
  EVENT_SCHEMA_VERSION,
12296
+ normalizePostHogEvent,
12297
+ shouldNormalizeEvent,
12298
+ createPostHogNormalizer,
12299
+ StandardEvents2 as StandardEvents,
11354
12300
  CanvasEvents,
11355
12301
  NotificationToastStack,
11356
12302
  MAX_VISIBLE_TOASTS,
@@ -11381,6 +12327,7 @@ export {
11381
12327
  createSessionMetricTracker,
11382
12328
  createNoopClient,
11383
12329
  createPostHogClient,
12330
+ InterventionTracker,
11384
12331
  ExecutorRegistry,
11385
12332
  executorRegistry,
11386
12333
  getExecutor,
@@ -11399,8 +12346,11 @@ export {
11399
12346
  evaluateSync,
11400
12347
  createDecisionEngine,
11401
12348
  createEventAccumulator,
12349
+ validateEventName,
12350
+ validateProps,
11402
12351
  EventBus,
11403
12352
  createEventBus,
12353
+ EventHistory,
11404
12354
  NavigationMonitor,
11405
12355
  StateStore,
11406
12356
  createStateStore,
@@ -11424,4 +12374,4 @@ export {
11424
12374
  encodeToken,
11425
12375
  Syntro
11426
12376
  };
11427
- //# sourceMappingURL=chunk-H3FAYTUV.js.map
12377
+ //# sourceMappingURL=chunk-GF364MMB.js.map