@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.
- package/CAPABILITIES.md +630 -463
- package/README.md +285 -62
- package/dist/RuntimeProvider.d.ts +51 -0
- package/dist/RuntimeProvider.js +114 -0
- package/dist/RuntimeProvider.js.map +1 -0
- package/dist/SmartCanvasApp.d.ts +9 -3
- package/dist/SmartCanvasApp.js +36 -38
- package/dist/SmartCanvasApp.js.map +1 -1
- package/dist/actions/ActionEngine.d.ts +11 -0
- package/dist/actions/ActionEngine.js +274 -0
- package/dist/actions/ActionEngine.js.map +1 -0
- package/dist/actions/executors/index.d.ts +118 -0
- package/dist/actions/executors/index.js +242 -0
- package/dist/actions/executors/index.js.map +1 -0
- package/dist/actions/executors/tour.d.ts +18 -0
- package/dist/actions/executors/tour.js +332 -0
- package/dist/actions/executors/tour.js.map +1 -0
- package/dist/actions/index.d.ts +10 -0
- package/dist/actions/index.js +12 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/types.d.ts +399 -0
- package/dist/actions/types.js +8 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/actions/validation.d.ts +14 -0
- package/dist/actions/validation.js +603 -0
- package/dist/actions/validation.js.map +1 -0
- package/dist/api.d.ts +32 -18
- package/dist/api.js +56 -39
- package/dist/api.js.map +1 -1
- package/dist/apps/AppContext.d.ts +31 -0
- package/dist/apps/AppContext.js +93 -0
- package/dist/apps/AppContext.js.map +1 -0
- package/dist/apps/AppLoader.d.ts +84 -0
- package/dist/apps/AppLoader.js +250 -0
- package/dist/apps/AppLoader.js.map +1 -0
- package/dist/apps/AppRegistry.d.ts +102 -0
- package/dist/apps/AppRegistry.js +317 -0
- package/dist/apps/AppRegistry.js.map +1 -0
- package/dist/apps/examples/gamification-app.example.d.ts +305 -0
- package/dist/apps/examples/gamification-app.example.js +329 -0
- package/dist/apps/examples/gamification-app.example.js.map +1 -0
- package/dist/apps/index.d.ts +18 -0
- package/dist/apps/index.js +26 -0
- package/dist/apps/index.js.map +1 -0
- package/dist/apps/types.d.ts +231 -0
- package/dist/apps/types.js +8 -0
- package/dist/apps/types.js.map +1 -0
- package/dist/bootstrap.d.ts +24 -0
- package/dist/bootstrap.js +133 -33
- package/dist/bootstrap.js.map +1 -1
- package/dist/components/ShadowCanvasOverlay.js +36 -9
- package/dist/components/ShadowCanvasOverlay.js.map +1 -1
- package/dist/components/TileCard.js +37 -18
- package/dist/components/TileCard.js.map +1 -1
- package/dist/context/schema.d.ts +16 -16
- package/dist/decisions/schema.d.ts +96 -96
- package/dist/earlyPatcher.d.ts +8 -20
- package/dist/earlyPatcher.js +13 -62
- package/dist/earlyPatcher.js.map +1 -1
- package/dist/editorLoader.d.ts +2 -0
- package/dist/editorLoader.js +46 -7
- package/dist/editorLoader.js.map +1 -1
- package/dist/events/normalizers/posthog.d.ts +24 -0
- package/dist/events/normalizers/posthog.js.map +1 -1
- package/dist/events/schema.d.ts +8 -8
- package/dist/events/types.d.ts +6 -0
- package/dist/events/types.js +8 -0
- package/dist/events/types.js.map +1 -1
- package/dist/hooks/useCanvasOverlays.d.ts +4 -1
- package/dist/hooks/useCanvasOverlays.js +53 -6
- package/dist/hooks/useCanvasOverlays.js.map +1 -1
- package/dist/hooks/useShadowCanvasConfig.d.ts +3 -7
- package/dist/hooks/useShadowCanvasConfig.js +2 -3
- package/dist/hooks/useShadowCanvasConfig.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/overlays/schema.d.ts +153 -153
- package/dist/runtime.d.ts +24 -0
- package/dist/runtime.js +75 -1
- package/dist/runtime.js.map +1 -1
- package/dist/smart-canvas.esm.js +162 -55
- package/dist/smart-canvas.esm.js.map +4 -4
- package/dist/smart-canvas.js +21133 -17957
- package/dist/smart-canvas.js.map +4 -4
- package/dist/smart-canvas.min.js +162 -55
- package/dist/smart-canvas.min.js.map +4 -4
- package/dist/store/example.d.ts +1 -0
- package/dist/store/example.js +43 -0
- package/dist/store/example.js.map +1 -0
- package/dist/store/mini-effector.d.ts +46 -0
- package/dist/store/mini-effector.js +90 -0
- package/dist/store/mini-effector.js.map +1 -0
- package/dist/surfaces/Surfaces.d.ts +11 -0
- package/dist/surfaces/Surfaces.js +361 -0
- package/dist/surfaces/Surfaces.js.map +1 -0
- package/dist/surfaces/index.d.ts +9 -0
- package/dist/surfaces/index.js +12 -0
- package/dist/surfaces/index.js.map +1 -0
- package/dist/surfaces/positioning.d.ts +50 -0
- package/dist/surfaces/positioning.js +231 -0
- package/dist/surfaces/positioning.js.map +1 -0
- package/dist/surfaces/types.d.ts +167 -0
- package/dist/surfaces/types.js +23 -0
- package/dist/surfaces/types.js.map +1 -0
- package/dist/telemetry/adapters/posthog.d.ts +6 -0
- package/dist/telemetry/adapters/posthog.js +9 -0
- package/dist/telemetry/adapters/posthog.js.map +1 -1
- package/dist/types-only.d.ts +32 -0
- package/dist/types-only.js +11 -0
- package/dist/types-only.js.map +1 -0
- package/dist/types.d.ts +26 -14
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/widgets/WidgetRegistry.d.ts +139 -0
- package/dist/widgets/WidgetRegistry.js +182 -0
- package/dist/widgets/WidgetRegistry.js.map +1 -0
- package/dist/widgets/index.d.ts +7 -0
- package/dist/widgets/index.js +7 -0
- package/dist/widgets/index.js.map +1 -0
- package/package.json +13 -3
- 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
|
|
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
|
-
- [
|
|
8
|
-
- [
|
|
9
|
-
- [
|
|
10
|
-
- [
|
|
11
|
-
- [
|
|
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
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
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
|
-
|
|
68
|
+
The SDK includes the following adaptive packages, each providing specific capabilities:
|
|
22
69
|
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
83
|
+
## Actions
|
|
44
84
|
|
|
45
|
-
|
|
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
|
-
"
|
|
51
|
-
"
|
|
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
|
-
###
|
|
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
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
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
|
-
###
|
|
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
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"
|
|
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
|
-
###
|
|
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
|
-
"
|
|
85
|
-
"
|
|
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
|
-
|
|
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": "
|
|
98
|
-
"
|
|
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
|
-
###
|
|
107
|
-
|
|
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": "
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"important": true // optional, adds !important
|
|
197
|
+
"kind": "remove_class",
|
|
198
|
+
"anchorId": ".pricing-card",
|
|
199
|
+
"className": "hidden"
|
|
114
200
|
}
|
|
115
201
|
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
###
|
|
127
|
-
|
|
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": "
|
|
131
|
-
"
|
|
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
|
-
|
|
139
|
-
|
|
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": "
|
|
143
|
-
"
|
|
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
|
-
|
|
151
|
-
|
|
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": "
|
|
155
|
-
"
|
|
156
|
-
"
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
-
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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": "
|
|
173
|
-
"
|
|
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
|
-
###
|
|
181
|
-
|
|
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": "
|
|
185
|
-
"
|
|
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
|
-
|
|
193
|
-
|
|
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": "
|
|
197
|
-
"
|
|
198
|
-
"
|
|
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
|
-
|
|
531
|
+
### tooltip
|
|
532
|
+
Shows a tooltip near an element with optional title, body, and CTA.
|
|
210
533
|
|
|
211
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
220
|
-
"anchor": {
|
|
221
|
-
"by": "css",
|
|
222
|
-
"value": ".pricing-card"
|
|
223
|
-
},
|
|
550
|
+
"anchorId": "#pricing-toggle",
|
|
224
551
|
"content": {
|
|
225
|
-
"title": "
|
|
226
|
-
"body": "
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
"
|
|
237
|
-
|
|
238
|
-
"closeButton": true
|
|
239
|
-
}
|
|
558
|
+
},
|
|
559
|
+
"placement": "bottom",
|
|
560
|
+
"trigger": "immediate"
|
|
240
561
|
}
|
|
241
562
|
```
|
|
242
563
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
- `
|
|
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": "
|
|
264
|
-
"
|
|
265
|
-
"
|
|
266
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
"
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
639
|
+
---
|
|
328
640
|
|
|
329
|
-
Every adaptive receives a `runtime` object:
|
|
330
641
|
|
|
331
|
-
|
|
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
|
-
|
|
644
|
+
## Surfaces
|
|
343
645
|
|
|
344
|
-
|
|
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
|
-
|
|
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
|
-
|
|
650
|
+
Fixed-position slots for common UI patterns:
|
|
357
651
|
|
|
358
|
-
|
|
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
|
-
|
|
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
|
-
|
|
368
|
-
console.log(ctx.page.url, ctx.session.sessionId);
|
|
369
|
-
```
|
|
665
|
+
Slots that position content relative to anchors:
|
|
370
666
|
|
|
371
|
-
**
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
674
|
+
The `anchorId` field identifies which DOM element to target. Multiple formats are supported:
|
|
419
675
|
|
|
420
|
-
|
|
676
|
+
### CSS Selectors
|
|
421
677
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
428
|
-
runtime.state.user.set("onboardingComplete", true);
|
|
686
|
+
### Data Attributes (Recommended)
|
|
429
687
|
|
|
430
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
if (runtime.state.frequency.count("upsell-shown") >= 3) { ... }
|
|
696
|
+
```json
|
|
697
|
+
{ "anchorId": "hero-title" }
|
|
448
698
|
```
|
|
449
699
|
|
|
450
|
-
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
## Decision Strategies
|
|
451
703
|
|
|
452
|
-
|
|
704
|
+
Control when adaptives activate using `DecisionStrategy`:
|
|
705
|
+
|
|
706
|
+
### Rules Strategy
|
|
453
707
|
|
|
454
708
|
```json
|
|
455
709
|
{
|
|
456
|
-
"
|
|
457
|
-
"
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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
|
-
"
|
|
718
|
+
"value": true
|
|
472
719
|
}
|
|
473
|
-
|
|
720
|
+
],
|
|
721
|
+
"default": false
|
|
474
722
|
}
|
|
475
723
|
```
|
|
476
724
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
| `
|
|
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
|
|
491
|
-
| `event_occurred` | `eventName`, `withinMs?` |
|
|
492
|
-
| `state_equals` | `key`, `value` | State
|
|
493
|
-
| `viewport` | `minWidth?`, `maxWidth?` | Viewport size
|
|
494
|
-
| `session_metric` | `key`, `operator`, `threshold` |
|
|
495
|
-
| `dismissed` | `key`, `inverted?` |
|
|
496
|
-
| `cooldown_active` | `key`, `inverted?` | Cooldown
|
|
497
|
-
| `frequency_limit` | `key`, `limit`, `inverted?` | Frequency cap
|
|
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
|
-
|
|
740
|
+
---
|
|
500
741
|
|
|
501
|
-
|
|
742
|
+
## Best Practices
|
|
502
743
|
|
|
503
|
-
|
|
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
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
756
|
+
### 2. Anchor Selection
|
|
534
757
|
|
|
535
|
-
|
|
536
|
-
-
|
|
537
|
-
-
|
|
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
|
-
###
|
|
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
|
-
|
|
546
|
-
-
|
|
547
|
-
-
|
|
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
|
-
|
|
556
|
-
-
|
|
557
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|