@syntrologie/runtime-sdk 0.2.20 → 0.2.21

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 (101) hide show
  1. package/CAPABILITIES.md +211 -0
  2. package/dist/api.js +7 -6
  3. package/dist/api.js.map +1 -1
  4. package/dist/bootstrap.d.ts +16 -2
  5. package/dist/bootstrap.js +63 -14
  6. package/dist/bootstrap.js.map +1 -1
  7. package/dist/context/ContextManager.d.ts +66 -0
  8. package/dist/context/ContextManager.js +268 -0
  9. package/dist/context/ContextManager.js.map +1 -0
  10. package/dist/context/index.d.ts +7 -0
  11. package/dist/context/index.js +7 -0
  12. package/dist/context/index.js.map +1 -0
  13. package/dist/context/schema.d.ts +360 -0
  14. package/dist/context/schema.js +50 -0
  15. package/dist/context/schema.js.map +1 -0
  16. package/dist/context/types.d.ts +101 -0
  17. package/dist/context/types.js +8 -0
  18. package/dist/context/types.js.map +1 -0
  19. package/dist/decisions/engine.d.ts +43 -0
  20. package/dist/decisions/engine.js +112 -0
  21. package/dist/decisions/engine.js.map +1 -0
  22. package/dist/decisions/index.d.ts +9 -0
  23. package/dist/decisions/index.js +10 -0
  24. package/dist/decisions/index.js.map +1 -0
  25. package/dist/decisions/schema.d.ts +2166 -0
  26. package/dist/decisions/schema.js +143 -0
  27. package/dist/decisions/schema.js.map +1 -0
  28. package/dist/decisions/strategies/rules.d.ts +24 -0
  29. package/dist/decisions/strategies/rules.js +152 -0
  30. package/dist/decisions/strategies/rules.js.map +1 -0
  31. package/dist/decisions/strategies/score.d.ts +10 -0
  32. package/dist/decisions/strategies/score.js +29 -0
  33. package/dist/decisions/strategies/score.js.map +1 -0
  34. package/dist/decisions/types.d.ts +242 -0
  35. package/dist/decisions/types.js +2 -0
  36. package/dist/decisions/types.js.map +1 -0
  37. package/dist/editorLoader.d.ts +10 -0
  38. package/dist/editorLoader.js +38 -0
  39. package/dist/editorLoader.js.map +1 -1
  40. package/dist/events/EventBus.d.ts +59 -0
  41. package/dist/events/EventBus.js +154 -0
  42. package/dist/events/EventBus.js.map +1 -0
  43. package/dist/events/index.d.ts +9 -0
  44. package/dist/events/index.js +10 -0
  45. package/dist/events/index.js.map +1 -0
  46. package/dist/events/normalizers/canvas.d.ts +67 -0
  47. package/dist/events/normalizers/canvas.js +116 -0
  48. package/dist/events/normalizers/canvas.js.map +1 -0
  49. package/dist/events/normalizers/posthog.d.ts +29 -0
  50. package/dist/events/normalizers/posthog.js +155 -0
  51. package/dist/events/normalizers/posthog.js.map +1 -0
  52. package/dist/events/schema.d.ts +70 -0
  53. package/dist/events/schema.js +30 -0
  54. package/dist/events/schema.js.map +1 -0
  55. package/dist/events/types.d.ts +73 -0
  56. package/dist/events/types.js +41 -0
  57. package/dist/events/types.js.map +1 -0
  58. package/dist/hooks/useShadowCanvasConfig.d.ts +5 -1
  59. package/dist/hooks/useShadowCanvasConfig.js +17 -3
  60. package/dist/hooks/useShadowCanvasConfig.js.map +1 -1
  61. package/dist/index.d.ts +6 -0
  62. package/dist/index.js +15 -0
  63. package/dist/index.js.map +1 -1
  64. package/dist/overlays/schema.d.ts +48 -48
  65. package/dist/runtime.d.ts +86 -0
  66. package/dist/runtime.js +132 -0
  67. package/dist/runtime.js.map +1 -0
  68. package/dist/smart-canvas.esm.js +11 -11
  69. package/dist/smart-canvas.esm.js.map +4 -4
  70. package/dist/smart-canvas.js +1504 -17
  71. package/dist/smart-canvas.js.map +4 -4
  72. package/dist/smart-canvas.min.js +11 -11
  73. package/dist/smart-canvas.min.js.map +4 -4
  74. package/dist/state/StateStore.d.ts +41 -0
  75. package/dist/state/StateStore.js +170 -0
  76. package/dist/state/StateStore.js.map +1 -0
  77. package/dist/state/helpers/cooldowns.d.ts +7 -0
  78. package/dist/state/helpers/cooldowns.js +31 -0
  79. package/dist/state/helpers/cooldowns.js.map +1 -0
  80. package/dist/state/helpers/dismissals.d.ts +7 -0
  81. package/dist/state/helpers/dismissals.js +34 -0
  82. package/dist/state/helpers/dismissals.js.map +1 -0
  83. package/dist/state/helpers/frequency.d.ts +8 -0
  84. package/dist/state/helpers/frequency.js +43 -0
  85. package/dist/state/helpers/frequency.js.map +1 -0
  86. package/dist/state/index.d.ts +7 -0
  87. package/dist/state/index.js +7 -0
  88. package/dist/state/index.js.map +1 -0
  89. package/dist/state/schema.d.ts +49 -0
  90. package/dist/state/schema.js +25 -0
  91. package/dist/state/schema.js.map +1 -0
  92. package/dist/state/types.d.ts +137 -0
  93. package/dist/state/types.js +9 -0
  94. package/dist/state/types.js.map +1 -0
  95. package/dist/telemetry/adapters/posthog.d.ts +1 -1
  96. package/dist/telemetry/adapters/posthog.js +1 -1
  97. package/dist/telemetry/adapters/posthog.js.map +1 -1
  98. package/dist/types.d.ts +8 -0
  99. package/package.json +2 -2
  100. package/schema/canvas-config.schema.json +205 -0
  101. package/schema/runtime-context.schema.json +131 -0
