@syntrologie/runtime-sdk 2.8.0-canary.183 → 2.8.0-canary.185

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 DELETED
@@ -1,1450 +0,0 @@
1
- # SmartCanvas SDK Capabilities
2
-
3
- This document describes all available operations and capabilities of the SmartCanvas SDK for DOM manipulation, interventions, and UI rendering.
4
-
5
- > **Auto-generated**: This file is assembled from individual adaptive package capabilities during build.
6
-
7
- ## Table of Contents
8
- - [Overview](#overview)
9
- - [Quick Start Example](#quick-start-example)
10
- - [Adaptive Packages](#adaptive-packages)
11
- - [Surfaces](#surfaces)
12
- - [Anchor Resolution](#anchor-resolution)
13
- - [Route Scoping](#route-scoping)
14
- - [Decision Strategies](#decision-strategies)
15
- - [Best Practices](#best-practices)
16
-
17
- ---
18
-
19
- ## Overview
20
-
21
- The SmartCanvas SDK provides three main systems for modifying web pages:
22
-
23
- 1. **ActionEngine** - Unified execution layer for interventions (highlight, tooltip, badge, DOM modifications)
24
- 2. **Surfaces** - Managed surface system for rendering UI into named slots
25
- 3. **Runtime** - Context, events, state, and decision management
26
-
27
- All modifications are reversible and publish events to the EventBus for tracking.
28
-
29
- ---
30
-
31
- ## Quick Start Example
32
-
33
- ### Simple Header Text Change
34
-
35
- Change a page header when the element is visible:
36
-
37
- ```json
38
- {
39
- "id": "welcome-header-change",
40
- "activation": {
41
- "routes": { "include": ["/", "/home"] },
42
- "strategy": {
43
- "type": "rules",
44
- "rules": [
45
- {
46
- "conditions": [
47
- { "type": "anchor_visible", "anchorId": "h1.hero-title", "state": "visible" }
48
- ],
49
- "value": true
50
- }
51
- ],
52
- "default": false
53
- }
54
- },
55
- "actions": [
56
- {
57
- "kind": "content:setText",
58
- "anchorId": { "selector": "h1.hero-title", "route": "/" },
59
- "text": "Welcome to Our New Experience"
60
- }
61
- ]
62
- }
63
- ```
64
-
65
- ---
66
-
67
- ## Adaptive Packages
68
-
69
- The SDK includes the following adaptive packages, each providing specific capabilities:
70
-
71
- - [@syntrologie/adapt-content](#syntrologieadapt-content)
72
- - [@syntrologie/adapt-faq](#syntrologieadapt-faq)
73
- - [@syntrologie/adapt-gamification](#syntrologieadapt-gamification)
74
- - [@syntrologie/adapt-mcp](#syntrologieadapt-mcp)
75
- - [@syntrologie/adapt-nav](#syntrologieadapt-nav)
76
- - [@syntrologie/adapt-overlays](#syntrologieadapt-overlays)
77
- - [@syntrologie/adapt-viz](#syntrologieadapt-viz)
78
-
79
- ---
80
-
81
- # @syntrologie/adapt-content
82
-
83
- DOM content modification capabilities for text, attributes, styles, HTML, and classes.
84
-
85
- ## When to use
86
-
87
- | Goal | Action |
88
- |------|--------|
89
- | Replace an element's text | `content:setText` |
90
- | Change an HTML attribute (href, src, data-*) | `content:setAttr` |
91
- | Modify inline styles (color, size, spacing) | `content:setStyle` |
92
- | Inject new HTML before/after/inside an element | `content:insertHtml` |
93
- | Add a CSS class (show/hide, animate) | `content:addClass` |
94
- | Remove a CSS class | `content:removeClass` |
95
-
96
- ## Actions
97
-
98
- ### content:setText
99
-
100
- Replaces the text content of an element.
101
-
102
- | Property | Type | Required | Description |
103
- | ---------- | ------------------- | -------- | ---------------- |
104
- | `kind` | `"content:setText"` | Yes | Action type |
105
- | `anchorId` | object | Yes | `{ selector, route }` |
106
- | `text` | string | Yes | New text content |
107
-
108
- ```json
109
- {
110
- "kind": "content:setText",
111
- "anchorId": { "selector": "h1.hero-title", "route": "/" },
112
- "text": "Start Your Free Trial Today"
113
- }
114
- ```
115
-
116
- ### content:setAttr
117
-
118
- Sets an HTML attribute on an element.
119
-
120
- | Property | Type | Required | Description |
121
- | ---------- | ------------------- | -------- | ---------------- |
122
- | `kind` | `"content:setAttr"` | Yes | Action type |
123
- | `anchorId` | object | Yes | `{ selector, route }` |
124
- | `attr` | string | Yes | Attribute name |
125
- | `value` | string | Yes | Attribute value |
126
-
127
- **Blocked attributes:** Event handlers (`onclick`, `onerror`, etc.) are not allowed.
128
-
129
- ```json
130
- {
131
- "kind": "content:setAttr",
132
- "anchorId": { "selector": "#signup-form", "route": "/" },
133
- "attr": "data-experiment",
134
- "value": "signup-v2"
135
- }
136
- ```
137
-
138
- ### content:setStyle
139
-
140
- Sets inline CSS styles on an element.
141
-
142
- | Property | Type | Required | Description |
143
- | ---------- | -------------------- | -------- | ------------------------ |
144
- | `kind` | `"content:setStyle"` | Yes | Action type |
145
- | `anchorId` | object | Yes | `{ selector, route }` |
146
- | `styles` | object | Yes | CSS property/value pairs |
147
-
148
- ```json
149
- {
150
- "kind": "content:setStyle",
151
- "anchorId": { "selector": ".hero-section", "route": "/" },
152
- "styles": {
153
- "background-color": "#1e40af",
154
- "padding": "2rem"
155
- }
156
- }
157
- ```
158
-
159
- ### content:insertHtml
160
-
161
- Inserts HTML content relative to an element.
162
-
163
- | Property | Type | Required | Description |
164
- | ---------- | --------------------- | -------- | ----------------------------------------------------------- |
165
- | `kind` | `"content:insertHtml"` | Yes | Action type |
166
- | `anchorId` | object | Yes | `{ selector, route }` |
167
- | `html` | string | Yes | HTML content (sanitized) |
168
- | `position` | string | Yes | `"before"`, `"after"`, `"prepend"`, `"append"`, `"replace"` |
169
- | `deepLink` | object | No | Makes the entire inserted element clickable to open the canvas panel and navigate to a specific tile |
170
-
171
- **Positions:**
172
-
173
- - `before` - Insert before the element
174
- - `after` - Insert after the element
175
- - `prepend` - Insert inside, before first child
176
- - `append` - Insert inside, after last child
177
- - `replace` - Replace the entire element
178
-
179
- ```json
180
- {
181
- "kind": "content:insertHtml",
182
- "anchorId": { "selector": ".cta-button", "route": "/" },
183
- "html": "<span class=\"badge\">NEW</span>",
184
- "position": "append"
185
- }
186
- ```
187
-
188
- **Deep-linking to canvas tiles:**
189
-
190
- Use `deepLink` to make inserted content open the canvas panel and navigate to a specific tile when clicked. This is the correct way to connect inserted buttons/links to canvas tiles — do NOT use `onclick` handlers with `window.SynOS`.
191
-
192
- | Property | Type | Required | Description |
193
- | ---------------- | ------ | -------- | ---------------------------------- |
194
- | `deepLink.tileId` | string | Yes | ID of the tile to open |
195
- | `deepLink.itemId` | string | No | Specific item within the tile |
196
-
197
- ```json
198
- {
199
- "kind": "content:insertHtml",
200
- "anchorId": { "selector": "[data-id='pricing-heading']", "route": "/" },
201
- "html": "<button style='background: #4a90e2; color: white; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;'>Help Me Choose</button>",
202
- "position": "after",
203
- "deepLink": { "tileId": "plan_selector_faq" }
204
- }
205
- ```
206
-
207
- The SDK automatically handles opening the canvas, setting the cursor to pointer, and publishing a `notification.deep_link` event. The click handler is wired up and cleaned up by the SDK — no JavaScript in the HTML is needed.
208
-
209
- ### content:addClass
210
-
211
- Adds a CSS class to an element.
212
-
213
- | Property | Type | Required | Description |
214
- | ----------- | -------------------- | -------- | ----------------- |
215
- | `kind` | `"content:addClass"` | Yes | Action type |
216
- | `anchorId` | object | Yes | `{ selector, route }` |
217
- | `className` | string | Yes | Class name to add |
218
-
219
- ```json
220
- {
221
- "kind": "content:addClass",
222
- "anchorId": { "selector": ".pricing-card", "route": "/pricing" },
223
- "className": "highlighted"
224
- }
225
- ```
226
-
227
- ### content:removeClass
228
-
229
- Removes a CSS class from an element.
230
-
231
- | Property | Type | Required | Description |
232
- | ----------- | ----------------------- | -------- | -------------------- |
233
- | `kind` | `"content:removeClass"` | Yes | Action type |
234
- | `anchorId` | object | Yes | `{ selector, route }` |
235
- | `className` | string | Yes | Class name to remove |
236
-
237
- ```json
238
- {
239
- "kind": "content:removeClass",
240
- "anchorId": { "selector": ".pricing-card", "route": "/pricing" },
241
- "className": "hidden"
242
- }
243
- ```
244
-
245
-
246
- ---
247
-
248
- # @syntrologie/adapt-faq
249
-
250
- Collapsible Q&A accordion with actions, rich content, feedback, and personalization.
251
-
252
- ## When to use
253
-
254
- | Goal | Action |
255
- |------|--------|
256
- | Add an FAQ accordion widget | Add a **tile** in `tiles[]` with `widget: "adaptive-faq:accordion"` |
257
- | Scroll to and expand a specific FAQ item | `faq:scroll_to` |
258
- | Open, close, or toggle a FAQ item | `faq:toggle_item` |
259
- | Add, remove, reorder, or replace FAQ items | `faq:update` |
260
-
261
- ## Mounting an FAQ Widget
262
-
263
- FAQ widgets are mounted via **tiles** (not actions). Add an entry to the `tiles[]` array in the config:
264
-
265
- ```json
266
- {
267
- "tiles": [
268
- {
269
- "id": "my-faq",
270
- "title": "Frequently Asked Questions",
271
- "content": {
272
- "type": "custom",
273
- "component": "adaptive-faq:accordion",
274
- "props": {
275
- "expandBehavior": "single",
276
- "searchable": true,
277
- "items": [ ... ]
278
- }
279
- }
280
- }
281
- ]
282
- }
283
- ```
284
-
285
- ### Tile Props
286
-
287
- | Property | Type | Required | Description |
288
- | ----------------------- | --------------------------------- | -------- | ------------------------------------------------------------------- |
289
- | `expandBehavior` | `"single"` \| `"multiple"` | No | Whether one or many items can be open at once (default: `"single"`) |
290
- | `searchable` | boolean | No | Show a search/filter input (default: `false`) |
291
- | `theme` | `"light"` \| `"dark"` \| `"auto"` | No | Color theme (default: `"auto"`) |
292
- | `items` | array | Yes | FAQ items (see below) |
293
- | `feedback` | boolean \| FeedbackConfig | No | Enable per-item feedback widget |
294
- | `ordering` | OrderingStrategy | No | Item ordering strategy (default: `"static"`) |
295
- | `injections` | InjectionRule[] | No | Dynamic item injection rules |
296
-
297
- ### FAQ Item Schema
298
-
299
- Each item in the `items` array:
300
-
301
- | Property | Type | Required | Description |
302
- | ----------------------- | ------------------------ | -------- | ----------------------------------------------- |
303
- | `kind` | `"faq:question"` | Yes | Compositional action type |
304
- | `config.id` | string | Yes | Unique identifier for this question |
305
- | `config.question` | string | Yes | The question text |
306
- | `config.answer` | FAQAnswer | Yes | Answer content (string, rich HTML, or markdown) |
307
- | `config.category` | string | No | Category for grouping items |
308
- | `config.priority` | number | No | Priority weight for ordering |
309
- | `config.answerStrategy` | AnswerStrategy | No | AI-generated answer configuration |
310
- | `triggerWhen` | DecisionStrategy \| null | No | Conditional visibility strategy |
311
-
312
- **Full tile example with FAQ items:**
313
-
314
- ```json
315
- {
316
- "tiles": [
317
- {
318
- "id": "help-faq",
319
- "title": "Frequently Asked Questions",
320
- "content": {
321
- "type": "custom",
322
- "component": "adaptive-faq:accordion",
323
- "props": {
324
- "expandBehavior": "single",
325
- "searchable": true,
326
- "feedback": {
327
- "style": "thumbs",
328
- "prompt": "Was this helpful?"
329
- },
330
- "ordering": "priority",
331
- "items": [
332
- {
333
- "kind": "faq:question",
334
- "config": {
335
- "id": "getting-started",
336
- "question": "How do I get started?",
337
- "answer": "Sign up for a free account and follow our quickstart guide.",
338
- "category": "General",
339
- "priority": 10
340
- }
341
- },
342
- {
343
- "kind": "faq:question",
344
- "config": {
345
- "id": "payment-methods",
346
- "question": "What payment methods do you accept?",
347
- "answer": "We accept all major credit cards and PayPal.",
348
- "category": "Billing",
349
- "priority": 5
350
- }
351
- }
352
- ]
353
- }
354
- }
355
- }
356
- ]
357
- }
358
- ```
359
-
360
- ### faq:scroll_to
361
-
362
- Scrolls the viewport to a specific FAQ item and optionally expands it.
363
-
364
- | Property | Type | Required | Default | Description |
365
- | -------------- | ----------------- | -------- | ---------- | ------------------------------------------ |
366
- | `kind` | `"faq:scroll_to"` | Yes | | Action type |
367
- | `itemId` | string | No\* | | Target item ID |
368
- | `itemQuestion` | string | No\* | | Target item question text (fuzzy match) |
369
- | `expand` | boolean | No | `true` | Whether to expand the item after scrolling |
370
- | `behavior` | string | No | `"smooth"` | `"smooth"`, `"instant"`, `"auto"` |
371
-
372
- \* Either `itemId` or `itemQuestion` is required.
373
-
374
- ```json
375
- {
376
- "kind": "faq:scroll_to",
377
- "itemId": "payment-methods",
378
- "expand": true,
379
- "behavior": "smooth"
380
- }
381
- ```
382
-
383
- ### faq:toggle_item
384
-
385
- Opens, closes, or toggles a FAQ item's expanded state.
386
-
387
- | Property | Type | Required | Default | Description |
388
- | -------------- | ------------------- | -------- | ---------- | --------------------------------------- |
389
- | `kind` | `"faq:toggle_item"` | Yes | | Action type |
390
- | `itemId` | string | No\* | | Target item ID |
391
- | `itemQuestion` | string | No\* | | Target item question text (fuzzy match) |
392
- | `state` | string | No | `"toggle"` | `"open"`, `"closed"`, `"toggle"` |
393
-
394
- \* Either `itemId` or `itemQuestion` is required.
395
-
396
- ```json
397
- {
398
- "kind": "faq:toggle_item",
399
- "itemId": "getting-started",
400
- "state": "open"
401
- }
402
- ```
403
-
404
- ### faq:update
405
-
406
- Dynamically adds, removes, reorders, or replaces FAQ items at runtime.
407
-
408
- | Property | Type | Required | Description |
409
- | ----------- | ------------------- | -------- | ------------------------------------------------------- |
410
- | `kind` | `"faq:update"` | Yes | Action type |
411
- | `operation` | string | Yes | `"add"`, `"remove"`, `"reorder"`, `"replace"` |
412
- | `items` | FAQQuestionAction[] | No | Items to add or replace with (required for add/replace) |
413
- | `itemId` | string | No | Item to remove (required for remove) |
414
- | `order` | string[] | No | Ordered list of item IDs (required for reorder) |
415
- | `position` | string | No | `"prepend"`, `"append"`, `"before"`, `"after"` |
416
- | `anchorId` | string | No | Reference item for before/after positioning |
417
-
418
- **Add items:**
419
-
420
- ```json
421
- {
422
- "kind": "faq:update",
423
- "operation": "add",
424
- "position": "append",
425
- "items": [
426
- {
427
- "kind": "faq:question",
428
- "config": {
429
- "id": "new-feature",
430
- "question": "What is the new feature?",
431
- "answer": "Our latest release includes AI-powered recommendations."
432
- }
433
- }
434
- ]
435
- }
436
- ```
437
-
438
- **Remove an item:**
439
-
440
- ```json
441
- {
442
- "kind": "faq:update",
443
- "operation": "remove",
444
- "itemId": "outdated-question"
445
- }
446
- ```
447
-
448
- **Reorder items:**
449
-
450
- ```json
451
- {
452
- "kind": "faq:update",
453
- "operation": "reorder",
454
- "order": ["getting-started", "new-feature", "payment-methods"]
455
- }
456
- ```
457
-
458
- **Replace all items:**
459
-
460
- ```json
461
- {
462
- "kind": "faq:update",
463
- "operation": "replace",
464
- "items": [
465
- {
466
- "kind": "faq:question",
467
- "config": {
468
- "id": "only-question",
469
- "question": "Is this the only question?",
470
- "answer": "Yes, after replacing all items."
471
- }
472
- }
473
- ]
474
- }
475
- ```
476
-
477
- ## Compositional Pattern
478
-
479
- The FAQ widget uses a **compositional action pattern** where `faq:question` actions serve as configuration data rendered by the widget, rather than being executed by the runtime. This allows:
480
-
481
- - **Per-item conditional visibility** via `triggerWhen` strategies -- items can appear or hide based on page URL, user segment, viewport, or any DecisionStrategy condition
482
- - **Category grouping** -- items with a `category` field are grouped under collapsible section headers
483
- - **Dynamic injection** -- `injections` rules can add items when trigger conditions are met, supporting contextual FAQ content
484
- - **Ordering control** -- the `ordering` strategy determines how items are sorted within categories
485
-
486
- Items without `triggerWhen` are always visible. Items without `category` appear in an ungrouped section.
487
-
488
- ## Rich Answer Content
489
-
490
- FAQ answers support three content formats via the `FAQAnswer` union type:
491
-
492
- - **Plain string** -- simple text, supports basic markdown
493
- - **Rich HTML** (`{ "type": "rich", "html": "<p>...</p>" }`) -- pre-rendered HTML content
494
- - **Enhanced markdown** (`{ "type": "markdown", "content": "...", "assets": [...] }`) -- markdown with embedded media assets (images, videos)
495
-
496
- ```json
497
- {
498
- "config": {
499
- "id": "rich-example",
500
- "question": "How does the visual editor work?",
501
- "answer": {
502
- "type": "markdown",
503
- "content": "The visual editor lets you create experiments with a point-and-click interface.\n\n![Editor screenshot](asset:editor-screenshot)",
504
- "assets": [
505
- {
506
- "id": "editor-screenshot",
507
- "type": "image",
508
- "src": "https://cdn.example.com/editor.png",
509
- "alt": "Visual editor interface",
510
- "width": 800,
511
- "height": 450
512
- }
513
- ]
514
- }
515
- }
516
- }
517
- ```
518
-
519
- ## Feedback
520
-
521
- Per-item feedback allows users to rate answer helpfulness. Enable with a boolean or detailed config:
522
-
523
- - `"feedback": true` -- enables thumbs up/down with default prompt
524
- - `"feedback": { "style": "thumbs", "prompt": "Was this helpful?" }` -- thumbs with custom prompt
525
- - `"feedback": { "style": "rating" }` -- numeric rating scale
526
-
527
- Feedback events are published via `context.publishEvent` for analytics integration.
528
-
529
- ## Personalization
530
-
531
- ### Ordering Strategies
532
-
533
- The `ordering` field controls how FAQ items are sorted:
534
-
535
- - **`"static"`** (default) -- items appear in the order defined in the config
536
- - **`"priority"`** -- items are sorted by their `priority` field (higher values first)
537
- - **Segment-based** -- items are ordered differently per user segment:
538
-
539
- ```json
540
- {
541
- "ordering": {
542
- "type": "segment",
543
- "segmentWeights": {
544
- "new_user": ["getting-started", "pricing", "support"],
545
- "power_user": ["api-docs", "advanced-config", "integrations"]
546
- }
547
- }
548
- }
549
- ```
550
-
551
- ### Contextual Hints (Companion Tooltips)
552
-
553
- FAQ questions can be surfaced proactively on the page using companion overlay tooltips. When a tooltip CTA has an `actionId` matching `faq:open:<questionId>`, clicking it expands the corresponding question in the FAQ widget and scrolls it into view.
554
-
555
- This is a convention-based integration — no special FAQ action is needed. Use a standard `overlays:tooltip` action with `ctaButtons`:
556
-
557
- ```json
558
- {
559
- "kind": "overlays:tooltip",
560
- "anchorId": "#complex-feature",
561
- "content": {
562
- "title": "Need help?",
563
- "body": "Check our FAQ for details.",
564
- "ctaButtons": [
565
- { "label": "View Answer", "actionId": "faq:open:getting-started", "primary": true },
566
- { "label": "Dismiss", "actionId": "dismiss" }
567
- ]
568
- },
569
- "trigger": "immediate",
570
- "placement": "bottom"
571
- }
572
- ```
573
-
574
- **How it works:**
575
-
576
- 1. The tooltip CTA click publishes an `action.tooltip_cta_clicked` event via the EventBus
577
- 2. The FAQ widget subscribes to these events and filters for `faq:open:*` actionIds
578
- 3. The matching question expands and scrolls into view
579
- 4. The widget also publishes `canvas.requestOpen` to open the canvas panel if needed
580
-
581
- **Late-mount support:** If the FAQ widget mounts after the CTA click (e.g., canvas was closed), it checks the EventBus history for recent `faq:open:*` events (within 10 seconds) and auto-expands the target question on mount.
582
-
583
- **`dismiss` actionId:** Destroys the tooltip without publishing an event. Use this for a "close" or "not now" button.
584
-
585
- ### Dynamic Injection
586
-
587
- Injection rules add contextual FAQ items when conditions are met:
588
-
589
- ```json
590
- {
591
- "injections": [
592
- {
593
- "trigger": {
594
- "type": "rules",
595
- "rules": [
596
- {
597
- "conditions": [{ "type": "page_url", "pattern": "/checkout*" }],
598
- "value": true
599
- }
600
- ],
601
- "default": false
602
- },
603
- "items": [
604
- {
605
- "kind": "faq:question",
606
- "config": {
607
- "id": "checkout-help",
608
- "question": "Having trouble with checkout?",
609
- "answer": "Contact support at help@example.com for immediate assistance."
610
- }
611
- }
612
- ],
613
- "position": "prepend",
614
- "once": true
615
- }
616
- ]
617
- }
618
- ```
619
-
620
- - `trigger` -- a DecisionStrategy that evaluates to `true` when injection should occur
621
- - `items` -- FAQ items to inject
622
- - `position` -- `"prepend"` or `"append"` relative to existing items
623
- - `once` -- if `true`, items are injected only on the first trigger match
624
-
625
-
626
- ---
627
-
628
- # @syntrologie/adapt-gamification
629
-
630
- Gamification capabilities including badges, points, and leaderboards.
631
-
632
- ## When to use
633
-
634
- | Goal | Action |
635
- |------|--------|
636
- | Award a badge to a user | `gamification:awardBadge` |
637
- | Add points to a user's score | `gamification:addPoints` |
638
- | Mount a gamification widget (leaderboard, progress) | Add a **tile** with `component: "adaptive-gamification:leaderboard"` |
639
-
640
- ## Actions
641
-
642
- ### gamification:awardBadge
643
-
644
- Awards a badge to the current user.
645
-
646
- | Property | Type | Required | Description |
647
- | -------- | -------------------------- | -------- | ---------------- |
648
- | `kind` | `"gamification:awardBadge"` | Yes | Action type |
649
- | `badgeId` | string | Yes | Badge identifier |
650
-
651
- ```json
652
- {
653
- "kind": "gamification:awardBadge",
654
- "badgeId": "first-purchase"
655
- }
656
- ```
657
-
658
- ### gamification:addPoints
659
-
660
- Adds points to the current user's score.
661
-
662
- | Property | Type | Required | Description |
663
- | -------- | -------------------------- | -------- | ---------------- |
664
- | `kind` | `"gamification:addPoints"` | Yes | Action type |
665
- | `points` | number | Yes | Points to add |
666
-
667
- ```json
668
- {
669
- "kind": "gamification:addPoints",
670
- "points": 50
671
- }
672
- ```
673
-
674
- ### Gamification Widget (via Tile)
675
-
676
- Mount gamification UI elements (leaderboard, badge display, progress tracker) via a **tile**:
677
-
678
- ```json
679
- {
680
- "tiles": [
681
- {
682
- "id": "gamification",
683
- "title": "Your Progress",
684
- "content": {
685
- "type": "custom",
686
- "component": "adaptive-gamification:leaderboard",
687
- "props": { ... }
688
- }
689
- }
690
- ]
691
- }
692
- ```
693
-
694
- ### Configuration Schema
695
-
696
- | Property | Type | Required | Default | Description |
697
- | ----------------------------- | ------- | -------- | ------- | ---------------------- |
698
- | `badges` | array | No | `[]` | Badge definitions |
699
- | `points.enabled` | boolean | No | `false` | Enable points system |
700
- | `points.multiplier` | number | No | `1` | Points multiplier |
701
- | `leaderboard.enabled` | boolean | No | `false` | Show leaderboard |
702
- | `leaderboard.refreshInterval` | number | No | `60000` | Refresh interval in ms |
703
-
704
- ### Badge Schema
705
-
706
- | Property | Type | Required | Description |
707
- | -------------------- | ------ | -------- | ----------------------------- |
708
- | `id` | string | Yes | Unique badge identifier |
709
- | `name` | string | Yes | Display name |
710
- | `icon` | string | Yes | Icon identifier |
711
- | `description` | string | No | Badge description |
712
- | `trigger.event` | string | Yes | Event that triggers the badge |
713
- | `trigger.conditions` | array | No | Additional conditions |
714
-
715
- ```json
716
- {
717
- "tiles": [{
718
- "id": "gamification-widget",
719
- "title": "Achievements",
720
- "content": {
721
- "type": "custom",
722
- "component": "adaptive-gamification:leaderboard",
723
- "props": {
724
- "badges": [
725
- {
726
- "id": "first-purchase",
727
- "name": "First Purchase",
728
- "icon": "shopping-cart",
729
- "description": "Made your first purchase!",
730
- "trigger": {
731
- "event": "purchase_completed"
732
- }
733
- }
734
- ],
735
- "points": {
736
- "enabled": true,
737
- "multiplier": 2
738
- },
739
- "leaderboard": {
740
- "enabled": true,
741
- "refreshInterval": 30000
742
- }
743
- }
744
- }
745
- ```
746
-
747
- ## Use Cases
748
-
749
- - **Engagement rewards**: Award badges for completing onboarding steps
750
- - **Loyalty programs**: Track points for purchases or interactions
751
- - **Social proof**: Display leaderboards to encourage participation
752
- - **Progress tracking**: Show achievement progress to motivate users
753
-
754
-
755
- ---
756
-
757
- # @syntrologie/adapt-mcp
758
-
759
- Loader adaptive for the MCP Apps protocol.
760
-
761
- ## What it does today
762
-
763
- Nothing observable from tile config. This adaptive is currently a pure loader:
764
- it pulls [`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps) into the page and exposes its `App`, `AppBridge`,
765
- and `PostMessageTransport` classes on `window.MCPApp`. No actions, no widgets,
766
- no surfaces.
767
-
768
- ## Why it exists separately from `runtime-sdk`
769
-
770
- The MCP Apps bridge adds a non-trivial amount of code and a new dep
771
- (`@modelcontextprotocol/ext-apps`) to any page that includes it. Most Syntro
772
- deployments don't need it — only pages that want to render interactive MCP-app
773
- cards do. Splitting it into its own adaptive means the runtime stays lean and
774
- the bridge code only loads when a tile references it.
775
-
776
- This package is deliberately **not** listed in `BUNDLED_APP_IDS` in
777
- `packages/runtime-sdk/src/apps/AppLoader.ts`, so the AppLoader treats it as a
778
- CDN-hosted adaptive and fetches it on demand.
779
-
780
- ## Loading
781
-
782
- The `./cdn` entrypoint is the CDN bundle. When it runs in a browser:
783
-
784
- 1. Imports `App`, `AppBridge`, `PostMessageTransport` from
785
- `@modelcontextprotocol/ext-apps/app-bridge`.
786
- 2. Assigns them to `window.MCPApp`.
787
- 3. Calls `window.SynOS.appRegistry.register(manifest)` if the host registry is
788
- present.
789
-
790
- After this runs, any subsequent page code can use `window.MCPApp.App` to
791
- construct an MCP App instance without reloading the library.
792
-
793
- ## Capabilities (none yet)
794
-
795
- | Kind | Available? | Notes |
796
- |------|------------|-------|
797
- | Actions | — | TBD once bridge requirements are defined. |
798
- | Widgets | — | TBD. |
799
- | Surfaces | — | TBD. |
800
-
801
- This file gets aggregated into `runtime-sdk/CAPABILITIES.md` during build. When
802
- actions land here, document them in the normal adaptive format (kind, description,
803
- params schema, example config).
804
-
805
- ## Next steps (not in this package yet)
806
-
807
- - Canvas/host wiring: instantiate `AppBridge` per rendered MCP-app iframe.
808
- - Action registry: map `data-action` clicks to `app.callTool(...)` invocations.
809
- - Per-channel MCP client proxy on the canvas server.
810
-
811
- Those live in other PRs once we lock in the full interactivity design.
812
-
813
-
814
- ---
815
-
816
- # @syntrologie/adapt-nav
817
-
818
- Navigation tips accordion widget with conditional item visibility and toast notifications.
819
-
820
- ## When to use
821
-
822
- | Goal | Action |
823
- |------|--------|
824
- | Scroll to an element on the page | `navigation:scrollTo` |
825
- | Navigate to a different URL | `navigation:navigate` |
826
- | Show contextual navigation tips widget | Add a **tile** with `component: "adaptive-nav:tips"` |
827
-
828
- ## Widget: `adaptive-nav:tips`
829
-
830
- Accordion of contextual navigation tips. Each tip has a collapsible header and expanded body with optional CTA link.
831
-
832
- ### Tile Config
833
-
834
- | Property | Type | Required | Default | Description |
835
- | ---------------------- | --------------------------------- | -------- | ---------- | --------------------- |
836
- | `widget` | `"adaptive-nav:tips"` | Yes | | Widget identifier |
837
- | `props.expandBehavior` | `"single"` \| `"multiple"` | No | `"single"` | Accordion expand mode |
838
- | `props.theme` | `"light"` \| `"dark"` \| `"auto"` | No | `"auto"` | Color theme |
839
- | `props.actions` | `NavTipAction[]` | Yes | | Navigation tip items |
840
-
841
- ### Nav Tip Action (`nav:tip`)
842
-
843
- Compositional action — rendered as an accordion item by the parent widget.
844
-
845
- | Property | Type | Required | Description |
846
- | -------------------- | --------------------------- | -------- | ------------------------------------ |
847
- | `kind` | `"nav:tip"` | Yes | Action type |
848
- | `config.id` | string | Yes | Unique tip identifier |
849
- | `config.title` | string | Yes | Accordion header text |
850
- | `config.description` | string | Yes | Expanded body text |
851
- | `config.href` | string | No | Optional CTA link URL |
852
- | `config.icon` | string | No | Icon (emoji or icon key) |
853
- | `config.external` | boolean | No | Open link in new tab |
854
- | `config.category` | string | No | Category for grouping |
855
- | `showWhen` | `DecisionStrategy<boolean>` | No | Conditional visibility |
856
- | `notify` | `{ title?, body?, icon? }` | No | Toast config for showWhen transition |
857
- | `rationale` | `{ why, confidence? }` | No | AI reasoning for recommendation |
858
-
859
- ### Events Published
860
-
861
- | Event | When | Props |
862
- | ------------------ | ----------------------------------- | --------------------------------- |
863
- | `nav:toggled` | User expands/collapses a tip | `{ instanceId, tipId, expanded }` |
864
- | `nav:tip_clicked` | User clicks a tip's CTA link | `{ instanceId, href, external }` |
865
- | `nav:tip_revealed` | `showWhen` transitions false → true | `{ tipId, title, body, icon }` |
866
-
867
- ### Notify Watchers
868
-
869
- Tips with both `showWhen` and `notify` are registered as notify watchers. The runtime evaluates these continuously (even when drawer is closed). When `showWhen` transitions false → true, publishes `nav:tip_revealed` which can trigger toast notifications via tile-level `notifications` rules.
870
-
871
- ```json
872
- {
873
- "id": "nav",
874
- "widget": "adaptive-nav:tips",
875
- "notifications": [
876
- {
877
- "on": "nav:tip_revealed",
878
- "title": "{{props.title}}",
879
- "body": "{{props.body}}",
880
- "icon": "🧭"
881
- }
882
- ],
883
- "props": {
884
- "expandBehavior": "single",
885
- "theme": "dark",
886
- "actions": [
887
- {
888
- "kind": "nav:tip",
889
- "config": {
890
- "id": "tip-analytics",
891
- "title": "Advanced Analytics",
892
- "description": "Unlock deeper insights with our analytics suite.",
893
- "href": "/analytics",
894
- "icon": "📊",
895
- "category": "Power User"
896
- },
897
- "notify": { "title": "New Tip", "body": "Advanced Analytics", "icon": "📊" },
898
- "showWhen": {
899
- "type": "rules",
900
- "rules": [
901
- {
902
- "conditions": [
903
- { "type": "event_count", "key": "link-clicks", "operator": "gte", "count": 3 }
904
- ],
905
- "value": true
906
- }
907
- ],
908
- "default": false
909
- }
910
- }
911
- ]
912
- }
913
- }
914
- ```
915
-
916
- ## Navigation Actions
917
-
918
- ### `navigation:scrollTo`
919
-
920
- Scrolls the viewport to bring an element into view.
921
-
922
- | Property | Type | Required | Default | Description |
923
- | ---------- | ----------------------- | -------- | ---------- | ------------------------------------------- |
924
- | `kind` | `"navigation:scrollTo"` | Yes | | Action type |
925
- | `anchorId` | string | Yes | | Element selector |
926
- | `behavior` | string | No | `"smooth"` | `"smooth"`, `"instant"`, `"auto"` |
927
- | `block` | string | No | `"center"` | `"start"`, `"center"`, `"end"`, `"nearest"` |
928
-
929
- ### `navigation:navigate`
930
-
931
- Navigates to a URL.
932
-
933
- | Property | Type | Required | Default | Description |
934
- | -------- | ----------------------- | -------- | --------- | ----------------------- |
935
- | `kind` | `"navigation:navigate"` | Yes | | Action type |
936
- | `url` | string | Yes | | Destination URL |
937
- | `target` | string | No | `"_self"` | `"_self"` or `"_blank"` |
938
-
939
- **Note:** `javascript:` URLs are blocked for security.
940
-
941
-
942
- ---
943
-
944
- # @syntrologie/adapt-overlays
945
-
946
- Visual overlay capabilities including highlights, tooltips, badges, pulse animations, and celebrations.
947
-
948
- ## When to use
949
-
950
- | Goal | Action |
951
- |------|--------|
952
- | Draw attention to an element (spotlight) | `overlays:highlight` |
953
- | Show contextual help or guidance near an element | `overlays:tooltip` |
954
- | Add a notification indicator (count, "NEW") | `overlays:badge` |
955
- | Subtle attention-grab animation | `overlays:pulse` |
956
- | Show a blocking or non-blocking dialog | `overlays:modal` |
957
- | Celebrate a user achievement | `overlays:celebrate` |
958
-
959
- ## Actions
960
-
961
- ### overlays:highlight
962
-
963
- Creates a spotlight effect around an element with a scrim overlay.
964
-
965
- | Property | Type | Required | Default | Description |
966
- | -------------------- | ------------- | -------- | ----------- | --------------------------------------- |
967
- | `kind` | `"overlays:highlight"` | Yes | | Action type |
968
- | `anchorId` | string | Yes | | Element selector |
969
- | `style.color` | string | No | `"#5b8cff"` | Ring color |
970
- | `style.scrimOpacity` | number | No | `0.55` | Backdrop opacity 0-1 (set to 0 to hide) |
971
- | `style.paddingPx` | number | No | `12` | Space around element |
972
- | `style.radiusPx` | number | No | `12` | Ring corner radius |
973
-
974
- ```json
975
- {
976
- "kind": "overlays:highlight",
977
- "anchorId": "#signup-button",
978
- "style": {
979
- "color": "#22c55e",
980
- "scrimOpacity": 0.4
981
- }
982
- }
983
- ```
984
-
985
- ### overlays:tooltip
986
-
987
- Shows a tooltip near an element with optional title, body, and CTA.
988
-
989
- | Property | Type | Required | Default | Description |
990
- | -------------------- | ----------- | -------- | ------------- | -------------------------------------- |
991
- | `kind` | `"overlays:tooltip"` | Yes | | Action type |
992
- | `anchorId` | string | Yes | | Element selector |
993
- | `content.title` | string | No | | Tooltip heading |
994
- | `content.body` | string | Yes | | Tooltip text |
995
- | `content.cta.label` | string | No | | CTA button text (single-CTA shorthand) |
996
- | `content.cta.action` | Action | No | | Action to execute on CTA click |
997
- | `content.ctaButtons` | array | No | | Multiple CTA buttons (see below) |
998
- | `trigger` | string | No | `"immediate"` | `"immediate"`, `"hover"`, `"click"` |
999
- | `placement` | string | No | `"top"` | See placement options below |
1000
-
1001
- **Placement options:** `top`, `top-start`, `top-end`, `bottom`, `bottom-start`, `bottom-end`, `left`, `left-start`, `left-end`, `right`, `right-start`, `right-end`
1002
-
1003
- **Single CTA (shorthand):**
1004
-
1005
- ```json
1006
- {
1007
- "kind": "overlays:tooltip",
1008
- "anchorId": "#pricing-toggle",
1009
- "content": {
1010
- "title": "Save 20%",
1011
- "body": "Switch to annual billing to save on your subscription.",
1012
- "cta": {
1013
- "label": "Switch Now",
1014
- "action": { "kind": "navigation:navigate", "url": "/billing?annual=true" }
1015
- }
1016
- },
1017
- "placement": "bottom",
1018
- "trigger": "immediate"
1019
- }
1020
- ```
1021
-
1022
- **Multiple CTAs (`ctaButtons`):**
1023
-
1024
- Use `ctaButtons` for tooltips with multiple actions. Each button has:
1025
-
1026
- - `label`: Button text
1027
- - `actionId`: Identifier published with the `action.tooltip_cta_clicked` event
1028
- - `primary`: Whether this is a primary button (default: false)
1029
-
1030
- ```json
1031
- {
1032
- "kind": "overlays:tooltip",
1033
- "anchorId": "[data-nav=\"features\"]",
1034
- "content": {
1035
- "title": "Learn About Features",
1036
- "body": "We have answers about our feature set.",
1037
- "ctaButtons": [
1038
- { "label": "View Answer", "actionId": "faq:open:q-features", "primary": true },
1039
- { "label": "Dismiss", "actionId": "dismiss" }
1040
- ]
1041
- },
1042
- "trigger": "immediate",
1043
- "placement": "bottom"
1044
- }
1045
- ```
1046
-
1047
- **Special actionId values:**
1048
-
1049
- - `"dismiss"` — Destroys the tooltip immediately without publishing an event
1050
- - `"faq:open:<questionId>"` — Convention for companion FAQ tooltips (see adaptive-faq CAPABILITIES for details). Publishes `action.tooltip_cta_clicked` which the FAQ widget listens for.
1051
- - Any other value — Publishes `action.tooltip_cta_clicked` with the `actionId` in event props for custom handling
1052
-
1053
- ### overlays:badge
1054
-
1055
- Adds a small badge indicator near an element.
1056
-
1057
- | Property | Type | Required | Default | Description |
1058
- | ---------- | --------- | -------- | ------------- | -------------------------------------------------------------- |
1059
- | `kind` | `"overlays:badge"` | Yes | | Action type |
1060
- | `anchorId` | string | Yes | | Element selector |
1061
- | `text` | string | Yes | | Badge text (e.g., "NEW", "3") |
1062
- | `position` | string | No | `"top-right"` | `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"` |
1063
-
1064
- ```json
1065
- {
1066
- "kind": "overlays:badge",
1067
- "anchorId": "#inbox-icon",
1068
- "text": "5",
1069
- "position": "top-right"
1070
- }
1071
- ```
1072
-
1073
- ### overlays:pulse
1074
-
1075
- Adds a pulsing animation to draw attention.
1076
-
1077
- | Property | Type | Required | Default | Description |
1078
- | ---------- | --------- | -------- | ------- | ------------------------ |
1079
- | `kind` | `"overlays:pulse"` | Yes | | Action type |
1080
- | `anchorId` | string | Yes | | Element selector |
1081
- | `duration` | number | No | `2000` | Animation duration in ms |
1082
-
1083
- ```json
1084
- {
1085
- "kind": "overlays:pulse",
1086
- "anchorId": ".notification-bell",
1087
- "duration": 3000
1088
- }
1089
- ```
1090
-
1091
- ### overlays:modal
1092
-
1093
- Shows a centered modal dialog with optional CTA buttons.
1094
-
1095
- | Property | Type | Required | Default | Description |
1096
- | --------------------- | ------------------ | -------- | ------- | ------------------------------------------------------------- |
1097
- | `kind` | `"overlays:modal"` | Yes | | Action type |
1098
- | `content.title` | string | No | | Modal heading |
1099
- | `content.body` | string | Yes | | Modal text |
1100
- | `size` | string | No | `"md"` | `"sm"`, `"md"`, `"lg"` |
1101
- | `blocking` | boolean | No | `false` | Block page interaction |
1102
- | `scrim.opacity` | number | No | `0.6` | Backdrop opacity 0-1 |
1103
- | `dismiss.onEsc` | boolean | No | `true` | Close on Escape key |
1104
- | `dismiss.closeButton` | boolean | No | `true` | Show close button |
1105
- | `dismiss.timeoutMs` | number | No | | Auto-close after timeout |
1106
- | `ctaButtons` | array | No | | Array of CTA buttons |
1107
- | `waitFor` | string | No | | When to complete: `"dismissed"`, `"cta-click"`, `"timeout:N"` |
1108
-
1109
- **CTA Button properties:**
1110
-
1111
- - `label`: Button text
1112
- - `actionId`: Identifier for the action
1113
- - `primary`: Whether this is a primary button (default: false)
1114
-
1115
- ```json
1116
- {
1117
- "kind": "overlays:modal",
1118
- "content": {
1119
- "title": "Welcome!",
1120
- "body": "Thanks for signing up. Let us show you around."
1121
- },
1122
- "size": "md",
1123
- "ctaButtons": [
1124
- { "label": "Skip", "actionId": "skip" },
1125
- { "label": "Start Tour", "actionId": "start", "primary": true }
1126
- ],
1127
- "waitFor": "cta-click"
1128
- }
1129
- ```
1130
-
1131
- ### overlays:celebrate
1132
-
1133
- Renders a fullscreen Canvas 2D celebration effect. One action kind with a pluggable `effect` parameter supporting multiple visual presets.
1134
-
1135
- | Property | Type | Required | Default | Description |
1136
- | ----------- | ---------------------------------- | -------- | -------------------------- | --------------------------------------------- |
1137
- | `kind` | `"overlays:celebrate"` | Yes | | Action type |
1138
- | `effect` | string | Yes | | Effect name (see presets below) |
1139
- | `duration` | number | No | `3000` | Animation duration in ms |
1140
- | `intensity` | `"light"` \| `"medium"` \| `"heavy"` | No | `"medium"` | Particle density |
1141
- | `colors` | string[] | No | Rainbow palette | Color palette for particles |
1142
- | `props` | object | No | | Effect-specific properties (see presets below) |
1143
-
1144
- **Available effect presets:**
1145
-
1146
- - **`confetti`** — Falling rectangular/circular confetti pieces with gravity, air resistance, and rotation
1147
- - **`fireworks`** — Multiple burst centers with radiating particles, deceleration, and glow effect
1148
- - **`sparkles`** — Diamond shapes that twinkle via sine-wave opacity oscillation and float gently upward
1149
- - **`emoji-rain`** — Emoji characters falling from the top with horizontal sine-wave wobble. Use `props.emoji` to set the character (default: `"🎉"`)
1150
-
1151
- **Confetti:**
1152
-
1153
- ```json
1154
- {
1155
- "kind": "overlays:celebrate",
1156
- "effect": "confetti",
1157
- "duration": 4000,
1158
- "intensity": "heavy",
1159
- "colors": ["#ff0000", "#00ff00", "#0000ff", "#ffff00"]
1160
- }
1161
- ```
1162
-
1163
- **Fireworks:**
1164
-
1165
- ```json
1166
- {
1167
- "kind": "overlays:celebrate",
1168
- "effect": "fireworks",
1169
- "duration": 3000,
1170
- "intensity": "medium"
1171
- }
1172
- ```
1173
-
1174
- **Sparkles:**
1175
-
1176
- ```json
1177
- {
1178
- "kind": "overlays:celebrate",
1179
- "effect": "sparkles",
1180
- "duration": 5000,
1181
- "colors": ["#ffd700", "#ffffff", "#fffacd"]
1182
- }
1183
- ```
1184
-
1185
- **Emoji rain:**
1186
-
1187
- ```json
1188
- {
1189
- "kind": "overlays:celebrate",
1190
- "effect": "emoji-rain",
1191
- "duration": 3000,
1192
- "props": { "emoji": "🔥" }
1193
- }
1194
- ```
1195
-
1196
-
1197
- ---
1198
-
1199
- # @syntrologie/adapt-viz
1200
-
1201
- Charts and tables for visualizing data — bar, line, and tabular layouts.
1202
-
1203
- ## Identity
1204
-
1205
- - **App ID:** `adaptive-viz`
1206
- - **Bundling:** CDN-only (lazy-loaded; not in the core SDK bundle)
1207
- - **Loader:** `cdn.ts` registers `<syntro-viz-chart>` and exports the widget mountable
1208
-
1209
- ## Tile Widgets
1210
-
1211
- | Widget ID | Purpose |
1212
- |-----------|---------|
1213
- | `adaptive-viz:chart` | Renders a chart given typed props (bar / line / table) |
1214
-
1215
- ## Configuration Shape (per tile)
1216
-
1217
- ```jsonc
1218
- {
1219
- "id": "tile-id",
1220
- "widget": "adaptive-viz:chart",
1221
- "props": {
1222
- "layout": "bar", // or "line", "table"
1223
- // ...layout-specific fields (see schema.ts)
1224
- }
1225
- }
1226
- ```
1227
-
1228
- ## Layouts
1229
-
1230
- ### `bar`
1231
- Single bar chart. Inputs: `data: { [key: string]: unknown }[]`, `xField`, `yField`, optional `colorField`.
1232
-
1233
- ### `line`
1234
- Continuous line chart. Inputs: `data`, `xField`, `yField`, optional `seriesField`.
1235
-
1236
- ### `table`
1237
- Tabular data display (rendered as Vega-Lite text marks in a grid). Inputs: `data`, `columns: { field, header }[]`.
1238
-
1239
- ## Actions
1240
-
1241
- None. This widget renders only; it does not emit DOM-mutation actions.
1242
-
1243
- ## Theme Tokens Consumed
1244
-
1245
- `--sc-color-primary`, `--sc-color-primary-hover`, `--sc-overlay-text-color`, `--sc-font-family`, `--sc-tile-background`. See `theme.ts` for the full mapping to Vega-Lite `config` properties.
1246
-
1247
-
1248
- ---
1249
-
1250
-
1251
- ---
1252
-
1253
- ## Surfaces
1254
-
1255
- Surfaces are named slots where widgets can be mounted. Use the `mount_widget` action to render content into a surface slot.
1256
-
1257
- ### Static Slots
1258
-
1259
- Fixed-position slots for common UI patterns:
1260
-
1261
- | Slot | Position | Use Case |
1262
- |------|----------|----------|
1263
- | `drawer_right` | Right edge, full height | Settings panels, details |
1264
- | `drawer_left` | Left edge, full height | Navigation, filters |
1265
- | `drawer_bottom` | Bottom edge, full width | Action sheets, keyboards |
1266
- | `overlay_center` | Centered modal | Dialogs, confirmations |
1267
- | `overlay_corner_br` | Bottom-right corner | Chat widgets, help |
1268
- | `overlay_corner_bl` | Bottom-left corner | Notifications |
1269
- | `toast_top` | Top center | Success/error messages |
1270
- | `toast_bottom` | Bottom center | Snackbar notifications |
1271
-
1272
- ### Dynamic Slots
1273
-
1274
- Slots that position content relative to anchors:
1275
-
1276
- - **Inline Slots**: Render content inside an anchor element. Format: `inline:{anchorId}`
1277
- - **Adjacent Slots**: Render content positioned near an anchor element. Format: `adjacent:{anchorId}`
1278
-
1279
- ---
1280
-
1281
- ## Anchor Resolution
1282
-
1283
- The `anchorId` field identifies which DOM element to target. Multiple formats are supported:
1284
-
1285
- ### CSS Selectors
1286
-
1287
- | Format | Example | Matches |
1288
- |--------|---------|---------|
1289
- | Class | `".hero-title"` | `<h1 class="hero-title">` |
1290
- | ID | `"#signup-button"` | `<button id="signup-button">` |
1291
- | Attribute | `"[data-testid='cta']"` | `<button data-testid="cta">` |
1292
- | Tag + class | `"h1.page-title"` | `<h1 class="page-title">` |
1293
- | Nested | `".card .title"` | `<div class="title">` inside `.card` |
1294
-
1295
- ### Data Attributes (Recommended)
1296
-
1297
- For stability, add `data-syntro-anchor` attributes to target elements:
1298
-
1299
- ```html
1300
- <h1 data-syntro-anchor="hero-title">Welcome</h1>
1301
- ```
1302
-
1303
- Then reference by the anchor name:
1304
-
1305
- ```json
1306
- { "anchorId": "hero-title" }
1307
- ```
1308
-
1309
- ---
1310
-
1311
- ## Route Scoping
1312
-
1313
- Every tile and action **must** declare which pages it applies to via `activation.routes`. The config itself is always active — individual tiles and actions decide independently whether to render.
1314
-
1315
- ### Per-Tile Activation
1316
-
1317
- ```json
1318
- {
1319
- "id": "faq-w2-boxes",
1320
- "widget": "adaptive-faq:accordion",
1321
- "activation": {
1322
- "routes": { "include": ["/dashboard/income"] }
1323
- },
1324
- "props": { ... }
1325
- }
1326
- ```
1327
-
1328
- ### Per-Action Activation
1329
-
1330
- ```json
1331
- {
1332
- "kind": "content:insertHtml",
1333
- "anchorId": "[data-section='refund-estimate']",
1334
- "activation": {
1335
- "routes": { "include": ["/dashboard"] }
1336
- },
1337
- "html": "...",
1338
- "position": "after"
1339
- }
1340
- ```
1341
-
1342
- ### Route Patterns
1343
-
1344
- | Pattern | Matches | Use Case |
1345
- |---------|---------|----------|
1346
- | `"/dashboard/income"` | Exact path only | Page-specific |
1347
- | `"/dashboard/**"` | `/dashboard` and all sub-paths | Section-wide |
1348
- | `"/**"` | All pages | Global (always active) |
1349
- | `"/dashboard/:id"` | Any single segment after `/dashboard/` | Dynamic routes |
1350
-
1351
- ### Pattern Matching Rules
1352
-
1353
- - Patterns match against **pathname only** (no domain, no query string)
1354
- - `**` matches anything (including `/`)
1355
- - `*` matches within a single path segment (not `/`)
1356
- - `exclude` takes priority over `include`
1357
-
1358
- ---
1359
-
1360
- ## Decision Strategies
1361
-
1362
- Control when adaptives activate using `DecisionStrategy`:
1363
-
1364
- ### Rules Strategy
1365
-
1366
- ```json
1367
- {
1368
- "type": "rules",
1369
- "rules": [
1370
- {
1371
- "conditions": [
1372
- { "type": "page_url", "pattern": "/pricing*" },
1373
- { "type": "viewport", "minWidth": 768 },
1374
- { "type": "dismissed", "key": "promo", "inverted": true }
1375
- ],
1376
- "value": true
1377
- }
1378
- ],
1379
- "default": false
1380
- }
1381
- ```
1382
-
1383
- ### Condition Types
1384
-
1385
- | Type | Parameters | Description |
1386
- |------|------------|-------------|
1387
- | `page_url` | `pattern` | URL matches glob pattern |
1388
- | `route` | `routeId` | Route ID matches |
1389
- | `anchor_visible` | `anchorId`, `state` | Anchor visibility |
1390
- | `event_occurred` | `eventName`, `withinMs?` | Recent event |
1391
- | `state_equals` | `key`, `value` | State matches |
1392
- | `viewport` | `minWidth?`, `maxWidth?`, `minHeight?`, `maxHeight?` | Viewport size |
1393
- | `session_metric` | `key`, `operator`, `threshold` | Metric comparison |
1394
- | `dismissed` | `key`, `inverted?` | Dismissal check |
1395
- | `cooldown_active` | `key`, `inverted?` | Cooldown check |
1396
- | `frequency_limit` | `key`, `limit`, `inverted?` | Frequency cap |
1397
-
1398
- ---
1399
-
1400
- ## Best Practices
1401
-
1402
- ### 1. Choose the Right Action Type
1403
-
1404
- Each adaptive package has a "When to use" guide at the top of its capabilities section above. Refer to those for action selection guidance.
1405
-
1406
- ### 2. Anchor Selection
1407
-
1408
- - **Prefer stable selectors:** `[data-testid]`, IDs over classes
1409
- - **Test across states:** Element may not exist on all pages
1410
- - **Be specific:** Avoid selectors matching multiple elements
1411
-
1412
- ### 3. Reversibility
1413
-
1414
- - Always store handles if you need to revert
1415
- - Use `applyBatch` for related changes (atomic rollback)
1416
- - Clean up on route changes
1417
-
1418
- ### 4. Performance
1419
-
1420
- - Batch related actions together
1421
- - Use `validate()` to check actions before applying
1422
- - Avoid applying many actions simultaneously
1423
-
1424
- ### 5. Events
1425
-
1426
- - Listen for `action.failed` to handle errors
1427
- - Track `action.applied` for analytics
1428
- - Use EventBus for cross-adaptive coordination
1429
-
1430
- ### 6. Opening the Canvas from Inserted Content
1431
-
1432
- When `content:insertHtml` needs to open the canvas panel (e.g., a "Help Me Choose" button that opens an FAQ tile), use the `deepLink` property — **never write `onclick` handlers or reference `window.SynOS` in HTML**.
1433
-
1434
- ```json
1435
- {
1436
- "kind": "content:insertHtml",
1437
- "anchorId": { "selector": "[data-id='pricing-heading']", "route": "/" },
1438
- "html": "<button style='background: #4a90e2; color: white; border: none; padding: 8px 16px; border-radius: 20px; cursor: pointer;'>Help Me Choose</button>",
1439
- "position": "after",
1440
- "deepLink": { "tileId": "plan_selector_faq" }
1441
- }
1442
- ```
1443
-
1444
- The `deepLink` object:
1445
- - `tileId` (required) — ID of the canvas tile to navigate to
1446
- - `itemId` (optional) — specific item within the tile (e.g., a FAQ question ID)
1447
-
1448
- The SDK automatically opens the canvas, navigates to the tile, sets cursor to pointer, and wires up click/cleanup handlers.
1449
-
1450
- **NEVER use `onclick`, `window.SynOS`, or any JavaScript in `content:insertHtml` HTML strings.** The HTML is sanitized and event handlers are stripped. Use `deepLink` instead.