@syntrologie/runtime-sdk 0.2.21 → 1.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 (311) hide show
  1. package/CAPABILITIES.md +721 -460
  2. package/README.md +310 -68
  3. package/dist/RuntimeProvider.d.ts +51 -0
  4. package/dist/RuntimeProvider.js +113 -0
  5. package/dist/RuntimeProvider.js.map +1 -0
  6. package/dist/SmartCanvasApp.d.ts +16 -10
  7. package/dist/SmartCanvasApp.js +45 -49
  8. package/dist/SmartCanvasApp.js.map +1 -1
  9. package/dist/SmartCanvasElement.d.ts +5 -5
  10. package/dist/SmartCanvasElement.js +24 -14
  11. package/dist/SmartCanvasElement.js.map +1 -1
  12. package/dist/SmartCanvasPortal.d.ts +2 -2
  13. package/dist/SmartCanvasPortal.js +2 -2
  14. package/dist/actions/ActionEngine.d.ts +11 -0
  15. package/dist/actions/ActionEngine.js +274 -0
  16. package/dist/actions/ActionEngine.js.map +1 -0
  17. package/dist/actions/executors/index.d.ts +117 -0
  18. package/dist/actions/executors/index.js +242 -0
  19. package/dist/actions/executors/index.js.map +1 -0
  20. package/dist/actions/executors/tour.d.ts +18 -0
  21. package/dist/actions/executors/tour.js +332 -0
  22. package/dist/actions/executors/tour.js.map +1 -0
  23. package/dist/actions/index.d.ts +10 -0
  24. package/dist/actions/index.js +12 -0
  25. package/dist/actions/index.js.map +1 -0
  26. package/dist/actions/types.d.ts +399 -0
  27. package/dist/actions/types.js +8 -0
  28. package/dist/actions/types.js.map +1 -0
  29. package/dist/actions/validation.d.ts +14 -0
  30. package/dist/actions/validation.js +602 -0
  31. package/dist/actions/validation.js.map +1 -0
  32. package/dist/antiFlicker.js +1 -1
  33. package/dist/api.d.ts +40 -26
  34. package/dist/api.js +86 -56
  35. package/dist/api.js.map +1 -1
  36. package/dist/apps/AppContext.d.ts +31 -0
  37. package/dist/apps/AppContext.js +93 -0
  38. package/dist/apps/AppContext.js.map +1 -0
  39. package/dist/apps/AppLoader.d.ts +84 -0
  40. package/dist/apps/AppLoader.js +256 -0
  41. package/dist/apps/AppLoader.js.map +1 -0
  42. package/dist/apps/AppRegistry.d.ts +102 -0
  43. package/dist/apps/AppRegistry.js +317 -0
  44. package/dist/apps/AppRegistry.js.map +1 -0
  45. package/dist/apps/adaptive-chatbot/index.js +7 -0
  46. package/dist/apps/adaptive-chatbot/index.js.map +7 -0
  47. package/dist/apps/examples/gamification-app.example.d.ts +305 -0
  48. package/dist/apps/examples/gamification-app.example.js +329 -0
  49. package/dist/apps/examples/gamification-app.example.js.map +1 -0
  50. package/dist/apps/faq/index.js +11 -0
  51. package/dist/apps/faq/index.js.map +7 -0
  52. package/dist/apps/gamification/index.js +2 -0
  53. package/dist/apps/gamification/index.js.map +7 -0
  54. package/dist/apps/index.d.ts +15 -0
  55. package/dist/apps/index.js +17 -0
  56. package/dist/apps/index.js.map +1 -0
  57. package/dist/apps/nav/index.js +11 -0
  58. package/dist/apps/nav/index.js.map +7 -0
  59. package/dist/apps/types.d.ts +231 -0
  60. package/dist/apps/types.js +8 -0
  61. package/dist/apps/types.js.map +1 -0
  62. package/dist/blocks/data/ComparisonBlock.d.ts +1 -1
  63. package/dist/blocks/data/ComparisonBlock.js +40 -40
  64. package/dist/blocks/data/ComparisonBlock.js.map +1 -1
  65. package/dist/blocks/data/StatsBlock.d.ts +1 -1
  66. package/dist/blocks/data/StatsBlock.js +41 -41
  67. package/dist/blocks/data/StatsBlock.js.map +1 -1
  68. package/dist/blocks/data/index.d.ts +2 -2
  69. package/dist/blocks/data/index.js +2 -2
  70. package/dist/blocks/index.d.ts +5 -5
  71. package/dist/blocks/index.js +29 -29
  72. package/dist/blocks/index.js.map +1 -1
  73. package/dist/blocks/interactive/ChecklistBlock.d.ts +1 -1
  74. package/dist/blocks/interactive/ChecklistBlock.js +60 -60
  75. package/dist/blocks/interactive/ChecklistBlock.js.map +1 -1
  76. package/dist/blocks/interactive/RatingBlock.d.ts +1 -1
  77. package/dist/blocks/interactive/RatingBlock.js +75 -65
  78. package/dist/blocks/interactive/RatingBlock.js.map +1 -1
  79. package/dist/blocks/interactive/index.d.ts +2 -2
  80. package/dist/blocks/interactive/index.js +2 -2
  81. package/dist/blocks/notification/NotificationBlock.d.ts +2 -2
  82. package/dist/blocks/notification/NotificationBlock.js +67 -63
  83. package/dist/blocks/notification/NotificationBlock.js.map +1 -1
  84. package/dist/blocks/notification/index.d.ts +1 -1
  85. package/dist/blocks/notification/index.js +1 -1
  86. package/dist/bootstrap.d.ts +32 -8
  87. package/dist/bootstrap.js +204 -85
  88. package/dist/bootstrap.js.map +1 -1
  89. package/dist/components/ShadowCanvasOverlay.d.ts +6 -6
  90. package/dist/components/ShadowCanvasOverlay.js +144 -107
  91. package/dist/components/ShadowCanvasOverlay.js.map +1 -1
  92. package/dist/components/TileCard.d.ts +5 -5
  93. package/dist/components/TileCard.js +204 -154
  94. package/dist/components/TileCard.js.map +1 -1
  95. package/dist/components/TileWheel.d.ts +3 -3
  96. package/dist/components/TileWheel.js +7 -7
  97. package/dist/components/TileWheel.js.map +1 -1
  98. package/dist/config-validator.d.ts +49 -0
  99. package/dist/config-validator.js +173 -0
  100. package/dist/config-validator.js.map +1 -0
  101. package/dist/configFetcher.d.ts +2 -2
  102. package/dist/configFetcher.js +20 -8
  103. package/dist/configFetcher.js.map +1 -1
  104. package/dist/context/ContextManager.d.ts +3 -3
  105. package/dist/context/ContextManager.js +15 -15
  106. package/dist/context/ContextManager.js.map +1 -1
  107. package/dist/context/index.d.ts +4 -4
  108. package/dist/context/index.js +3 -3
  109. package/dist/context/schema.d.ts +17 -17
  110. package/dist/context/schema.js +1 -1
  111. package/dist/decisions/engine.d.ts +5 -5
  112. package/dist/decisions/engine.js +13 -13
  113. package/dist/decisions/index.d.ts +6 -6
  114. package/dist/decisions/index.js +5 -5
  115. package/dist/decisions/schema.d.ts +97 -97
  116. package/dist/decisions/schema.js +20 -20
  117. package/dist/decisions/strategies/rules.d.ts +1 -1
  118. package/dist/decisions/strategies/rules.js +24 -24
  119. package/dist/decisions/strategies/rules.js.map +1 -1
  120. package/dist/decisions/strategies/score.d.ts +1 -1
  121. package/dist/decisions/strategies/score.js +3 -3
  122. package/dist/decisions/types.d.ts +19 -19
  123. package/dist/earlyPatcher.d.ts +8 -20
  124. package/dist/earlyPatcher.js +13 -62
  125. package/dist/earlyPatcher.js.map +1 -1
  126. package/dist/editorLoader.d.ts +2 -0
  127. package/dist/editorLoader.js +104 -58
  128. package/dist/editorLoader.js.map +1 -1
  129. package/dist/events/EventBus.d.ts +3 -3
  130. package/dist/events/EventBus.js +5 -7
  131. package/dist/events/EventBus.js.map +1 -1
  132. package/dist/events/index.d.ts +6 -6
  133. package/dist/events/index.js +5 -5
  134. package/dist/events/normalizers/canvas.d.ts +2 -2
  135. package/dist/events/normalizers/canvas.js +3 -3
  136. package/dist/events/normalizers/canvas.js.map +1 -1
  137. package/dist/events/normalizers/posthog.d.ts +25 -1
  138. package/dist/events/normalizers/posthog.js +35 -27
  139. package/dist/events/normalizers/posthog.js.map +1 -1
  140. package/dist/events/schema.d.ts +13 -13
  141. package/dist/events/schema.js +2 -2
  142. package/dist/events/types.d.ts +7 -1
  143. package/dist/events/types.js +29 -21
  144. package/dist/events/types.js.map +1 -1
  145. package/dist/experiments/adapters/growthbook.d.ts +4 -4
  146. package/dist/experiments/adapters/growthbook.js +5 -5
  147. package/dist/experiments/adapters/growthbook.js.map +1 -1
  148. package/dist/experiments/index.d.ts +3 -3
  149. package/dist/experiments/index.js +1 -1
  150. package/dist/experiments/registry.d.ts +2 -2
  151. package/dist/experiments/registry.js +2 -2
  152. package/dist/experiments/types.d.ts +5 -1
  153. package/dist/fetchers/cdnFetcher.d.ts +1 -1
  154. package/dist/fetchers/cdnFetcher.js +4 -8
  155. package/dist/fetchers/cdnFetcher.js.map +1 -1
  156. package/dist/fetchers/experimentsFetcher.d.ts +2 -2
  157. package/dist/fetchers/experimentsFetcher.js +7 -7
  158. package/dist/fetchers/experimentsFetcher.js.map +1 -1
  159. package/dist/fetchers/index.d.ts +3 -3
  160. package/dist/fetchers/index.js +2 -2
  161. package/dist/fetchers/index.js.map +1 -1
  162. package/dist/fetchers/registry.d.ts +1 -1
  163. package/dist/fetchers/registry.js +4 -4
  164. package/dist/fetchers/types.d.ts +1 -1
  165. package/dist/hooks/useCanvasOverlays.d.ts +8 -5
  166. package/dist/hooks/useCanvasOverlays.js +66 -17
  167. package/dist/hooks/useCanvasOverlays.js.map +1 -1
  168. package/dist/hooks/useHostPatches.d.ts +2 -2
  169. package/dist/hooks/useHostPatches.js +8 -8
  170. package/dist/hooks/useHostPatches.js.map +1 -1
  171. package/dist/hooks/useShadowCanvasConfig.d.ts +5 -9
  172. package/dist/hooks/useShadowCanvasConfig.js +7 -5
  173. package/dist/hooks/useShadowCanvasConfig.js.map +1 -1
  174. package/dist/hostPatcher/core/patcher.d.ts +1 -1
  175. package/dist/hostPatcher/core/patcher.js +18 -9
  176. package/dist/hostPatcher/core/patcher.js.map +1 -1
  177. package/dist/hostPatcher/core/sanitizer.js +24 -3
  178. package/dist/hostPatcher/core/sanitizer.js.map +1 -1
  179. package/dist/hostPatcher/policy/defaultPolicy.js +15 -5
  180. package/dist/hostPatcher/policy/defaultPolicy.js.map +1 -1
  181. package/dist/hostPatcher/utils/anchors.js +4 -6
  182. package/dist/hostPatcher/utils/anchors.js.map +1 -1
  183. package/dist/index.d.ts +34 -27
  184. package/dist/index.js +36 -24
  185. package/dist/index.js.map +1 -1
  186. package/dist/logger.d.ts +29 -0
  187. package/dist/logger.js +81 -0
  188. package/dist/logger.js.map +1 -0
  189. package/dist/metrics/index.d.ts +1 -1
  190. package/dist/metrics/index.js +1 -1
  191. package/dist/metrics/sessionMetrics.d.ts +1 -1
  192. package/dist/metrics/sessionMetrics.js +6 -6
  193. package/dist/overlays/fetcher.d.ts +2 -2
  194. package/dist/overlays/fetcher.js +4 -4
  195. package/dist/overlays/recipeRegistry.js +2 -2
  196. package/dist/overlays/recipeRegistry.js.map +1 -1
  197. package/dist/overlays/runtime/anchor/resolve.js +1 -1
  198. package/dist/overlays/runtime/anchor/resolve.js.map +1 -1
  199. package/dist/overlays/runtime/index.d.ts +7 -7
  200. package/dist/overlays/runtime/index.js +7 -7
  201. package/dist/overlays/runtime/overlay/highlight.js +39 -39
  202. package/dist/overlays/runtime/overlay/highlight.js.map +1 -1
  203. package/dist/overlays/runtime/overlay/modal.js +5 -5
  204. package/dist/overlays/runtime/overlay/modal.js.map +1 -1
  205. package/dist/overlays/runtime/overlay/root.js +1 -1
  206. package/dist/overlays/runtime/overlay/runner.js +70 -23
  207. package/dist/overlays/runtime/overlay/runner.js.map +1 -1
  208. package/dist/overlays/runtime/overlay/tooltip.d.ts +1 -1
  209. package/dist/overlays/runtime/overlay/tooltip.js +10 -10
  210. package/dist/overlays/runtime/overlay/tooltip.js.map +1 -1
  211. package/dist/overlays/runtime/utils/dom.js +4 -1
  212. package/dist/overlays/runtime/utils/dom.js.map +1 -1
  213. package/dist/overlays/schema.d.ts +146 -146
  214. package/dist/overlays/schema.js +12 -8
  215. package/dist/overlays/schema.js.map +1 -1
  216. package/dist/react.d.ts +7 -7
  217. package/dist/react.js +4 -4
  218. package/dist/react.js.map +1 -1
  219. package/dist/render/RenderContext.d.ts +2 -2
  220. package/dist/render/RenderContext.js +5 -5
  221. package/dist/render/RenderContext.js.map +1 -1
  222. package/dist/render/index.d.ts +3 -3
  223. package/dist/render/index.js +1 -1
  224. package/dist/render/types.d.ts +4 -4
  225. package/dist/runtime.d.ts +32 -8
  226. package/dist/runtime.js +87 -13
  227. package/dist/runtime.js.map +1 -1
  228. package/dist/smart-canvas.esm.js +155 -78
  229. package/dist/smart-canvas.esm.js.map +4 -4
  230. package/dist/smart-canvas.js +38776 -33216
  231. package/dist/smart-canvas.js.map +4 -4
  232. package/dist/smart-canvas.min.js +156 -78
  233. package/dist/smart-canvas.min.js.map +4 -4
  234. package/dist/state/StateStore.d.ts +1 -1
  235. package/dist/state/StateStore.js +9 -9
  236. package/dist/state/StateStore.js.map +1 -1
  237. package/dist/state/helpers/cooldowns.d.ts +1 -1
  238. package/dist/state/helpers/cooldowns.js +1 -1
  239. package/dist/state/helpers/dismissals.d.ts +1 -1
  240. package/dist/state/helpers/dismissals.js +1 -1
  241. package/dist/state/helpers/frequency.d.ts +1 -1
  242. package/dist/state/helpers/frequency.js +1 -1
  243. package/dist/state/index.d.ts +4 -4
  244. package/dist/state/index.js +3 -3
  245. package/dist/state/schema.d.ts +1 -1
  246. package/dist/state/schema.js +1 -1
  247. package/dist/store/example.d.ts +1 -0
  248. package/dist/store/example.js +43 -0
  249. package/dist/store/example.js.map +1 -0
  250. package/dist/store/mini-effector.d.ts +46 -0
  251. package/dist/store/mini-effector.js +88 -0
  252. package/dist/store/mini-effector.js.map +1 -0
  253. package/dist/surfaces/Surfaces.d.ts +11 -0
  254. package/dist/surfaces/Surfaces.js +361 -0
  255. package/dist/surfaces/Surfaces.js.map +1 -0
  256. package/dist/surfaces/index.d.ts +9 -0
  257. package/dist/surfaces/index.js +12 -0
  258. package/dist/surfaces/index.js.map +1 -0
  259. package/dist/surfaces/positioning.d.ts +50 -0
  260. package/dist/surfaces/positioning.js +228 -0
  261. package/dist/surfaces/positioning.js.map +1 -0
  262. package/dist/surfaces/types.d.ts +167 -0
  263. package/dist/surfaces/types.js +23 -0
  264. package/dist/surfaces/types.js.map +1 -0
  265. package/dist/telemetry/adapters/noop.d.ts +12 -0
  266. package/dist/telemetry/adapters/noop.js +42 -0
  267. package/dist/telemetry/adapters/noop.js.map +1 -0
  268. package/dist/telemetry/adapters/posthog.d.ts +8 -2
  269. package/dist/telemetry/adapters/posthog.js +36 -14
  270. package/dist/telemetry/adapters/posthog.js.map +1 -1
  271. package/dist/telemetry/index.d.ts +4 -3
  272. package/dist/telemetry/index.js +3 -2
  273. package/dist/telemetry/index.js.map +1 -1
  274. package/dist/telemetry/registry.d.ts +2 -2
  275. package/dist/telemetry/registry.js +4 -2
  276. package/dist/telemetry/registry.js.map +1 -1
  277. package/dist/telemetry/types.d.ts +1 -1
  278. package/dist/theme/ThemeProvider.d.ts +2 -2
  279. package/dist/theme/ThemeProvider.js +21 -21
  280. package/dist/theme/ThemeProvider.js.map +1 -1
  281. package/dist/theme/defaultTheme.d.ts +2 -2
  282. package/dist/theme/defaultTheme.js +111 -111
  283. package/dist/theme/defaultTheme.js.map +1 -1
  284. package/dist/theme/extractHostTheme.d.ts +1 -1
  285. package/dist/theme/extractHostTheme.js +42 -44
  286. package/dist/theme/extractHostTheme.js.map +1 -1
  287. package/dist/theme/index.d.ts +5 -5
  288. package/dist/theme/index.js +3 -3
  289. package/dist/theme/index.js.map +1 -1
  290. package/dist/theme/types.d.ts +2 -2
  291. package/dist/token.d.ts +2 -0
  292. package/dist/token.js +3 -6
  293. package/dist/token.js.map +1 -1
  294. package/dist/types-only.d.ts +32 -0
  295. package/dist/types-only.js +11 -0
  296. package/dist/types-only.js.map +1 -0
  297. package/dist/types.d.ts +89 -56
  298. package/dist/types.js +15 -2
  299. package/dist/types.js.map +1 -1
  300. package/dist/version.d.ts +13 -0
  301. package/dist/version.js +14 -0
  302. package/dist/version.js.map +1 -0
  303. package/dist/widgets/WidgetRegistry.d.ts +139 -0
  304. package/dist/widgets/WidgetRegistry.js +182 -0
  305. package/dist/widgets/WidgetRegistry.js.map +1 -0
  306. package/dist/widgets/index.d.ts +7 -0
  307. package/dist/widgets/index.js +7 -0
  308. package/dist/widgets/index.js.map +1 -0
  309. package/package.json +26 -10
  310. package/schema/canvas-config.schema.json +488 -254
  311. package/schema/runtime-context.schema.json +1 -5