@@ -24551,33 +24551,94 @@ var SyntrologieSDK = (() => {
24551
24551
  // src/index.ts
24552
24552
  var index_exports = {};
24553
24553
  __export(index_exports, {
24554
+ ActivationConfigZ: () => ActivationConfigZ,
24555
+ AnchorStateZ: () => AnchorStateZ,
24556
+ AnchorVisibleConditionZ: () => AnchorVisibleConditionZ,
24557
+ AugmentationZ: () => AugmentationZ,
24558
+ BoundingBoxZ: () => BoundingBoxZ,
24559
+ CanvasEvents: () => CanvasEvents,
24554
24560
  CanvasRecipeZ: () => CanvasRecipeZ,
24561
+ ConditionZ: () => ConditionZ,
24562
+ ContextManager: () => ContextManager,
24563
+ CooldownActiveConditionZ: () => CooldownActiveConditionZ,
24564
+ DecisionStrategyZ: () => DecisionStrategyZ,
24565
+ DismissedConditionZ: () => DismissedConditionZ,
24566
+ EVENT_SCHEMA_VERSION: () => EVENT_SCHEMA_VERSION,
24567
+ EventBus: () => EventBus,
24568
+ EventFilterZ: () => EventFilterZ,
24569
+ EventOccurredConditionZ: () => EventOccurredConditionZ,
24570
+ EventSourceZ: () => EventSourceZ,
24571
+ ExternalStrategyZ: () => ExternalStrategyZ,
24572
+ FrequencyEntryZ: () => FrequencyEntryZ,
24573
+ FrequencyLimitConditionZ: () => FrequencyLimitConditionZ,
24555
24574
  HighlightStepZ: () => HighlightStepZ,
24575
+ ModelStrategyZ: () => ModelStrategyZ,
24576
+ NormalizedEventZ: () => NormalizedEventZ,
24577
+ PageContextZ: () => PageContextZ,
24578
+ PageHistoryEntryZ: () => PageHistoryEntryZ,
24579
+ PageUrlConditionZ: () => PageUrlConditionZ,
24580
+ RUNTIME_VERSION: () => RUNTIME_VERSION,
24581
+ RouteConditionZ: () => RouteConditionZ,
24582
+ RouteFilterZ: () => RouteFilterZ,
24583
+ RuleStrategyZ: () => RuleStrategyZ,
24584
+ RuleZ: () => RuleZ,
24585
+ RuntimeContextZ: () => RuntimeContextZ,
24586
+ ScoreStrategyZ: () => ScoreStrategyZ,
24556
24587
  SelectorZ: () => SelectorZ,
24588
+ SessionContextZ: () => SessionContextZ,
24589
+ SessionMetricConditionZ: () => SessionMetricConditionZ,
24557
24590
  SessionMetricTracker: () => SessionMetricTracker,
24558
24591
  ShadowCanvasOverlay: () => ShadowCanvasOverlay,
24559
24592
  SmartCanvasApp: () => SmartCanvasApp,
24560
24593
  SmartCanvasController: () => SmartCanvasController,
24561
24594
  SmartCanvasElement: () => SmartCanvasElement,
24562
24595
  SmartCanvasPortal: () => SmartCanvasPortal,
24596
+ StandardEvents: () => StandardEvents,
24597
+ StateEqualsConditionZ: () => StateEqualsConditionZ,
24598
+ StateStore: () => StateStore,
24599
+ StoredValueZ: () => StoredValueZ,
24563
24600
  Syntro: () => Syntro,
24564
24601
  TileCard: () => TileCard,
24565
24602
  TileWheel: () => TileWheel,
24566
24603
  TooltipStepZ: () => TooltipStepZ,
24604
+ ViewportConditionZ: () => ViewportConditionZ,
24605
+ ViewportContextZ: () => ViewportContextZ,
24567
24606
  createCanvasConfigFetcher: () => createCanvasConfigFetcher,
24607
+ createContextManager: () => createContextManager,
24608
+ createDecisionEngine: () => createDecisionEngine,
24609
+ createEventBus: () => createEventBus,
24568
24610
  createGrowthBookClient: () => createGrowthBookClient,
24569
24611
  createOverlayRecipeFetcher: () => createOverlayRecipeFetcher,
24570
24612
  createPostHogClient: () => createPostHogClient,
24613
+ createPostHogNormalizer: () => createPostHogNormalizer,
24571
24614
  createSessionMetricTracker: () => createSessionMetricTracker,
24572
24615
  createSmartCanvas: () => createSmartCanvas,
24573
24616
  createSmartCanvasController: () => createSmartCanvasController,
24617
+ createSmartCanvasRuntime: () => createSmartCanvasRuntime,
24618
+ createStateStore: () => createStateStore,
24574
24619
  decodeToken: () => decodeToken,
24575
24620
  encodeToken: () => encodeToken,
24621
+ evaluate: () => evaluate2,
24622
+ evaluateCondition: () => evaluateCondition,
24623
+ evaluateRule: () => evaluateRule,
24624
+ evaluateRuleStrategy: () => evaluateRuleStrategy,
24625
+ evaluateScoreStrategy: () => evaluateScoreStrategy,
24626
+ evaluateSync: () => evaluateSync,
24576
24627
  getAntiFlickerSnippet: () => getAntiFlickerSnippet,
24628
+ normalizePostHogEvent: () => normalizePostHogEvent,
24577
24629
  registerSmartCanvasElement: () => registerSmartCanvasElement,
24578
24630
  resolveConfigUri: () => resolveConfigUri,
24631
+ shouldNormalizeEvent: () => shouldNormalizeEvent,
24579
24632
  useShadowCanvasConfig: () => useShadowCanvasConfig,
24580
- validateRecipe: () => validateRecipe
24633
+ validateActivationConfig: () => validateActivationConfig,
24634
+ validateCondition: () => validateCondition,
24635
+ validateEventFilter: () => validateEventFilter,
24636
+ validateFrequencyEntry: () => validateFrequencyEntry,
24637
+ validateNormalizedEvent: () => validateNormalizedEvent,
24638
+ validateRecipe: () => validateRecipe,
24639
+ validateRuntimeContext: () => validateRuntimeContext,
24640
+ validateStoredValue: () => validateStoredValue,
24641
+ validateStrategy: () => validateStrategy
24581
24642
  });
24582
24643
 
24583
24644
  // node_modules/posthog-js/dist/module.js
@@ -29118,7 +29179,7 @@ var SyntrologieSDK = (() => {
29118
29179
  this.client = Uo;
29119
29180
  const enableFeatureFlags = options.enableFeatureFlags ?? true;
29120
29181
  this.client.init(options.apiKey, {
29121
- api_host: options.apiHost ?? "https://posthog-dev.syntrologie.com",
29182
+ api_host: options.apiHost ?? "https://telemetry.syntrologie.com",
29122
29183
  // Feature flags for segment membership (in_segment_* flags)
29123
29184
  // When enabled, /decide is called to get segment flags
29124
29185
  advanced_disable_feature_flags: !enableFeatureFlags,
@@ -32186,7 +32247,8 @@ var SyntrologieSDK = (() => {
32186
32247
  function useShadowCanvasConfig({
32187
32248
  fetcher,
32188
32249
  pollIntervalMs = 3e4,
32189
- experiments
32250
+ experiments,
32251
+ runtime
32190
32252
  }) {
32191
32253
  const [state, setState] = (0, import_react.useState)({
32192
32254
  tiles: [],
@@ -32197,7 +32259,15 @@ var SyntrologieSDK = (() => {
32197
32259
  setState((prev) => ({ ...prev, isLoading: true, error: void 0 }));
32198
32260
  const response = await fetcher();
32199
32261
  let tiles = response.tiles || [];
32200
- if (experiments) {
32262
+ if (runtime && response.routes) {
32263
+ runtime.setRoutes(response.routes);
32264
+ }
32265
+ if (runtime) {
32266
+ tiles = await runtime.filterTiles(tiles);
32267
+ if (experiments) {
32268
+ tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
32269
+ }
32270
+ } else if (experiments) {
32201
32271
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
32202
32272
  }
32203
32273
  setState({
@@ -32220,7 +32290,7 @@ var SyntrologieSDK = (() => {
32220
32290
  error: err instanceof Error ? err.message : "Unknown error"
32221
32291
  }));
32222
32292
  }
32223
- }, [experiments, fetcher]);
32293
+ }, [experiments, fetcher, runtime]);
32224
32294
  (0, import_react.useEffect)(() => {
32225
32295
  load();
32226
32296
  if (!pollIntervalMs) return;
@@ -39021,6 +39091,33 @@ var SyntrologieSDK = (() => {
39021
39091
  console.log("[Syntro Runtime] ================================");
39022
39092
  return shouldLoad;
39023
39093
  };
39094
+ var isAuditMode = () => {
39095
+ console.log("[Syntro Runtime] ====== AUDIT MODE CHECK ======");
39096
+ if (typeof window === "undefined") {
39097
+ console.log("[Syntro Runtime] Not in browser - skipping audit");
39098
+ return false;
39099
+ }
39100
+ const params = new URLSearchParams(window.location.search);
39101
+ const hasAuditFlag = params.has("syntro_audit");
39102
+ const auditSessionId = params.get("audit_session_id");
39103
+ console.log("[Syntro Runtime] Audit flags:", {
39104
+ hasAuditFlag,
39105
+ auditSessionId: auditSessionId ? `${auditSessionId.slice(0, 8)}...` : "none"
39106
+ });
39107
+ if (!hasAuditFlag) {
39108
+ console.log("[Syntro Runtime] No syntro_audit param - not in audit mode");
39109
+ console.log("[Syntro Runtime] ================================");
39110
+ return false;
39111
+ }
39112
+ console.log("[Syntro Runtime] \u2713 Audit mode active");
39113
+ console.log("[Syntro Runtime] ================================");
39114
+ return true;
39115
+ };
39116
+ var getActiveSdkMode = () => {
39117
+ if (shouldLoadEditor()) return "editor";
39118
+ if (isAuditMode()) return "audit";
39119
+ return null;
39120
+ };
39024
39121
  var loadEditorSdk = async (editorUrlOrOptions) => {
39025
39122
  console.log("[Syntro Runtime] ====== LOAD EDITOR SDK ======");
39026
39123
  if (typeof window === "undefined") {
@@ -39107,7 +39204,9 @@ var SyntrologieSDK = (() => {
39107
39204
  if (removeAntiFlicker) {
39108
39205
  removeAntiFlicker();
39109
39206
  }
39110
- if (shouldLoadEditor()) {
39207
+ const sdkMode = getActiveSdkMode();
39208
+ if (sdkMode) {
39209
+ console.log(`[SmartCanvas] Loading editor SDK for ${sdkMode} mode`);
39111
39210
  loadEditorSdk(config.editorUrl).catch(console.error);
39112
39211
  }
39113
39212
  registerSmartCanvasElement();
@@ -39205,9 +39304,8 @@ var SyntrologieSDK = (() => {
39205
39304
  }
39206
39305
  };
39207
39306
  if (typeof window !== "undefined") {
39208
- const isEditorMode2 = new URLSearchParams(window.location.search).has("editor_token");
39209
39307
  const isDev = true;
39210
- if (isEditorMode2 || isDev) {
39308
+ if (sdkMode || isDev) {
39211
39309
  window.__smartCanvasHandle = handle;
39212
39310
  }
39213
39311
  }
@@ -39366,6 +39464,1363 @@ var SyntrologieSDK = (() => {
39366
39464
  return new SessionMetricTracker(options);
39367
39465
  }
39368
39466
 
39467
+ // src/context/schema.ts
39468
+ var PageContextZ = external_exports.object({
39469
+ url: external_exports.string(),
39470
+ routeId: external_exports.string().optional(),
39471
+ title: external_exports.string().optional(),
39472
+ locale: external_exports.string().optional()
39473
+ });
39474
+ var PageHistoryEntryZ = external_exports.object({
39475
+ url: external_exports.string(),
39476
+ ts: external_exports.number()
39477
+ });
39478
+ var SessionContextZ = external_exports.object({
39479
+ sessionId: external_exports.string(),
39480
+ startTs: external_exports.number(),
39481
+ pageHistory: external_exports.array(PageHistoryEntryZ).optional()
39482
+ });
39483
+ var ViewportContextZ = external_exports.object({
39484
+ width: external_exports.number(),
39485
+ height: external_exports.number()
39486
+ });
39487
+ var BoundingBoxZ = external_exports.object({
39488
+ x: external_exports.number(),
39489
+ y: external_exports.number(),
39490
+ w: external_exports.number(),
39491
+ h: external_exports.number()
39492
+ });
39493
+ var AnchorStateZ = external_exports.object({
39494
+ anchorId: external_exports.string(),
39495
+ present: external_exports.boolean(),
39496
+ visible: external_exports.boolean().optional(),
39497
+ boundingBox: BoundingBoxZ.optional()
39498
+ });
39499
+ var AugmentationZ = external_exports.record(
39500
+ external_exports.union([external_exports.number(), external_exports.string(), external_exports.boolean(), external_exports.undefined()])
39501
+ );
39502
+ var RuntimeContextZ = external_exports.object({
39503
+ page: PageContextZ,
39504
+ session: SessionContextZ,
39505
+ viewport: ViewportContextZ,
39506
+ anchors: external_exports.array(AnchorStateZ).optional(),
39507
+ augmented: AugmentationZ.optional()
39508
+ });
39509
+ function validateRuntimeContext(data) {
39510
+ return RuntimeContextZ.safeParse(data);
39511
+ }
39512
+
39513
+ // src/context/ContextManager.ts
39514
+ function createDefaultContext() {
39515
+ const now = Date.now();
39516
+ return {
39517
+ page: {
39518
+ url: typeof window !== "undefined" ? window.location.href : "",
39519
+ title: typeof document !== "undefined" ? document.title : void 0
39520
+ },
39521
+ session: {
39522
+ sessionId: "",
39523
+ startTs: now,
39524
+ pageHistory: []
39525
+ },
39526
+ viewport: {
39527
+ width: typeof window !== "undefined" ? window.innerWidth : 0,
39528
+ height: typeof window !== "undefined" ? window.innerHeight : 0
39529
+ }
39530
+ };
39531
+ }
39532
+ function matchRoute2(url, routes) {
39533
+ if (!routes) return void 0;
39534
+ let pathname;
39535
+ try {
39536
+ pathname = new URL(url).pathname;
39537
+ } catch {
39538
+ pathname = url;
39539
+ }
39540
+ if (routes.exclude) {
39541
+ for (const pattern of routes.exclude) {
39542
+ if (matchPattern(pathname, pattern)) {
39543
+ return void 0;
39544
+ }
39545
+ }
39546
+ }
39547
+ if (routes.include) {
39548
+ for (const pattern of routes.include) {
39549
+ if (matchPattern(pathname, pattern)) {
39550
+ return pattern;
39551
+ }
39552
+ }
39553
+ return void 0;
39554
+ }
39555
+ return pathname;
39556
+ }
39557
+ function matchPattern(pathname, pattern) {
39558
+ const normalizedPath = pathname.replace(/\/$/, "") || "/";
39559
+ const normalizedPattern = pattern.replace(/\/$/, "") || "/";
39560
+ if (normalizedPath === normalizedPattern) return true;
39561
+ const regexPattern = normalizedPattern.replace(/:[^/]+/g, "[^/]+").replace(/\*/g, ".*");
39562
+ const regex = new RegExp(`^${regexPattern}$`);
39563
+ return regex.test(normalizedPath);
39564
+ }
39565
+ var ContextManager = class {
39566
+ constructor(options = {}) {
39567
+ __publicField(this, "context");
39568
+ __publicField(this, "previousContext");
39569
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
39570
+ __publicField(this, "telemetry");
39571
+ __publicField(this, "routes");
39572
+ // Event listener cleanup functions
39573
+ __publicField(this, "cleanupFns", []);
39574
+ this.telemetry = options.telemetry;
39575
+ this.routes = options.routes;
39576
+ this.context = createDefaultContext();
39577
+ this.previousContext = { ...this.context };
39578
+ if (options.telemetry?.getSessionId) {
39579
+ const sessionId = options.telemetry.getSessionId();
39580
+ if (sessionId) {
39581
+ this.context.session.sessionId = sessionId;
39582
+ }
39583
+ }
39584
+ if (options.initialPageHistory) {
39585
+ this.context.session.pageHistory = options.initialPageHistory;
39586
+ }
39587
+ this.context.page.routeId = matchRoute2(this.context.page.url, this.routes);
39588
+ this.addPageToHistory(this.context.page.url);
39589
+ if (typeof window !== "undefined") {
39590
+ this.setupBrowserListeners();
39591
+ }
39592
+ }
39593
+ /**
39594
+ * Get the current runtime context.
39595
+ */
39596
+ get() {
39597
+ return { ...this.context };
39598
+ }
39599
+ /**
39600
+ * Subscribe to context changes.
39601
+ * Returns an unsubscribe function.
39602
+ */
39603
+ subscribe(callback) {
39604
+ this.listeners.add(callback);
39605
+ return () => {
39606
+ this.listeners.delete(callback);
39607
+ };
39608
+ }
39609
+ /**
39610
+ * Update the routes config (e.g., when config is fetched).
39611
+ */
39612
+ setRoutes(routes) {
39613
+ this.routes = routes;
39614
+ const newRouteId = matchRoute2(this.context.page.url, this.routes);
39615
+ if (newRouteId !== this.context.page.routeId) {
39616
+ this.updateContext({ page: { ...this.context.page, routeId: newRouteId } });
39617
+ }
39618
+ }
39619
+ /**
39620
+ * Update anchor states.
39621
+ */
39622
+ setAnchors(anchors) {
39623
+ this.updateContext({ anchors });
39624
+ }
39625
+ /**
39626
+ * Manually update the session ID (e.g., when telemetry initializes).
39627
+ */
39628
+ setSessionId(sessionId) {
39629
+ if (sessionId !== this.context.session.sessionId) {
39630
+ this.updateContext({
39631
+ session: { ...this.context.session, sessionId }
39632
+ });
39633
+ }
39634
+ }
39635
+ /**
39636
+ * Clean up event listeners and subscriptions.
39637
+ */
39638
+ destroy() {
39639
+ for (const cleanup of this.cleanupFns) {
39640
+ cleanup();
39641
+ }
39642
+ this.cleanupFns = [];
39643
+ this.listeners.clear();
39644
+ }
39645
+ // ==================== Private Methods ====================
39646
+ setupBrowserListeners() {
39647
+ let resizeTimeout;
39648
+ const handleResize = () => {
39649
+ clearTimeout(resizeTimeout);
39650
+ resizeTimeout = setTimeout(() => {
39651
+ this.updateViewport();
39652
+ }, 100);
39653
+ };
39654
+ window.addEventListener("resize", handleResize);
39655
+ this.cleanupFns.push(() => window.removeEventListener("resize", handleResize));
39656
+ const handlePopState = () => {
39657
+ this.updatePage();
39658
+ };
39659
+ window.addEventListener("popstate", handlePopState);
39660
+ this.cleanupFns.push(() => window.removeEventListener("popstate", handlePopState));
39661
+ const originalPushState = history.pushState.bind(history);
39662
+ const originalReplaceState = history.replaceState.bind(history);
39663
+ history.pushState = (...args) => {
39664
+ originalPushState(...args);
39665
+ this.updatePage();
39666
+ };
39667
+ history.replaceState = (...args) => {
39668
+ originalReplaceState(...args);
39669
+ this.updatePage();
39670
+ };
39671
+ this.cleanupFns.push(() => {
39672
+ history.pushState = originalPushState;
39673
+ history.replaceState = originalReplaceState;
39674
+ });
39675
+ }
39676
+ updateViewport() {
39677
+ const newViewport = {
39678
+ width: window.innerWidth,
39679
+ height: window.innerHeight
39680
+ };
39681
+ if (newViewport.width !== this.context.viewport.width || newViewport.height !== this.context.viewport.height) {
39682
+ this.updateContext({ viewport: newViewport });
39683
+ }
39684
+ }
39685
+ updatePage() {
39686
+ const url = window.location.href;
39687
+ const title = document.title;
39688
+ const routeId = matchRoute2(url, this.routes);
39689
+ const newPage = {
39690
+ url,
39691
+ title,
39692
+ routeId
39693
+ };
39694
+ if (newPage.url !== this.context.page.url || newPage.title !== this.context.page.title || newPage.routeId !== this.context.page.routeId) {
39695
+ if (newPage.url !== this.context.page.url) {
39696
+ this.addPageToHistory(newPage.url);
39697
+ }
39698
+ this.updateContext({ page: newPage });
39699
+ }
39700
+ }
39701
+ addPageToHistory(url) {
39702
+ const entry = {
39703
+ url,
39704
+ ts: Date.now()
39705
+ };
39706
+ const pageHistory = [...this.context.session.pageHistory || [], entry];
39707
+ if (pageHistory.length > 50) {
39708
+ pageHistory.shift();
39709
+ }
39710
+ this.context.session.pageHistory = pageHistory;
39711
+ }
39712
+ updateContext(partial) {
39713
+ this.previousContext = { ...this.context };
39714
+ this.context = {
39715
+ ...this.context,
39716
+ ...partial
39717
+ };
39718
+ this.notifyListeners();
39719
+ }
39720
+ notifyListeners() {
39721
+ for (const callback of this.listeners) {
39722
+ try {
39723
+ callback(this.context, this.previousContext);
39724
+ } catch (err) {
39725
+ console.error("[ContextManager] Listener error:", err);
39726
+ }
39727
+ }
39728
+ }
39729
+ };
39730
+ function createContextManager(options = {}) {
39731
+ return new ContextManager(options);
39732
+ }
39733
+
39734
+ // src/events/types.ts
39735
+ var StandardEvents = {
39736
+ // UI events (from PostHog autocapture)
39737
+ UI_CLICK: "ui.click",
39738
+ UI_SCROLL: "ui.scroll",
39739
+ UI_INPUT: "ui.input",
39740
+ UI_CHANGE: "ui.change",
39741
+ UI_SUBMIT: "ui.submit",
39742
+ // Navigation events
39743
+ NAV_PAGE_VIEW: "nav.page_view",
39744
+ NAV_PAGE_LEAVE: "nav.page_leave",
39745
+ // Canvas events
39746
+ CANVAS_OPENED: "canvas.opened",
39747
+ CANVAS_CLOSED: "canvas.closed",
39748
+ TILE_VIEWED: "tile.viewed",
39749
+ TILE_EXPANDED: "tile.expanded",
39750
+ TILE_COLLAPSED: "tile.collapsed",
39751
+ TILE_ACTION: "tile.action",
39752
+ // Overlay/tour events
39753
+ OVERLAY_STARTED: "overlay.started",
39754
+ OVERLAY_COMPLETED: "overlay.completed",
39755
+ OVERLAY_DISMISSED: "overlay.dismissed",
39756
+ OVERLAY_STEP_VIEWED: "overlay.step_viewed",
39757
+ // Derived behavioral signals (Phase 3)
39758
+ BEHAVIOR_RAGE_CLICK: "behavior.rage_click",
39759
+ BEHAVIOR_HESITATION: "behavior.hesitation",
39760
+ BEHAVIOR_CONFUSION: "behavior.confusion"
39761
+ };
39762
+ var EVENT_SCHEMA_VERSION = "1.0.0";
39763
+
39764
+ // src/events/schema.ts
39765
+ var EventSourceZ = external_exports.enum(["posthog", "canvas", "derived"]);
39766
+ var NormalizedEventZ = external_exports.object({
39767
+ ts: external_exports.number(),
39768
+ name: external_exports.string(),
39769
+ source: EventSourceZ,
39770
+ props: external_exports.record(external_exports.unknown()).optional(),
39771
+ schemaVersion: external_exports.string()
39772
+ });
39773
+ var EventFilterZ = external_exports.object({
39774
+ names: external_exports.array(external_exports.string()).optional(),
39775
+ patterns: external_exports.array(external_exports.string()).optional(),
39776
+ sources: external_exports.array(EventSourceZ).optional()
39777
+ });
39778
+ function validateNormalizedEvent(data) {
39779
+ return NormalizedEventZ.safeParse(data);
39780
+ }
39781
+ function validateEventFilter(data) {
39782
+ return EventFilterZ.safeParse(data);
39783
+ }
39784
+
39785
+ // src/events/EventBus.ts
39786
+ function matchesFilter(event, filter) {
39787
+ if (!filter) return true;
39788
+ if (filter.names && filter.names.length > 0) {
39789
+ if (!filter.names.includes(event.name)) {
39790
+ return false;
39791
+ }
39792
+ }
39793
+ if (filter.patterns && filter.patterns.length > 0) {
39794
+ const matched = filter.patterns.some((pattern) => {
39795
+ try {
39796
+ const regex = new RegExp(pattern);
39797
+ return regex.test(event.name);
39798
+ } catch {
39799
+ return false;
39800
+ }
39801
+ });
39802
+ if (!matched) return false;
39803
+ }
39804
+ if (filter.sources && filter.sources.length > 0) {
39805
+ if (!filter.sources.includes(event.source)) {
39806
+ return false;
39807
+ }
39808
+ }
39809
+ return true;
39810
+ }
39811
+ var EventBus = class {
39812
+ constructor(options = {}) {
39813
+ __publicField(this, "subscriptions", /* @__PURE__ */ new Set());
39814
+ __publicField(this, "history", []);
39815
+ __publicField(this, "maxHistorySize");
39816
+ this.maxHistorySize = options.maxHistorySize ?? 100;
39817
+ }
39818
+ /**
39819
+ * Subscribe to events matching an optional filter.
39820
+ * Returns an unsubscribe function.
39821
+ */
39822
+ subscribe(filterOrCallback, maybeCallback) {
39823
+ let filter;
39824
+ let callback;
39825
+ if (typeof filterOrCallback === "function") {
39826
+ filter = void 0;
39827
+ callback = filterOrCallback;
39828
+ } else {
39829
+ filter = filterOrCallback;
39830
+ callback = maybeCallback;
39831
+ }
39832
+ const subscription = { filter, callback };
39833
+ this.subscriptions.add(subscription);
39834
+ return () => {
39835
+ this.subscriptions.delete(subscription);
39836
+ };
39837
+ }
39838
+ /**
39839
+ * Publish an event to all matching subscribers.
39840
+ */
39841
+ publish(name, props, source = "canvas") {
39842
+ const event = {
39843
+ ts: Date.now(),
39844
+ name,
39845
+ source,
39846
+ props,
39847
+ schemaVersion: EVENT_SCHEMA_VERSION
39848
+ };
39849
+ this.publishEvent(event);
39850
+ }
39851
+ /**
39852
+ * Publish a pre-constructed NormalizedEvent.
39853
+ */
39854
+ publishEvent(event) {
39855
+ this.history.push(event);
39856
+ if (this.history.length > this.maxHistorySize) {
39857
+ this.history.shift();
39858
+ }
39859
+ for (const subscription of this.subscriptions) {
39860
+ if (matchesFilter(event, subscription.filter)) {
39861
+ try {
39862
+ subscription.callback(event);
39863
+ } catch (err) {
39864
+ console.error("[EventBus] Subscriber error:", err);
39865
+ }
39866
+ }
39867
+ }
39868
+ }
39869
+ /**
39870
+ * Get recent events matching an optional filter.
39871
+ */
39872
+ getRecent(filter, limit) {
39873
+ let events = this.history;
39874
+ if (filter) {
39875
+ events = events.filter((e2) => matchesFilter(e2, filter));
39876
+ }
39877
+ if (limit && limit > 0) {
39878
+ events = events.slice(-limit);
39879
+ }
39880
+ return events;
39881
+ }
39882
+ /**
39883
+ * Check if an event with a specific name occurred within a time window.
39884
+ */
39885
+ hasRecentEvent(eventName, withinMs, source) {
39886
+ const cutoff = Date.now() - withinMs;
39887
+ return this.history.some(
39888
+ (e2) => e2.name === eventName && e2.ts >= cutoff && (source === void 0 || e2.source === source)
39889
+ );
39890
+ }
39891
+ /**
39892
+ * Count events matching a filter within a time window.
39893
+ */
39894
+ countRecentEvents(filter, withinMs) {
39895
+ let events = this.history.filter((e2) => matchesFilter(e2, filter));
39896
+ if (withinMs !== void 0) {
39897
+ const cutoff = Date.now() - withinMs;
39898
+ events = events.filter((e2) => e2.ts >= cutoff);
39899
+ }
39900
+ return events.length;
39901
+ }
39902
+ /**
39903
+ * Clear all event history.
39904
+ */
39905
+ clearHistory() {
39906
+ this.history = [];
39907
+ }
39908
+ /**
39909
+ * Get the number of current subscribers.
39910
+ */
39911
+ getSubscriberCount() {
39912
+ return this.subscriptions.size;
39913
+ }
39914
+ };
39915
+ function createEventBus(options = {}) {
39916
+ return new EventBus(options);
39917
+ }
39918
+
39919
+ // src/events/normalizers/posthog.ts
39920
+ var POSTHOG_EVENT_MAP = {
39921
+ // Autocapture events
39922
+ $autocapture: "ui.click",
39923
+ // Default autocapture is usually clicks
39924
+ $click: StandardEvents.UI_CLICK,
39925
+ $scroll: StandardEvents.UI_SCROLL,
39926
+ $input: StandardEvents.UI_INPUT,
39927
+ $change: StandardEvents.UI_CHANGE,
39928
+ $submit: StandardEvents.UI_SUBMIT,
39929
+ // Navigation events
39930
+ $pageview: StandardEvents.NAV_PAGE_VIEW,
39931
+ $pageleave: StandardEvents.NAV_PAGE_LEAVE,
39932
+ // Session events
39933
+ $session_start: "session.start",
39934
+ // Identify events
39935
+ $identify: "user.identify"
39936
+ };
39937
+ function getEventName(phEvent) {
39938
+ const eventName = phEvent.event;
39939
+ if (POSTHOG_EVENT_MAP[eventName]) {
39940
+ return POSTHOG_EVENT_MAP[eventName];
39941
+ }
39942
+ if (eventName === "$autocapture") {
39943
+ const tagName = phEvent.properties?.$tag_name;
39944
+ const eventType = phEvent.properties?.$event_type;
39945
+ if (eventType === "submit") return StandardEvents.UI_SUBMIT;
39946
+ if (eventType === "change") return StandardEvents.UI_CHANGE;
39947
+ if (tagName === "input" || tagName === "textarea") return StandardEvents.UI_INPUT;
39948
+ return StandardEvents.UI_CLICK;
39949
+ }
39950
+ if (!eventName.startsWith("$")) {
39951
+ return `posthog.${eventName}`;
39952
+ }
39953
+ return eventName.replace("$", "posthog.");
39954
+ }
39955
+ function extractProps(phEvent) {
39956
+ const props = {};
39957
+ const phProps = phEvent.properties || {};
39958
+ if (phProps.$tag_name) props.tagName = phProps.$tag_name;
39959
+ if (phProps.$el_text) props.elementText = phProps.$el_text;
39960
+ if (phProps.$elements) props.elements = phProps.$elements;
39961
+ if (phProps.$current_url) props.url = phProps.$current_url;
39962
+ if (phProps.$pathname) props.pathname = phProps.$pathname;
39963
+ if (phProps.$host) props.host = phProps.$host;
39964
+ if (phProps.$viewport_width) props.viewportWidth = phProps.$viewport_width;
39965
+ if (phProps.$viewport_height) props.viewportHeight = phProps.$viewport_height;
39966
+ if (phProps.$session_id) props.sessionId = phProps.$session_id;
39967
+ if (phProps.$scroll_depth) props.scrollDepth = phProps.$scroll_depth;
39968
+ if (phProps.$scroll_percentage) props.scrollPercentage = phProps.$scroll_percentage;
39969
+ props.originalEvent = phEvent.event;
39970
+ return props;
39971
+ }
39972
+ function normalizePostHogEvent(phEvent) {
39973
+ let ts2;
39974
+ if (typeof phEvent.timestamp === "number") {
39975
+ ts2 = phEvent.timestamp;
39976
+ } else if (typeof phEvent.timestamp === "string") {
39977
+ ts2 = new Date(phEvent.timestamp).getTime();
39978
+ } else {
39979
+ ts2 = Date.now();
39980
+ }
39981
+ return {
39982
+ ts: ts2,
39983
+ name: getEventName(phEvent),
39984
+ source: "posthog",
39985
+ props: extractProps(phEvent),
39986
+ schemaVersion: EVENT_SCHEMA_VERSION
39987
+ };
39988
+ }
39989
+ function shouldNormalizeEvent(phEvent) {
39990
+ const eventName = phEvent.event;
39991
+ const skipEvents = [
39992
+ "$feature_flag_called",
39993
+ "$feature_flags",
39994
+ "$groups",
39995
+ "$groupidentify",
39996
+ "$set",
39997
+ "$set_once",
39998
+ "$unset",
39999
+ "$create_alias",
40000
+ "$capture_metrics",
40001
+ "$performance_event",
40002
+ "$web_vitals",
40003
+ "$exception",
40004
+ "$dead_click",
40005
+ "$heatmap"
40006
+ ];
40007
+ if (skipEvents.includes(eventName)) {
40008
+ return false;
40009
+ }
40010
+ return true;
40011
+ }
40012
+ function createPostHogNormalizer(publishFn) {
40013
+ return (eventName, properties) => {
40014
+ const phEvent = {
40015
+ event: eventName,
40016
+ properties,
40017
+ timestamp: Date.now()
40018
+ };
40019
+ if (shouldNormalizeEvent(phEvent)) {
40020
+ const normalizedEvent = normalizePostHogEvent(phEvent);
40021
+ publishFn(normalizedEvent);
40022
+ }
40023
+ };
40024
+ }
40025
+
40026
+ // src/events/normalizers/canvas.ts
40027
+ function createCanvasEvent(name, props) {
40028
+ return {
40029
+ ts: Date.now(),
40030
+ name,
40031
+ source: "canvas",
40032
+ props,
40033
+ schemaVersion: EVENT_SCHEMA_VERSION
40034
+ };
40035
+ }
40036
+ function canvasOpened(surface) {
40037
+ return createCanvasEvent(StandardEvents.CANVAS_OPENED, { surface });
40038
+ }
40039
+ function canvasClosed(surface) {
40040
+ return createCanvasEvent(StandardEvents.CANVAS_CLOSED, { surface });
40041
+ }
40042
+ function tileViewed(tileId, surface) {
40043
+ return createCanvasEvent(StandardEvents.TILE_VIEWED, { tileId, surface });
40044
+ }
40045
+ function tileExpanded(tileId, surface) {
40046
+ return createCanvasEvent(StandardEvents.TILE_EXPANDED, { tileId, surface });
40047
+ }
40048
+ function tileCollapsed(tileId, surface) {
40049
+ return createCanvasEvent(StandardEvents.TILE_COLLAPSED, { tileId, surface });
40050
+ }
40051
+ function tileAction(tileId, actionId, surface) {
40052
+ return createCanvasEvent(StandardEvents.TILE_ACTION, {
40053
+ tileId,
40054
+ actionId,
40055
+ surface
40056
+ });
40057
+ }
40058
+ function overlayStarted(recipeId, recipeName) {
40059
+ return createCanvasEvent(StandardEvents.OVERLAY_STARTED, {
40060
+ recipeId,
40061
+ recipeName
40062
+ });
40063
+ }
40064
+ function overlayCompleted(recipeId, recipeName) {
40065
+ return createCanvasEvent(StandardEvents.OVERLAY_COMPLETED, {
40066
+ recipeId,
40067
+ recipeName
40068
+ });
40069
+ }
40070
+ function overlayDismissed(recipeId, recipeName, stepIndex) {
40071
+ return createCanvasEvent(StandardEvents.OVERLAY_DISMISSED, {
40072
+ recipeId,
40073
+ recipeName,
40074
+ stepIndex
40075
+ });
40076
+ }
40077
+ function overlayStepViewed(recipeId, stepIndex, stepTitle) {
40078
+ return createCanvasEvent(StandardEvents.OVERLAY_STEP_VIEWED, {
40079
+ recipeId,
40080
+ stepIndex,
40081
+ stepTitle
40082
+ });
40083
+ }
40084
+ function customCanvasEvent(name, props) {
40085
+ const eventName = name.startsWith("canvas.") ? name : `canvas.${name}`;
40086
+ return createCanvasEvent(eventName, props);
40087
+ }
40088
+ var CanvasEvents = {
40089
+ canvasOpened,
40090
+ canvasClosed,
40091
+ tileViewed,
40092
+ tileExpanded,
40093
+ tileCollapsed,
40094
+ tileAction,
40095
+ overlayStarted,
40096
+ overlayCompleted,
40097
+ overlayDismissed,
40098
+ overlayStepViewed,
40099
+ custom: customCanvasEvent
40100
+ };
40101
+
40102
+ // src/state/schema.ts
40103
+ var StoredValueZ = external_exports.object({
40104
+ value: external_exports.unknown(),
40105
+ expiresAt: external_exports.number().optional()
40106
+ });
40107
+ var FrequencyEntryZ = external_exports.object({
40108
+ count: external_exports.number(),
40109
+ resetAt: external_exports.number().optional()
40110
+ });
40111
+ function validateStoredValue(data) {
40112
+ return StoredValueZ.safeParse(data);
40113
+ }
40114
+ function validateFrequencyEntry(data) {
40115
+ return FrequencyEntryZ.safeParse(data);
40116
+ }
40117
+
40118
+ // src/state/helpers/dismissals.ts
40119
+ var DISMISSAL_PREFIX = "dismissed:";
40120
+ function createDismissalStore(sessionStorage2, userStorage) {
40121
+ return {
40122
+ mark(key, permanent = false) {
40123
+ const storageKey = DISMISSAL_PREFIX + key;
40124
+ const storage = permanent ? userStorage : sessionStorage2;
40125
+ storage.set(storageKey, true);
40126
+ },
40127
+ isDismissed(key) {
40128
+ const storageKey = DISMISSAL_PREFIX + key;
40129
+ return sessionStorage2.has(storageKey) || userStorage.has(storageKey);
40130
+ },
40131
+ clear(key) {
40132
+ const storageKey = DISMISSAL_PREFIX + key;
40133
+ sessionStorage2.remove(storageKey);
40134
+ userStorage.remove(storageKey);
40135
+ },
40136
+ clearAll() {
40137
+ for (const key of sessionStorage2.keys()) {
40138
+ if (key.startsWith(DISMISSAL_PREFIX)) {
40139
+ sessionStorage2.remove(key);
40140
+ }
40141
+ }
40142
+ for (const key of userStorage.keys()) {
40143
+ if (key.startsWith(DISMISSAL_PREFIX)) {
40144
+ userStorage.remove(key);
40145
+ }
40146
+ }
40147
+ }
40148
+ };
40149
+ }
40150
+
40151
+ // src/state/helpers/cooldowns.ts
40152
+ var COOLDOWN_PREFIX = "cooldown:";
40153
+ function createCooldownStore(storage) {
40154
+ return {
40155
+ set(key, durationMs) {
40156
+ const storageKey = COOLDOWN_PREFIX + key;
40157
+ const expiresAt = Date.now() + durationMs;
40158
+ storage.set(storageKey, expiresAt);
40159
+ },
40160
+ isActive(key) {
40161
+ return this.remaining(key) > 0;
40162
+ },
40163
+ remaining(key) {
40164
+ const storageKey = COOLDOWN_PREFIX + key;
40165
+ const expiresAt = storage.get(storageKey);
40166
+ if (expiresAt === void 0) return 0;
40167
+ const remaining = expiresAt - Date.now();
40168
+ if (remaining <= 0) {
40169
+ storage.remove(storageKey);
40170
+ return 0;
40171
+ }
40172
+ return remaining;
40173
+ },
40174
+ clear(key) {
40175
+ const storageKey = COOLDOWN_PREFIX + key;
40176
+ storage.remove(storageKey);
40177
+ }
40178
+ };
40179
+ }
40180
+
40181
+ // src/state/helpers/frequency.ts
40182
+ var FREQUENCY_PREFIX = "freq:";
40183
+ function createFrequencyStore(storage) {
40184
+ function getEntry(key) {
40185
+ const storageKey = FREQUENCY_PREFIX + key;
40186
+ const entry = storage.get(storageKey);
40187
+ if (!entry) return void 0;
40188
+ if (entry.resetAt && Date.now() >= entry.resetAt) {
40189
+ storage.remove(storageKey);
40190
+ return void 0;
40191
+ }
40192
+ return entry;
40193
+ }
40194
+ return {
40195
+ increment(key, resetAfterMs) {
40196
+ const storageKey = FREQUENCY_PREFIX + key;
40197
+ const existing = getEntry(key);
40198
+ const newCount = (existing?.count ?? 0) + 1;
40199
+ const entry = {
40200
+ count: newCount,
40201
+ // Keep existing resetAt or set new one
40202
+ resetAt: existing?.resetAt ?? (resetAfterMs ? Date.now() + resetAfterMs : void 0)
40203
+ };
40204
+ storage.set(storageKey, entry);
40205
+ return newCount;
40206
+ },
40207
+ count(key) {
40208
+ const entry = getEntry(key);
40209
+ return entry?.count ?? 0;
40210
+ },
40211
+ reset(key) {
40212
+ const storageKey = FREQUENCY_PREFIX + key;
40213
+ storage.remove(storageKey);
40214
+ },
40215
+ hasReachedLimit(key, limit) {
40216
+ return this.count(key) >= limit;
40217
+ }
40218
+ };
40219
+ }
40220
+
40221
+ // src/state/StateStore.ts
40222
+ var DEFAULT_NAMESPACE = "syntro";
40223
+ function createBrowserStorage(storage, namespace) {
40224
+ const prefix = `${namespace}:`;
40225
+ return {
40226
+ get(key) {
40227
+ if (!storage) return void 0;
40228
+ try {
40229
+ const raw = storage.getItem(prefix + key);
40230
+ if (!raw) return void 0;
40231
+ const stored = JSON.parse(raw);
40232
+ if (stored.expiresAt && Date.now() >= stored.expiresAt) {
40233
+ storage.removeItem(prefix + key);
40234
+ return void 0;
40235
+ }
40236
+ return stored.value;
40237
+ } catch {
40238
+ return void 0;
40239
+ }
40240
+ },
40241
+ set(key, value, ttlMs) {
40242
+ if (!storage) return;
40243
+ try {
40244
+ const stored = {
40245
+ value,
40246
+ expiresAt: ttlMs ? Date.now() + ttlMs : void 0
40247
+ };
40248
+ storage.setItem(prefix + key, JSON.stringify(stored));
40249
+ } catch (err) {
40250
+ console.warn("[StateStore] Failed to save:", err);
40251
+ }
40252
+ },
40253
+ remove(key) {
40254
+ if (!storage) return;
40255
+ storage.removeItem(prefix + key);
40256
+ },
40257
+ has(key) {
40258
+ if (!storage) return false;
40259
+ return this.get(key) !== void 0;
40260
+ },
40261
+ keys() {
40262
+ if (!storage) return [];
40263
+ const keys = [];
40264
+ for (let i2 = 0; i2 < storage.length; i2++) {
40265
+ const key = storage.key(i2);
40266
+ if (key?.startsWith(prefix)) {
40267
+ keys.push(key.slice(prefix.length));
40268
+ }
40269
+ }
40270
+ return keys;
40271
+ },
40272
+ clear() {
40273
+ if (!storage) return;
40274
+ const keysToRemove = this.keys();
40275
+ for (const key of keysToRemove) {
40276
+ storage.removeItem(prefix + key);
40277
+ }
40278
+ }
40279
+ };
40280
+ }
40281
+ function createMemoryStorage(namespace) {
40282
+ const store = /* @__PURE__ */ new Map();
40283
+ const prefix = `${namespace}:`;
40284
+ return {
40285
+ get(key) {
40286
+ const stored = store.get(prefix + key);
40287
+ if (!stored) return void 0;
40288
+ if (stored.expiresAt && Date.now() >= stored.expiresAt) {
40289
+ store.delete(prefix + key);
40290
+ return void 0;
40291
+ }
40292
+ return stored.value;
40293
+ },
40294
+ set(key, value, ttlMs) {
40295
+ store.set(prefix + key, {
40296
+ value,
40297
+ expiresAt: ttlMs ? Date.now() + ttlMs : void 0
40298
+ });
40299
+ },
40300
+ remove(key) {
40301
+ store.delete(prefix + key);
40302
+ },
40303
+ has(key) {
40304
+ return this.get(key) !== void 0;
40305
+ },
40306
+ keys() {
40307
+ const keys = [];
40308
+ for (const key of store.keys()) {
40309
+ if (key.startsWith(prefix)) {
40310
+ keys.push(key.slice(prefix.length));
40311
+ }
40312
+ }
40313
+ return keys;
40314
+ },
40315
+ clear() {
40316
+ for (const key of store.keys()) {
40317
+ if (key.startsWith(prefix)) {
40318
+ store.delete(key);
40319
+ }
40320
+ }
40321
+ }
40322
+ };
40323
+ }
40324
+ var StateStore = class {
40325
+ constructor(options = {}) {
40326
+ /** Session-scoped storage (cleared on browser close) */
40327
+ __publicField(this, "session");
40328
+ /** User-scoped storage (persists across sessions) */
40329
+ __publicField(this, "user");
40330
+ /** Dismissal tracking */
40331
+ __publicField(this, "dismissals");
40332
+ /** Cooldown tracking */
40333
+ __publicField(this, "cooldowns");
40334
+ /** Frequency tracking */
40335
+ __publicField(this, "frequency");
40336
+ __publicField(this, "namespace");
40337
+ __publicField(this, "namespacedStorages", /* @__PURE__ */ new Map());
40338
+ this.namespace = options.namespace ?? DEFAULT_NAMESPACE;
40339
+ if (typeof window !== "undefined") {
40340
+ this.session = createBrowserStorage(
40341
+ typeof sessionStorage !== "undefined" ? sessionStorage : void 0,
40342
+ this.namespace
40343
+ );
40344
+ this.user = createBrowserStorage(
40345
+ typeof localStorage !== "undefined" ? localStorage : void 0,
40346
+ this.namespace
40347
+ );
40348
+ } else {
40349
+ this.session = createMemoryStorage(this.namespace);
40350
+ this.user = createMemoryStorage(this.namespace);
40351
+ }
40352
+ this.dismissals = createDismissalStore(this.session, this.user);
40353
+ this.cooldowns = createCooldownStore(this.user);
40354
+ this.frequency = createFrequencyStore(this.session);
40355
+ }
40356
+ /**
40357
+ * Create a namespaced storage scope.
40358
+ * Useful for isolating state by adaptive/tile.
40359
+ *
40360
+ * @param namespace - Additional namespace segment
40361
+ * @returns A ScopedStorage with the nested namespace
40362
+ */
40363
+ ns(namespace) {
40364
+ const fullNamespace = `${this.namespace}:${namespace}`;
40365
+ if (!this.namespacedStorages.has(fullNamespace)) {
40366
+ const storage = typeof window !== "undefined" && typeof sessionStorage !== "undefined" ? createBrowserStorage(sessionStorage, fullNamespace) : createMemoryStorage(fullNamespace);
40367
+ this.namespacedStorages.set(fullNamespace, storage);
40368
+ }
40369
+ return this.namespacedStorages.get(fullNamespace);
40370
+ }
40371
+ };
40372
+ function createStateStore(options = {}) {
40373
+ return new StateStore(options);
40374
+ }
40375
+
40376
+ // src/decisions/schema.ts
40377
+ var PageUrlConditionZ = external_exports.object({
40378
+ type: external_exports.literal("page_url"),
40379
+ url: external_exports.string()
40380
+ });
40381
+ var RouteConditionZ = external_exports.object({
40382
+ type: external_exports.literal("route"),
40383
+ routeId: external_exports.string()
40384
+ });
40385
+ var AnchorVisibleConditionZ = external_exports.object({
40386
+ type: external_exports.literal("anchor_visible"),
40387
+ anchorId: external_exports.string(),
40388
+ state: external_exports.enum(["visible", "present", "absent"])
40389
+ });
40390
+ var EventOccurredConditionZ = external_exports.object({
40391
+ type: external_exports.literal("event_occurred"),
40392
+ eventName: external_exports.string(),
40393
+ withinMs: external_exports.number().optional()
40394
+ });
40395
+ var StateEqualsConditionZ = external_exports.object({
40396
+ type: external_exports.literal("state_equals"),
40397
+ key: external_exports.string(),
40398
+ value: external_exports.unknown()
40399
+ });
40400
+ var ViewportConditionZ = external_exports.object({
40401
+ type: external_exports.literal("viewport"),
40402
+ minWidth: external_exports.number().optional(),
40403
+ maxWidth: external_exports.number().optional(),
40404
+ minHeight: external_exports.number().optional(),
40405
+ maxHeight: external_exports.number().optional()
40406
+ });
40407
+ var SessionMetricConditionZ = external_exports.object({
40408
+ type: external_exports.literal("session_metric"),
40409
+ key: external_exports.string(),
40410
+ operator: external_exports.enum(["gte", "lte", "eq", "gt", "lt"]),
40411
+ threshold: external_exports.number()
40412
+ });
40413
+ var DismissedConditionZ = external_exports.object({
40414
+ type: external_exports.literal("dismissed"),
40415
+ key: external_exports.string(),
40416
+ inverted: external_exports.boolean().optional()
40417
+ });
40418
+ var CooldownActiveConditionZ = external_exports.object({
40419
+ type: external_exports.literal("cooldown_active"),
40420
+ key: external_exports.string(),
40421
+ inverted: external_exports.boolean().optional()
40422
+ });
40423
+ var FrequencyLimitConditionZ = external_exports.object({
40424
+ type: external_exports.literal("frequency_limit"),
40425
+ key: external_exports.string(),
40426
+ limit: external_exports.number(),
40427
+ inverted: external_exports.boolean().optional()
40428
+ });
40429
+ var ConditionZ = external_exports.discriminatedUnion("type", [
40430
+ PageUrlConditionZ,
40431
+ RouteConditionZ,
40432
+ AnchorVisibleConditionZ,
40433
+ EventOccurredConditionZ,
40434
+ StateEqualsConditionZ,
40435
+ ViewportConditionZ,
40436
+ SessionMetricConditionZ,
40437
+ DismissedConditionZ,
40438
+ CooldownActiveConditionZ,
40439
+ FrequencyLimitConditionZ
40440
+ ]);
40441
+ var RuleZ = external_exports.object({
40442
+ conditions: external_exports.array(ConditionZ),
40443
+ value: external_exports.unknown()
40444
+ });
40445
+ var RuleStrategyZ = external_exports.object({
40446
+ type: external_exports.literal("rules"),
40447
+ rules: external_exports.array(RuleZ),
40448
+ default: external_exports.unknown()
40449
+ });
40450
+ var ScoreStrategyZ = external_exports.object({
40451
+ type: external_exports.literal("score"),
40452
+ field: external_exports.string(),
40453
+ threshold: external_exports.number(),
40454
+ above: external_exports.unknown(),
40455
+ below: external_exports.unknown()
40456
+ });
40457
+ var ModelStrategyZ = external_exports.object({
40458
+ type: external_exports.literal("model"),
40459
+ modelId: external_exports.string(),
40460
+ inputs: external_exports.array(external_exports.string()),
40461
+ outputMapping: external_exports.record(external_exports.unknown()),
40462
+ default: external_exports.unknown()
40463
+ });
40464
+ var ExternalStrategyZ = external_exports.object({
40465
+ type: external_exports.literal("external"),
40466
+ endpoint: external_exports.string(),
40467
+ method: external_exports.enum(["GET", "POST"]).optional(),
40468
+ default: external_exports.unknown(),
40469
+ timeoutMs: external_exports.number().optional()
40470
+ });
40471
+ var DecisionStrategyZ = external_exports.discriminatedUnion("type", [
40472
+ RuleStrategyZ,
40473
+ ScoreStrategyZ,
40474
+ ModelStrategyZ,
40475
+ ExternalStrategyZ
40476
+ ]);
40477
+ var RouteFilterZ = external_exports.object({
40478
+ include: external_exports.array(external_exports.string()).optional(),
40479
+ exclude: external_exports.array(external_exports.string()).optional()
40480
+ });
40481
+ var ActivationConfigZ = external_exports.object({
40482
+ routes: RouteFilterZ.optional(),
40483
+ strategy: DecisionStrategyZ.optional()
40484
+ });
40485
+ function validateCondition(data) {
40486
+ return ConditionZ.safeParse(data);
40487
+ }
40488
+ function validateStrategy(data) {
40489
+ return DecisionStrategyZ.safeParse(data);
40490
+ }
40491
+ function validateActivationConfig(data) {
40492
+ return ActivationConfigZ.safeParse(data);
40493
+ }
40494
+
40495
+ // src/decisions/strategies/rules.ts
40496
+ function evaluateCondition(condition, evalContext) {
40497
+ const { context, state, events } = evalContext;
40498
+ switch (condition.type) {
40499
+ case "page_url": {
40500
+ const { url } = condition;
40501
+ const currentUrl = context.page.url;
40502
+ const pattern = url.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
40503
+ const regex = new RegExp(`^${pattern}$`);
40504
+ return regex.test(currentUrl);
40505
+ }
40506
+ case "route": {
40507
+ return context.page.routeId === condition.routeId;
40508
+ }
40509
+ case "anchor_visible": {
40510
+ const anchor = context.anchors?.find(
40511
+ (a2) => a2.anchorId === condition.anchorId
40512
+ );
40513
+ switch (condition.state) {
40514
+ case "visible":
40515
+ return anchor?.visible === true;
40516
+ case "present":
40517
+ return anchor?.present === true;
40518
+ case "absent":
40519
+ return !anchor?.present;
40520
+ default:
40521
+ return false;
40522
+ }
40523
+ }
40524
+ case "event_occurred": {
40525
+ if (!events) return false;
40526
+ const withinMs = condition.withinMs ?? 6e4;
40527
+ return events.hasRecentEvent(condition.eventName, withinMs);
40528
+ }
40529
+ case "state_equals": {
40530
+ if (!state) return false;
40531
+ return false;
40532
+ }
40533
+ case "viewport": {
40534
+ const { width, height } = context.viewport;
40535
+ if (condition.minWidth !== void 0 && width < condition.minWidth)
40536
+ return false;
40537
+ if (condition.maxWidth !== void 0 && width > condition.maxWidth)
40538
+ return false;
40539
+ if (condition.minHeight !== void 0 && height < condition.minHeight)
40540
+ return false;
40541
+ if (condition.maxHeight !== void 0 && height > condition.maxHeight)
40542
+ return false;
40543
+ return true;
40544
+ }
40545
+ case "session_metric": {
40546
+ if (!state) return false;
40547
+ const metricValue = state.getSessionMetric(condition.key);
40548
+ const { operator, threshold } = condition;
40549
+ switch (operator) {
40550
+ case "gte":
40551
+ return metricValue >= threshold;
40552
+ case "lte":
40553
+ return metricValue <= threshold;
40554
+ case "eq":
40555
+ return metricValue === threshold;
40556
+ case "gt":
40557
+ return metricValue > threshold;
40558
+ case "lt":
40559
+ return metricValue < threshold;
40560
+ default:
40561
+ return false;
40562
+ }
40563
+ }
40564
+ case "dismissed": {
40565
+ if (!state) return condition.inverted ?? false;
40566
+ const isDismissed = state.isDismissed(condition.key);
40567
+ return condition.inverted ? !isDismissed : isDismissed;
40568
+ }
40569
+ case "cooldown_active": {
40570
+ if (!state) return condition.inverted ?? false;
40571
+ const isActive = state.isCooldownActive(condition.key);
40572
+ return condition.inverted ? !isActive : isActive;
40573
+ }
40574
+ case "frequency_limit": {
40575
+ if (!state) return condition.inverted ?? false;
40576
+ const count = state.getFrequencyCount(condition.key);
40577
+ const limitReached = count >= condition.limit;
40578
+ return condition.inverted ? !limitReached : limitReached;
40579
+ }
40580
+ default:
40581
+ console.warn("[RuleStrategy] Unknown condition type:", condition.type);
40582
+ return false;
40583
+ }
40584
+ }
40585
+ function evaluateRule(rule, evalContext) {
40586
+ const conditionResults = [];
40587
+ for (const condition of rule.conditions) {
40588
+ const result = evaluateCondition(condition, evalContext);
40589
+ conditionResults.push({ condition, result });
40590
+ if (!result) {
40591
+ return { matched: false, conditionResults };
40592
+ }
40593
+ }
40594
+ return { matched: true, conditionResults };
40595
+ }
40596
+ function evaluateRuleStrategy(strategy, evalContext) {
40597
+ for (let i2 = 0; i2 < strategy.rules.length; i2++) {
40598
+ const rule = strategy.rules[i2];
40599
+ const { matched, conditionResults } = evaluateRule(rule, evalContext);
40600
+ if (matched) {
40601
+ return {
40602
+ value: rule.value,
40603
+ isFallback: false,
40604
+ matchInfo: {
40605
+ strategyType: "rules",
40606
+ matchedRuleIndex: i2,
40607
+ evaluatedConditions: conditionResults
40608
+ }
40609
+ };
40610
+ }
40611
+ }
40612
+ return {
40613
+ value: strategy.default,
40614
+ isFallback: true,
40615
+ matchInfo: {
40616
+ strategyType: "rules"
40617
+ }
40618
+ };
40619
+ }
40620
+
40621
+ // src/decisions/strategies/score.ts
40622
+ function evaluateScoreStrategy(strategy, evalContext) {
40623
+ const { context } = evalContext;
40624
+ const score = context.augmented?.[strategy.field];
40625
+ if (score === void 0 || typeof score !== "number") {
40626
+ return {
40627
+ value: strategy.below,
40628
+ isFallback: true,
40629
+ matchInfo: {
40630
+ strategyType: "score"
40631
+ }
40632
+ };
40633
+ }
40634
+ const isAbove = score >= strategy.threshold;
40635
+ return {
40636
+ value: isAbove ? strategy.above : strategy.below,
40637
+ isFallback: false,
40638
+ matchInfo: {
40639
+ strategyType: "score"
40640
+ }
40641
+ };
40642
+ }
40643
+
40644
+ // src/decisions/engine.ts
40645
+ function createEvaluationContext(context, options) {
40646
+ const { state, events, sessionMetrics } = options;
40647
+ return {
40648
+ context,
40649
+ state: state ? {
40650
+ isDismissed: (key) => state.dismissals.isDismissed(key),
40651
+ isCooldownActive: (key) => state.cooldowns.isActive(key),
40652
+ getFrequencyCount: (key) => state.frequency.count(key),
40653
+ getSessionMetric: (key) => sessionMetrics?.get(key) ?? 0
40654
+ } : void 0,
40655
+ events: events ? {
40656
+ hasRecentEvent: (eventName, withinMs) => events.hasRecentEvent(eventName, withinMs)
40657
+ } : void 0
40658
+ };
40659
+ }
40660
+ async function evaluate2(strategy, context, options = {}) {
40661
+ const evalContext = createEvaluationContext(context, options);
40662
+ switch (strategy.type) {
40663
+ case "rules":
40664
+ return evaluateRuleStrategy(strategy, evalContext);
40665
+ case "score":
40666
+ return evaluateScoreStrategy(strategy, evalContext);
40667
+ case "model":
40668
+ return {
40669
+ value: strategy.default,
40670
+ isFallback: true,
40671
+ matchInfo: {
40672
+ strategyType: "model"
40673
+ }
40674
+ };
40675
+ case "external":
40676
+ return {
40677
+ value: strategy.default,
40678
+ isFallback: true,
40679
+ matchInfo: {
40680
+ strategyType: "external"
40681
+ }
40682
+ };
40683
+ default:
40684
+ console.warn("[DecisionEngine] Unknown strategy type:", strategy.type);
40685
+ return {
40686
+ value: void 0,
40687
+ isFallback: true,
40688
+ matchInfo: {
40689
+ strategyType: strategy.type
40690
+ }
40691
+ };
40692
+ }
40693
+ }
40694
+ function evaluateSync(strategy, context, options = {}) {
40695
+ const evalContext = createEvaluationContext(context, options);
40696
+ switch (strategy.type) {
40697
+ case "rules":
40698
+ return evaluateRuleStrategy(strategy, evalContext);
40699
+ case "score":
40700
+ return evaluateScoreStrategy(strategy, evalContext);
40701
+ case "model":
40702
+ case "external":
40703
+ return {
40704
+ value: strategy.default,
40705
+ isFallback: true,
40706
+ matchInfo: {
40707
+ strategyType: strategy.type
40708
+ }
40709
+ };
40710
+ default:
40711
+ return {
40712
+ value: void 0,
40713
+ isFallback: true,
40714
+ matchInfo: {
40715
+ strategyType: strategy.type
40716
+ }
40717
+ };
40718
+ }
40719
+ }
40720
+ function createDecisionEngine(options) {
40721
+ return {
40722
+ evaluate: (strategy, context) => evaluate2(strategy, context, options),
40723
+ evaluateSync: (strategy, context) => evaluateSync(strategy, context, options)
40724
+ };
40725
+ }
40726
+
40727
+ // src/runtime.ts
40728
+ var RUNTIME_VERSION = "2.0.0";
40729
+ function matchesRouteFilter(url, filter) {
40730
+ if (!filter) return true;
40731
+ let pathname;
40732
+ try {
40733
+ pathname = new URL(url).pathname;
40734
+ } catch {
40735
+ pathname = url;
40736
+ }
40737
+ const normalizedPath = pathname.replace(/\/$/, "") || "/";
40738
+ if (filter.exclude) {
40739
+ for (const pattern of filter.exclude) {
40740
+ if (matchRoutePattern(normalizedPath, pattern)) {
40741
+ return false;
40742
+ }
40743
+ }
40744
+ }
40745
+ if (filter.include && filter.include.length > 0) {
40746
+ for (const pattern of filter.include) {
40747
+ if (matchRoutePattern(normalizedPath, pattern)) {
40748
+ return true;
40749
+ }
40750
+ }
40751
+ return false;
40752
+ }
40753
+ return true;
40754
+ }
40755
+ function matchRoutePattern(pathname, pattern) {
40756
+ const normalizedPattern = pattern.replace(/\/$/, "") || "/";
40757
+ if (pathname === normalizedPattern) return true;
40758
+ const regexPattern = normalizedPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/:[^/]+/g, "[^/]+");
40759
+ const regex = new RegExp(`^${regexPattern}$`);
40760
+ return regex.test(pathname);
40761
+ }
40762
+ function createSmartCanvasRuntime(options = {}) {
40763
+ const { telemetry, sessionMetrics, routes, mode = "production", namespace } = options;
40764
+ const context = createContextManager({
40765
+ telemetry,
40766
+ routes
40767
+ });
40768
+ const events = createEventBus();
40769
+ const state = createStateStore({
40770
+ namespace
40771
+ });
40772
+ const decisionEngine = createDecisionEngine({
40773
+ state,
40774
+ events,
40775
+ sessionMetrics
40776
+ });
40777
+ const runtime = {
40778
+ telemetry,
40779
+ context,
40780
+ events,
40781
+ state,
40782
+ sessionMetrics,
40783
+ version: RUNTIME_VERSION,
40784
+ mode,
40785
+ async evaluate(strategy) {
40786
+ return decisionEngine.evaluate(strategy, context.get());
40787
+ },
40788
+ evaluateSync(strategy) {
40789
+ return decisionEngine.evaluateSync(strategy, context.get());
40790
+ },
40791
+ async filterTiles(tiles) {
40792
+ const currentUrl = context.get().page.url;
40793
+ const results = [];
40794
+ for (const tile of tiles) {
40795
+ const activation = tile.activation;
40796
+ if (!activation) {
40797
+ results.push(tile);
40798
+ continue;
40799
+ }
40800
+ if (!matchesRouteFilter(currentUrl, activation.routes)) {
40801
+ continue;
40802
+ }
40803
+ if (!activation.strategy) {
40804
+ results.push(tile);
40805
+ continue;
40806
+ }
40807
+ const result = await this.evaluate(activation.strategy);
40808
+ if (result.value) {
40809
+ results.push(tile);
40810
+ }
40811
+ }
40812
+ return results;
40813
+ },
40814
+ setRoutes(routes2) {
40815
+ context.setRoutes(routes2);
40816
+ },
40817
+ destroy() {
40818
+ context.destroy();
40819
+ }
40820
+ };
40821
+ return runtime;
40822
+ }
40823
+
39369
40824
  // src/token.ts
39370
40825
  var TOKEN_PREFIX = "syn_";
39371
40826
  function decodeToken(token) {
@@ -39617,6 +41072,20 @@ var SyntrologieSDK = (() => {
39617
41072
  });
39618
41073
  return hasToken;
39619
41074
  }
41075
+ function isAuditMode3() {
41076
+ if (typeof window === "undefined") {
41077
+ console.log("[Syntro Bootstrap] isAuditMode: not in browser");
41078
+ return false;
41079
+ }
41080
+ const params = new URLSearchParams(window.location.search);
41081
+ const hasAuditFlag = params.has("syntro_audit");
41082
+ console.log("[Syntro Bootstrap] isAuditMode check:", {
41083
+ url: window.location.href,
41084
+ hasAuditFlag,
41085
+ auditSessionId: params.get("audit_session_id") ?? "none"
41086
+ });
41087
+ return hasAuditFlag;
41088
+ }
39620
41089
  var SEGMENT_CACHE_KEY = "syntro_segment_attributes";
39621
41090
  function loadCachedSegmentAttributes() {
39622
41091
  if (typeof window === "undefined") return {};
@@ -39659,24 +41128,26 @@ var SyntrologieSDK = (() => {
39659
41128
  hasCanvasOptions: !!options.canvas
39660
41129
  });
39661
41130
  const editorMode = isEditorMode();
39662
- console.log("[Syntro Bootstrap] Editor mode:", editorMode);
41131
+ const auditMode = isAuditMode3();
41132
+ const sdkMode = editorMode ? "editor" : auditMode ? "audit" : null;
41133
+ console.log("[Syntro Bootstrap] SDK mode:", sdkMode ?? "normal");
39663
41134
  let payload;
39664
41135
  if (options.token) {
39665
41136
  if (options.token.startsWith("syn_")) {
39666
41137
  console.log("[Syntro Bootstrap] Token starts with syn_, decoding...");
39667
41138
  payload = decodeToken(options.token);
39668
- } else if (!editorMode) {
39669
- console.error("[Syntro Bootstrap] \u274C Token does not start with syn_ and NOT in editor mode!");
41139
+ } else if (!sdkMode) {
41140
+ console.error("[Syntro Bootstrap] \u274C Token does not start with syn_ and NOT in editor/audit mode!");
39670
41141
  console.error("[Syntro Bootstrap] Token received:", options.token);
39671
41142
  throw new Error("Invalid Syntro token: must start with 'syn_'");
39672
41143
  } else {
39673
- console.log("[Syntro Bootstrap] \u2713 Non-syn_ token allowed (editor mode)");
41144
+ console.log(`[Syntro Bootstrap] \u2713 Non-syn_ token allowed (${sdkMode} mode)`);
39674
41145
  }
39675
- } else if (!editorMode) {
39676
- console.error("[Syntro Bootstrap] \u274C No token provided and NOT in editor mode!");
39677
- throw new Error("Syntro token is required (unless in editor mode)");
41146
+ } else if (!sdkMode) {
41147
+ console.error("[Syntro Bootstrap] \u274C No token provided and NOT in editor/audit mode!");
41148
+ throw new Error("Syntro token is required (unless in editor or audit mode)");
39678
41149
  } else {
39679
- console.log("[Syntro Bootstrap] \u2713 No token, but editor mode - proceeding");
41150
+ console.log(`[Syntro Bootstrap] \u2713 No token, but ${sdkMode} mode - proceeding`);
39680
41151
  }
39681
41152
  const experimentHost = getEnvVar("NEXT_PUBLIC_SYNTRO_EXPERIMENT_HOST") || getEnvVar("VITE_SYNTRO_EXPERIMENT_HOST") || payload?.eh;
39682
41153
  const telemetryHost = getEnvVar("NEXT_PUBLIC_SYNTRO_TELEMETRY_HOST") || getEnvVar("VITE_SYNTRO_TELEMETRY_HOST") || payload?.th;
@@ -39752,7 +41223,23 @@ var SyntrologieSDK = (() => {
39752
41223
  integrations: { experiments, telemetry },
39753
41224
  editorUrl
39754
41225
  });
39755
- return { canvas, experiments, telemetry, sessionMetrics };
41226
+ let runtimeMode = "production";
41227
+ if (editorMode) runtimeMode = "editor";
41228
+ else if (auditMode) runtimeMode = "audit";
41229
+ else if (getEnvVar("NODE_ENV") === "development") runtimeMode = "development";
41230
+ const runtime = createSmartCanvasRuntime({
41231
+ telemetry,
41232
+ sessionMetrics,
41233
+ mode: runtimeMode
41234
+ });
41235
+ console.log("[Syntro Bootstrap] Runtime created:", {
41236
+ version: runtime.version,
41237
+ mode: runtime.mode,
41238
+ hasContext: !!runtime.context,
41239
+ hasEvents: !!runtime.events,
41240
+ hasState: !!runtime.state
41241
+ });
41242
+ return { canvas, runtime, experiments, telemetry, sessionMetrics };
39756
41243
  }
39757
41244
  var Syntro = {
39758
41245
  init,