@picked/client 0.0.1-dev.1785305085063 → 0.0.1-dev.1785908062293

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 (291) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/api-03c5jvVk.js +38 -0
  3. package/dist/api-CQxCVIrm.cjs +1 -0
  4. package/dist/cjs/form/index.js +1 -1
  5. package/dist/cjs/index.js +2 -2
  6. package/dist/cjs/plugins/dom-observer/index.js +1 -1
  7. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  8. package/dist/cjs/server/index.js +1 -1
  9. package/dist/docs/explanation/conditions-and-the-logic-engine.md +94 -0
  10. package/dist/docs/explanation/feature-flag-allocation.md +176 -0
  11. package/dist/docs/explanation/how-picked-works.md +124 -0
  12. package/dist/docs/explanation/how-pickedql-works.md +76 -0
  13. package/dist/docs/explanation/pages-and-flows.md +64 -0
  14. package/dist/docs/explanation/the-content-model.md +77 -0
  15. package/dist/docs/explanation/the-two-apis.md +68 -0
  16. package/dist/docs/explanation/why-server-side-rendering.md +76 -0
  17. package/dist/docs/explanation/workspaces-and-versions.md +71 -0
  18. package/dist/docs/how-to/add-a-step-field-or-action.md +43 -0
  19. package/dist/docs/{pickedql/examples.md → how-to/analytics/index.md} +53 -28
  20. package/dist/docs/how-to/archive-or-delete-a-workspace.md +46 -0
  21. package/dist/docs/how-to/authenticate-with-a-magic-link.md +40 -0
  22. package/dist/docs/how-to/authenticate-with-a-password.md +51 -0
  23. package/dist/docs/how-to/authenticate-with-an-api-key.md +51 -0
  24. package/dist/docs/how-to/authenticate-with-oauth.md +40 -0
  25. package/dist/docs/how-to/build-a-flow.md +83 -0
  26. package/dist/docs/how-to/connect-the-mcp-server.md +38 -0
  27. package/dist/docs/how-to/create-a-project.md +29 -0
  28. package/dist/docs/how-to/create-an-experience.md +42 -0
  29. package/dist/docs/how-to/create-and-revoke-an-api-key.md +37 -0
  30. package/dist/docs/how-to/debug-with-the-chrome-extension.md +57 -0
  31. package/dist/docs/how-to/define-a-page-layout.md +39 -0
  32. package/dist/docs/how-to/fill-and-publish-a-page.md +50 -0
  33. package/dist/docs/how-to/install-the-gtm-template.md +84 -0
  34. package/dist/docs/how-to/install-the-sdk.md +68 -0
  35. package/dist/docs/how-to/install-the-vite-plugin.md +56 -0
  36. package/dist/docs/how-to/invite-and-remove-members.md +34 -0
  37. package/dist/docs/how-to/preview-a-workspace.md +28 -0
  38. package/dist/docs/how-to/publish-a-workspace.md +30 -0
  39. package/dist/docs/how-to/read-a-feature-flag.md +101 -0
  40. package/dist/docs/how-to/register-a-component.md +55 -0
  41. package/dist/docs/how-to/register-an-action-handler.md +67 -0
  42. package/dist/docs/how-to/render-a-step-form.md +132 -0
  43. package/dist/docs/how-to/render-an-unpublished-preview.md +41 -0
  44. package/dist/docs/how-to/reuse-a-section.md +37 -0
  45. package/dist/docs/how-to/reuse-a-step-field-or-action.md +28 -0
  46. package/dist/docs/how-to/roll-back-a-published-version.md +26 -0
  47. package/dist/docs/how-to/run-an-ab-test.md +49 -0
  48. package/dist/docs/how-to/run-code-on-a-form-submission.md +125 -0
  49. package/dist/docs/how-to/server-render-a-page.md +81 -0
  50. package/dist/docs/how-to/set-a-content-security-policy.md +32 -0
  51. package/dist/docs/how-to/set-up-mfa.md +69 -0
  52. package/dist/docs/how-to/upload-and-use-an-asset.mdx +73 -0
  53. package/dist/docs/how-to/validate-a-field.md +52 -0
  54. package/dist/docs/how-to/write-a-condition.md +45 -0
  55. package/dist/docs/how-to/write-a-predicate.md +60 -0
  56. package/dist/docs/index.md +41 -59
  57. package/dist/docs/reference/dashboard/actions.mdx +53 -0
  58. package/dist/docs/reference/dashboard/api-keys.mdx +37 -0
  59. package/dist/docs/reference/dashboard/conditions.mdx +42 -0
  60. package/dist/docs/reference/dashboard/content/asset-library.mdx +63 -0
  61. package/dist/docs/reference/dashboard/content/content-entries.mdx +44 -0
  62. package/dist/docs/reference/dashboard/content/layouts.mdx +62 -0
  63. package/dist/docs/reference/dashboard/content/pages.mdx +35 -0
  64. package/dist/docs/reference/dashboard/content/sections.mdx +28 -0
  65. package/dist/docs/reference/dashboard/experiences.mdx +51 -0
  66. package/dist/docs/reference/dashboard/features.mdx +114 -0
  67. package/dist/docs/reference/dashboard/fields.mdx +100 -0
  68. package/dist/docs/reference/dashboard/flow-builder/canvas.mdx +58 -0
  69. package/dist/docs/reference/dashboard/flow-builder/condition-builder.md +57 -0
  70. package/dist/docs/reference/dashboard/flow-builder/keyboard-shortcuts.md +72 -0
  71. package/dist/docs/reference/dashboard/flow-builder/nodes.md +63 -0
  72. package/dist/docs/reference/dashboard/projects.mdx +44 -0
  73. package/dist/docs/reference/dashboard/reports.mdx +43 -0
  74. package/dist/docs/reference/dashboard/steps.mdx +37 -0
  75. package/dist/docs/reference/dashboard/team-management.md +29 -0
  76. package/dist/docs/reference/dashboard/workspace-versioning.mdx +30 -0
  77. package/dist/docs/reference/dashboard/workspaces.mdx +76 -0
  78. package/dist/docs/{delivery-api → reference/delivery-api}/experience-content.md +17 -5
  79. package/dist/docs/reference/delivery-api/index.md +45 -0
  80. package/dist/docs/reference/delivery-api/preview-caching-crawling.md +79 -0
  81. package/dist/docs/{delivery-api → reference/delivery-api}/project-config.md +15 -6
  82. package/dist/docs/reference/glossary.md +173 -0
  83. package/dist/docs/reference/management-api/authentication.md +74 -0
  84. package/dist/docs/{api → reference/management-api}/authorization-and-scopes.md +70 -47
  85. package/dist/docs/reference/management-api/error-handling.md +233 -0
  86. package/dist/docs/{api → reference/management-api}/pagination.md +36 -20
  87. package/dist/docs/reference/pickedql/data-sources.md +174 -0
  88. package/dist/docs/reference/pickedql/dimensions.md +87 -0
  89. package/dist/docs/{pickedql → reference/pickedql}/filtering.md +17 -6
  90. package/dist/docs/reference/pickedql/index.md +18 -0
  91. package/dist/docs/reference/pickedql/schema.md +172 -0
  92. package/dist/docs/reference/pickedql/virtual-functions.md +237 -0
  93. package/dist/docs/reference/sdk/actions.md +30 -0
  94. package/dist/docs/{sdk → reference/sdk}/conditions.md +15 -5
  95. package/dist/docs/reference/sdk/content-security-policy.md +21 -0
  96. package/dist/docs/reference/sdk/experiences.md +51 -0
  97. package/dist/docs/reference/sdk/features.md +233 -0
  98. package/dist/docs/reference/sdk/fields.md +209 -0
  99. package/dist/docs/reference/sdk/index.md +68 -0
  100. package/dist/docs/{sdk → reference/sdk}/plugins/create-experience.md +25 -34
  101. package/dist/docs/reference/sdk/plugins/dynamic-experiences.md +74 -0
  102. package/dist/docs/reference/sdk/plugins/index.md +29 -0
  103. package/dist/docs/{sdk → reference/sdk}/plugins/window-object.md +6 -0
  104. package/dist/docs/reference/sdk/predicates.md +43 -0
  105. package/dist/docs/reference/sdk/ssr/index.md +33 -0
  106. package/dist/docs/reference/sdk/ssr/rendering-and-hydration.md +103 -0
  107. package/dist/docs/reference/sdk/ssr/serialize.md +77 -0
  108. package/dist/docs/reference/sdk/ssr/slots-and-placements.md +59 -0
  109. package/dist/docs/reference/sdk/steps.md +87 -0
  110. package/dist/docs/reference/tools/chrome-extension.md +16 -0
  111. package/dist/docs/reference/tools/image-optimisation-service.md +104 -0
  112. package/dist/docs/reference/tools/mcp.md +88 -0
  113. package/dist/docs/reference/tools/vite-plugin.md +88 -0
  114. package/dist/docs/tutorials/first-api-call.md +228 -0
  115. package/dist/docs/tutorials/first-experience-in-the-dashboard.mdx +143 -0
  116. package/dist/docs/tutorials/first-experience.mdx +255 -0
  117. package/dist/docs/tutorials/first-server-rendered-page.mdx +186 -0
  118. package/dist/{dom-BwmTHeT7.js → dom-BqcToNL0.js} +1 -3
  119. package/dist/{dom-CDrHc_j9.cjs → dom-DlLH3FxV.cjs} +1 -1
  120. package/dist/esm/form/index.js +2 -4
  121. package/dist/esm/index.js +225 -186
  122. package/dist/esm/plugins/dom-observer/index.js +1 -1
  123. package/dist/esm/plugins/dynamic-experiences/index.js +3 -3
  124. package/dist/esm/preview/index.js +1 -1
  125. package/dist/esm/server/index.js +33 -31
  126. package/dist/{experience-instance-DOJShRk5.js → experience-instance-DGk11d8a.js} +3 -3
  127. package/dist/experience-instance-Dej3P1sE.cjs +1 -0
  128. package/dist/plugin-CHOVsx7Z.js +287 -0
  129. package/dist/plugin-DsaGXQHx.cjs +1 -0
  130. package/dist/{preview-channel-DW9J8t4B.js → preview-channel-CSr96XAo.js} +1 -1
  131. package/dist/{protocol-DSqOlW2r.js → protocol-C5KkkiZc.js} +1 -1
  132. package/dist/types/client.d.ts +7 -6
  133. package/dist/types/data-source/api.d.ts +60 -0
  134. package/dist/types/data-source/api.d.ts.map +1 -0
  135. package/dist/types/data-source/provider.d.ts +97 -0
  136. package/dist/types/data-source/provider.d.ts.map +1 -0
  137. package/dist/types/entities/action/action.d.ts +5 -4
  138. package/dist/types/entities/action/action.d.ts.map +1 -1
  139. package/dist/types/entities/component/component.d.ts +6 -5
  140. package/dist/types/entities/component/component.d.ts.map +1 -1
  141. package/dist/types/entities/condition/condition.d.ts +5 -5
  142. package/dist/types/entities/content/content.d.ts +4 -3
  143. package/dist/types/entities/experience/experience.d.ts +11 -10
  144. package/dist/types/entities/experience/experience.d.ts.map +1 -1
  145. package/dist/types/entities/experience-instance/experience-instance.d.ts +10 -9
  146. package/dist/types/entities/feature/feature.d.ts +19 -2
  147. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  148. package/dist/types/entities/field/field.d.ts +7 -6
  149. package/dist/types/entities/framework/framework.d.ts +7 -6
  150. package/dist/types/entities/framework/framework.d.ts.map +1 -1
  151. package/dist/types/entities/history/history.d.ts +6 -6
  152. package/dist/types/entities/plugin/plugin.d.ts +3 -2
  153. package/dist/types/entities/plugin/plugin.d.ts.map +1 -1
  154. package/dist/types/entities/predicate/predicate.d.ts +4 -3
  155. package/dist/types/entities/predicate/predicate.d.ts.map +1 -1
  156. package/dist/types/entities/response/response-validation.d.ts +3 -2
  157. package/dist/types/entities/response/response.d.ts +12 -11
  158. package/dist/types/entities/session/session.d.ts +2 -1
  159. package/dist/types/entities/step/step.d.ts +6 -5
  160. package/dist/types/exports.d.ts +2 -1
  161. package/dist/types/exports.d.ts.map +1 -1
  162. package/dist/types/form/build-responses.d.ts +2 -1
  163. package/dist/types/form/create-field-evaluator.d.ts +4 -3
  164. package/dist/types/form/create-field-validation.d.ts +5 -4
  165. package/dist/types/form/create-form.d.ts +7 -6
  166. package/dist/types/form/create-submit-pipeline.d.ts +7 -6
  167. package/dist/types/form/get-step-fields.d.ts +3 -2
  168. package/dist/types/lifecycle/condition-selection.d.ts +14 -4
  169. package/dist/types/lifecycle/lifecycle.d.ts +4 -3
  170. package/dist/types/lifecycle/rules-logic.d.ts +4 -3
  171. package/dist/types/lifecycle/state-snapshot.d.ts +4 -3
  172. package/dist/types/picked.d.ts +79 -62
  173. package/dist/types/picked.d.ts.map +1 -1
  174. package/dist/types/ping.d.ts +1 -1
  175. package/dist/types/ping.d.ts.map +1 -1
  176. package/dist/types/plugins/create-experience/create-experience.d.ts +9 -8
  177. package/dist/types/plugins/create-experience/create-experience.d.ts.map +1 -1
  178. package/dist/types/plugins/create-experience/plugin.d.ts +3 -2
  179. package/dist/types/plugins/create-experience/plugin.d.ts.map +1 -1
  180. package/dist/types/plugins/create-experience/serialization.d.ts.map +1 -1
  181. package/dist/types/plugins/dom-observer/plugin.d.ts +1 -1
  182. package/dist/types/plugins/dynamic-experiences/hash.d.ts +40 -5
  183. package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
  184. package/dist/types/plugins/dynamic-experiences/index.d.ts +2 -0
  185. package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
  186. package/dist/types/plugins/dynamic-experiences/plugin.d.ts +4 -36
  187. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  188. package/dist/types/plugins/dynamic-experiences/types.d.ts +42 -11
  189. package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
  190. package/dist/types/plugins/experience-browser-storage/plugin.d.ts +1 -1
  191. package/dist/types/plugins/experience-browser-storage/plugin.d.ts.map +1 -1
  192. package/dist/types/plugins/history-browser-storage/plugin.d.ts +1 -1
  193. package/dist/types/plugins/history-browser-storage/plugin.d.ts.map +1 -1
  194. package/dist/types/plugins/responses-browser-storage/plugin.d.ts +1 -1
  195. package/dist/types/plugins/responses-browser-storage/plugin.d.ts.map +1 -1
  196. package/dist/types/plugins/responses-indexeddb/plugin.d.ts +1 -1
  197. package/dist/types/plugins/single-page-app/plugin.d.ts +1 -1
  198. package/dist/types/plugins/single-page-app/plugin.d.ts.map +1 -1
  199. package/dist/types/plugins/web-components/plugin.d.ts +2 -1
  200. package/dist/types/plugins/window-object/plugin.d.ts +2 -2
  201. package/dist/types/preview-channel.d.ts +5 -1
  202. package/dist/types/preview-receiver/index.d.ts +18 -1
  203. package/dist/types/preview-receiver/protocol.d.ts +10 -1
  204. package/dist/types/server/index.d.ts +3 -3
  205. package/dist/types/server/serialize.d.ts +8 -2
  206. package/dist/types/server/serialize.d.ts.map +1 -1
  207. package/dist/types/utils/dom.d.ts +2 -1
  208. package/dist/types/utils/events.d.ts +6 -6
  209. package/dist/types/utils/events.d.ts.map +1 -1
  210. package/dist/types/utils/global-obj.d.ts +1 -1
  211. package/dist/types/utils/global-obj.d.ts.map +1 -1
  212. package/dist/types/utils/id.d.ts.map +1 -1
  213. package/dist/types/utils/logger.d.ts +2 -1
  214. package/dist/types/utils/logger.d.ts.map +1 -1
  215. package/dist/types/utils/node-tree/get-next-node-from-action.d.ts +2 -1
  216. package/dist/types/utils/node-tree/get-next-node-from-condition.d.ts +2 -1
  217. package/dist/types/utils/node-tree/get-next-node.d.ts +1 -1
  218. package/dist/types/utils/node-tree/get-next-node.d.ts.map +1 -1
  219. package/dist/types/utils/node-tree/get-origin-node.d.ts +1 -1
  220. package/dist/types/utils/node-tree/get-origin-node.d.ts.map +1 -1
  221. package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts +1 -1
  222. package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts.map +1 -1
  223. package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts +1 -1
  224. package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts.map +1 -1
  225. package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts +1 -1
  226. package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts.map +1 -1
  227. package/dist/types/utils/nodes.d.ts +1 -1
  228. package/dist/types/utils/picked-config.d.ts +2 -1
  229. package/dist/types/utils/rehydrate-scripts.d.ts +6 -0
  230. package/dist/types/utils/rehydrate-scripts.d.ts.map +1 -1
  231. package/dist/types/utils/rules-logic.d.ts +6 -2
  232. package/dist/types/utils/storage.d.ts +1 -1
  233. package/dist/types/utils/storage.d.ts.map +1 -1
  234. package/package.json +22 -23
  235. package/dist/api-CPL40b7A.js +0 -40
  236. package/dist/api-Dz5rgRgQ.cjs +0 -1
  237. package/dist/docs/actions.mdx +0 -78
  238. package/dist/docs/api/authentication.md +0 -376
  239. package/dist/docs/api/error-handling.md +0 -211
  240. package/dist/docs/api/index.md +0 -197
  241. package/dist/docs/api-keys.mdx +0 -40
  242. package/dist/docs/chrome-extension.md +0 -51
  243. package/dist/docs/conditions.mdx +0 -59
  244. package/dist/docs/content/asset-library.mdx +0 -93
  245. package/dist/docs/content/content-entries.mdx +0 -30
  246. package/dist/docs/content/layouts.mdx +0 -70
  247. package/dist/docs/content/pages.mdx +0 -67
  248. package/dist/docs/content/sections.mdx +0 -33
  249. package/dist/docs/delivery-api/index.md +0 -46
  250. package/dist/docs/delivery-api/preview-caching-crawling.md +0 -59
  251. package/dist/docs/experiences.mdx +0 -42
  252. package/dist/docs/features.mdx +0 -58
  253. package/dist/docs/fields.mdx +0 -79
  254. package/dist/docs/flow-builder/building-flows.md +0 -61
  255. package/dist/docs/flow-builder/condition-builder.md +0 -54
  256. package/dist/docs/flow-builder/keyboard-shortcuts.md +0 -66
  257. package/dist/docs/flow-builder/nodes.md +0 -43
  258. package/dist/docs/flow-builder/overview.mdx +0 -50
  259. package/dist/docs/gtm-template.md +0 -69
  260. package/dist/docs/guides/build-a-personalized-page.mdx +0 -143
  261. package/dist/docs/image-optimisation-service.md +0 -80
  262. package/dist/docs/mcp.md +0 -120
  263. package/dist/docs/pickedql/data-sources.md +0 -145
  264. package/dist/docs/pickedql/dimensions.md +0 -65
  265. package/dist/docs/pickedql/index.md +0 -36
  266. package/dist/docs/pickedql/schema.md +0 -156
  267. package/dist/docs/pickedql/virtual-functions.md +0 -204
  268. package/dist/docs/projects.mdx +0 -37
  269. package/dist/docs/reports.mdx +0 -18
  270. package/dist/docs/sdk/actions.md +0 -66
  271. package/dist/docs/sdk/content-security-policy.md +0 -32
  272. package/dist/docs/sdk/experiences.md +0 -46
  273. package/dist/docs/sdk/features.md +0 -212
  274. package/dist/docs/sdk/fields.md +0 -263
  275. package/dist/docs/sdk/index.md +0 -83
  276. package/dist/docs/sdk/plugins/dynamic-experiences.md +0 -58
  277. package/dist/docs/sdk/plugins/index.md +0 -9
  278. package/dist/docs/sdk/predicates.md +0 -112
  279. package/dist/docs/sdk/ssr/index.md +0 -62
  280. package/dist/docs/sdk/ssr/rendering-and-hydration.md +0 -141
  281. package/dist/docs/sdk/ssr/serialize.md +0 -86
  282. package/dist/docs/sdk/ssr/slots-and-placements.md +0 -35
  283. package/dist/docs/sdk/steps.md +0 -434
  284. package/dist/docs/steps.mdx +0 -44
  285. package/dist/docs/team-management.md +0 -40
  286. package/dist/docs/vite-plugin.md +0 -82
  287. package/dist/docs/workspace-versioning.mdx +0 -28
  288. package/dist/docs/workspaces.mdx +0 -102
  289. package/dist/experience-instance-DurC3pE-.cjs +0 -1
  290. package/dist/plugin-CFXwbXeM.js +0 -247
  291. package/dist/plugin-m4qekgtA.cjs +0 -1