package/CAPABILITIES.md CHANGED
@@ -1,611 +1,872 @@
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
+ ---
20
65
 
21
- ## Policy Tiers
66
+ ## Adaptive Packages
22
67
 
23
- The SDK uses three policy tiers to control what operations are allowed:
68
+ The SDK includes the following adaptive packages, each providing specific capabilities:
24
69
 
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
70
+ - [@syntrologie/adapt-chatbot](#syntrologieadapt-chatbot)
71
+ - [@syntrologie/adapt-content](#syntrologieadapt-content)
72
+ - [@syntrologie/adapt-faq](#syntrologieadapt-faq)
73
+ - [@syntrologie/adapt-gamification](#syntrologieadapt-gamification)
74
+ - [@syntrologie/adapt-nav](#syntrologieadapt-nav)
75
+ - [@syntrologie/adapt-navigation](#syntrologieadapt-navigation)
76
+ - [@syntrologie/adapt-overlays](#syntrologieadapt-overlays)
30
77
 
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
78
+ ---
36
79
 
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
80
+ # @syntrologie/adapt-chatbot
42
81
 
43
- ## Anchor Selectors
82
+ AI chat assistant widget with action execution capabilities.
44
83
 
45
- Anchors define how to find elements in the DOM. The SDK supports four types:
84
+ ## Widgets
85
+
86
+ ### adaptive-chatbot:assistant
87
+
88
+ Mounts an AI-powered chat assistant that connects to a backend LLM endpoint. The assistant can parse action instructions from the LLM response and execute them on the page via the runtime's ActionEngine.
89
+
90
+ **Tile config example:**
46
91
 
47
- ### 1. CSS Selector
48
92
  ```json
49
93
  {
50
- "by": "css",
51
- "value": "h1.hero-title"
94
+ "id": "chatbot-assistant",
95
+ "title": "Ask Assistant",
96
+ "content": {
97
+ "type": "custom",
98
+ "component": "adaptive-chatbot:assistant",
99
+ "props": {
100
+ "backendUrl": "/api/chat/message",
101
+ "mlflowRunId": "abc123",
102
+ "greeting": "Hi! How can I help?"
103
+ }
104
+ },
105
+ "size": "full",
106
+ "defaultExpanded": true
52
107
  }
53
108
  ```
54
- - **Use**: Standard CSS selectors
55
- - **Example**: `"h1"`, `".button-primary"`, `"#hero-section h2"`
56
109
 
57
- ### 2. Data Attribute
110
+ ### Config Props
111
+
112
+ | Property | Type | Required | Default | Description |
113
+ | ------------- | ------ | -------- | --------------------- | --------------------------------------- |
114
+ | `backendUrl` | string | Yes | — | URL for the chat API endpoint |
115
+ | `mlflowRunId` | string | No | — | MLflow run ID for experiment tracking |
116
+ | `greeting` | string | No | "Hi! How can I help?" | Initial greeting message |
117
+ | `maxHistory` | number | No | 20 | Max messages sent as history to backend |
118
+
119
+ ## Action Execution
120
+
121
+ The chatbot does **not** define its own action kinds. Instead, it parses JSON action blocks from the LLM response and executes them via `runtime.actions.applyBatch()`. This means the chatbot can trigger any action kind registered in the runtime (highlights, tooltips, text changes, navigation, tours, etc.).
122
+
123
+ ### LLM Response Format
124
+
125
+ The backend returns a text reply that may contain embedded JSON action blocks in fenced code blocks:
126
+
127
+ ```
128
+ Here's what I found. Let me highlight the pricing section for you.
129
+
130
+ \`\`\`json
131
+ {"kind": "overlays:highlight", "anchorId": "pricing-section"}
132
+ \`\`\`
133
+
134
+ Is there anything else you'd like to know?
135
+ ```
136
+
137
+ - JSON blocks with a `kind` field are extracted as actions and removed from display text
138
+ - JSON blocks without `kind` are left in the display text as-is
139
+ - Multiple action blocks can appear in a single response
140
+ - Actions with unknown kinds are silently ignored by the ActionEngine
141
+
142
+ ## Authentication
143
+
144
+ The widget reads auth credentials from the browser:
145
+
146
+ - **JWT**: `stytch_session_jwt` cookie
147
+ - **Workspace ID**: `syntrologie_workspace_id` from localStorage
148
+ - Headers: `Authorization: Bearer <jwt>` + `X-Workspace-Id: <id>`
149
+
150
+ ## Events
151
+
152
+ | Event | Props | Description |
153
+ | ------------------------- | ---------------------------- | --------------------------------- |
154
+ | `chatbot.actions_applied` | `count`, `kinds[]`, `tileId` | Emitted when actions are executed |
155
+
156
+
157
+ ---
158
+
159
+ # @syntrologie/adapt-content
160
+
161
+ DOM content modification capabilities for text, attributes, styles, HTML, and classes.
162
+
163
+ ## Actions
164
+
165
+ ### set_text
166
+
167
+ Replaces the text content of an element.
168
+
169
+ | Property | Type | Required | Description |
170
+ | ---------- | ------------ | -------- | ---------------- |
171
+ | `kind` | `"set_text"` | Yes | Action type |
172
+ | `anchorId` | string | Yes | Element selector |
173
+ | `text` | string | Yes | New text content |
174
+
58
175
  ```json
59
176
  {
60
- "by": "data",
61
- "key": "testid",
62
- "value": "hero-banner" // optional
177
+ "kind": "set_text",
178
+ "anchorId": "h1.hero-title",
179
+ "text": "Start Your Free Trial Today"
63
180
  }
64
181
  ```
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
182
 
69
- ### 3. ARIA Selector
183
+ ### set_attr
184
+
185
+ Sets an HTML attribute on an element.
186
+
187
+ | Property | Type | Required | Description |
188
+ | ---------- | ------------ | -------- | ---------------- |
189
+ | `kind` | `"set_attr"` | Yes | Action type |
190
+ | `anchorId` | string | Yes | Element selector |
191
+ | `attr` | string | Yes | Attribute name |
192
+ | `value` | string | Yes | Attribute value |
193
+
194
+ **Blocked attributes:** Event handlers (`onclick`, `onerror`, etc.) are not allowed.
195
+
70
196
  ```json
71
197
  {
72
- "by": "aria",
73
- "role": "button", // optional
74
- "label": "Submit" // optional
198
+ "kind": "set_attr",
199
+ "anchorId": "#signup-form",
200
+ "attr": "data-experiment",
201
+ "value": "signup-v2"
75
202
  }
76
203
  ```
77
- - **Use**: Target accessible elements
78
- - **Example**: Finds `<button role="button" aria-label="Submit">`
79
- - **Note**: Can use role, label, or both
80
204
 
81
- ### 4. Element Reference
205
+ ### set_style
206
+
207
+ Sets inline CSS styles on an element.
208
+
209
+ | Property | Type | Required | Description |
210
+ | ---------- | ------------- | -------- | ------------------------ |
211
+ | `kind` | `"set_style"` | Yes | Action type |
212
+ | `anchorId` | string | Yes | Element selector |
213
+ | `styles` | object | Yes | CSS property/value pairs |
214
+
82
215
  ```json
83
216
  {
84
- "by": "ref",
85
- "el": elementReference
217
+ "kind": "set_style",
218
+ "anchorId": ".hero-section",
219
+ "styles": {
220
+ "background-color": "#1e40af",
221
+ "padding": "2rem"
222
+ }
86
223
  }
87
224
  ```
88
- - **Use**: Direct element reference (programmatic use only)
89
- - **Note**: Cannot be serialized in JSON configs
90
225
 
91
- ## Operations
226
+ ### insert_html
227
+
228
+ Inserts HTML content relative to an element.
229
+
230
+ | Property | Type | Required | Description |
231
+ | ---------- | --------------- | -------- | ----------------------------------------------------------- |
232
+ | `kind` | `"insert_html"` | Yes | Action type |
233
+ | `anchorId` | string | Yes | Element selector |
234
+ | `html` | string | Yes | HTML content (sanitized) |
235
+ | `position` | string | Yes | `"before"`, `"after"`, `"prepend"`, `"append"`, `"replace"` |
236
+
237
+ **Positions:**
238
+
239
+ - `before` - Insert before the element
240
+ - `after` - Insert after the element
241
+ - `prepend` - Insert inside, before first child
242
+ - `append` - Insert inside, after last child
243
+ - `replace` - Replace the entire element
92
244
 
93
- ### 1. setText
94
- **Purpose**: Replace the text content of an element
95
245
  ```json
96
246
  {
97
- "kind": "setText",
98
- "text": "New headline text"
247
+ "kind": "insert_html",
248
+ "anchorId": ".cta-button",
249
+ "html": "<span class=\"badge\">NEW</span>",
250
+ "position": "append"
99
251
  }
100
252
  ```
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
253
 
106
- ### 2. setStyle
107
- **Purpose**: Modify CSS styles on an element
254
+ ### add_class
255
+
256
+ Adds a CSS class to an element.
257
+
258
+ | Property | Type | Required | Description |
259
+ | ----------- | ------------- | -------- | ----------------- |
260
+ | `kind` | `"add_class"` | Yes | Action type |
261
+ | `anchorId` | string | Yes | Element selector |
262
+ | `className` | string | Yes | Class name to add |
263
+
108
264
  ```json
109
265
  {
110
- "kind": "setStyle",
111
- "prop": "backgroundColor",
112
- "value": "#ff0000",
113
- "important": true // optional, adds !important
266
+ "kind": "add_class",
267
+ "anchorId": ".pricing-card",
268
+ "className": "highlighted"
114
269
  }
115
270
  ```
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
271
+
272
+ ### remove_class
273
+
274
+ Removes a CSS class from an element.
275
+
276
+ | Property | Type | Required | Description |
277
+ | ----------- | ---------------- | -------- | -------------------- |
278
+ | `kind` | `"remove_class"` | Yes | Action type |
279
+ | `anchorId` | string | Yes | Element selector |
280
+ | `className` | string | Yes | Class name to remove |
281
+
128
282
  ```json
129
283
  {
130
- "kind": "addClass",
131
- "className": "syntro-highlight"
284
+ "kind": "remove_class",
285
+ "anchorId": ".pricing-card",
286
+ "className": "hidden"
132
287
  }
133
288
  ```
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
289
 
138
- ### 4. removeClass
139
- **Purpose**: Remove a CSS class from an element
290
+
291
+ ---
292
+
293
+ # @syntrologie/adapt-faq
294
+
295
+ FAQ accordion widget with conditional item visibility.
296
+
297
+ ## Actions
298
+
299
+ ### mount_faq
300
+
301
+ Mounts an FAQ accordion widget to a surface slot.
302
+
303
+ | Property | Type | Required | Description |
304
+ | -------------- | ------------- | -------- | -------------------------------------------------------- |
305
+ | `kind` | `"mount_faq"` | Yes | Action type |
306
+ | `slot` | string | Yes | Target slot (e.g., `"drawer_right"`, `"overlay_center"`) |
307
+ | `config.title` | string | No | Widget title |
308
+ | `config.items` | array | Yes | FAQ items (see below) |
309
+
310
+ ### FAQ Item Schema
311
+
312
+ Each item in the `items` array:
313
+
314
+ | Property | Type | Required | Description |
315
+ | ---------- | ---------------- | -------- | ----------------------------------- |
316
+ | `question` | string | Yes | The question text |
317
+ | `answer` | string | Yes | The answer text (supports markdown) |
318
+ | `showWhen` | DecisionStrategy | No | Conditional visibility strategy |
319
+
140
320
  ```json
141
321
  {
142
- "kind": "removeClass",
143
- "className": "syntro-hidden"
322
+ "kind": "mount_faq",
323
+ "slot": "drawer_right",
324
+ "config": {
325
+ "title": "Frequently Asked Questions",
326
+ "items": [
327
+ {
328
+ "question": "How do I get started?",
329
+ "answer": "Sign up for a free account and follow our quickstart guide."
330
+ },
331
+ {
332
+ "question": "What payment methods do you accept?",
333
+ "answer": "We accept all major credit cards and PayPal.",
334
+ "showWhen": {
335
+ "type": "rules",
336
+ "rules": [
337
+ {
338
+ "conditions": [{ "type": "page_url", "pattern": "/pricing*" }],
339
+ "value": true
340
+ }
341
+ ],
342
+ "default": false
343
+ }
344
+ }
345
+ ]
346
+ }
144
347
  }
145
348
  ```
146
- - **Requirement**: Can only remove prefixed classes
147
- - **Common Use**: Revealing hidden elements, removing states
148
- - **Note**: Safe if class doesn't exist
149
349
 
150
- ### 5. setAttr
151
- **Purpose**: Set or remove HTML attributes
350
+ ## Compositional Pattern
351
+
352
+ The FAQ widget supports **per-item conditional visibility** using `showWhen` strategies. This allows different FAQ items to appear based on:
353
+
354
+ - Current page/route
355
+ - User segment or state
356
+ - Viewport conditions
357
+ - Any other DecisionStrategy condition
358
+
359
+ Items without `showWhen` are always visible.
360
+
361
+
362
+ ---
363
+
364
+ # @syntrologie/adapt-gamification
365
+
366
+ Gamification capabilities including badges, points, and leaderboards.
367
+
368
+ ## Actions
369
+
370
+ ### mount_gamification
371
+
372
+ Mounts gamification UI elements.
373
+
374
+ | Property | Type | Required | Description |
375
+ | -------- | ---------------------- | -------- | -------------------------- |
376
+ | `kind` | `"mount_gamification"` | Yes | Action type |
377
+ | `slot` | string | Yes | Target slot |
378
+ | `config` | object | Yes | Gamification configuration |
379
+
380
+ ### Configuration Schema
381
+
382
+ | Property | Type | Required | Default | Description |
383
+ | ----------------------------- | ------- | -------- | ------- | ---------------------- |
384
+ | `badges` | array | No | `[]` | Badge definitions |
385
+ | `points.enabled` | boolean | No | `false` | Enable points system |
386
+ | `points.multiplier` | number | No | `1` | Points multiplier |
387
+ | `leaderboard.enabled` | boolean | No | `false` | Show leaderboard |
388
+ | `leaderboard.refreshInterval` | number | No | `60000` | Refresh interval in ms |
389
+
390
+ ### Badge Schema
391
+
392
+ | Property | Type | Required | Description |
393
+ | -------------------- | ------ | -------- | ----------------------------- |
394
+ | `id` | string | Yes | Unique badge identifier |
395
+ | `name` | string | Yes | Display name |
396
+ | `icon` | string | Yes | Icon identifier |
397
+ | `description` | string | No | Badge description |
398
+ | `trigger.event` | string | Yes | Event that triggers the badge |
399
+ | `trigger.conditions` | array | No | Additional conditions |
400
+
152
401
  ```json
153
402
  {
154
- "kind": "setAttr",
155
- "name": "data-tracked",
156
- "value": "true" // or null to remove
403
+ "kind": "mount_gamification",
404
+ "slot": "overlay_corner_br",
405
+ "config": {
406
+ "badges": [
407
+ {
408
+ "id": "first-purchase",
409
+ "name": "First Purchase",
410
+ "icon": "shopping-cart",
411
+ "description": "Made your first purchase!",
412
+ "trigger": {
413
+ "event": "purchase_completed"
414
+ }
415
+ },
416
+ {
417
+ "id": "explorer",
418
+ "name": "Explorer",
419
+ "icon": "compass",
420
+ "description": "Visited 10 different pages",
421
+ "trigger": {
422
+ "event": "page_view",
423
+ "conditions": [
424
+ { "type": "session_metric", "key": "unique_pages", "operator": ">=", "threshold": 10 }
425
+ ]
426
+ }
427
+ }
428
+ ],
429
+ "points": {
430
+ "enabled": true,
431
+ "multiplier": 2
432
+ },
433
+ "leaderboard": {
434
+ "enabled": true,
435
+ "refreshInterval": 30000
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
+ ## Use Cases
442
+
443
+ - **Engagement rewards**: Award badges for completing onboarding steps
444
+ - **Loyalty programs**: Track points for purchases or interactions
445
+ - **Social proof**: Display leaderboards to encourage participation
446
+ - **Progress tracking**: Show achievement progress to motivate users
447
+
448
+
449
+ ---
450
+
451
+ # @syntrologie/adapt-nav
452
+
453
+ Navigation link list widget with conditional item visibility.
454
+
455
+ ## Actions
456
+
457
+ ### mount_nav
458
+
459
+ Mounts a navigation link list widget to a surface slot.
460
+
461
+ | Property | Type | Required | Description |
462
+ | -------------- | ------------- | -------- | ---------------------------------------------------------- |
463
+ | `kind` | `"mount_nav"` | Yes | Action type |
464
+ | `slot` | string | Yes | Target slot (e.g., `"drawer_left"`, `"inline:{anchorId}"`) |
465
+ | `config.title` | string | No | Widget title |
466
+ | `config.items` | array | Yes | Navigation items (see below) |
467
+
468
+ ### Nav Item Schema
469
+
470
+ Each item in the `items` array:
471
+
472
+ | Property | Type | Required | Description |
473
+ | ---------- | ---------------- | -------- | ------------------------------- |
474
+ | `label` | string | Yes | Link text |
475
+ | `href` | string | Yes | Link destination |
476
+ | `icon` | string | No | Icon identifier |
477
+ | `showWhen` | DecisionStrategy | No | Conditional visibility strategy |
478
+
170
479
  ```json
171
480
  {
172
- "kind": "append",
173
- "html": "<span class='syntro-badge'>NEW</span>"
481
+ "kind": "mount_nav",
482
+ "slot": "drawer_left",
483
+ "config": {
484
+ "title": "Quick Links",
485
+ "items": [
486
+ {
487
+ "label": "Dashboard",
488
+ "href": "/dashboard",
489
+ "icon": "home"
490
+ },
491
+ {
492
+ "label": "Admin Settings",
493
+ "href": "/admin",
494
+ "icon": "settings",
495
+ "showWhen": {
496
+ "type": "rules",
497
+ "rules": [
498
+ {
499
+ "conditions": [{ "type": "state_equals", "key": "user.role", "value": "admin" }],
500
+ "value": true
501
+ }
502
+ ],
503
+ "default": false
504
+ }
505
+ },
506
+ {
507
+ "label": "Upgrade",
508
+ "href": "/pricing",
509
+ "icon": "sparkles",
510
+ "showWhen": {
511
+ "type": "rules",
512
+ "rules": [
513
+ {
514
+ "conditions": [{ "type": "state_equals", "key": "user.plan", "value": "free" }],
515
+ "value": true
516
+ }
517
+ ],
518
+ "default": false
519
+ }
520
+ }
521
+ ]
522
+ }
174
523
  }
175
524
  ```
176
- - **Effect**: Inserts after existing children
177
- - **Common Use**: Adding badges, icons, supplementary content
178
- - **Note**: HTML is sanitized for safety
179
525
 
180
- ### 7. prepend
181
- **Purpose**: Add HTML content at the beginning of an element
526
+ ## Compositional Pattern
527
+
528
+ The nav widget supports **per-item conditional visibility** using `showWhen` strategies. This allows different navigation items to appear based on:
529
+
530
+ - User role or permissions
531
+ - Subscription tier
532
+ - Feature flags
533
+ - Any other DecisionStrategy condition
534
+
535
+ Items without `showWhen` are always visible.
536
+
537
+
538
+ ---
539
+
540
+ # @syntrologie/adapt-navigation
541
+
542
+ Navigation capabilities for scrolling and URL navigation.
543
+
544
+ ## Actions
545
+
546
+ ### scroll_to
547
+
548
+ Scrolls the viewport to bring an element into view.
549
+
550
+ | Property | Type | Required | Default | Description |
551
+ | ---------- | ------------- | -------- | ----------- | ------------------------------------------- |
552
+ | `kind` | `"scroll_to"` | Yes | | Action type |
553
+ | `anchorId` | string | Yes | | Element selector |
554
+ | `behavior` | string | No | `"smooth"` | `"smooth"`, `"instant"`, `"auto"` |
555
+ | `block` | string | No | `"center"` | `"start"`, `"center"`, `"end"`, `"nearest"` |
556
+ | `inline` | string | No | `"nearest"` | `"start"`, `"center"`, `"end"`, `"nearest"` |
557
+
558
+ ```json
559
+ {
560
+ "kind": "scroll_to",
561
+ "anchorId": "#pricing-section",
562
+ "behavior": "smooth",
563
+ "block": "start"
564
+ }
565
+ ```
566
+
567
+ ### navigate
568
+
569
+ Navigates to a URL.
570
+
571
+ | Property | Type | Required | Default | Description |
572
+ | -------- | ------------ | -------- | --------- | ----------------------- |
573
+ | `kind` | `"navigate"` | Yes | | Action type |
574
+ | `url` | string | Yes | | Destination URL |
575
+ | `target` | string | No | `"_self"` | `"_self"` or `"_blank"` |
576
+
182
577
  ```json
183
578
  {
184
- "kind": "prepend",
185
- "html": "<span class='syntro-icon'>★</span>"
579
+ "kind": "navigate",
580
+ "url": "/signup?ref=banner",
581
+ "target": "_self"
186
582
  }
187
583
  ```
188
- - **Effect**: Inserts before existing children
189
- - **Common Use**: Adding icons, prefixes, notifications
190
- - **Note**: HTML is sanitized for safety
191
584
 
192
- ### 8. insertAdjacent
193
- **Purpose**: Insert HTML at specific positions relative to element
585
+ **Note:** `javascript:` URLs are blocked for security.
586
+
587
+
588
+ ---
589
+
590
+ # @syntrologie/adapt-overlays
591
+
592
+ Visual overlay capabilities including highlights, tooltips, badges, and pulse animations.
593
+
594
+ ## Actions
595
+
596
+ ### highlight
597
+
598
+ Creates a spotlight effect around an element with a scrim overlay.
599
+
600
+ | Property | Type | Required | Default | Description |
601
+ | -------------------- | ------------- | -------- | ----------- | --------------------------------------- |
602
+ | `kind` | `"highlight"` | Yes | | Action type |
603
+ | `anchorId` | string | Yes | | Element selector |
604
+ | `style.color` | string | No | `"#5b8cff"` | Ring color |
605
+ | `style.scrimOpacity` | number | No | `0.55` | Backdrop opacity 0-1 (set to 0 to hide) |
606
+ | `style.paddingPx` | number | No | `12` | Space around element |
607
+ | `style.radiusPx` | number | No | `12` | Ring corner radius |
608
+
194
609
  ```json
195
610
  {
196
- "kind": "insertAdjacent",
197
- "where": "afterbegin",
198
- "html": "<div class='syntro-alert'>Important!</div>"
611
+ "kind": "highlight",
612
+ "anchorId": "#signup-button",
613
+ "style": {
614
+ "color": "#22c55e",
615
+ "scrimOpacity": 0.4
616
+ }
199
617
  }
200
618
  ```
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
619
 
209
- ## Overlays
620
+ ### tooltip
621
+
622
+ Shows a tooltip near an element with optional title, body, and CTA.
210
623
 
211
- The SDK supports overlay elements that don't modify the DOM. These are defined in `overlayRecipe.steps`:
624
+ | Property | Type | Required | Default | Description |
625
+ | -------------------- | ----------- | -------- | ------------- | ----------------------------------- |
626
+ | `kind` | `"tooltip"` | Yes | | Action type |
627
+ | `anchorId` | string | Yes | | Element selector |
628
+ | `content.title` | string | No | | Tooltip heading |
629
+ | `content.body` | string | Yes | | Tooltip text |
630
+ | `content.cta.label` | string | No | | CTA button text |
631
+ | `content.cta.action` | Action | No | | Action to execute on CTA click |
632
+ | `trigger` | string | No | `"immediate"` | `"immediate"`, `"hover"`, `"click"` |
633
+ | `placement` | string | No | `"top"` | See placement options below |
212
634
 
213
- ### Tooltips
214
- Floating information bubbles that appear near target elements.
635
+ **Placement options:** `top`, `top-start`, `top-end`, `bottom`, `bottom-start`, `bottom-end`, `left`, `left-start`, `left-end`, `right`, `right-start`, `right-end`
215
636
 
216
637
  ```json
217
638
  {
218
639
  "kind": "tooltip",
219
- "id": "feature_tooltip_1",
220
- "anchor": {
221
- "by": "css",
222
- "value": ".pricing-card"
223
- },
640
+ "anchorId": "#pricing-toggle",
224
641
  "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"
642
+ "title": "Save 20%",
643
+ "body": "Switch to annual billing to save on your subscription.",
644
+ "cta": {
645
+ "label": "Switch Now",
646
+ "action": { "kind": "navigate", "url": "/billing?annual=true" }
234
647
  }
235
- ],
236
- "dismiss": {
237
- "onEsc": true,
238
- "closeButton": true
239
- }
648
+ },
649
+ "placement": "bottom",
650
+ "trigger": "immediate"
240
651
  }
241
652
  ```
242
653
 
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.
654
+ ### badge
655
+
656
+ Adds a small badge indicator near an element.
657
+
658
+ | Property | Type | Required | Default | Description |
659
+ | ---------- | --------- | -------- | ------------- | -------------------------------------------------------------- |
660
+ | `kind` | `"badge"` | Yes | | Action type |
661
+ | `anchorId` | string | Yes | | Element selector |
662
+ | `text` | string | Yes | | Badge text (e.g., "NEW", "3") |
663
+ | `position` | string | No | `"top-right"` | `"top-left"`, `"top-right"`, `"bottom-left"`, `"bottom-right"` |
260
664
 
261
665
  ```json
262
666
  {
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
- }
667
+ "kind": "badge",
668
+ "anchorId": "#inbox-icon",
669
+ "text": "5",
670
+ "position": "top-right"
283
671
  }
284
672
  ```
285
673
 
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
674
+ ### pulse
299
675
 
300
- ### Overlay Recipe Structure
676
+ Adds a pulsing animation to draw attention.
301
677
 
302
- Overlays are defined in `overlayRecipe`:
678
+ | Property | Type | Required | Default | Description |
679
+ | ---------- | --------- | -------- | ------- | ------------------------ |
680
+ | `kind` | `"pulse"` | Yes | | Action type |
681
+ | `anchorId` | string | Yes | | Element selector |
682
+ | `duration` | number | No | `2000` | Animation duration in ms |
303
683
 
304
684
  ```json
305
685
  {
306
- "overlayRecipe": {
307
- "id": "onboarding_flow",
308
- "version": 1,
309
- "routes": ["/dashboard", "/home"],
310
- "steps": [
311
- { "kind": "tooltip", ... },
312
- { "kind": "highlight", ... }
313
- ]
314
- }
686
+ "kind": "pulse",
687
+ "anchorId": ".notification-bell",
688
+ "duration": 3000
315
689
  }
316
690
  ```
317
691
 
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
692
+ ### modal
322
693
 
323
- ## Runtime v2 Providers
694
+ Shows a centered modal dialog with optional CTA buttons.
324
695
 
325
- The v2 runtime provides adaptives with a unified interface for context, events, state, and decisions.
696
+ | Property | Type | Required | Default | Description |
697
+ | --------------------- | ------------------ | -------- | ------- | ------------------------------------------------------------- |
698
+ | `kind` | `"overlays:modal"` | Yes | | Action type |
699
+ | `content.title` | string | No | | Modal heading |
700
+ | `content.body` | string | Yes | | Modal text |
701
+ | `size` | string | No | `"md"` | `"sm"`, `"md"`, `"lg"` |
702
+ | `blocking` | boolean | No | `false` | Block page interaction |
703
+ | `scrim.opacity` | number | No | `0.6` | Backdrop opacity 0-1 |
704
+ | `dismiss.onEsc` | boolean | No | `true` | Close on Escape key |
705
+ | `dismiss.closeButton` | boolean | No | `true` | Show close button |
706
+ | `dismiss.timeoutMs` | number | No | | Auto-close after timeout |
707
+ | `ctaButtons` | array | No | | Array of CTA buttons |
708
+ | `waitFor` | string | No | | When to complete: `"dismissed"`, `"cta-click"`, `"timeout:N"` |
326
709
 
327
- ### SmartCanvasRuntime
710
+ **CTA Button properties:**
328
711
 
329
- Every adaptive receives a `runtime` object:
712
+ - `label`: Button text
713
+ - `actionId`: Identifier for the action
714
+ - `primary`: Whether this is a primary button (default: false)
330
715
 
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
- };
716
+ ```json
717
+ {
718
+ "kind": "overlays:modal",
719
+ "content": {
720
+ "title": "Welcome!",
721
+ "body": "Thanks for signing up. Let us show you around."
722
+ },
723
+ "size": "md",
724
+ "ctaButtons": [
725
+ { "label": "Skip", "actionId": "skip" },
726
+ { "label": "Start Tour", "actionId": "start", "primary": true }
727
+ ],
728
+ "waitFor": "cta-click"
729
+ }
340
730
  ```
341
731
 
342
- ### Usage
343
732
 
344
- ```typescript
345
- const { canvas, runtime } = await Syntro.init({
346
- token: "syn_..."
347
- });
733
+ ---
348
734
 
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
- ```
355
735
 
356
- ### Context Manager
736
+ ---
357
737
 
358
- Subscribe to runtime context changes:
738
+ ## Surfaces
359
739
 
360
- ```typescript
361
- const unsubscribe = runtime.context.subscribe((ctx, prev) => {
362
- if (ctx.page.url !== prev.page.url) {
363
- // Handle route change
364
- }
365
- });
740
+ Surfaces are named slots where widgets can be mounted. Use the `mount_widget` action to render content into a surface slot.
366
741
 
367
- const ctx = runtime.context.get();
368
- console.log(ctx.page.url, ctx.session.sessionId);
369
- ```
742
+ ### Static Slots
370
743
 
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
- ```
744
+ Fixed-position slots for common UI patterns:
395
745
 
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
- ```
746
+ | Slot | Position | Use Case |
747
+ |------|----------|----------|
748
+ | `drawer_right` | Right edge, full height | Settings panels, details |
749
+ | `drawer_left` | Left edge, full height | Navigation, filters |
750
+ | `drawer_bottom` | Bottom edge, full width | Action sheets, keyboards |
751
+ | `overlay_center` | Centered modal | Dialogs, confirmations |
752
+ | `overlay_corner_br` | Bottom-right corner | Chat widgets, help |
753
+ | `overlay_corner_bl` | Bottom-left corner | Notifications |
754
+ | `toast_top` | Top center | Success/error messages |
755
+ | `toast_bottom` | Bottom center | Snackbar notifications |
406
756
 
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 |
757
+ ### Dynamic Slots
417
758
 
418
- ### State Store
759
+ Slots that position content relative to anchors:
419
760
 
420
- Persist state across sessions:
761
+ - **Inline Slots**: Render content inside an anchor element. Format: `inline:{anchorId}`
762
+ - **Adjacent Slots**: Render content positioned near an anchor element. Format: `adjacent:{anchorId}`
421
763
 
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");
764
+ ---
426
765
 
427
- // User storage (persists across sessions)
428
- runtime.state.user.set("onboardingComplete", true);
766
+ ## Anchor Resolution
429
767
 
430
- // Namespace by adaptive
431
- const ns = runtime.state.ns("my-adaptive");
432
- ns.session.set("step", 3);
433
- ```
768
+ The `anchorId` field identifies which DOM element to target. Multiple formats are supported:
434
769
 
435
- **Built-in Helpers:**
436
- ```typescript
437
- // Track dismissals
438
- runtime.state.dismissals.mark("tooltip-1");
439
- if (runtime.state.dismissals.isDismissed("tooltip-1")) { ... }
770
+ ### CSS Selectors
440
771
 
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")) { ... }
772
+ | Format | Example | Matches |
773
+ |--------|---------|---------|
774
+ | Class | `".hero-title"` | `<h1 class="hero-title">` |
775
+ | ID | `"#signup-button"` | `<button id="signup-button">` |
776
+ | Attribute | `"[data-testid='cta']"` | `<button data-testid="cta">` |
777
+ | Tag + class | `"h1.page-title"` | `<h1 class="page-title">` |
778
+ | Nested | `".card .title"` | `<div class="title">` inside `.card` |
444
779
 
445
- // Frequency caps
446
- runtime.state.frequency.increment("upsell-shown");
447
- if (runtime.state.frequency.count("upsell-shown") >= 3) { ... }
448
- ```
780
+ ### Data Attributes (Recommended)
449
781
 
450
- ### Decision Strategies
782
+ For stability, add `data-syntro-anchor` attributes to target elements:
451
783
 
452
- Tile activation uses `DecisionStrategy` for conditional rendering:
784
+ ```html
785
+ <h1 data-syntro-anchor="hero-title">Welcome</h1>
786
+ ```
787
+
788
+ Then reference by the anchor name:
453
789
 
454
790
  ```json
455
- {
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
- }
470
- ],
471
- "default": false
472
- }
473
- }
474
- }
791
+ { "anchorId": "hero-title" }
475
792
  ```
476
793
 
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 |
489
- | `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 |
794
+ ---
498
795
 
499
- ### Migration from v1
796
+ ## Decision Strategies
500
797
 
501
- The new `activation` field replaces the deprecated `experiment` field:
798
+ Control when adaptives activate using `DecisionStrategy`:
502
799
 
503
- **Before (v1):**
504
- ```json
505
- {
506
- "experiment": {
507
- "featureKey": "my-feature",
508
- "variationId": 1
509
- }
510
- }
511
- ```
800
+ ### Rules Strategy
512
801
 
513
- **After (v2):**
514
802
  ```json
515
803
  {
516
- "activation": {
517
- "strategy": {
518
- "type": "rules",
519
- "rules": [
520
- {
521
- "conditions": [
522
- { "type": "route", "routeId": "/dashboard" }
523
- ],
524
- "value": true
525
- }
804
+ "type": "rules",
805
+ "rules": [
806
+ {
807
+ "conditions": [
808
+ { "type": "page_url", "pattern": "/pricing*" },
809
+ { "type": "viewport", "minWidth": 768 },
810
+ { "type": "dismissed", "key": "promo", "inverted": true }
526
811
  ],
527
- "default": false
812
+ "value": true
528
813
  }
529
- }
814
+ ],
815
+ "default": false
530
816
  }
531
817
  ```
532
818
 
819
+ ### Condition Types
820
+
821
+ | Type | Parameters | Description |
822
+ |------|------------|-------------|
823
+ | `page_url` | `pattern` | URL matches glob pattern |
824
+ | `route` | `routeId` | Route ID matches |
825
+ | `anchor_visible` | `anchorId`, `state` | Anchor visibility |
826
+ | `event_occurred` | `eventName`, `withinMs?` | Recent event |
827
+ | `state_equals` | `key`, `value` | State matches |
828
+ | `viewport` | `minWidth?`, `maxWidth?`, `minHeight?`, `maxHeight?` | Viewport size |
829
+ | `session_metric` | `key`, `operator`, `threshold` | Metric comparison |
830
+ | `dismissed` | `key`, `inverted?` | Dismissal check |
831
+ | `cooldown_active` | `key`, `inverted?` | Cooldown check |
832
+ | `frequency_limit` | `key`, `limit`, `inverted?` | Frequency cap |
833
+
834
+ ---
835
+
533
836
  ## Best Practices
534
837
 
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
838
+ ### 1. Choose the Right Action Type
539
839
 
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
840
+ | Goal | Action |
841
+ |------|--------|
842
+ | Change text | `set_text` |
843
+ | Add visual indicator | `badge`, `pulse` |
844
+ | Show help text | `tooltip` |
845
+ | Draw attention | `highlight` |
846
+ | Add content | `insert_html` |
847
+ | Navigate user | `scroll_to`, `navigate` |
848
+ | Show UI panel | `mount_widget` + Surfaces |
544
849
 
545
- ### 3. Operation Order
546
- - Operations are applied in array order
547
- - Plan sequences carefully (e.g., addClass before setStyle)
548
- - Test operation combinations
850
+ ### 2. Anchor Selection
549
851
 
550
- ### 4. Performance
551
- - Batch related patches together
552
- - Avoid selecting too many elements
553
- - Use CSS classes instead of inline styles when possible
852
+ - **Prefer stable selectors:** `[data-testid]`, IDs over classes
853
+ - **Test across states:** Element may not exist on all pages
854
+ - **Be specific:** Avoid selectors matching multiple elements
554
855
 
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
856
+ ### 3. Reversibility
559
857
 
560
- ## Example: Complete A/B Test
858
+ - Always store handles if you need to revert
859
+ - Use `applyBatch` for related changes (atomic rollback)
860
+ - Clean up on route changes
561
861
 
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
- ```
862
+ ### 4. Performance
863
+
864
+ - Batch related actions together
865
+ - Use `validate()` to check actions before applying
866
+ - Avoid applying many actions simultaneously
867
+
868
+ ### 5. Events
607
869
 
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
870
+ - Listen for `action.failed` to handle errors
871
+ - Track `action.applied` for analytics
872
+ - Use EventBus for cross-adaptive coordination