@syntrologie/runtime-sdk 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CAPABILITIES.md CHANGED
@@ -331,29 +331,46 @@ Collapsible Q&A accordion with actions, rich content, feedback, and personalizat
331
331
 
332
332
  | Goal | Action |
333
333
  |------|--------|
334
- | Mount an FAQ accordion widget | `core:mountWidget` with `widget: "adaptive-faq:accordion"` |
334
+ | Add an FAQ accordion widget | Add a **tile** in `tiles[]` with `widget: "adaptive-faq:accordion"` |
335
335
  | Scroll to and expand a specific FAQ item | `faq:scroll_to` |
336
336
  | Open, close, or toggle a FAQ item | `faq:toggle_item` |
337
337
  | Add, remove, reorder, or replace FAQ items | `faq:update` |
338
338
 
339
- ## Actions
339
+ ## Mounting an FAQ Widget
340
+
341
+ FAQ widgets are mounted via **tiles** (not actions). Add an entry to the `tiles[]` array in the config:
340
342
 
341
- ### core:mountWidget (FAQ)
343
+ ```json
344
+ {
345
+ "tiles": [
346
+ {
347
+ "id": "my-faq",
348
+ "title": "Frequently Asked Questions",
349
+ "content": {
350
+ "type": "custom",
351
+ "component": "adaptive-faq:accordion",
352
+ "props": {
353
+ "expandBehavior": "single",
354
+ "searchable": true,
355
+ "items": [ ... ]
356
+ }
357
+ }
358
+ }
359
+ ]
360
+ }
361
+ ```
342
362
 
343
- Mounts an FAQ accordion widget to a surface slot.
363
+ ### Tile Props
344
364
 
345
365
  | Property | Type | Required | Description |
346
366
  | ----------------------- | --------------------------------- | -------- | ------------------------------------------------------------------- |
347
- | `kind` | `"core:mountWidget"` | Yes | Action type |
348
- | `slot` | string | Yes | Target slot (e.g., `"drawer_right"`, `"overlay_center"`) |
349
- | `config.title` | string | No | Widget title |
350
- | `config.expandBehavior` | `"single"` \| `"multiple"` | No | Whether one or many items can be open at once (default: `"single"`) |
351
- | `config.searchable` | boolean | No | Show a search/filter input (default: `false`) |
352
- | `config.theme` | `"light"` \| `"dark"` \| `"auto"` | No | Color theme (default: `"auto"`) |
353
- | `config.items` | array | Yes | FAQ items (see below) |
354
- | `config.feedback` | boolean \| FeedbackConfig | No | Enable per-item feedback widget |
355
- | `config.ordering` | OrderingStrategy | No | Item ordering strategy (default: `"static"`) |
356
- | `config.injections` | InjectionRule[] | No | Dynamic item injection rules |
367
+ | `expandBehavior` | `"single"` \| `"multiple"` | No | Whether one or many items can be open at once (default: `"single"`) |
368
+ | `searchable` | boolean | No | Show a search/filter input (default: `false`) |
369
+ | `theme` | `"light"` \| `"dark"` \| `"auto"` | No | Color theme (default: `"auto"`) |
370
+ | `items` | array | Yes | FAQ items (see below) |
371
+ | `feedback` | boolean \| FeedbackConfig | No | Enable per-item feedback widget |
372
+ | `ordering` | OrderingStrategy | No | Item ordering strategy (default: `"static"`) |
373
+ | `injections` | InjectionRule[] | No | Dynamic item injection rules |
357
374
 
358
375
  ### FAQ Item Schema
359
376
 
@@ -370,53 +387,51 @@ Each item in the `items` array:
370
387
  | `config.answerStrategy` | AnswerStrategy | No | AI-generated answer configuration |
371
388
  | `triggerWhen` | DecisionStrategy \| null | No | Conditional visibility strategy |
372
389
 
