@syntrologie/runtime-sdk 2.2.0-canary.9 → 2.2.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.
@@ -5,6 +5,56 @@ import {
5
5
  __publicField
6
6
  } from "./chunk-AYTRRBR5.js";
7
7
 
8
+ // ../adaptives/adaptive-content/dist/reconciliation-guard.js
9
+ function guardAgainstReconciliation(container, anchor, reinsertFn, opts) {
10
+ var _a2, _b, _c;
11
+ const maxRetries = (_a2 = opts == null ? void 0 : opts.maxRetries) != null ? _a2 : 3;
12
+ const debounceMs = (_b = opts == null ? void 0 : opts.debounceMs) != null ? _b : 50;
13
+ const observeTarget = (_c = container.parentElement) != null ? _c : anchor.parentElement;
14
+ if (!observeTarget)
15
+ return () => {
16
+ };
17
+ let retries = 0;
18
+ let debounceTimer = null;
19
+ let disconnected = false;
20
+ const observer = new MutationObserver((mutations) => {
21
+ if (disconnected)
22
+ return;
23
+ for (const mutation of mutations) {
24
+ for (const removed of mutation.removedNodes) {
25
+ if (removed !== container)
26
+ continue;
27
+ if (retries >= maxRetries) {
28
+ observer.disconnect();
29
+ disconnected = true;
30
+ return;
31
+ }
32
+ if (debounceTimer)
33
+ clearTimeout(debounceTimer);
34
+ debounceTimer = setTimeout(() => {
35
+ if (disconnected)
36
+ return;
37
+ retries++;
38
+ try {
39
+ reinsertFn();
40
+ } catch {
41
+ observer.disconnect();
42
+ disconnected = true;
43
+ }
44
+ }, debounceMs);
45
+ return;
46
+ }
47
+ }
48
+ });
49
+ observer.observe(observeTarget, { childList: true, subtree: true });
50
+ return () => {
51
+ disconnected = true;
52
+ observer.disconnect();
53
+ if (debounceTimer)
54
+ clearTimeout(debounceTimer);
55
+ };
56
+ }
57
+
8
58
  // ../adaptives/adaptive-content/dist/sanitizer.js