@@ -0,0 +1,67 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to register an action handler
7
+
8
+ This guide shows you how to connect an action node in a flow to a function in your
9
+ application. You need an action in your project first, with a key. See
10
+ [Actions](/reference/dashboard/actions).
11
+
12
+ ## 1. Register the handler
13
+
14
+ Pass an `actions` object to `createClient()`. Each key must match an action key from the
15
+ dashboard:
16
+
17
+ ```ts
18
+ import { createClient } from '@picked/client'
19
+
20
+ const picked = createClient({
21
+ projectId: 'your-project-id',
22
+ actions: {
23
+ 'track-signup': () => {
24
+ analytics.track('signup_completed')
25
+ },
26
+ notify: (params) => {
27
+ sendNotification(params)
28
+ },
29
+ },
30
+ })
31
+ ```
32
+
33
+ The SDK calls the function that matches when the flow reaches the action node.
34
+
35
+ ## 2. Throw only for a true error
36
+
37
+ The flow takes the failure branch when your handler throws or its promise rejects. Throw
38
+ when the request itself did not complete:
39
+
40
+ ```ts
41
+ 'check-eligibility': async () => {
42
+ const response = await fetch('/api/eligibility')
43
+ // Throw only when the request itself fails. That takes the failure branch.
44
+ if (!response.ok) throw new Error('Eligibility request failed')
45
+ }
46
+ ```
47
+
48
+ ## 3. Record a negative answer as a response
49
+
50
+ A valid negative result is not a failure. An endpoint can answer `200` with a "no". Store
51
+ that value as a response and branch on it with a condition instead:
52
+
53
+ ```ts
54
+ 'check-serviced-area': async ({ client, responses }) => {
55
+ const postcode = responses.find((r) => r.fieldKey === 'postcode')?.value
56
+ const { serviced } = await fetch(`/api/serviced?postcode=${postcode}`).then((r) => r.json())
57
+ // Store the outcome as a response. A later condition then takes the branch.
58
+ client.responses.register('is-serviced', serviced)
59
+ }
60
+ ```
61
+
62
+ ## Related
63
+
64
+ - [About how Picked works](/explanation/how-picked-works#how-an-action-decides-which-way-to-go)
65
+ — why a returned `false` is a success.
66
+ - [SDK actions](/reference/sdk/actions) — what the handler receives and returns.
67
+ - [Actions](/reference/dashboard/actions) — the dashboard options.
@@ -0,0 +1,132 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to render a step form
7
+
8
+ This guide shows you how to render the fields of the active step and let a visitor submit
9
+ them.
10
+
11
+ ## 1. Put the steps in a router
12
+
13
+ `StepRouter` watches the step state and activates the `Step` whose `id` matches.
14
+
15
+ :::code-group
16
+
17
+ ```tsx [React]
18
+ import { StepRouter, Step } from '@picked/react/router'
19
+
20
+ function MyExperience() {
21
+ return (
22
+ <StepRouter>
23
+ <Step id="welcome">
24
+ <WelcomeScreen />
25
+ </Step>
26
+ <Step id="contact">
27
+ <ContactForm />
28
+ </Step>
29
+ </StepRouter>
30
+ )
31
+ }
32
+ ```
33
+
34
+ ```svelte [Svelte]
35
+ <script lang="ts">
36
+ import { StepRouter, Step } from '@picked/svelte/router'
37
+ </script>
38
+
39
+ <StepRouter>
40
+ <Step id="welcome">
41
+ <WelcomeScreen />
42
+ </Step>
43
+ <Step id="contact">
44
+ <ContactForm />
45
+ </Step>
46
+ </StepRouter>
47
+ ```
48
+
49
+ :::
50
+
51
+ :::note
52
+ A router must hold every Step, even when there is only one Step.
53
+ :::
54
+
55
+ ## 2. Render the fields
56
+
57
+ Bind `form.props` to your `<form>` element and map over the fields.
58
+
59
+ :::code-group
60
+
61
+ ```tsx [React]
62
+ import { useStep } from '@picked/react/hooks'
63
+ import { useUnit } from 'effector-react'
64
+
65
+ function StepForm() {
66
+ const { fields, form } = useStep()
67
+ const currentFields = useUnit(fields)
68
+ const isSubmitting = useUnit(form.submitting)
69
+
70
+ return (
71
+ <form {...form.props}>
72
+ {currentFields.map((field) => (
73
+ <div key={field.id}>
74
+ <label htmlFor={field.id}>{field.label}</label>
75
+ <input
76
+ id={field.id}
77
+ name={field.id}
78
+ value={field.value ?? ''}
79
+ onChange={field.onChange}
80
+ onBlur={field.onBlur}
81
+ />
82
+ {field.error && field.touched && <p className="error">{field.error.userMessage}</p>}
83
+ </div>
84
+ ))}
85
+ <button type="submit" disabled={isSubmitting}>Continue</button>
86
+ </form>
87
+ )
88
+ }
89
+ ```
90
+
91
+ ```svelte [Svelte]
92
+ <script lang="ts">
93
+ import { getStep } from '@picked/svelte/hooks'
94
+
95
+ const { fields, form } = getStep()
96
+ const isSubmitting = form.submitting
97
+ </script>
98
+
99
+ <form {...form.props}>
100
+ {#each $fields as field}
101
+ <div>
102
+ <label for={field.id}>{field.label}</label>
103
+ <input
104
+ id={field.id}
105
+ name={field.id}
106
+ value={field.value ?? ''}
107
+ onchange={field.onChange}
108
+ onblur={field.onBlur}
109
+ />
110
+ {#if field.error && field.touched}
111
+ <p class="error">{field.error.userMessage}</p>
112
+ {/if}
113
+ </div>
114
+ {/each}
115
+ <button type="submit" disabled={$isSubmitting}>Continue</button>
116
+ </form>
117
+ ```
118
+
119
+ :::
120
+
121
+ The SDK checks a field on blur, and not while the visitor types.
122
+
123
+ ## 3. Load a large step lazily
124
+
125
+ Pass a dynamic import to the `component` prop of `<Step>`. The SDK does not load the
126
+ content until that step becomes active.
127
+
128
+ ## Related
129
+
130
+ - [How to run code on a form submission](/how-to/run-code-on-a-form-submission) — the
131
+ callbacks.
132
+ - [SDK steps](/reference/sdk/steps) — the `StepContext` shape.
@@ -0,0 +1,41 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to render an unpublished preview
7
+
8
+ This guide shows you how to render draft content on your server. Your server cannot read
9
+ the query string of the browser, so you must pass the token yourself.
10
+
11
+ ## 1. Get the token
12
+
13
+ The management API holds the preview token of a workspace in the `previewToken` field. The
14
+ preview action in the dashboard also makes one for you.
15
+
16
+ ## 2. Read the config with the token
17
+
18
+ ```bash
19
+ curl "https://api.picked.app/api/v1/projects/proj_123?preview_token=<token>"
20
+ ```
21
+
22
+ A preview response returns `versionId: null`. An invalid token returns `401`.
23
+
24
+ ## 3. Pass the same token to `serialize()`
25
+
26
+ ```ts
27
+ const snapshot = await serialize(client, {
28
+ projectConfig,
29
+ attributes: { location: { pathname: '/promo' } },
30
+ previewToken,
31
+ })
32
+ ```
33
+
34
+ Use the same token for both requests. The configuration and the content then come from the same
35
+ preview.
36
+
37
+ ## Related
38
+
39
+ - [About the two APIs](/explanation/the-two-apis) — how a preview stays out of the search
40
+ index.
41
+ - [Preview, cache, and crawlers](/reference/delivery-api/preview-caching-crawling).
@@ -0,0 +1,37 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to reuse a section
7
+
8
+ This guide shows you how to define a common structure one time and use it in more than
9
+ one layout.
10
+
11
+ ## Create a section
12
+
13
+ 1. Open your workspace.
14
+ 2. Click **Pages** in the sidebar, then **Sections**.
15
+ 3. Click **New block**.
16
+ 4. Enter a **Name** and a **Key**.
17
+ 5. Add your fields.
18
+ 6. Click **Create block**.
19
+
20
+ ## Use it in a layout
21
+
22
+ 1. Open a page layout or a step layout.
23
+ 2. Add a **Section** field.
24
+ 3. Point that field at your section.
25
+
26
+ The layout now carries the fields of the section at every place where that field appears.
27
+
28
+ :::warning
29
+ An edit to a section does not migrate the entries that use it. Check each affected content
30
+ entry again, and correct every field error before you publish.
31
+ :::
32
+
33
+ ## Related
34
+
35
+ - [About the content model](/explanation/the-content-model) — why a schema change does not
36
+ migrate your data.
37
+ - [Sections](/reference/dashboard/content/sections) — the reference.
@@ -0,0 +1,28 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to reuse a step, a field, or an action
7
+
8
+ This guide shows you how to use one step, field, action, or condition in more than one
9
+ flow. All four belong to the project, and not to the experience that you made them in.
10
+
11
+ ## Steps
12
+
13
+ 1. Open the flow builder for the second experience.
14
+ 2. Find the step, the action, or the condition in the library on the left.
15
+ 3. Drag it onto the canvas, or onto a step node for a field.
16
+
17
+ The same one now appears in both flows.
18
+
19
+ :::warning
20
+ A change to the options applies at every place that uses it. Check the other flows before
21
+ you change a shared step, field, action, or condition.
22
+ :::
23
+
24
+ ## Related
25
+
26
+ - [Steps](/reference/dashboard/steps) — the step options.
27
+ - [Fields](/reference/dashboard/fields) — the field options.
28
+ - [Actions](/reference/dashboard/actions) — the action options.
@@ -0,0 +1,26 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to roll back a published version
7
+
8
+ This guide shows you how to make an earlier published version live again. Your current
9
+ draft does not change.
10
+
11
+ ## Steps
12
+
13
+ 1. Open **Publish history** from the project sidebar.
14
+ 2. Find the version that you want to restore.
15
+ 3. Click **Restore**.
16
+ 4. Read the diff preview. It compares the current live version against the one that you
17
+ chose.
18
+ 5. Click **Restore this version** to confirm.
19
+
20
+ The restored version is live immediately.
21
+
22
+ ## Related
23
+
24
+ - [About workspaces and versions](/explanation/workspaces-and-versions) — why a roll-back
25
+ changes no data.
26
+ - [Publish history](/reference/dashboard/workspace-versioning) — the version list.
@@ -0,0 +1,49 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to run an A/B test
7
+
8
+ This guide shows you how to split your traffic with a feature flag and read the result in
9
+ your application.
10
+
11
+ Read [About feature flag allocation](/explanation/feature-flag-allocation) first. The
12
+ point at which you read the feature flag decides when a visitor enters the test.
13
+
14
+ ## 1. Create the feature flag
15
+
16
+ 1. Open your project.
17
+ 2. Click **Feature flags** in the sidebar.
18
+ 3. Click **New feature flag**.
19
+ 4. Enter a name.
20
+ 5. Click **Create feature flag**.
21
+
22
+ You cannot change the key after you create the feature flag.
23
+
24
+ ## 2. Set the allocation
25
+
26
+ Open the detail page of the feature flag and set these controls:
27
+
28
+ 1. Turn **Enabled** on. A feature flag that is off allocates nobody.
29
+ 2. Set **Weight** to the percentage of visitors that you want in the test.
30
+ 3. Enter a **Description** that says what the feature flag tests.
31
+
32
+ ## 3. Read the feature flag in your application
33
+
34
+ See [How to read a feature flag](/how-to/read-a-feature-flag). Read it at the point where
35
+ the decision matters, and not at startup.
36
+
37
+ ## 4. Change the weight later
38
+
39
+ 1. Change **Weight** on the detail page.
40
+ 2. Increment the version.
41
+
42
+ The new weight then applies to a visitor with a cached decision. Leave the version as it
43
+ is to apply the new weight only to a visitor who is not allocated yet.
44
+
45
+ ## Related
46
+
47
+ - [About feature flag allocation](/explanation/feature-flag-allocation) — why two feature
48
+ flags at the same weight give four groups, not two.
49
+ - [Feature flags](/reference/dashboard/features) — the controls.
@@ -0,0 +1,125 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to run code on a form submission
7
+
8
+ This guide shows you how to act on a step submission. Pass your callbacks to `useStep()`
9
+ in React, or to `getStep()` in Svelte.
10
+
11
+ ## What the SDK does on submit
12
+
13
+ 1. It collects the field values from the HTML form.
14
+ 2. It runs each `onBeforeSubmit` callback, one after another.
15
+ 3. It checks each required field and each validation rule.
16
+ 4. It commits the responses to the experience state.
17
+ 5. It fires `form_submit_success` or `form_submit_error` on the event bus.
18
+ 6. It runs your `onSuccess` or `onError` callback.
19
+ 7. It moves to the next step.
20
+
21
+ ## Change the data before it is stored
22
+
23
+ :::code-group
24
+
25
+ ```tsx [React]
26
+ const { form } = useStep({
27
+ onBeforeSubmit: async (data) => ({
28
+ ...data,
29
+ 'email-address': String(data['email-address']).toLowerCase(),
30
+ }),
31
+ })
32
+ ```
33
+
34
+ ```svelte [Svelte]
35
+ <script lang="ts">
36
+ const { form } = getStep({
37
+ onBeforeSubmit: async (data) => ({
38
+ ...data,
39
+ 'email-address': String(data['email-address']).toLowerCase(),
40
+ }),
41
+ })
42
+ </script>
43
+ ```
44
+
45
+ :::
46
+
47
+ :::warning
48
+ An `onBeforeSubmit` callback that throws aborts the submit before the SDK commits
49
+ anything, and the step does not advance. Use it for a transform and for a check. Put a
50
+ side effect, such as an analytics call, in `onSuccess`.
51
+ :::
52
+
53
+ ## Act on the outcome
54
+
55
+ :::code-group
56
+
57
+ ```tsx [React]
58
+ const { form } = useStep({
59
+ onSuccess: ({ data }) => analytics.track('lead_captured', data),
60
+ onError: (errors) => console.error('Validation errors:', errors),
61
+ })
62
+ ```
63
+
64
+ ```svelte [Svelte]
65
+ <script lang="ts">
66
+ const { form } = getStep({
67
+ onSuccess: ({ data }) => analytics.track('lead_captured', data),
68
+ onError: (errors) => console.error('Validation errors:', errors),
69
+ })
70
+ </script>
71
+ ```
72
+
73
+ :::
74
+
75
+ A callback belongs to the component that passes it, and the SDK releases it when that
76
+ component unmounts. An `onSuccess` callback that throws cannot stop the flow. The SDK
77
+ reports the failure as `form_submit_callback_error`.
78
+
79
+ ## Choose the next step yourself
80
+
81
+ Call `stopNavigation()` inside `onSuccess`, then move where you want:
82
+
83
+ :::code-group
84
+
85
+ ```tsx [React]
86
+ const experience = useExperience()
87
+
88
+ const { form } = useStep({
89
+ onSuccess: ({ data, stopNavigation }) => {
90
+ stopNavigation()
91
+ if (data['plan-type'] === 'enterprise') {
92
+ experience.goToStepById('enterprise-contact')
93
+ } else {
94
+ experience.goToNextStep()
95
+ }
96
+ },
97
+ })
98
+ ```
99
+
100
+ ```svelte [Svelte]
101
+ <script lang="ts">
102
+ const experience = getExperience()
103
+
104
+ const { form } = getStep({
105
+ onSuccess: ({ data, stopNavigation }) => {
106
+ stopNavigation()
107
+ if (data['plan-type'] === 'enterprise') {
108
+ experience.goToStepById('enterprise-contact')
109
+ } else {
110
+ experience.goToNextStep()
111
+ }
112
+ },
113
+ })
114
+ </script>
115
+ ```
116
+
117
+ :::
118
+
119
+ `goToStepById()` takes a step `id`. `goToStepByKey()` takes the key of the step. When no
120
+ step matches, the call does nothing.
121
+
122
+ ## Related
123
+
124
+ - [How to render a step form](/how-to/render-a-step-form).
125
+ - [SDK steps](/reference/sdk/steps) — the `form` object.
@@ -0,0 +1,81 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to server-render a page
7
+
8
+ This guide shows you how to render a Picked Page on your server and hydrate it in the
9
+ browser. You need a published Page and a server that renders React or Svelte.
10
+
11
+ ## 1. Make the snapshot
12
+
13
+ Create one client for each request, then call `serialize()`:
14
+
15
+ ```ts
16
+ import { createClient } from '@picked/client'
17
+ import { serialize } from '@picked/client/server'
18
+
19
+ const client = createClient(options)
20
+
21
+ const snapshot = await serialize(client, {
22
+ projectConfig,
23
+ attributes: { location: { pathname: url.pathname } },
24
+ })
25
+ ```
26
+
27
+ :::warning
28
+ Give `serialize()` one client for each request. It does not support two calls at the same
29
+ time on one client.
30
+ :::
31
+
32
+ ## 2. Embed the snapshot
33
+
34
+ ```ts
35
+ const html = `
36
+ <div id="root">${appHtml}</div>
37
+ <script>window.__PICKED__ = ${JSON.stringify(snapshot)}</script>
38
+ `
39
+ ```
40
+
41
+ ## 3. Seed the client and render the slot
42
+
43
+ :::code-group
44
+
45
+ ```tsx [React]
46
+ import { PickedProvider, ExperienceSlot } from '@picked/react'
47
+
48
+ export function Page({ snapshot }) {
49
+ return (
50
+ <PickedProvider options={options} snapshot={snapshot}>
51
+ <ExperienceSlot name="page" />
52
+ </PickedProvider>
53
+ )
54
+ }
55
+ ```
56
+
57
+ ```svelte [Svelte]
58
+ <script lang="ts">
59
+ import { ExperienceSlot, setPickedContext } from '@picked/svelte'
60
+
61
+ const { snapshot } = $props()
62
+ setPickedContext({ options, snapshot })
63
+ </script>
64
+
65
+ <ExperienceSlot name="page" />
66
+ ```
67
+
68
+ :::
69
+
70
+ The provider seeds the client at once, so the server HTML and the first paint match.
71
+
72
+ ## 4. Point the experience at the slot
73
+
74
+ Set the target selector of your Page experience to `.picked-slot-page`, then publish.
75
+
76
+ ## Related
77
+
78
+ - [About server-side rendering](/explanation/why-server-side-rendering) — why the
79
+ snapshot carries condition results.
80
+ - [Server snapshot](/reference/sdk/ssr/serialize) — every parameter.
81
+ - [Your first server-rendered page](/tutorials/first-server-rendered-page).
@@ -0,0 +1,32 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to set a Content Security Policy
7
+
8
+ This guide shows you how to let the Picked SDK load its resources under a CSP.
9
+
10
+ ## Steps
11
+
12
+ Add the Picked domains to your policy.
13
+
14
+ As an HTTP header:
15
+
16
+ ```
17
+ Content-Security-Policy: connect-src 'self' https://api.picked.app https://evnts.pckd.dev; img-src 'self' https://img.picked-content.com;
18
+ ```
19
+
20
+ Or as a `<meta>` tag:
21
+
22
+ ```html
23
+ <meta
24
+ http-equiv="Content-Security-Policy"
25
+ content="connect-src 'self' https://api.picked.app https://evnts.pckd.dev; img-src 'self' https://img.picked-content.com;"
26
+ />
27
+ ```
28
+
29
+ ## Related
30
+
31
+ - [Content Security Policy](/reference/sdk/content-security-policy) — what each domain is
32
+ for.
@@ -0,0 +1,69 @@
1
+ ---
2
+ type: how-to
3
+ layout: docs
4
+ ---
5
+
6
+ # How to set up MFA
7
+
8
+ This guide shows you how to add a second factor to an account. Every MFA mutation needs
9
+ authentication.
10
+
11
+ ## 1. Add a factor
12
+
13
+ ```graphql
14
+ mutation {
15
+ mfaFactorEnroll(input: {
16
+ factorType: PHONE
17
+ phone: "+15551234567"
18
+ friendlyName: "My Phone"
19
+ }) {
20
+ factor { id factorType status }
21
+ }
22
+ }
23
+ ```
24
+
25
+ A new factor has the status `UNVERIFIED`.
26
+
27
+ ## 2. Send a challenge
28
+
29
+ ```graphql
30
+ mutation {
31
+ mfaChallengeSend(input: { factorId: "factor-id" }) {
32
+ challengeId
33
+ }
34
+ }
35
+ ```
36
+
37
+ This sends a code to the telephone number of the factor.
38
+
39
+ ## 3. Verify the challenge
40
+
41
+ ```graphql
42
+ mutation {
43
+ mfaChallengeVerify(input: {
44
+ factorId: "factor-id"
45
+ challengeId: "challenge-id"
46
+ code: "123456"
47
+ }) {
48
+ session { accessToken refreshToken aal }
49
+ }
50
+ }
51
+ ```
52
+
53
+ The session moves to `AAL2`, and the factor status changes to `VERIFIED`.
54
+
55
+ ## Remove a factor
56
+
57
+ ```graphql
58
+ mutation {
59
+ mfaFactorUnenroll(input: { factorId: "factor-id" }) {
60
+ success
61
+ }
62
+ }
63
+ ```
64
+
65
+ ## Related
66
+
67
+ - [Authorization and scopes](/reference/management-api/authorization-and-scopes) — which
68
+ operations need `AAL2`.
69
+ - [Authentication](/reference/management-api/authentication) — every mutation.