390
+ **Full tile example with FAQ items:**
391
+
373
392
  ```json
374
393
  {
375
- "kind": "core:mountWidget",
376
- "slot": "drawer_right",
377
- "config": {
378
- "title": "Frequently Asked Questions",
379
- "expandBehavior": "single",
380
- "searchable": true,
381
- "theme": "auto",
382
- "feedback": {
383
- "style": "thumbs",
384
- "prompt": "Was this helpful?"
385
- },
386
- "ordering": "priority",
387
- "items": [
388
- {
389
- "kind": "faq:question",
390
- "config": {
391
- "id": "getting-started",
392
- "question": "How do I get started?",
393
- "answer": "Sign up for a free account and follow our quickstart guide.",
394
- "category": "General",
395
- "priority": 10
396
- }
397
- },
398
- {
399
- "kind": "faq:question",
400
- "config": {
401
- "id": "payment-methods",
402
- "question": "What payment methods do you accept?",
403
- "answer": "We accept all major credit cards and PayPal.",
404
- "category": "Billing",
405
- "priority": 5
406
- },
407
- "triggerWhen": {
408
- "type": "rules",
409
- "rules": [
394
+ "tiles": [
395
+ {
396
+ "id": "help-faq",
397
+ "title": "Frequently Asked Questions",
398
+ "content": {
399
+ "type": "custom",
400
+ "component": "adaptive-faq:accordion",
401
+ "props": {
402
+ "expandBehavior": "single",
403
+ "searchable": true,
404
+ "feedback": {
405
+ "style": "thumbs",
406
+ "prompt": "Was this helpful?"
407
+ },
408
+ "ordering": "priority",
409
+ "items": [
410
410
  {
411
- "conditions": [{ "type": "page_url", "pattern": "/pricing*" }],
412
- "value": true
411
+ "kind": "faq:question",
412
+ "config": {
413
+ "id": "getting-started",
414
+ "question": "How do I get started?",
415
+ "answer": "Sign up for a free account and follow our quickstart guide.",
416
+ "category": "General",
417
+ "priority": 10
418
+ }
419
+ },
420
+ {
421
+ "kind": "faq:question",
422
+ "config": {
423
+ "id": "payment-methods",
424
+ "question": "What payment methods do you accept?",
425
+ "answer": "We accept all major credit cards and PayPal.",
426
+ "category": "Billing",
427
+ "priority": 5
428
+ }
413
429
  }
414
- ],
415
- "default": false
430
+ ]
416
431
  }
417
432
  }
418
- ]
419
- }
433
+ }
434
+ ]
420
435
  }
421
436
  ```
422
437
 
@@ -698,7 +713,7 @@ Gamification capabilities including badges, points, and leaderboards.
698
713
  |------|--------|
699
714
  | Award a badge to a user | `gamification:awardBadge` |
700
715
  | Add points to a user's score | `gamification:addPoints` |
701
- | Mount a gamification widget (leaderboard, progress) | `core:mountWidget` |
716
+ | Mount a gamification widget (leaderboard, progress) | Add a **tile** with `component: "adaptive-gamification:leaderboard"` |
702
717
 
703
718
  ## Actions
704
719
 
@@ -734,15 +749,25 @@ Adds points to the current user's score.
734
749
  }
735
750
  ```
736
751
 
737
- ### core:mountWidget (Gamification)
752
+ ### Gamification Widget (via Tile)
738
753
 
739
- Mounts gamification UI elements (leaderboard, badge display, progress tracker).
754
+ Mount gamification UI elements (leaderboard, badge display, progress tracker) via a **tile**:
740
755
 
741
- | Property | Type | Required | Description |
742
- | -------- | ---------------------- | -------- | -------------------------- |
743
- | `kind` | `"core:mountWidget"` | Yes | Action type |
744
- | `slot` | string | Yes | Target slot |
745
- | `config` | object | Yes | Gamification configuration |
756
+ ```json
757
+ {
758
+ "tiles": [
759
+ {
760
+ "id": "gamification",
761
+ "title": "Your Progress",
762
+ "content": {
763
+ "type": "custom",
764
+ "component": "adaptive-gamification:leaderboard",
765
+ "props": { ... }
766
+ }
767
+ }
768
+ ]
769
+ }
770
+ ```
746
771
 
747
772
  ### Configuration Schema
748
773
 
@@ -767,9 +792,13 @@ Mounts gamification UI elements (leaderboard, badge display, progress tracker).
767
792
 
768
793
  ```json
