@syntrologie/runtime-sdk 1.0.1-canary.3 → 2.0.0-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CAPABILITIES.md +630 -463
  2. package/README.md +285 -62
  3. package/dist/RuntimeProvider.d.ts +51 -0
  4. package/dist/RuntimeProvider.js +114 -0
  5. package/dist/RuntimeProvider.js.map +1 -0
  6. package/dist/SmartCanvasApp.d.ts +9 -3
  7. package/dist/SmartCanvasApp.js +36 -38
  8. package/dist/SmartCanvasApp.js.map +1 -1
  9. package/dist/actions/ActionEngine.d.ts +11 -0
  10. package/dist/actions/ActionEngine.js +274 -0
  11. package/dist/actions/ActionEngine.js.map +1 -0
  12. package/dist/actions/executors/index.d.ts +118 -0
  13. package/dist/actions/executors/index.js +242 -0
  14. package/dist/actions/executors/index.js.map +1 -0
  15. package/dist/actions/executors/tour.d.ts +18 -0
  16. package/dist/actions/executors/tour.js +332 -0
  17. package/dist/actions/executors/tour.js.map +1 -0
  18. package/dist/actions/index.d.ts +10 -0
  19. package/dist/actions/index.js +12 -0
  20. package/dist/actions/index.js.map +1 -0
  21. package/dist/actions/types.d.ts +399 -0
  22. package/dist/actions/types.js +8 -0
  23. package/dist/actions/types.js.map +1 -0
  24. package/dist/actions/validation.d.ts +14 -0
  25. package/dist/actions/validation.js +603 -0
  26. package/dist/actions/validation.js.map +1 -0
  27. package/dist/api.d.ts +32 -18
  28. package/dist/api.js +56 -39
  29. package/dist/api.js.map +1 -1
  30. package/dist/apps/AppContext.d.ts +31 -0
  31. package/dist/apps/AppContext.js +93 -0
  32. package/dist/apps/AppContext.js.map +1 -0
  33. package/dist/apps/AppLoader.d.ts +84 -0
  34. package/dist/apps/AppLoader.js +250 -0
  35. package/dist/apps/AppLoader.js.map +1 -0
  36. package/dist/apps/AppRegistry.d.ts +102 -0
  37. package/dist/apps/AppRegistry.js +317 -0
  38. package/dist/apps/AppRegistry.js.map +1 -0
  39. package/dist/apps/examples/gamification-app.example.d.ts +305 -0
  40. package/dist/apps/examples/gamification-app.example.js +329 -0
  41. package/dist/apps/examples/gamification-app.example.js.map +1 -0
  42. package/dist/apps/index.d.ts +18 -0
  43. package/dist/apps/index.js +26 -0
  44. package/dist/apps/index.js.map +1 -0
  45. package/dist/apps/types.d.ts +231 -0
  46. package/dist/apps/types.js +8 -0
  47. package/dist/apps/types.js.map +1 -0
  48. package/dist/bootstrap.d.ts +24 -0
  49. package/dist/bootstrap.js +133 -33
  50. package/dist/bootstrap.js.map +1 -1
  51. package/dist/components/ShadowCanvasOverlay.js +36 -9
  52. package/dist/components/ShadowCanvasOverlay.js.map +1 -1
  53. package/dist/components/TileCard.js +37 -18
  54. package/dist/components/TileCard.js.map +1 -1
  55. package/dist/context/schema.d.ts +16 -16
  56. package/dist/decisions/schema.d.ts +96 -96
  57. package/dist/earlyPatcher.d.ts +8 -20
  58. package/dist/earlyPatcher.js +13 -62
  59. package/dist/earlyPatcher.js.map +1 -1
  60. package/dist/editorLoader.d.ts +2 -0
  61. package/dist/editorLoader.js +46 -7
  62. package/dist/editorLoader.js.map +1 -1
  63. package/dist/events/normalizers/posthog.d.ts +24 -0
  64. package/dist/events/normalizers/posthog.js.map +1 -1
  65. package/dist/events/schema.d.ts +8 -8
  66. package/dist/events/types.d.ts +6 -0
  67. package/dist/events/types.js +8 -0
  68. package/dist/events/types.js.map +1 -1
  69. package/dist/hooks/useCanvasOverlays.d.ts +4 -1
  70. package/dist/hooks/useCanvasOverlays.js +53 -6
  71. package/dist/hooks/useCanvasOverlays.js.map +1 -1
  72. package/dist/hooks/useShadowCanvasConfig.d.ts +3 -7
  73. package/dist/hooks/useShadowCanvasConfig.js +2 -3
  74. package/dist/hooks/useShadowCanvasConfig.js.map +1 -1
  75. package/dist/index.d.ts +5 -0
  76. package/dist/index.js +10 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/overlays/schema.d.ts +153 -153
  79. package/dist/runtime.d.ts +24 -0
  80. package/dist/runtime.js +75 -1
  81. package/dist/runtime.js.map +1 -1
  82. package/dist/smart-canvas.esm.js +162 -55
  83. package/dist/smart-canvas.esm.js.map +4 -4
  84. package/dist/smart-canvas.js +21133 -17957
  85. package/dist/smart-canvas.js.map +4 -4
  86. package/dist/smart-canvas.min.js +162 -55
  87. package/dist/smart-canvas.min.js.map +4 -4
  88. package/dist/store/example.d.ts +1 -0
  89. package/dist/store/example.js +43 -0
  90. package/dist/store/example.js.map +1 -0
  91. package/dist/store/mini-effector.d.ts +46 -0
  92. package/dist/store/mini-effector.js +90 -0
  93. package/dist/store/mini-effector.js.map +1 -0
  94. package/dist/surfaces/Surfaces.d.ts +11 -0
  95. package/dist/surfaces/Surfaces.js +361 -0
  96. package/dist/surfaces/Surfaces.js.map +1 -0
  97. package/dist/surfaces/index.d.ts +9 -0
  98. package/dist/surfaces/index.js +12 -0
  99. package/dist/surfaces/index.js.map +1 -0
  100. package/dist/surfaces/positioning.d.ts +50 -0
  101. package/dist/surfaces/positioning.js +231 -0
  102. package/dist/surfaces/positioning.js.map +1 -0
  103. package/dist/surfaces/types.d.ts +167 -0
  104. package/dist/surfaces/types.js +23 -0
  105. package/dist/surfaces/types.js.map +1 -0
  106. package/dist/telemetry/adapters/posthog.d.ts +6 -0
  107. package/dist/telemetry/adapters/posthog.js +9 -0
  108. package/dist/telemetry/adapters/posthog.js.map +1 -1
  109. package/dist/types-only.d.ts +32 -0
  110. package/dist/types-only.js +11 -0
  111. package/dist/types-only.js.map +1 -0
  112. package/dist/types.d.ts +26 -14
  113. package/dist/types.js +1 -1
  114. package/dist/types.js.map +1 -1
  115. package/dist/widgets/WidgetRegistry.d.ts +139 -0
  116. package/dist/widgets/WidgetRegistry.js +182 -0
  117. package/dist/widgets/WidgetRegistry.js.map +1 -0
  118. package/dist/widgets/index.d.ts +7 -0
  119. package/dist/widgets/index.js +7 -0
  120. package/dist/widgets/index.js.map +1 -0
  121. package/package.json +13 -3
  122. package/schema/canvas-config.schema.json +444 -254