9
59
  var ALLOWED_TAGS = /* @__PURE__ */ new Set([
10
60
  "b",
@@ -107,8 +157,28 @@ var executeInsertHtml = async (action, context) => {
107
157
  anchorId: action.anchorId,
108
158
  position: action.position
109
159
  });
160
+ const reinsertFn = () => {
161
+ switch (action.position) {
162
+ case "before":
163
+ anchorEl.insertAdjacentElement("beforebegin", container);
164
+ break;
165
+ case "after":
166
+ anchorEl.insertAdjacentElement("afterend", container);
167
+ break;
168
+ case "prepend":
169
+ anchorEl.insertBefore(container, anchorEl.firstChild);
170
+ break;
171
+ case "append":
172
+ anchorEl.appendChild(container);
173
+ break;
174
+ case "replace":
175
+ break;
176
+ }
177
+ };
178
+ const guardCleanup = guardAgainstReconciliation(container, anchorEl, reinsertFn);
110
179
  return {
111
180
  cleanup: () => {
181
+ guardCleanup();
112
182
  if (action.position === "replace" && originalContent !== null) {
113
183
  const restoredEl = document.createElement(anchorEl.tagName);
114
184
  restoredEl.innerHTML = originalContent;
@@ -729,6 +799,10 @@ function showHighlight(anchorEl, overlayRoot, opts) {
729
799
  scrim.style.webkitClipPath = path;
730
800
  };
731
801
  const update = () => {
802
+ if (!anchorEl.isConnected) {
803
+ handle.destroy();
804
+ return;
805
+ }
732
806
  const rect = anchorEl.getBoundingClientRect();
733
807
  const x = Math.max(0, rect.left - padding);
734
808
  const y = Math.max(0, rect.top - padding);
@@ -1146,9 +1220,14 @@ function showTooltip(anchorEl, overlayRoot, opts) {
1146
1220
  ];
1147
1221
  const placement = opts.placement && opts.placement !== "auto" ? opts.placement : "top";
1148
1222
  const cleanup = autoUpdate(anchorEl, div, async () => {
1223
+ if (!anchorEl.isConnected) {
1224
+ handle.destroy();
1225
+ return;
1226
+ }
1149
1227
  const currentAnchorRef = getAnchorReference(anchorEl);
1150
1228
  const result = await computePosition(currentAnchorRef, div, {
1151
1229
  placement,
1230
+ strategy: "fixed",
1152
1231
  middleware
1153
1232
  });
1154
1233
  const { x, y, strategy, middlewareData, placement: finalPlacement } = result;
@@ -1858,8 +1937,8 @@ var AppRegistry = class {
1858
1937
  if (!registration) {
1859
1938
  throw new Error(`App not registered: ${appId}`);
1860
1939
  }
1861
- if (registration.state === "active") {
1862
- console.warn(`[AppRegistry] App already active: ${appId}`);
1940
+ if (registration.state === "active" || registration.state === "activating") {
1941
+ console.warn(`[AppRegistry] App already ${registration.state}: ${appId}`);
1863
1942
  return;
1864
1943
  }
1865
1944
  if (!this.runtime) {
@@ -2062,21 +2141,44 @@ var appRegistry = new AppRegistry();
2062
2141
  var DEFAULT_CONFIG = {
2063
2142
  timeout: 300,
2064
2143
  className: "sc-anti-flicker",
2065
- style: "opacity: 0 !important"
2144
+ style: "opacity: 0 !important",
2145
+ hostLoadingSelector: "",
2146
+ waitForHostReady: ""
2066
2147
  };
2067
2148
  function initAntiFlicker(config = {}) {
2068
- const { timeout, className, style } = { ...DEFAULT_CONFIG, ...config };
2149
+ const { timeout, className, style, hostLoadingSelector, waitForHostReady } = {
2150
+ ...DEFAULT_CONFIG,
2151
+ ...config
2152
+ };
2153
+ if (hostLoadingSelector && document.querySelector(hostLoadingSelector)) {
2154
+ return () => {
2155
+ };
2156
+ }
2069
2157
  const styleEl = document.createElement("style");
2070
2158
  styleEl.textContent = `.${className} { ${style} }`;
2071
2159
  document.head.appendChild(styleEl);
2072
2160
  document.documentElement.classList.add(className);
2073
- const timeoutId = setTimeout(() => {
2074
- document.documentElement.classList.remove(className);
2075
- }, timeout);
2076
- return () => {
2161
+ let removed = false;
2162
+ const remove = () => {
2163
+ if (removed) return;
2164
+ removed = true;
2077
2165
  clearTimeout(timeoutId);
2078
2166
  document.documentElement.classList.remove(className);
2079
2167
  styleEl.remove();
2168
+ if (waitForHostReady && hostReadyHandler) {
2169
+ document.removeEventListener(waitForHostReady, hostReadyHandler);
2170
+ }
2171
+ };
2172
+ let hostReadyHandler;
2173
+ if (waitForHostReady) {
2174
+ hostReadyHandler = () => remove();
2175
+ document.addEventListener(waitForHostReady, hostReadyHandler);
2176
+ }
2177
+ const timeoutId = setTimeout(() => {
2178
+ remove();
2179
+ }, timeout);
2180
+ return () => {
2181
+ remove();
2080
2182
  };
2081
2183
  }
2082
2184
  function getAntiFlickerSnippet(config = {}) {
@@ -2091,7 +2193,7 @@ function getAntiFlickerSnippet(config = {}) {
2091
2193
  }
2092
2194
 
2093
2195
  // src/version.ts
2094
- var SDK_VERSION = "2.2.0-canary.9";
2196
+ var SDK_VERSION = "2.2.0";
2095
2197
 
2096
2198
  // src/types.ts
2097
2199
  var SDK_SCHEMA_VERSION = "2.0";
@@ -2202,6 +2304,44 @@ function isSameOrigin(uri) {
2202
2304
  return false;
2203
2305
  }
2204
2306
  }
2307
+ var CONFIG_CACHE_KEY = "syntro_config_cache";
2308
+ var CACHE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
2309
+ function hasLocalStorage() {
2310
+ try {
2311
+ return typeof localStorage !== "undefined" && localStorage !== null;
2312
+ } catch {
2313
+ return false;
2314
+ }
2315
+ }
2316
+ function cacheConfig(config, sdkVersion) {
2317
+ if (!hasLocalStorage()) return;
2318
+ try {
2319
+ const entry = {
2320
+ config,
2321
+ timestamp: Date.now(),
2322
+ sdkVersion
2323
+ };
2324
+ localStorage.setItem(CONFIG_CACHE_KEY, JSON.stringify(entry));
2325
+ } catch {
2326
+ }
2327
+ }
2328
+ function getCachedConfig(sdkVersion) {
2329
+ if (!hasLocalStorage()) return null;
2330
+ try {
2331
+ const raw = localStorage.getItem(CONFIG_CACHE_KEY);
2332
+ if (!raw) return null;
2333
+ const entry = JSON.parse(raw);
2334
+ if (!entry.config || typeof entry.timestamp !== "number" || !entry.sdkVersion) {
2335
+ return null;
2336
+ }
2337
+ if (entry.sdkVersion !== sdkVersion) return null;
2338
+ const age = Date.now() - entry.timestamp;
2339
+ if (age > CACHE_MAX_AGE_MS) return null;
2340
+ return entry.config;
2341
+ } catch {
2342
+ return null;
2343
+ }
2344
+ }
2205
2345
  var resolveConfigUri = ({
2206
2346
  configUri,
2207
2347
  experiments,
@@ -2235,7 +2375,8 @@ var createCanvasConfigFetcher = ({
2235
2375
  credentials,
2236
2376
  configFeatureKey = "smart-canvas-config",
2237
2377
  manifestKey,
2238
- variantFlagPrefix
2378
+ variantFlagPrefix,
2379
+ sdkVersion
2239
2380
  }) => async () => {
2240
2381
  var _a2;
2241
2382
  if (experiments && configFeatureKey) {
@@ -2264,19 +2405,33 @@ var createCanvasConfigFetcher = ({
2264
2405
  throw new Error(`SmartCanvas: config URI not allowed: ${uri}`);
2265
2406
  }
2266
2407
  const effectiveCredentials = credentials != null ? credentials : isSameOrigin(uri) ? "include" : "omit";
2267
- const response = await fetch(uri, {
2268
- credentials: effectiveCredentials,
2269
- headers: {
2270
- "X-SDK-Version": SDK_VERSION,
2271
- "X-SDK-Schema-Version": SDK_SCHEMA_VERSION
2408
+ try {
2409
+ const response = await fetch(uri, {
2410
+ credentials: effectiveCredentials,
2411
+ headers: {
2412
+ "X-SDK-Version": SDK_VERSION,
2413
+ "X-SDK-Schema-Version": SDK_SCHEMA_VERSION
2414
+ }
2415
+ });
2416
+ if (!response.ok) {
2417
+ throw new Error(`SmartCanvas: failed to fetch config (${response.status})`);
2272
2418
  }
2273
- });
2274
- if (!response.ok) {
2275
- throw new Error(`SmartCanvas: failed to fetch config (${response.status})`);
2419
+ const config = await response.json();
2420
+ debug("SmartCanvas Config", "Fetched config from URI", config);
2421
+ if (sdkVersion) {
2422
+ cacheConfig(config, sdkVersion);
2423
+ }
2424
+ return config;
2425
+ } catch (error2) {
2426
+ if (sdkVersion) {
2427
+ const cached = getCachedConfig(sdkVersion);
2428
+ if (cached) {
2429
+ debug("SmartCanvas Config", "Serving config from offline cache");
2430
+ return cached;
2431
+ }
2432
+ }
2433
+ throw error2;
2276
2434
  }
2277
- const config = await response.json();
2278
- debug("SmartCanvas Config", "Fetched config from URI", config);
2279
- return config;
2280
2435
  };
2281
2436
 
2282
2437
  // src/events/registerConfigPredicates.ts
@@ -3125,26 +3280,33 @@ function useDecision(strategy, defaultValue) {
3125
3280
  }
3126
3281
 
3127
3282
  // src/components/TileCard.tsx
3128
- import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef3, useState as useState4 } from "react";
3283
+ import { useCallback as useCallback2, useEffect as useEffect5, useReducer, useRef as useRef3, useState as useState4 } from "react";
3129
3284
  import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
3130
3285
  function WidgetMount({ widgetId, props }) {
3286
+ var _a2;
3131
3287
  const runtime3 = useRuntime();
3132
3288
  const containerRef = useRef3(null);
3133
3289
  const handleRef = useRef3(null);
3134
3290
  const registry = runtime3 == null ? void 0 : runtime3.widgets;
3291
+ const widgetAvailable = (_a2 = registry == null ? void 0 : registry.has(widgetId)) != null ? _a2 : false;
3292
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
3135
3293
  useEffect5(() => {
3136
- if (!containerRef.current || !registry) return;
3137
- if (!registry.has(widgetId)) {
3138
- console.warn(`[TileCard] Widget not found in registry: ${widgetId}`);
3139
- return;
3140
- }
3294
+ if (!registry || widgetAvailable) return;
3295
+ return registry.subscribe((event) => {
3296
+ if (event.type === "registered" && event.widgetId === widgetId) {
3297
+ forceUpdate();
3298
+ }
3299
+ });
3300
+ }, [registry, widgetId, widgetAvailable]);
3301
+ useEffect5(() => {
3302
+ if (!containerRef.current || !registry || !widgetAvailable) return;
3141
3303
  const handle = registry.mount(widgetId, containerRef.current, props);
3142
3304
  handleRef.current = handle;
3143
3305
  return () => {
3144
3306
  handle.unmount();
3145
3307
  handleRef.current = null;
3146
3308
  };
3147
- }, [registry, widgetId, props]);
3309
+ }, [registry, widgetId, props, widgetAvailable]);
3148
3310
  if (!registry || !registry.has(widgetId)) {
3149
3311
  return /* @__PURE__ */ jsxs2(
3150
3312
  "div",
@@ -4203,18 +4365,31 @@ function decodeParam(paramName) {
4203
4365
  var _cachedSource;
4204
4366
  var _cachedConf;
4205
4367
  function getEditorSource() {
4206
- if (_cachedSource === void 0) {
4368
+ var _a2;
4369
+ if (_cachedSource === void 0 || _cachedSource === null) {
4207
4370
  const globalParams = typeof window !== "undefined" ? window.__SYNTRO_EDITOR_PARAMS__ : null;
4208
4371
  if (globalParams == null ? void 0 : globalParams.token) {
4209
4372
  _cachedSource = { token: globalParams.token, api_base: globalParams.apiBase || "" };
4373
+ console.log(
4374
+ `[DIAG] getEditorSource: from __SYNTRO_EDITOR_PARAMS__ (token=${_cachedSource.token.slice(0, 15)}...)`
4375
+ );
4210
4376
  } else {
4211
- _cachedSource = decodeParam("editor_source");
4377
+ const decoded = decodeParam("editor_source");
4378
+ if (decoded) {
4379
+ _cachedSource = decoded;
4380
+ }
4381
+ console.log(
4382
+ `[DIAG] getEditorSource: from URL param \u2192 ${decoded ? `token=${(_a2 = decoded.token) == null ? void 0 : _a2.slice(0, 15)}...` : "NULL"}`
4383
+ );
4384
+ if (!decoded && typeof window !== "undefined") {
4385
+ console.log(`[DIAG] getEditorSource: URL search = ${window.location.search.slice(0, 200)}`);
4386
+ }
4212
4387
  }
4213
4388
  }
4214
- return _cachedSource;
4389
+ return _cachedSource != null ? _cachedSource : null;
4215
4390
  }
4216
4391
  function getEditorConf() {
4217
- if (_cachedConf === void 0) {
4392
+ if (_cachedConf === void 0 || _cachedConf === null) {
4218
4393
  const globalParams = typeof window !== "undefined" ? window.__SYNTRO_EDITOR_PARAMS__ : null;
4219
4394
  if (globalParams == null ? void 0 : globalParams.mode) {
4220
4395
  _cachedConf = {
@@ -4223,11 +4398,18 @@ function getEditorConf() {
4223
4398
  syntro_token: globalParams.syntroToken,
4224
4399
  workspace_id: globalParams.workspaceId
4225
4400
  };
4401
+ console.log(`[DIAG] getEditorConf: from __SYNTRO_EDITOR_PARAMS__ (mode=${_cachedConf.mode})`);
4226
4402
  } else {
4227
- _cachedConf = decodeParam("editor_conf");
4403
+ const decoded = decodeParam("editor_conf");
4404
+ if (decoded) {
4405
+ _cachedConf = decoded;
4406
+ }
4407
+ console.log(
4408
+ `[DIAG] getEditorConf: from URL param \u2192 ${decoded ? `mode=${decoded.mode}` : "NULL"}`
4409
+ );
4228
4410
  }
4229
4411
  }
4230
- return _cachedConf;
4412
+ return _cachedConf != null ? _cachedConf : null;
4231
4413
  }
4232
4414
  function validateEditorUrl(url) {
4233
4415
  debug("Syntro Runtime", "Validating editor URL:", url);
@@ -5145,67 +5327,96 @@ var PostHogAdapter = class {
5145
5327
  __publicField(this, "client");
5146
5328
  __publicField(this, "featureFlagsCallback");
5147
5329
  __publicField(this, "captureCallback");
5148
- var _a2, _b, _c, _d, _e, _f;
5330
+ __publicField(this, "consentUnsub");
5149
5331
  this.client = options.client;
5150
5332
  this.featureFlagsCallback = options.onFeatureFlagsLoaded;
5151
5333
  this.captureCallback = options.onCapture;
5334
+ if (!this.client && options.consent && options.requireExplicitConsent && typeof window !== "undefined" && options.apiKey) {
5335
+ const consent = options.consent;
5336
+ const currentStatus = consent.getStatus();
5337
+ if (currentStatus === "granted") {
5338
+ this.initPostHog();
5339
+ }
5340
+ this.consentUnsub = consent.subscribe((status) => {
5341
+ if (status === "granted") {
5342
+ if (!this.client) {
5343
+ this.initPostHog();
5344
+ } else {
5345
+ this.client.opt_in_capturing();
5346
+ }
5347
+ } else if (status === "denied" && this.client) {
5348
+ this.client.opt_out_capturing();
5349
+ }
5350
+ });
5351
+ return;
5352
+ }
5152
5353
  if (!this.client && typeof window !== "undefined" && options.apiKey) {
5153
- const enableFeatureFlags = (_a2 = options.enableFeatureFlags) != null ? _a2 : true;
5154
- const instanceName = `syntro_${options.apiKey.slice(-6) || "sdk"}`;
5155
- this.client = posthog.init(
5156
- options.apiKey,
5157
- {
5158
- api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
5159
- // Feature flags for segment membership (in_segment_* flags)
5160
- // When enabled, /decide is called to get segment flags
5161
- advanced_disable_feature_flags: !enableFeatureFlags,
5162
- advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
5163
- // Full-page tracking - all ON by default
5164
- autocapture: (_c = options.autocapture) != null ? _c : true,
5165
- capture_pageview: (_d = options.capturePageview) != null ? _d : true,
5166
- capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
5167
- disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
5168
- // CRITICAL: Disable user agent filtering to allow headless Chrome
5169
- // PostHog blocks "HeadlessChrome" user agents by default as bot detection
5170
- // This enables session recording in Playwright/crawler sessions
5171
- opt_out_useragent_filter: true,
5172
- // Cross-domain iframe recording for embeds
5173
- session_recording: {
5174
- recordCrossDomainIFrames: true
5175
- },
5176
- // Capture performance metrics
5177
- capture_performance: true,
5178
- // Enable web vitals
5179
- enable_recording_console_log: true,
5180
- // Bootstrap callback for when flags are loaded
5181
- loaded: (ph) => {
5182
- if (enableFeatureFlags && this.featureFlagsCallback) {
5183
- ph.onFeatureFlags(() => {
5184
- const allFlags = this.getAllFeatureFlags();
5185
- if (allFlags && this.featureFlagsCallback) {
5186
- this.featureFlagsCallback(allFlags);
5187
- }
5188
- });
5189
- const existingFlags = this.getAllFeatureFlags();
5190
- if (existingFlags && Object.keys(existingFlags).length > 0) {
5191
- this.featureFlagsCallback(existingFlags);
5354
+ this.initPostHog();
5355
+ }
5356
+ }
5357
+ /**
5358
+ * Initialize the PostHog client. Called immediately (no consent gate)
5359
+ * or deferred (when consent gate grants).
5360
+ */
5361
+ initPostHog() {
5362
+ var _a2, _b, _c, _d, _e, _f;
5363
+ const options = this.options;
5364
+ if (!options.apiKey) return;
5365
+ const enableFeatureFlags = (_a2 = options.enableFeatureFlags) != null ? _a2 : true;
5366
+ const instanceName = `syntro_${options.apiKey.slice(-6) || "sdk"}`;
5367
+ this.client = posthog.init(
5368
+ options.apiKey,
5369
+ {
5370
+ api_host: (_b = options.apiHost) != null ? _b : "https://telemetry.syntrologie.com",
5371
+ // Feature flags for segment membership (in_segment_* flags)
5372
+ // When enabled, /decide is called to get segment flags
5373
+ advanced_disable_feature_flags: !enableFeatureFlags,
5374
+ advanced_disable_feature_flags_on_first_load: !enableFeatureFlags,
5375
+ // Full-page tracking - all ON by default
5376
+ autocapture: (_c = options.autocapture) != null ? _c : true,
5377
+ capture_pageview: (_d = options.capturePageview) != null ? _d : true,
5378
+ capture_pageleave: (_e = options.capturePageleave) != null ? _e : true,
5379
+ disable_session_recording: !((_f = options.sessionRecording) != null ? _f : true),
5380
+ // CRITICAL: Disable user agent filtering to allow headless Chrome
5381
+ // PostHog blocks "HeadlessChrome" user agents by default as bot detection
5382
+ // This enables session recording in Playwright/crawler sessions
5383
+ opt_out_useragent_filter: true,
5384
+ // Cross-domain iframe recording for embeds
5385
+ session_recording: {
5386
+ recordCrossDomainIFrames: true
5387
+ },
5388
+ // Capture performance metrics
5389
+ capture_performance: true,
5390
+ // Enable web vitals
5391
+ enable_recording_console_log: true,
5392
+ // Bootstrap callback for when flags are loaded
5393
+ loaded: (ph) => {
5394
+ if (enableFeatureFlags && this.featureFlagsCallback) {
5395
+ ph.onFeatureFlags(() => {
5396
+ const allFlags = this.getAllFeatureFlags();
5397
+ if (allFlags && this.featureFlagsCallback) {
5398
+ this.featureFlagsCallback(allFlags);
5192
5399
  }
5193
- }
5194
- if (this.captureCallback) {
5195
- ph.on("eventCaptured", (data) => {
5196
- var _a3;
5197
- const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
5198
- const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
5199
- if (typeof eventName === "string") {
5200
- (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
5201
- }
5202
- });
5400
+ });
5401
+ const existingFlags = this.getAllFeatureFlags();
5402
+ if (existingFlags && Object.keys(existingFlags).length > 0) {
5403
+ this.featureFlagsCallback(existingFlags);
5203
5404
  }
5204
5405
  }
5205
- },
5206
- instanceName
5207
- );
5208
- }
5406
+ if (this.captureCallback) {
5407
+ ph.on("eventCaptured", (data) => {
5408
+ var _a3;
5409
+ const eventName = typeof data === "string" ? data : data == null ? void 0 : data.event;
5410
+ const properties = typeof data === "string" ? void 0 : data == null ? void 0 : data.properties;
5411
+ if (typeof eventName === "string") {
5412
+ (_a3 = this.captureCallback) == null ? void 0 : _a3.call(this, eventName, properties);
5413
+ }
5414
+ });
5415
+ }
5416
+ }
5417
+ },
5418
+ instanceName
5419
+ );
5209
5420
  }
5210
5421
  /**
5211
5422
  * Get all feature flags from PostHog.
@@ -5574,6 +5785,9 @@ var executeTour = async (action, context) => {
5574
5785
  });
5575
5786
  }
5576
5787
  };
5788
+ if (context.subscribeNavigation) {
5789
+ return context.subscribeNavigation(() => checkRoute());
5790
+ }
5577
5791
  window.addEventListener("popstate", checkRoute);
5578
5792
  const origPushState = history.pushState.bind(history);
5579
5793
  const origReplaceState = history.replaceState.bind(history);
@@ -6382,7 +6596,8 @@ function createActionEngine(options) {
6382
6596
  surfaces,
6383
6597
  anchorResolver,
6384
6598
  adaptiveId,
6385
- executorRegistry: executorRegistry2 = executorRegistry
6599
+ executorRegistry: executorRegistry2 = executorRegistry,
6600
+ subscribeNavigation
6386
6601
  } = options;
6387
6602
  const activeActions = /* @__PURE__ */ new Map();
6388
6603
  function generateId() {
@@ -6403,7 +6618,9 @@ function createActionEngine(options) {
6403
6618
  // Allow composite executors (like tours) to execute nested actions
6404
6619
  applyAction: apply,
6405
6620
  // Allow composite executors to subscribe to events
6406
- subscribeEvent: eventBus ? (name, callback) => eventBus.subscribe({ names: [name] }, (event) => callback(event.props)) : void 0
6621
+ subscribeEvent: eventBus ? (name, callback) => eventBus.subscribe({ names: [name] }, (event) => callback(event.props)) : void 0,
6622
+ // Allow composite executors to subscribe to navigation changes
6623
+ subscribeNavigation
6407
6624
  };
6408
6625
  }
6409
6626
  async function executeMountWidget(action, context) {
@@ -6684,11 +6901,13 @@ var ContextManager = class {
6684
6901
  __publicField(this, "listeners", /* @__PURE__ */ new Set());
6685
6902
  __publicField(this, "telemetry");
6686
6903
  __publicField(this, "routes");
6904
+ __publicField(this, "navigation");
6687
6905
  // Event listener cleanup functions
6688
6906
  __publicField(this, "cleanupFns", []);
6689
6907
  var _a2;
6690
6908
  this.telemetry = options.telemetry;
6691
6909
  this.routes = options.routes;
6910
+ this.navigation = options.navigation;
6692
6911
  this.context = createDefaultContext();
6693
6912
  this.previousContext = { ...this.context };
6694
6913
  if ((_a2 = options.telemetry) == null ? void 0 : _a2.getSessionId) {
@@ -6769,25 +6988,30 @@ var ContextManager = class {
6769
6988
  };
6770
6989
  window.addEventListener("resize", handleResize);
6771
6990
  this.cleanupFns.push(() => window.removeEventListener("resize", handleResize));
6772
- const handlePopState = () => {
6773
- this.updatePage();
6774
- };
6775
- window.addEventListener("popstate", handlePopState);
6776
- this.cleanupFns.push(() => window.removeEventListener("popstate", handlePopState));
6777
- const originalPushState = history.pushState.bind(history);
6778
- const originalReplaceState = history.replaceState.bind(history);
6779
- history.pushState = (...args) => {
6780
- originalPushState(...args);
6781
- queueMicrotask(() => this.updatePage());
6782
- };
6783
- history.replaceState = (...args) => {
6784
- originalReplaceState(...args);
6785
- queueMicrotask(() => this.updatePage());
6786
- };
6787
- this.cleanupFns.push(() => {
6788
- history.pushState = originalPushState;
6789
- history.replaceState = originalReplaceState;
6790
- });
6991
+ if (this.navigation) {
6992
+ const unsub = this.navigation.subscribe(() => this.updatePage());
6993
+ this.cleanupFns.push(unsub);
6994
+ } else {
6995
+ const handlePopState = () => {
6996
+ this.updatePage();
6997
+ };
6998
+ window.addEventListener("popstate", handlePopState);
6999
+ this.cleanupFns.push(() => window.removeEventListener("popstate", handlePopState));
7000
+ const originalPushState = history.pushState.bind(history);
7001
+ const originalReplaceState = history.replaceState.bind(history);
7002
+ history.pushState = (...args) => {
7003
+ originalPushState(...args);
7004
+ queueMicrotask(() => this.updatePage());
7005
+ };
7006
+ history.replaceState = (...args) => {
7007
+ originalReplaceState(...args);
7008
+ queueMicrotask(() => this.updatePage());
7009
+ };
7010
+ this.cleanupFns.push(() => {
7011
+ history.pushState = originalPushState;
7012
+ history.replaceState = originalReplaceState;
7013
+ });
7014
+ }
6791
7015
  }
6792
7016
  updateViewport() {
6793
7017
  const newViewport = {
@@ -7432,6 +7656,79 @@ function createPostHogNormalizer(publishFn) {
7432
7656
  };
7433
7657
  }
7434
7658
 
7659
+ // src/navigation/NavigationMonitor.ts
7660
+ var NavigationMonitor = class {
7661
+ constructor() {
7662
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
7663
+ __publicField(this, "capturedPushState");
7664
+ __publicField(this, "capturedReplaceState");
7665
+ __publicField(this, "wasNative");
7666
+ __publicField(this, "destroyed", false);
7667
+ // ─── Private ─────────────────────────────────────────────────────
7668
+ __publicField(this, "onPopState", () => {
7669
+ this.notify(window.location.href, "popstate");
7670
+ });
7671
+ this.wasNative = this.isNativeFunction(history.pushState);
7672
+ this.capturedPushState = history.pushState;
7673
+ this.capturedReplaceState = history.replaceState;
7674
+ history.pushState = (...args) => {
7675
+ this.capturedPushState.call(history, ...args);
7676
+ queueMicrotask(() => this.notify(window.location.href, "pushState"));
7677
+ };
7678
+ history.replaceState = (...args) => {
7679
+ this.capturedReplaceState.call(history, ...args);
7680
+ queueMicrotask(() => this.notify(window.location.href, "replaceState"));
7681
+ };
7682
+ window.addEventListener("popstate", this.onPopState);
7683
+ }
7684
+ /**
7685
+ * Subscribe to navigation events.
7686
+ * Returns an unsubscribe function.
7687
+ */
7688
+ subscribe(listener) {
7689
+ this.listeners.add(listener);
7690
+ return () => {
7691
+ this.listeners.delete(listener);
7692
+ };
7693
+ }
7694
+ /**
7695
+ * Get diagnostics about the History API chain.
7696
+ */
7697
+ diagnose() {
7698
+ return {
7699
+ isNative: this.wasNative,
7700
+ subscriberCount: this.listeners.size
7701
+ };
7702
+ }
7703
+ /**
7704
+ * Restore the History API to the state we captured and remove all listeners.
7705
+ */
7706
+ destroy() {
7707
+ if (this.destroyed) return;
7708
+ this.destroyed = true;
7709
+ history.pushState = this.capturedPushState;
7710
+ history.replaceState = this.capturedReplaceState;
7711
+ window.removeEventListener("popstate", this.onPopState);
7712
+ this.listeners.clear();
7713
+ }
7714
+ notify(url, method) {
7715
+ if (this.destroyed) return;
7716
+ for (const listener of this.listeners) {
7717
+ try {
7718
+ listener(url, method);
7719
+ } catch {
7720
+ }
7721
+ }
7722
+ }
7723
+ isNativeFunction(fn) {
7724
+ try {
7725
+ return Function.prototype.toString.call(fn).includes("[native code]");
7726
+ } catch {
7727
+ return false;
7728
+ }
7729
+ }
7730
+ };
7731
+
7435
7732
  // src/state/helpers/cooldowns.ts
7436
7733
  var COOLDOWN_PREFIX = "cooldown:";
7437
7734
  function createCooldownStore(storage) {
@@ -8236,6 +8533,7 @@ var WidgetRegistry = class {
8236
8533
  __publicField(this, "mountedWidgets", /* @__PURE__ */ new Map());
8237
8534
  __publicField(this, "mountIdCounter", 0);
8238
8535
  __publicField(this, "runtimeRef");
8536
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
8239
8537
  }
8240
8538
  /**
8241
8539
  * Bind a runtime reference so it can be injected into widget mount() calls.
@@ -8266,6 +8564,7 @@ var WidgetRegistry = class {
8266
8564
  source,
8267
8565
  metadata
8268
8566
  });
8567
+ this.notify({ type: "registered", widgetId: id });
8269
8568
  }
8270
8569
  /**
8271
8570
  * Unregister a widget.
@@ -8283,6 +8582,7 @@ var WidgetRegistry = class {
8283
8582
  return false;
8284
8583
  }
8285
8584
  this.widgets.delete(id);
8585
+ this.notify({ type: "unregistered", widgetId: id });
8286
8586
  return true;
8287
8587
  }
8288
8588
  /**
@@ -8390,6 +8690,24 @@ var WidgetRegistry = class {
8390
8690
  (registration) => registration.source === source
8391
8691
  );
8392
8692
  }
8693
+ /**
8694
+ * Subscribe to widget registration changes.
8695
+ */
8696
+ subscribe(callback) {
8697
+ this.listeners.add(callback);
8698
+ return () => {
8699
+ this.listeners.delete(callback);
8700
+ };
8701
+ }
8702
+ notify(event) {
8703
+ for (const listener of this.listeners) {
8704
+ try {
8705
+ listener(event);
8706
+ } catch (error2) {
8707
+ console.error("[WidgetRegistry] Listener error:", error2);
8708
+ }
8709
+ }
8710
+ }
8393
8711
  /**
8394
8712
  * Clean up all mounted widgets.
8395
8713
  */
@@ -8452,9 +8770,11 @@ function createSmartCanvasRuntime(options = {}) {
8452
8770
  const widgets = (_a2 = options.widgets) != null ? _a2 : widgetRegistry;
8453
8771
  const executors3 = (_b = options.executors) != null ? _b : executorRegistry;
8454
8772
  const apps = (_c = options.apps) != null ? _c : appRegistry;
8773
+ const navigation = new NavigationMonitor();
8455
8774
  const context = createContextManager({
8456
8775
  telemetry,
8457
- routes
8776
+ routes,
8777
+ navigation
8458
8778
  });
8459
8779
  const events = (_d = options.events) != null ? _d : createEventBus();
8460
8780
  const state = createStateStore({
@@ -8483,7 +8803,8 @@ function createSmartCanvasRuntime(options = {}) {
8483
8803
  eventBus: events,
8484
8804
  surfaces,
8485
8805
  anchorResolver,
8486
- executorRegistry: executors3
8806
+ executorRegistry: executors3,
8807
+ subscribeNavigation: (callback) => navigation.subscribe(callback)
8487
8808
  });
8488
8809
  const runtime3 = {
8489
8810
  telemetry,
@@ -8497,6 +8818,7 @@ function createSmartCanvasRuntime(options = {}) {
8497
8818
  executors: executors3,
8498
8819
  apps,
8499
8820
  accumulator,
8821
+ navigation,
8500
8822
  version: RUNTIME_VERSION,
8501
8823
  mode,
8502
8824
  async evaluate(strategy) {
@@ -8536,6 +8858,7 @@ function createSmartCanvasRuntime(options = {}) {
8536
8858
  console.error("[Runtime] Error unbinding apps registry:", err);
8537
8859
  });
8538
8860
  accumulator.destroy();
8861
+ navigation.destroy();
8539
8862
  context.destroy();
8540
8863
  actions.destroy();
8541
8864
  surfaces.destroy();
@@ -8906,7 +9229,30 @@ async function init(options) {
8906
9229
  const auditMode = isAuditMode();
8907
9230
  const reviewMode = isReviewMode();
8908
9231
  const sdkMode = editorMode ? "editor" : auditMode ? "audit" : reviewMode ? "review" : null;
9232
+ console.log(
9233
+ `[DIAG] Mode detection: editor=${editorMode}, audit=${auditMode}, review=${reviewMode}, sdkMode=${sdkMode}`
9234
+ );
9235
+ console.log(`[DIAG] editorSource:`, getEditorSource());
9236
+ console.log(`[DIAG] editorConf:`, getEditorConf());
8909
9237
  debug("Syntro Bootstrap", "SDK mode:", sdkMode != null ? sdkMode : "normal");
9238
+ if (!sdkMode && typeof window !== "undefined") {
9239
+ const hasEditorGlobal = !!window.__SYNTRO_EDITOR_PARAMS__;
9240
+ const hasAuditLegend = !!window.__SYNTRO_AUDIT_LEGEND__;
9241
+ const hasReviewData = !!window.__SYNTRO_REVIEW_DATA__;
9242
+ if (hasEditorGlobal || hasAuditLegend || hasReviewData) {
9243
+ const globals = [
9244
+ hasEditorGlobal && "__SYNTRO_EDITOR_PARAMS__",
9245
+ hasAuditLegend && "__SYNTRO_AUDIT_LEGEND__",
9246
+ hasReviewData && "__SYNTRO_REVIEW_DATA__"
9247
+ ].filter(Boolean);
9248
+ console.warn(
9249
+ `[Syntro Bootstrap] \u26A0 Mode detection returned null but mode-specific globals found: ${globals.join(", ")}. This suggests URL params were lost (redirect?) but the service worker injected mode data. Check if replaceState failed or if page JS changed the URL before init().`
9250
+ );
9251
+ }
9252
+ }
9253
+ console.log(
9254
+ `[DIAG] Token resolution: options.token=${options.token ? options.token.slice(0, 15) + "..." : "null"}`
9255
+ );
8910
9256
  let resolvedToken = options.token;
8911
9257
  if (!resolvedToken && sdkMode) {
8912
9258
  const conf = getEditorConf();
@@ -9045,15 +9391,24 @@ async function init(options) {
9045
9391
  debug("Syntro Bootstrap", "Global SynOS object exposed");
9046
9392
  }
9047
9393
  const registeredApps = (_e = (_d = (_c = runtime3.apps).list) == null ? void 0 : _d.call(_c)) != null ? _e : [];
9394
+ console.log(
9395
+ `[DIAG] Activation loop: ${registeredApps.length} apps in registry:`,
9396
+ registeredApps.map((a) => `${a.manifest.id}(${a.state})`).join(", ")
9397
+ );
9048
9398
  for (const app of registeredApps) {
9049
9399
  if (app.state === "registered") {
9050
9400
  try {
9051
9401
  await runtime3.apps.activate(app.manifest.id);
9402
+ console.log(
9403
+ `[DIAG] Activated: ${app.manifest.id} \u2192 widgets: [${runtime3.widgets.list().join(", ")}]`
9404
+ );
9052
9405
  } catch (err) {
9406
+ console.error(`[DIAG] FAILED to activate: ${app.manifest.id}`, err);
9053
9407
  warn("Syntro Bootstrap", `Failed to auto-activate built-in app: ${app.manifest.id}`, err);
9054
9408
  }
9055
9409
  }
9056
9410
  }
9411
+ console.log(`[DIAG] Activation complete. Total widgets: [${runtime3.widgets.list().join(", ")}]`);
9057
9412
  if (experiments == null ? void 0 : experiments.refreshFeatures) {
9058
9413
  try {
9059
9414
  await experiments.refreshFeatures();
@@ -9241,6 +9596,7 @@ export {
9241
9596
  normalizePostHogEvent,
9242
9597
  shouldNormalizeEvent,
9243
9598
  createPostHogNormalizer,
9599
+ NavigationMonitor,
9244
9600
  StateStore,
9245
9601
  createStateStore,
9246
9602
  getSlotType,
@@ -9263,4 +9619,4 @@ export {
9263
9619
  encodeToken,
9264
9620
  Syntro
9265
9621
  };
9266
- //# sourceMappingURL=chunk-UEAP7WOK.js.map
9622
+ //# sourceMappingURL=chunk-V4MDQX67.js.map