@syntrologie/runtime-sdk 0.2.21 → 1.0.0

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