package/CAPABILITIES.md CHANGED
@@ -1,611 +1,778 @@
1
1
  # SmartCanvas SDK Capabilities
2
2
 
3
- This document describes all available operations and capabilities of the SmartCanvas SDK for DOM manipulation and enhancement.
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.
4
6
 
5
7
  ## Table of Contents
6
8
  - [Overview](#overview)
7
- - [Policy Tiers](#policy-tiers)
8
- - [Anchor Selectors](#anchor-selectors)
9
- - [Operations](#operations)
10
- - [Overlays](#overlays)
11
- - [Runtime v2 Providers](#runtime-v2-providers)
9
+ - [Quick Start Example](#quick-start-example)
10
+ - [Adaptive Packages](#adaptive-packages)
11
+ - [Surfaces](#surfaces)
12
+ - [Anchor Resolution](#anchor-resolution)
13
+ - [Decision Strategies](#decision-strategies)
12
14
  - [Best Practices](#best-practices)
13
15
 
16
+ ---
17
+
14
18
  ## Overview
15
19
 
16
- The SmartCanvas SDK allows you to safely modify web pages through a controlled patching system. All modifications are applied through "patches" that contain:
17
- - An anchor selector to find the target element
18
- - One or more operations to apply
19
- - A policy tier that controls what modifications are allowed
20
+ The SmartCanvas SDK provides three main systems for modifying web pages:
21
+
22
+ 1. **ActionEngine** - Unified execution layer for interventions (highlight, tooltip, badge, DOM modifications)
23
+ 2. **Surfaces** - Managed surface system for rendering UI into named slots
24
+ 3. **Runtime** - Context, events, state, and decision management
25
+
26
+ All modifications are reversible and publish events to the EventBus for tracking.
27
+
28
+ ---
29
+
30
+ ## Quick Start Example
31
+
32
+ ### Simple Header Text Change
33
+
34
+ Change a page header when the element is visible:
35
+
36
+ ```json
37
+ {
38
+ "id": "welcome-header-change",
39
+ "activation": {
40
+ "routes": { "include": ["/", "/home"] },
41
+ "strategy": {
42
+ "type": "rules",
43
+ "rules": [
44
+ {
45
+ "conditions": [
46
+ { "type": "anchor_visible", "anchorId": "h1.hero-title", "state": "visible" }
47
+ ],
48
+ "value": true
49
+ }
50
+ ],
51
+ "default": false
52
+ }
53
+ },
54
+ "actions": [
55
+ {
56
+ "kind": "set_text",
57
+ "anchorId": "h1.hero-title",
58
+ "text": "Welcome to Our New Experience"
59
+ }
60
+ ]
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Adaptive Packages
20
67
 
21
- ## Policy Tiers
68
+ The SDK includes the following adaptive packages, each providing specific capabilities:
22
69
 
23
- The SDK uses three policy tiers to control what operations are allowed:
70
+ - [@syntrologie/app-content](#syntrologieapp-content)
71
+ - [@syntrologie/app-faq](#syntrologieapp-faq)
72
+ - [@syntrologie/app-gamification](#syntrologieapp-gamification)
73
+ - [@syntrologie/app-nav](#syntrologieapp-nav)
74
+ - [@syntrologie/app-navigation](#syntrologieapp-navigation)
75
+ - [@syntrologie/app-overlays](#syntrologieapp-overlays)
24
76
 
25
- ### 1. **additive** (default)
26
- - **Purpose**: Safest tier - only add new elements and classes
27
- - **Allowed**: Adding HTML, adding prefixed classes, safe styles
28
- - **Not Allowed**: Modifying existing text or attributes
29
- - **Use When**: Adding badges, tooltips, or decorative elements
77
+ ---
30
78
 
31
- ### 2. **moderate**
32
- - **Purpose**: Allows limited modifications to existing elements
33
- - **Allowed**: Everything from additive + title attribute
34
- - **Not Allowed**: Text changes, color changes
35
- - **Use When**: Adding accessibility improvements or metadata
79
+ # @syntrologie/app-content
36
80
 
37
- ### 3. **surgical**
38
- - **Purpose**: Full control over content modification
39
- - **Allowed**: Everything including setText, background/text colors
40
- - **Required For**: Changing headlines, CTAs, or any text content
41
- - **Use When**: A/B testing copy variations or personalizing content
81
+ DOM content modification capabilities for text, attributes, styles, HTML, and classes.
42
82
 
43
- ## Anchor Selectors
83
+ ## Actions
44
84
 
45
- Anchors define how to find elements in the DOM. The SDK supports four types:
85
+ ### set_text
86
+ Replaces the text content of an element.
87
+
88
+ | Property | Type | Required | Description |
89
+ |----------|------|----------|-------------|
90
+ | `kind` | `"set_text"` | Yes | Action type |
91
+ | `anchorId` | string | Yes | Element selector |
92
+ | `text` | string | Yes | New text content |
46
93
 
47
- ### 1. CSS Selector
48
94
  ```json
49
95
  {
50
- "by": "css",
51
- "value": "h1.hero-title"
96
+ "kind": "set_text",
97
+ "anchorId": "h1.hero-title",
98
+ "text": "Start Your Free Trial Today"
52
99
  }
53
100
  ```
54
- - **Use**: Standard CSS selectors
55
- - **Example**: `"h1"`, `".button-primary"`, `"#hero-section h2"`
56
101
 
57
- ### 2. Data Attribute
102
+ ### set_attr
103
+ Sets an HTML attribute on an element.
104
+
105
+ | Property | Type | Required | Description |
106
+ |----------|------|----------|-------------|
107
+ | `kind` | `"set_attr"` | Yes | Action type |
108
+ | `anchorId` | string | Yes | Element selector |
109
+ | `attr` | string | Yes | Attribute name |
110
+ | `value` | string | Yes | Attribute value |
111
+
112
+ **Blocked attributes:** Event handlers (`onclick`, `onerror`, etc.) are not allowed.
113
+
58
114
  ```json
59
115
  {
60
- "by": "data",
61
- "key": "testid",
62
- "value": "hero-banner" // optional
116
+ "kind": "set_attr",
117
+ "anchorId": "#signup-form",
118
+ "attr": "data-experiment",
119
+ "value": "signup-v2"
63
120
  }
64
121
  ```
65
- - **Use**: Target elements by data attributes
66
- - **Example**: Finds `<div data-testid="hero-banner">`
67
- - **Note**: If value is omitted, matches any element with that data attribute
68
122
 
69
- ### 3. ARIA Selector
123
+ ### set_style
124
+ Sets inline CSS styles on an element.
125
+
126
+ | Property | Type | Required | Description |
127
+ |----------|------|----------|-------------|
128
+ | `kind` | `"set_style"` | Yes | Action type |
129
+ | `anchorId` | string | Yes | Element selector |
130
+ | `styles` | object | Yes | CSS property/value pairs |
131
+
70
132
  ```json
71
133
  {
72
- "by": "aria",
73
- "role": "button", // optional
74
- "label": "Submit" // optional
134
+ "kind": "set_style",
135
+ "anchorId": ".hero-section",
136
+ "styles": {
137
+ "background-color": "#1e40af",
138
+ "padding": "2rem"
139
+ }
75
140
  }
76
141
  ```
77
- - **Use**: Target accessible elements
78
- - **Example**: Finds `<button role="button" aria-label="Submit">`
79
- - **Note**: Can use role, label, or both
80
142
 
81
- ### 4. Element Reference
143
+ ### insert_html
144
+ Inserts HTML content relative to an element.
145
+
146
+ | Property | Type | Required | Description |
147
+ |----------|------|----------|-------------|
148
+ | `kind` | `"insert_html"` | Yes | Action type |
149
+ | `anchorId` | string | Yes | Element selector |
150
+ | `html` | string | Yes | HTML content (sanitized) |
151
+ | `position` | string | Yes | `"before"`, `"after"`, `"prepend"`, `"append"`, `"replace"` |
152
+
153
+ **Positions:**
154
+ - `before` - Insert before the element
155
+ - `after` - Insert after the element
156
+ - `prepend` - Insert inside, before first child
157
+ - `append` - Insert inside, after last child
158
+ - `replace` - Replace the entire element
159
+
82
160
  ```json
83
161
  {
84
- "by": "ref",
85
- "el": elementReference
162
+ "kind": "insert_html",
163
+ "anchorId": ".cta-button",
164
+ "html": "<span class=\"badge\">NEW</span>",
165
+ "position": "append"
86
166
  }
87
167
  ```
88
- - **Use**: Direct element reference (programmatic use only)
89
- - **Note**: Cannot be serialized in JSON configs
90
168
 
91
- ## Operations
169
+ ### add_class
170
+ Adds a CSS class to an element.
171
+
172
+ | Property | Type | Required | Description |
173
+ |----------|------|----------|-------------|
174
+ | `kind` | `"add_class"` | Yes | Action type |
175
+ | `anchorId` | string | Yes | Element selector |
176
+ | `className` | string | Yes | Class name to add |
92
177
 
93
- ### 1. setText
94
- **Purpose**: Replace the text content of an element
95
178
  ```json
96
179
  {
97
- "kind": "setText",
98
- "text": "New headline text"
180
+ "kind": "add_class",
181
+ "anchorId": ".pricing-card",
182
+ "className": "highlighted"
99
183
  }
100
184
  ```
101
- - **Requires**: `tier: "surgical"`
102
- - **Effect**: Replaces all text content in the element
103
- - **Common Use**: A/B testing headlines, CTAs, product descriptions
104
- - **Note**: Removes all child elements, use carefully
105
185
 
106
- ### 2. setStyle
107
- **Purpose**: Modify CSS styles on an element
186
+ ### remove_class
187
+ Removes a CSS class from an element.
188
+
189
+ | Property | Type | Required | Description |
190
+ |----------|------|----------|-------------|
191
+ | `kind` | `"remove_class"` | Yes | Action type |
192
+ | `anchorId` | string | Yes | Element selector |
193
+ | `className` | string | Yes | Class name to remove |
194
+
108
195
  ```json
109
196
  {
110
- "kind": "setStyle",
111
- "prop": "backgroundColor",
112
- "value": "#ff0000",
113
- "important": true // optional, adds !important
197
+ "kind": "remove_class",
198
+ "anchorId": ".pricing-card",
199
+ "className": "hidden"
114
200
  }
115
201
  ```
116
- - **Allowed Properties (any tier)**:
117
- - `outline`, `outlineColor`, `outlineWidth`, `outlineStyle`
118
- - `boxShadow`, `filter`
119
- - `scrollMargin` and related properties
120
- - CSS variables (`--custom-var`)
121
- - **Additional Properties (surgical tier)**:
122
- - `backgroundColor`, `color`
123
- - **Common Use**: Highlighting elements, adding visual emphasis
124
- - **Note**: Value can be `null` to remove a style
125
-
126
- ### 3. addClass
127
- **Purpose**: Add a CSS class to an element
202
+
203
+
204
+ ---
205
+
206
+ # @syntrologie/app-faq
207
+
208
+ FAQ accordion widget with conditional item visibility.
209
+
210
+ ## Actions
211
+
212
+ ### mount_faq
213
+ Mounts an FAQ accordion widget to a surface slot.
214
+
215
+ | Property | Type | Required | Description |
216
+ |----------|------|----------|-------------|
217
+ | `kind` | `"mount_faq"` | Yes | Action type |
218
+ | `slot` | string | Yes | Target slot (e.g., `"drawer_right"`, `"overlay_center"`) |
219
+ | `config.title` | string | No | Widget title |
220
+ | `config.items` | array | Yes | FAQ items (see below) |
221
+
222
+ ### FAQ Item Schema
223
+
224
+ Each item in the `items` array:
225
+
226
+ | Property | Type | Required | Description |
227
+ |----------|------|----------|-------------|
228
+ | `question` | string | Yes | The question text |
229
+ | `answer` | string | Yes | The answer text (supports markdown) |
230
+ | `showWhen` | DecisionStrategy | No | Conditional visibility strategy |
231
+
128
232
  ```json
129
233
  {
130
- "kind": "addClass",
131
- "className": "syntro-highlight"
234
+ "kind": "mount_faq",
235
+ "slot": "drawer_right",
236
+ "config": {
237
+ "title": "Frequently Asked Questions",
238
+ "items": [
239
+ {
240
+ "question": "How do I get started?",
241
+ "answer": "Sign up for a free account and follow our quickstart guide."
242
+ },
243
+ {
244
+ "question": "What payment methods do you accept?",
245
+ "answer": "We accept all major credit cards and PayPal.",
246
+ "showWhen": {
247
+ "type": "rules",
248
+ "rules": [
249
+ {
250
+ "conditions": [
251
+ { "type": "page_url", "pattern": "/pricing*" }
252
+ ],
253
+ "value": true
254
+ }
255
+ ],
256
+ "default": false
257
+ }
258
+ }
259
+ ]
260
+ }
132
261
  }
133
262
  ```
134
- - **Requirement**: Class must start with `syntro-`, `sc-`, or `sx-`
135
- - **Common Use**: Apply pre-defined styles, mark elements
136
- - **Note**: Won't add duplicate classes
137
263
 
138
- ### 4. removeClass
139
- **Purpose**: Remove a CSS class from an element
264
+ ## Compositional Pattern
265
+
266
+ The FAQ widget supports **per-item conditional visibility** using `showWhen` strategies. This allows different FAQ items to appear based on:
267
+ - Current page/route
268
+ - User segment or state
269
+ - Viewport conditions
270
+ - Any other DecisionStrategy condition
271
+
272
+ Items without `showWhen` are always visible.
273
+
274
+
275
+ ---
276
+
277
+ # @syntrologie/app-gamification
278
+
279
+ Gamification capabilities including badges, points, and leaderboards.
280
+
281
+ ## Actions
282
+
283
+ ### mount_gamification
284
+ Mounts gamification UI elements.
285
+
286
+ | Property | Type | Required | Description |
287
+ |----------|------|----------|-------------|
288
+ | `kind` | `"mount_gamification"` | Yes | Action type |
289
+ | `slot` | string | Yes | Target slot |
290
+ | `config` | object | Yes | Gamification configuration |
291
+
292
+ ### Configuration Schema
293
+
294
+ | Property | Type | Required | Default | Description |
295
+ |----------|------|----------|---------|-------------|
296
+ | `badges` | array | No | `[]` | Badge definitions |
297
+ | `points.enabled` | boolean | No | `false` | Enable points system |
298
+ | `points.multiplier` | number | No | `1` | Points multiplier |
299
+ | `leaderboard.enabled` | boolean | No | `false` | Show leaderboard |
300
+ | `leaderboard.refreshInterval` | number | No | `60000` | Refresh interval in ms |
301
+
302
+ ### Badge Schema
303
+
304
+ | Property | Type | Required | Description |
305
+ |----------|------|----------|-------------|
306
+ | `id` | string | Yes | Unique badge identifier |
307
+ | `name` | string | Yes | Display name |
308
+ | `icon` | string | Yes | Icon identifier |
309
+ | `description` | string | No | Badge description |
310
+ | `trigger.event` | string | Yes | Event that triggers the badge |
311
+ | `trigger.conditions` | array | No | Additional conditions |
312
+
140
313
  ```json
141
314
  {
142
- "kind": "removeClass",
143
- "className": "syntro-hidden"
315
+ "kind": "mount_gamification",
316
+ "slot": "overlay_corner_br",
317
+ "config": {
318
+ "badges": [
319
+ {
320
+ "id": "first-purchase",
321
+ "name": "First Purchase",
322
+ "icon": "shopping-cart",
323
+ "description": "Made your first purchase!",
324
+ "trigger": {
325
+ "event": "purchase_completed"
326
+ }
327
+ },
328
+ {
329
+ "id": "explorer",
330
+ "name": "Explorer",
331
+ "icon": "compass",
332
+ "description": "Visited 10 different pages",
333
+ "trigger": {
334
+ "event": "page_view",
335
+ "conditions": [
336
+ { "type": "session_metric", "key": "unique_pages", "operator": ">=", "threshold": 10 }
337
+ ]
338
+ }
339
+ }
340
+ ],
341
+ "points": {
342
+ "enabled": true,
343
+ "multiplier": 2
344
+ },
345
+ "leaderboard": {
346
+ "enabled": true,
347
+ "refreshInterval": 30000
348
+ }
349
+ }
144
350
  }
145
351
  ```
146
- - **Requirement**: Can only remove prefixed classes
147
- - **Common Use**: Revealing hidden elements, removing states
148
- - **Note**: Safe if class doesn't exist
149
352
 
150
- ### 5. setAttr
151
- **Purpose**: Set or remove HTML attributes
353
+ ## Use Cases
354
+
355
+ - **Engagement rewards**: Award badges for completing onboarding steps
356
+ - **Loyalty programs**: Track points for purchases or interactions
357
+ - **Social proof**: Display leaderboards to encourage participation
358
+ - **Progress tracking**: Show achievement progress to motivate users
359
+
360
+
361
+ ---
362
+
363
+ # @syntrologie/app-nav
364
+
365
+ Navigation link list widget with conditional item visibility.
366
+
367
+ ## Actions
368
+
369
+ ### mount_nav
370
+ Mounts a navigation link list widget to a surface slot.
371
+
372
+ | Property | Type | Required | Description |
373
+ |----------|------|----------|-------------|
374
+ | `kind` | `"mount_nav"` | Yes | Action type |
375
+ | `slot` | string | Yes | Target slot (e.g., `"drawer_left"`, `"inline:{anchorId}"`) |
376
+ | `config.title` | string | No | Widget title |
377
+ | `config.items` | array | Yes | Navigation items (see below) |
378
+
379
+ ### Nav Item Schema
380
+
381
+ Each item in the `items` array:
382
+
383
+ | Property | Type | Required | Description |
384
+ |----------|------|----------|-------------|
385
+ | `label` | string | Yes | Link text |
386
+ | `href` | string | Yes | Link destination |
387
+ | `icon` | string | No | Icon identifier |
388
+ | `showWhen` | DecisionStrategy | No | Conditional visibility strategy |
389
+
152
390
  ```json
153
391
  {
154
- "kind": "setAttr",
155
- "name": "data-tracked",
156
- "value": "true" // or null to remove
392
+ "kind": "mount_nav",
393
+ "slot": "drawer_left",
394
+ "config": {
395
+ "title": "Quick Links",
396
+ "items": [
397
+ {
398
+ "label": "Dashboard",
399
+ "href": "/dashboard",
400
+ "icon": "home"
401
+ },
402
+ {
403
+ "label": "Admin Settings",
404
+ "href": "/admin",
405
+ "icon": "settings",
406
+ "showWhen": {
407
+ "type": "rules",
408
+ "rules": [
409
+ {
410
+ "conditions": [
411
+ { "type": "state_equals", "key": "user.role", "value": "admin" }
412
+ ],
413
+ "value": true
414
+ }
415
+ ],
416
+ "default": false
417
+ }
418
+ },
419
+ {
420
+ "label": "Upgrade",
421
+ "href": "/pricing",
422
+ "icon": "sparkles",
423
+ "showWhen": {
424
+ "type": "rules",
425
+ "rules": [
426
+ {
427
+ "conditions": [
428
+ { "type": "state_equals", "key": "user.plan", "value": "free" }
429
+ ],
430
+ "value": true
431
+ }
432
+ ],
433
+ "default": false
434
+ }
435
+ }
436
+ ]
437
+ }
157
438
  }
158
439
  ```
159
- - **Allowed Attributes (any tier)**:
160
- - `data-*` (any data attribute)
161
- - `aria-*` (accessibility attributes)
162
- - `role`
163
- - **Additional Attributes (moderate/surgical)**:
164
- - `title`
165
- - **Common Use**: Tracking, accessibility improvements
166
- - **Note**: Setting to `null` removes the attribute
167
-
168
- ### 6. append
169
- **Purpose**: Add HTML content at the end of an element
440
+
441
+ ## Compositional Pattern
442
+
443
+ The nav widget supports **per-item conditional visibility** using `showWhen` strategies. This allows different navigation items to appear based on:
444
+ - User role or permissions
445
+ - Subscription tier
446
+ - Feature flags
447
+ - Any other DecisionStrategy condition
448
+
449
+ Items without `showWhen` are always visible.
450
+
451
+
452
+ ---
453
+
454
+ # @syntrologie/app-navigation
455
+
456
+ Navigation capabilities for scrolling and URL navigation.
457
+
458
+ ## Actions
459
+
460
+ ### scroll_to
461
+ Scrolls the viewport to bring an element into view.
462
+
463
+ | Property | Type | Required | Default | Description |
464
+ |----------|------|----------|---------|-------------|
465
+ | `kind` | `"scroll_to"` | Yes | | Action type |
466
+ | `anchorId` | string | Yes | | Element selector |
467
+ | `behavior` | string | No | `"smooth"` | `"smooth"`, `"instant"`, `"auto"` |
468
+ | `block` | string | No | `"center"` | `"start"`, `"center"`, `"end"`, `"nearest"` |
469
+ | `inline` | string | No | `"nearest"` | `"start"`, `"center"`, `"end"`, `"nearest"` |
470
+
170
471
  ```json
171
472
  {
172
- "kind": "append",
173
- "html": "<span class='syntro-badge'>NEW</span>"
473
+ "kind": "scroll_to",
474
+ "anchorId": "#pricing-section",
475
+ "behavior": "smooth",
476
+ "block": "start"
174
477
  }
175
478
  ```
176
- - **Effect**: Inserts after existing children
177
- - **Common Use**: Adding badges, icons, supplementary content
178
- - **Note**: HTML is sanitized for safety
179
479
 
180
- ### 7. prepend
181
- **Purpose**: Add HTML content at the beginning of an element
480
+ ### navigate
481
+ Navigates to a URL.
482
+
483
+ | Property | Type | Required | Default | Description |
484
+ |----------|------|----------|---------|-------------|
485
+ | `kind` | `"navigate"` | Yes | | Action type |
486
+ | `url` | string | Yes | | Destination URL |
487
+ | `target` | string | No | `"_self"` | `"_self"` or `"_blank"` |
488
+
182
489
  ```json
183
490
  {
184
- "kind": "prepend",
185
- "html": "<span class='syntro-icon'>★</span>"
491
+ "kind": "navigate",
492
+ "url": "/signup?ref=banner",
493
+ "target": "_self"
186
494
  }
187
495
  ```
188
- - **Effect**: Inserts before existing children
189
- - **Common Use**: Adding icons, prefixes, notifications
190
- - **Note**: HTML is sanitized for safety
191
496
 
192
- ### 8. insertAdjacent
193
- **Purpose**: Insert HTML at specific positions relative to element
497
+ **Note:** `javascript:` URLs are blocked for security.
498
+
499
+
500
+ ---
501
+
502
+ # @syntrologie/app-overlays
503
+
504
+ Visual overlay capabilities including highlights, tooltips, badges, and pulse animations.
505
+
506
+ ## Actions
507
+
508
+ ### highlight
509
+ Creates a spotlight effect around an element with a scrim overlay.
510
+
511
+ | Property | Type | Required | Default | Description |
512
+ |----------|------|----------|---------|-------------|
513
+ | `kind` | `"highlight"` | Yes | | Action type |
514
+ | `anchorId` | string | Yes | | Element selector |
515
+ | `style.color` | string | No | `"#5b8cff"` | Ring color |
516
+ | `style.scrimOpacity` | number | No | `0.55` | Backdrop opacity 0-1 (set to 0 to hide) |
517
+ | `style.paddingPx` | number | No | `12` | Space around element |
518
+ | `style.radiusPx` | number | No | `12` | Ring corner radius |
519
+
194
520
  ```json
195
521
  {
196
- "kind": "insertAdjacent",
197
- "where": "afterbegin",
198
- "html": "<div class='syntro-alert'>Important!</div>"
522
+ "kind": "highlight",
523
+ "anchorId": "#signup-button",
524
+ "style": {
525
+ "color": "#22c55e",
526
+ "scrimOpacity": 0.4
527
+ }
199
528
  }
200
529
  ```
201
- - **Positions**:
202
- - `beforebegin`: Before the element itself
203
- - `afterbegin`: Inside element, before first child
204
- - `beforeend`: Inside element, after last child
205
- - `afterend`: After the element itself
206
- - **Common Use**: Complex insertions, wrapping elements
207
- - **Note**: More flexible than append/prepend
208
530
 
209
- ## Overlays
531
+ ### tooltip
532
+ Shows a tooltip near an element with optional title, body, and CTA.
210
533
 
211
- The SDK supports overlay elements that don't modify the DOM. These are defined in `overlayRecipe.steps`:
534
+ | Property | Type | Required | Default | Description |
535
+ |----------|------|----------|---------|-------------|
536
+ | `kind` | `"tooltip"` | Yes | | Action type |
537
+ | `anchorId` | string | Yes | | Element selector |
538
+ | `content.title` | string | No | | Tooltip heading |
539
+ | `content.body` | string | Yes | | Tooltip text |
540
+ | `content.cta.label` | string | No | | CTA button text |
541
+ | `content.cta.action` | Action | No | | Action to execute on CTA click |
542
+ | `trigger` | string | No | `"immediate"` | `"immediate"`, `"hover"`, `"click"` |
543
+ | `placement` | string | No | `"top"` | See placement options below |
212
544
 
213
- ### Tooltips
214
- Floating information bubbles that appear near target elements.
545
+ **Placement options:** `top`, `top-start`, `top-end`, `bottom`, `bottom-start`, `bottom-end`, `left`, `left-start`, `left-end`, `right`, `right-start`, `right-end`
215
546
 
216
547
  ```json
217
548
  {
218
549
  "kind": "tooltip",
219
- "id": "feature_tooltip_1",
220
- "anchor": {
221
- "by": "css",
222
- "value": ".pricing-card"
223
- },
550
+ "anchorId": "#pricing-toggle",
224
551
  "content": {
225
- "title": "Best Value",
226
- "body": "This plan includes all features at the lowest per-user cost"
227
- },
228
- "placement": "top",
229
- "trigger": "hover",
230
- "ctaButtons": [
231
- {
232
- "label": "Learn More",
233
- "actionId": "learn_more_click"
552
+ "title": "Save 20%",
553
+ "body": "Switch to annual billing to save on your subscription.",
554
+ "cta": {
555
+ "label": "Switch Now",
556
+ "action": { "kind": "navigate", "url": "/billing?annual=true" }
234
557
  }
235
- ],
236
- "dismiss": {
237
- "onEsc": true,
238
- "closeButton": true
239
- }
558
+ },
559
+ "placement": "bottom",
560
+ "trigger": "immediate"
240
561
  }
241
562
  ```
242
563
 
243
- **Properties:**
244
- - `kind`: Must be `"tooltip"`
245
- - `id`: Unique identifier for tracking
246
- - `anchor`: Element selector (same as patches)
247
- - `content.title`: Optional heading text
248
- - `content.body`: Required tooltip body text
249
- - `placement`: `"top"`, `"bottom"`, `"left"`, `"right"`, or `"auto"`
250
- - `trigger`: `"immediate"`, `"hover"`, or `"click"`
251
- - `offsetPx`: Distance from anchor element (optional)
252
- - `blocking`: If true, blocks page interaction (optional)
253
- - `ctaButtons`: Array of action buttons (optional)
254
- - `dismiss.onEsc`: Close on Escape key
255
- - `dismiss.closeButton`: Show X button
256
- - `dismiss.timeoutMs`: Auto-dismiss after milliseconds
257
-
258
- ### Highlights
259
- Visual emphasis rings that draw attention to elements.
564
+ ### badge
565
+ Adds a small badge indicator near an element.
566
+
567
+ | Property | Type | Required | Default | Description |
568
+ |----------|------|----------|---------|-------------|
569
+ | `kind` | `"badge"` | Yes | | Action type |
570
+ | `anchorId` | string | Yes | | Element selector |
571
+ | `text` | string | Yes | | Badge text (e.g., "NEW", "3") |
572
+ | `position` | string | No | `"top-right"` | `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"` |
260
573
 
261
574
  ```json
262
575
  {
263
- "kind": "highlight",
264
- "id": "cta_highlight_1",
265
- "anchor": {
266
- "by": "css",
267
- "value": ".hero-cta button"
268
- },
269
- "copy": "Click here to get started",
270
- "ring": {
271
- "paddingPx": 8,
272
- "radiusPx": 4
273
- },
274
- "ringColor": "#3b82f6",
275
- "scrim": {
276
- "opacity": 0.5
277
- },
278
- "blocking": true,
279
- "dismiss": {
280
- "onClickOutside": true,
281
- "onEsc": true
282
- }
576
+ "kind": "badge",
577
+ "anchorId": "#inbox-icon",
578
+ "text": "5",
579
+ "position": "top-right"
283
580
  }
284
581
  ```
285
582
 
286
- **Properties:**
287
- - `kind`: Must be `"highlight"`
288
- - `id`: Unique identifier for tracking
289
- - `anchor`: Element selector (same as patches)
290
- - `copy`: Text to display near the highlight (optional)
291
- - `ring.paddingPx`: Space between element and ring
292
- - `ring.radiusPx`: Corner radius of ring
293
- - `ringColor`: Color of the highlight ring
294
- - `scrim.opacity`: Opacity of background dimming (0-1)
295
- - `blocking`: If true, blocks interaction outside highlight
296
- - `dismiss.onClickOutside`: Close when clicking outside
297
- - `dismiss.onEsc`: Close on Escape key
298
- - `dismiss.timeoutMs`: Auto-dismiss after milliseconds
583
+ ### pulse
584
+ Adds a pulsing animation to draw attention.
299
585
 
300
- ### Overlay Recipe Structure
301
-
302
- Overlays are defined in `overlayRecipe`:
586
+ | Property | Type | Required | Default | Description |
587
+ |----------|------|----------|---------|-------------|
588
+ | `kind` | `"pulse"` | Yes | | Action type |
589
+ | `anchorId` | string | Yes | | Element selector |
590
+ | `duration` | number | No | `2000` | Animation duration in ms |
303
591
 
304
592
  ```json
305
593
  {
306
- "overlayRecipe": {
307
- "id": "onboarding_flow",
308
- "version": 1,
309
- "routes": ["/dashboard", "/home"],
310
- "steps": [
311
- { "kind": "tooltip", ... },
312
- { "kind": "highlight", ... }
313
- ]
314
- }
594
+ "kind": "pulse",
595
+ "anchorId": ".notification-bell",
596
+ "duration": 3000
315
597
  }
316
598
  ```
317
599
 
318
- - `id`: Recipe identifier
319
- - `version`: Version number for tracking changes
320
- - `routes`: URL paths where this recipe applies (optional)
321
- - `steps`: Array of tooltip and highlight steps
600
+ ### modal
601
+ Shows a centered modal dialog with optional CTA buttons.
602
+
603
+ | Property | Type | Required | Default | Description |
604
+ |----------|------|----------|---------|-------------|
605
+ | `kind` | `"overlays:modal"` | Yes | | Action type |
606
+ | `content.title` | string | No | | Modal heading |
607
+ | `content.body` | string | Yes | | Modal text |
608
+ | `size` | string | No | `"md"` | `"sm"`, `"md"`, `"lg"` |
609
+ | `blocking` | boolean | No | `false` | Block page interaction |
610
+ | `scrim.opacity` | number | No | `0.6` | Backdrop opacity 0-1 |
611
+ | `dismiss.onEsc` | boolean | No | `true` | Close on Escape key |
612
+ | `dismiss.closeButton` | boolean | No | `true` | Show close button |
613
+ | `dismiss.timeoutMs` | number | No | | Auto-close after timeout |
614
+ | `ctaButtons` | array | No | | Array of CTA buttons |
615
+ | `waitFor` | string | No | | When to complete: `"dismissed"`, `"cta-click"`, `"timeout:N"` |
616
+
617
+ **CTA Button properties:**
618
+ - `label`: Button text
619
+ - `actionId`: Identifier for the action
620
+ - `primary`: Whether this is a primary button (default: false)
322
621
 
323
- ## Runtime v2 Providers
622
+ ```json
623
+ {
624
+ "kind": "overlays:modal",
625
+ "content": {
626
+ "title": "Welcome!",
627
+ "body": "Thanks for signing up. Let us show you around."
628
+ },
629
+ "size": "md",
630
+ "ctaButtons": [
631
+ { "label": "Skip", "actionId": "skip" },
632
+ { "label": "Start Tour", "actionId": "start", "primary": true }
633
+ ],
634
+ "waitFor": "cta-click"
635
+ }
636
+ ```
324
637
 
325
- The v2 runtime provides adaptives with a unified interface for context, events, state, and decisions.
326
638
 
327
- ### SmartCanvasRuntime
639
+ ---
328
640
 
329
- Every adaptive receives a `runtime` object:
330
641
 
331
- ```typescript
332
- type SmartCanvasRuntime = {
333
- telemetry: TelemetryClient; // Emit events to PostHog
334
- context: ContextManager; // Subscribe to page/session state
335
- events: EventBus; // Subscribe to normalized events
336
- state: StateStore; // Persist dismissals, cooldowns
337
- version: string;
338
- mode: "production" | "development";
339
- };
340
- ```
642
+ ---
341
643
 
342
- ### Usage
644
+ ## Surfaces
343
645
 
344
- ```typescript
345
- const { canvas, runtime } = await Syntro.init({
346
- token: "syn_..."
347
- });
646
+ Surfaces are named slots where widgets can be mounted. Use the `mount_widget` action to render content into a surface slot.
348
647
 
349
- // Access runtime providers
350
- runtime.telemetry?.trackAction(...);
351
- runtime.context.subscribe((ctx, prev) => { ... });
352
- runtime.events.subscribe({ names: ['ui.click'] }, (event) => { ... });
353
- runtime.state.dismissals.mark('my-tile');
354
- ```
648
+ ### Static Slots
355
649
 
356
- ### Context Manager
650
+ Fixed-position slots for common UI patterns:
357
651
 
358
- Subscribe to runtime context changes:
652
+ | Slot | Position | Use Case |
653
+ |------|----------|----------|
654
+ | `drawer_right` | Right edge, full height | Settings panels, details |
655
+ | `drawer_left` | Left edge, full height | Navigation, filters |
656
+ | `drawer_bottom` | Bottom edge, full width | Action sheets, keyboards |
657
+ | `overlay_center` | Centered modal | Dialogs, confirmations |
658
+ | `overlay_corner_br` | Bottom-right corner | Chat widgets, help |
659
+ | `overlay_corner_bl` | Bottom-left corner | Notifications |
660
+ | `toast_top` | Top center | Success/error messages |
661
+ | `toast_bottom` | Bottom center | Snackbar notifications |
359
662
 
360
- ```typescript
361
- const unsubscribe = runtime.context.subscribe((ctx, prev) => {
362
- if (ctx.page.url !== prev.page.url) {
363
- // Handle route change
364
- }
365
- });
663
+ ### Dynamic Slots
366
664
 
367
- const ctx = runtime.context.get();
368
- console.log(ctx.page.url, ctx.session.sessionId);
369
- ```
665
+ Slots that position content relative to anchors:
370
666
 
371
- **Available Context:**
372
- | Field | Type | Description |
373
- |-------|------|-------------|
374
- | `page.url` | string | Current page URL |
375
- | `page.routeId` | string? | Matched route pattern |
376
- | `page.title` | string? | Document title |
377
- | `session.sessionId` | string | PostHog session ID |
378
- | `session.startTs` | number | Session start timestamp |
379
- | `viewport.width` | number | Viewport width in pixels |
380
- | `viewport.height` | number | Viewport height in pixels |
381
- | `anchors` | AnchorState[]? | Tracked anchor visibility |
382
-
383
- ### Event Bus
384
-
385
- Subscribe to normalized events:
386
-
387
- ```typescript
388
- const unsubscribe = runtime.events.subscribe(
389
- { names: ["ui.click", "nav.page_view"] },
390
- (event) => {
391
- console.log(event.name, event.props);
392
- }
393
- );
394
- ```
667
+ - **Inline Slots**: Render content inside an anchor element. Format: `inline:{anchorId}`
668
+ - **Adjacent Slots**: Render content positioned near an anchor element. Format: `adjacent:{anchorId}`
395
669
 
396
- **Event Schema:**
397
- ```typescript
398
- type NormalizedEvent = {
399
- ts: number; // Timestamp
400
- name: string; // e.g., "ui.click", "canvas.opened"
401
- source: "posthog" | "canvas" | "derived";
402
- props?: Record<string, any>;
403
- schemaVersion: string;
404
- };
405
- ```
670
+ ---
406
671
 
407
- **Standard Events:**
408
- | Event | Source | Description |
409
- |-------|--------|-------------|
410
- | `ui.click` | posthog | User clicked element |
411
- | `ui.scroll` | posthog | User scrolled |
412
- | `nav.page_view` | posthog | Page navigation |
413
- | `canvas.opened` | canvas | Smart Canvas opened |
414
- | `canvas.closed` | canvas | Smart Canvas closed |
415
- | `tile.viewed` | canvas | Tile became visible |
416
- | `tile.action` | canvas | User clicked tile action |
672
+ ## Anchor Resolution
417
673
 
418
- ### State Store
674
+ The `anchorId` field identifies which DOM element to target. Multiple formats are supported:
419
675
 
420
- Persist state across sessions:
676
+ ### CSS Selectors
421
677
 
422
- ```typescript
423
- // Session storage (cleared on close)
424
- runtime.state.session.set("lastTileViewed", "tile-123");
425
- const last = runtime.state.session.get<string>("lastTileViewed");
678
+ | Format | Example | Matches |
679
+ |--------|---------|---------|
680
+ | Class | `".hero-title"` | `<h1 class="hero-title">` |
681
+ | ID | `"#signup-button"` | `<button id="signup-button">` |
682
+ | Attribute | `"[data-testid='cta']"` | `<button data-testid="cta">` |
683
+ | Tag + class | `"h1.page-title"` | `<h1 class="page-title">` |
684
+ | Nested | `".card .title"` | `<div class="title">` inside `.card` |
426
685
 
427
- // User storage (persists across sessions)
428
- runtime.state.user.set("onboardingComplete", true);
686
+ ### Data Attributes (Recommended)
429
687
 
430
- // Namespace by adaptive
431
- const ns = runtime.state.ns("my-adaptive");
432
- ns.session.set("step", 3);
433
- ```
688
+ For stability, add `data-syntro-anchor` attributes to target elements:
434
689
 
435
- **Built-in Helpers:**
436
- ```typescript
437
- // Track dismissals
438
- runtime.state.dismissals.mark("tooltip-1");
439
- if (runtime.state.dismissals.isDismissed("tooltip-1")) { ... }
690
+ ```html
691
+ <h1 data-syntro-anchor="hero-title">Welcome</h1>
692
+ ```
440
693
 
441
- // Cooldowns (don't show again for X ms)
442
- runtime.state.cooldowns.set("promo-banner", 86400000); // 24h
443
- if (runtime.state.cooldowns.isActive("promo-banner")) { ... }
694
+ Then reference by the anchor name:
444
695
 
445
- // Frequency caps
446
- runtime.state.frequency.increment("upsell-shown");
447
- if (runtime.state.frequency.count("upsell-shown") >= 3) { ... }
696
+ ```json
697
+ { "anchorId": "hero-title" }
448
698
  ```
449
699
 
450
- ### Decision Strategies
700
+ ---
701
+
702
+ ## Decision Strategies
451
703
 
452
- Tile activation uses `DecisionStrategy` for conditional rendering:
704
+ Control when adaptives activate using `DecisionStrategy`:
705
+
706
+ ### Rules Strategy
453
707
 
454
708
  ```json
455
709
  {
456
- "id": "promo-tile",
457
- "title": "Limited Offer",
458
- "activation": {
459
- "routes": { "include": ["/pricing", "/upgrade"] },
460
- "strategy": {
461
- "type": "rules",
462
- "rules": [
463
- {
464
- "conditions": [
465
- { "type": "viewport", "minWidth": 768 },
466
- { "type": "dismissed", "key": "promo-tile", "inverted": true }
467
- ],
468
- "value": true
469
- }
710
+ "type": "rules",
711
+ "rules": [
712
+ {
713
+ "conditions": [
714
+ { "type": "page_url", "pattern": "/pricing*" },
715
+ { "type": "viewport", "minWidth": 768 },
716
+ { "type": "dismissed", "key": "promo", "inverted": true }
470
717
  ],
471
- "default": false
718
+ "value": true
472
719
  }
473
- }
720
+ ],
721
+ "default": false
474
722
  }
475
723
  ```
476
724
 
477
- **Strategy Types:**
478
- | Type | Description |
479
- |------|-------------|
480
- | `rules` | Condition-based matching (if/then) |
481
- | `score` | Threshold on augmented field |
482
- | `model` | ML model prediction (future) |
483
- | `external` | Remote decision endpoint (future) |
484
-
485
- **Condition Types:**
486
- | Condition | Parameters | Description |
487
- |-----------|------------|-------------|
488
- | `page_url` | `url` (pattern) | URL matches pattern |
725
+ ### Condition Types
726
+
727
+ | Type | Parameters | Description |
728
+ |------|------------|-------------|
729
+ | `page_url` | `pattern` | URL matches glob pattern |
489
730
  | `route` | `routeId` | Route ID matches |
490
- | `anchor_visible` | `anchorId`, `state` | Anchor visibility state |
491
- | `event_occurred` | `eventName`, `withinMs?` | Event happened recently |
492
- | `state_equals` | `key`, `value` | State value matches |
493
- | `viewport` | `minWidth?`, `maxWidth?` | Viewport size range |
494
- | `session_metric` | `key`, `operator`, `threshold` | Session metric comparison |
495
- | `dismissed` | `key`, `inverted?` | Item has been dismissed |
496
- | `cooldown_active` | `key`, `inverted?` | Cooldown is active |
497
- | `frequency_limit` | `key`, `limit`, `inverted?` | Frequency cap reached |
731
+ | `anchor_visible` | `anchorId`, `state` | Anchor visibility |
732
+ | `event_occurred` | `eventName`, `withinMs?` | Recent event |
733
+ | `state_equals` | `key`, `value` | State matches |
734
+ | `viewport` | `minWidth?`, `maxWidth?`, `minHeight?`, `maxHeight?` | Viewport size |
735
+ | `session_metric` | `key`, `operator`, `threshold` | Metric comparison |
736
+ | `dismissed` | `key`, `inverted?` | Dismissal check |
737
+ | `cooldown_active` | `key`, `inverted?` | Cooldown check |
738
+ | `frequency_limit` | `key`, `limit`, `inverted?` | Frequency cap |
498
739
 
499
- ### Migration from v1
740
+ ---
500
741
 
501
- The new `activation` field replaces the deprecated `experiment` field:
742
+ ## Best Practices
502
743
 
503
- **Before (v1):**
504
- ```json
505
- {
506
- "experiment": {
507
- "featureKey": "my-feature",
508
- "variationId": 1
509
- }
510
- }
511
- ```
744
+ ### 1. Choose the Right Action Type
512
745
 
513
- **After (v2):**
514
- ```json
515
- {
516
- "activation": {
517
- "strategy": {
518
- "type": "rules",
519
- "rules": [
520
- {
521
- "conditions": [
522
- { "type": "route", "routeId": "/dashboard" }
523
- ],
524
- "value": true
525
- }
526
- ],
527
- "default": false
528
- }
529
- }
530
- }
531
- ```
746
+ | Goal | Action |
747
+ |------|--------|
748
+ | Change text | `set_text` |
749
+ | Add visual indicator | `badge`, `pulse` |
750
+ | Show help text | `tooltip` |
751
+ | Draw attention | `highlight` |
752
+ | Add content | `insert_html` |
753
+ | Navigate user | `scroll_to`, `navigate` |
754
+ | Show UI panel | `mount_widget` + Surfaces |
532
755
 
533
- ## Best Practices
756
+ ### 2. Anchor Selection
534
757
 
535
- ### 1. Element Selection
536
- - Use the most specific selector that won't break
537
- - Prefer data attributes over classes for stability
538
- - Test selectors across different page states
758
+ - **Prefer stable selectors:** `[data-testid]`, IDs over classes
759
+ - **Test across states:** Element may not exist on all pages
760
+ - **Be specific:** Avoid selectors matching multiple elements
539
761
 
540
- ### 2. Tier Selection
541
- - Start with the lowest tier that works
542
- - Only use `surgical` when modifying text
543
- - Document why a specific tier is needed
762
+ ### 3. Reversibility
544
763
 
545
- ### 3. Operation Order
546
- - Operations are applied in array order
547
- - Plan sequences carefully (e.g., addClass before setStyle)
548
- - Test operation combinations
764
+ - Always store handles if you need to revert
765
+ - Use `applyBatch` for related changes (atomic rollback)
766
+ - Clean up on route changes
549
767
 
550
768
  ### 4. Performance
551
- - Batch related patches together
552
- - Avoid selecting too many elements
553
- - Use CSS classes instead of inline styles when possible
554
769
 
555
- ### 5. Compatibility
556
- - All HTML is sanitized to prevent XSS
557
- - Classes must be prefixed to avoid conflicts
558
- - Test across different browsers and devices
770
+ - Batch related actions together
771
+ - Use `validate()` to check actions before applying
772
+ - Avoid applying many actions simultaneously
559
773
 
560
- ## Example: Complete A/B Test
561
-
562
- ```json
563
- {
564
- "configVersion": "1.0",
565
- "patches": [
566
- {
567
- "id": "hero_headline_test",
568
- "anchor": {
569
- "by": "css",
570
- "value": "h1.hero-title"
571
- },
572
- "tier": "surgical",
573
- "operations": [
574
- {
575
- "kind": "setText",
576
- "text": "Start Your Free Trial Today"
577
- },
578
- {
579
- "kind": "addClass",
580
- "className": "syntro-tested"
581
- }
582
- ]
583
- },
584
- {
585
- "id": "cta_enhancement",
586
- "anchor": {
587
- "by": "data",
588
- "key": "testid",
589
- "value": "primary-cta"
590
- },
591
- "tier": "additive",
592
- "operations": [
593
- {
594
- "kind": "setStyle",
595
- "prop": "boxShadow",
596
- "value": "0 4px 6px rgba(0,0,0,0.1)"
597
- },
598
- {
599
- "kind": "append",
600
- "html": "<span class='syntro-arrow'>→</span>"
601
- }
602
- ]
603
- }
604
- ]
605
- }
606
- ```
774
+ ### 5. Events
607
775
 
608
- This example:
609
- 1. Changes the hero headline text (requires surgical tier)
610
- 2. Enhances the CTA button with shadow and an arrow icon (uses additive tier)
611
- 3. Properly tracks both changes with IDs
776
+ - Listen for `action.failed` to handle errors
777
+ - Track `action.applied` for analytics
778
+ - Use EventBus for cross-adaptive coordination