769
794
  {
770
- "kind": "core:mountWidget",
771
- "slot": "overlay_corner_br",
772
- "config": {
795
+ "tiles": [{
796
+ "id": "gamification-widget",
797
+ "title": "Achievements",
798
+ "content": {
799
+ "type": "custom",
800
+ "component": "adaptive-gamification:leaderboard",
801
+ "props": {
773
802
  "badges": [
774
803
  {
775
804
  "id": "first-purchase",
@@ -813,7 +842,7 @@ Navigation tips accordion widget with conditional item visibility and toast noti
813
842
  |------|--------|
814
843
  | Scroll to an element on the page | `navigation:scrollTo` |
815
844
  | Navigate to a different URL | `navigation:navigate` |
816
- | Show contextual navigation tips widget | `core:mountWidget` with `widget: "adaptive-nav:tips"` |
845
+ | Show contextual navigation tips widget | Add a **tile** with `component: "adaptive-nav:tips"` |
817
846
 
818
847
  ## Widget: `adaptive-nav:tips`
819
848
 
@@ -18,6 +18,7 @@
18
18
  */
19
19
  import type { appRegistry } from './apps';
20
20
  import type { SmartCanvasRuntime } from './runtime';
21
+ import type { InterventionTracker } from './telemetry/InterventionTracker';
21
22
  import { decodeToken, encodeToken } from './token';
22
23
  export { collectBrowserMetadata, fetchGeo } from './bootstrap-init';
23
24
  export type { SyntroInitOptions, SyntroInitResult } from './bootstrap-types';
@@ -63,6 +64,7 @@ declare global {
63
64
  appRegistry: typeof appRegistry;
64
65
  runtime: SmartCanvasRuntime;
65
66
  version: string;
67
+ interventionTracker?: InterventionTracker;
66
68
  };
67
69
  }
68
70
  }
@@ -3456,7 +3456,7 @@ function getAntiFlickerSnippet(config = {}) {
3456
3456
  }
3457
3457
 
3458
3458
  // src/version.ts
3459
- var SDK_VERSION = "2.12.0";
3459
+ var SDK_VERSION = "2.13.0";
3460
3460
 
3461
3461
  // src/types.ts
3462
3462
  var SDK_SCHEMA_VERSION = "2.0";
@@ -3611,9 +3611,8 @@ var resolveConfigUri = ({
3611
3611
  }) => {
3612
3612
  var _a2;
3613
3613
  if (configUri) return configUri;
3614
- if (experiments) {
3615
- const key = featureKey != null ? featureKey : "smart-canvas-config";
3616
- const fromFeature = (_a2 = experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, key, null);
3614
+ if (experiments && featureKey) {
3615
+ const fromFeature = (_a2 = experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, featureKey, null);
3617
3616
  if (fromFeature) return fromFeature;
3618
3617
  }
3619
3618
  return void 0;
@@ -3647,9 +3646,8 @@ var createCanvasConfigFetcher = ({
3647
3646
  sdkVersion
3648
3647
  }) => async () => {
3649
3648
  var _a2;
3650
- const effectiveConfigKey = configFeatureKey != null ? configFeatureKey : "smart-canvas-config";
3651
- if (experiments) {
3652
- const directConfig = (_a2 = experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, effectiveConfigKey, null);
3649
+ if (experiments && configFeatureKey) {
3650
+ const directConfig = (_a2 = experiments.getFeatureValue) == null ? void 0 : _a2.call(experiments, configFeatureKey, null);
3653
3651
  if (directConfig && typeof directConfig === "object") {
3654
3652
  debug("SmartCanvas Config", "Resolved config directly from feature flag", directConfig);
3655
3653
  return directConfig;
@@ -5075,6 +5073,19 @@ function WidgetMount({ widgetId, props }) {
5075
5073
  }
5076
5074
  return /* @__PURE__ */ jsx6("div", { ref: parentRef });
5077
5075
  }
5076
+ var INTERACTION_PATTERNS = [
5077
+ ":toggled",
5078
+ ":clicked",
5079
+ ":feedback",
5080
+ ":navigate",
5081
+ ":expanded",
5082
+ ":collapsed",
5083
+ ":dismissed",
5084
+ ":submitted",
5085
+ ":interacted",
5086
+ ":tip_clicked",
5087
+ ":tip_focused"
5088
+ ];
5078
5089
  function TileCard({
5079
5090
  config,
5080
5091
  surface: _surface,
@@ -5083,10 +5094,42 @@ function TileCard({
5083
5094
  }) {
5084
5095
  const { title, subtitle, widget, props, icon } = config;
5085
5096
  const [, setTick] = useState4(0);
5097
+ const articleRef = useRef4(null);
5086
5098
  const runtime3 = useRuntime();
5087
5099
  useEffect5(() => {
5088
5100
  if (runtime3) setTick((t) => t + 1);
5089
5101
  }, [runtime3]);
5102
+ useEffect5(() => {
5103
+ var _a2;
5104
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5105
+ if (!articleRef.current || !tracker) return;
5106
+ const observer = new IntersectionObserver(
5107
+ ([entry]) => {
5108
+ var _a3;
5109
+ if (entry.isIntersecting) {
5110
+ tracker.trackSeen(config.id, (_a3 = config.widget) != null ? _a3 : "unknown");
5111
+ observer.disconnect();
5112
+ }
5113
+ },
5114
+ { threshold: 0.5 }
5115
+ );
5116
+ observer.observe(articleRef.current);
5117
+ return () => observer.disconnect();
5118
+ }, [config.id, config.widget]);
5119
+ useEffect5(() => {
5120
+ var _a2;
5121
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
5122
+ if (!(runtime3 == null ? void 0 : runtime3.events) || !tracker) return;
5123
+ return runtime3.events.subscribe((event) => {
5124
+ var _a3, _b;
5125
+ if (!INTERACTION_PATTERNS.some((p) => {
5126
+ var _a4;
5127
+ return (_a4 = event.name) == null ? void 0 : _a4.includes(p);
5128
+ })) return;
5129
+ if (((_a3 = event.props) == null ? void 0 : _a3.instanceId) !== config.id) return;
5130
+ tracker.trackInteracted(config.id, (_b = config.widget) != null ? _b : "unknown", event.name);
5131
+ });
5132
+ }, [runtime3 == null ? void 0 : runtime3.events, config.id, config.widget]);
5090
5133
  const registration = useMemo3(
5091
5134
  () => {
5092
5135
  var _a2, _b;
@@ -5140,6 +5183,7 @@ function TileCard({
5140
5183
  return /* @__PURE__ */ jsxs2(
5141
5184
  "article",
5142
5185
  {
5186
+ ref: articleRef,
5143
5187
  "data-shadow-canvas-id": `tile-${config.id}`,
5144
5188
  style: cardStyle,
5145
5189
  onMouseEnter,
@@ -6162,6 +6206,14 @@ var sortTiles = (tiles) => [...tiles].sort((a, b) => {
6162
6206
  var _a2, _b;
6163
6207
  return ((_a2 = b.priority) != null ? _a2 : 0) - ((_b = a.priority) != null ? _b : 0);
6164
6208
  });
6209
+ function fireTriggeredForTiles(tiles) {
6210
+ var _a2, _b;
6211
+ const tracker = typeof window !== "undefined" ? (_a2 = window.SynOS) == null ? void 0 : _a2.interventionTracker : null;
6212
+ if (!tracker) return;
6213
+ for (const tile of tiles) {
6214
+ tracker.trackTriggered(tile.id, (_b = tile.widget) != null ? _b : "unknown");
6215
+ }
6216
+ }
6165
6217
  function useShadowCanvasConfig({
6166
6218
  fetcher,
6167
6219
  experiments,
@@ -6184,6 +6236,7 @@ function useShadowCanvasConfig({
6184
6236
  if (experiments) {
6185
6237
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
6186
6238
  }
6239
+ fireTriggeredForTiles(tiles);
6187
6240
  setState((prev) => ({ ...prev, tiles: sortTiles(tiles) }));
6188
6241
  }, [runtime3, experiments]);
6189
6242
  const load = useCallback5(async () => {
@@ -6201,6 +6254,7 @@ function useShadowCanvasConfig({
6201
6254
  } else if (experiments) {
6202
6255
  tiles = tiles.filter((tile) => experiments.shouldRenderRectangle(tile));
6203
6256
  }
6257
+ fireTriggeredForTiles(tiles);
6204
6258
  debug("SmartCanvas Config", `Tile count after filtering: ${tiles.length}`);
6205
6259
  const newActions = response.actions || [];
6206
6260
  const newActionsJson = JSON.stringify(newActions);
@@ -7851,6 +7905,59 @@ function createPostHogClient(options = {}) {
7851
7905
  return new PostHogAdapter(options);
7852
7906
  }
7853
7907
 
7908
+ // src/telemetry/InterventionTracker.ts
7909
+ var InterventionTracker = class {
7910
+ constructor(telemetry, variantId) {
7911
+ __publicField(this, "telemetry");
7912
+ __publicField(this, "variantId");
7913
+ __publicField(this, "seenSet", /* @__PURE__ */ new Set());
7914
+ __publicField(this, "triggeredSet", /* @__PURE__ */ new Set());
7915
+ this.telemetry = telemetry;
7916
+ this.variantId = variantId;
7917
+ }
7918
+ trackServed(tiles, actions) {
7919
+ var _a2, _b;
7920
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_config_served", {
7921
+ variant_id: this.variantId,
7922
+ tiles,
7923
+ actions
7924
+ });
7925
+ }
7926
+ trackSeen(interventionId, interventionKind) {
7927
+ var _a2, _b;
7928
+ if (this.seenSet.has(interventionId)) return;
7929
+ this.seenSet.add(interventionId);
7930
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_seen", {
7931
+ variant_id: this.variantId,
7932
+ intervention_id: interventionId,
7933
+ intervention_kind: interventionKind
7934
+ });
7935
+ }
7936
+ trackTriggered(interventionId, interventionKind) {
7937
+ var _a2, _b;
7938
+ if (this.triggeredSet.has(interventionId)) return;
7939
+ this.triggeredSet.add(interventionId);
7940
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_triggered", {
7941
+ variant_id: this.variantId,
7942
+ intervention_id: interventionId,
7943
+ intervention_kind: interventionKind
7944
+ });
7945
+ }
7946
+ trackInteracted(interventionId, interventionKind, interactionType) {
7947
+ var _a2, _b;
7948
+ (_b = (_a2 = this.telemetry).track) == null ? void 0 : _b.call(_a2, "syntro_intervention_interacted", {
7949
+ variant_id: this.variantId,
7950
+ intervention_id: interventionId,
7951
+ intervention_kind: interventionKind,
7952
+ interaction_type: interactionType
7953
+ });
7954
+ }
7955
+ resetPage() {
7956
+ this.seenSet.clear();
7957
+ this.triggeredSet.clear();
7958
+ }
7959
+ };
7960
+
7854
7961
  // src/actions/executors/core-flow.ts
7855
7962
  var executeSequence = async (action, context) => {
7856
7963
  const handles = [];
@@ -12037,15 +12144,35 @@ async function _initCore(options) {
12037
12144
  }
12038
12145
  const warnedAppFailures = /* @__PURE__ */ new Set();
12039
12146
  const appLoadingFetcher = baseFetcher ? async () => {
12040
- var _a3, _b2, _c2, _d2, _e2, _f2, _g2;
12147
+ var _a3, _b2, _c2, _d2, _e2, _f2, _g2, _h, _i, _j, _k, _l;
12041
12148
  const config = await baseFetcher();
12149
+ const tileCount = (_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0;
12150
+ const actionCount = (_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0;
12151
+ const variantId = (_e2 = config.meta) == null ? void 0 : _e2.variant_id;
12152
+ if (tileCount > 0 || actionCount > 0) {
12153
+ if (!variantId) {
12154
+ console.warn(
12155
+ "[Syntro] Config has content but no meta.variant_id \u2014 intervention tracking disabled"
12156
+ );
12157
+ }
12158
+ }
12159
+ if (telemetry && variantId) {
12160
+ const tracker = new InterventionTracker(telemetry, variantId);
12161
+ tracker.trackServed(tileCount, actionCount);
12162
+ if (typeof window !== "undefined") {
12163
+ window.SynOS.interventionTracker = tracker;
12164
+ }
12165
+ runtime3.navigation.subscribe(() => {
12166
+ tracker.resetPage();
12167
+ });
12168
+ }
12042
12169
  console.log(
12043
12170
  "[Syntro Bootstrap] Config fetched:",
12044
- `tiles=${(_b2 = (_a3 = config.tiles) == null ? void 0 : _a3.length) != null ? _b2 : 0},`,
12045
- `actions=${(_d2 = (_c2 = config.actions) == null ? void 0 : _c2.length) != null ? _d2 : 0},`,
12046
- `theme=${(_f2 = (_e2 = config.theme) == null ? void 0 : _e2.name) != null ? _f2 : "none"}`
12171
+ `tiles=${(_g2 = (_f2 = config.tiles) == null ? void 0 : _f2.length) != null ? _g2 : 0},`,
12172
+ `actions=${(_i = (_h = config.actions) == null ? void 0 : _h.length) != null ? _i : 0},`,
12173
+ `theme=${(_k = (_j = config.theme) == null ? void 0 : _j.name) != null ? _k : "none"}`
12047
12174
  );
12048
- if (((_g2 = config.actions) == null ? void 0 : _g2.length) > 0) {
12175
+ if (((_l = config.actions) == null ? void 0 : _l.length) > 0) {
12049
12176
  console.log(
12050
12177
  "[Syntro Bootstrap] Actions in config:",
12051
12178
  config.actions.map(
@@ -12200,6 +12327,7 @@ export {
12200
12327
  createSessionMetricTracker,
12201
12328
  createNoopClient,
12202
12329
  createPostHogClient,
12330
+ InterventionTracker,
12203
12331
  ExecutorRegistry,
12204
12332
  executorRegistry,
12205
12333
  getExecutor,
@@ -12246,4 +12374,4 @@ export {
12246
12374
  encodeToken,
12247
12375
  Syntro
12248
12376
  };
12249
- //# sourceMappingURL=chunk-J2LGX2PV.js.map
12377
+ //# sourceMappingURL=chunk-GF364MMB.js.map