@picked/client 0.0.70-next.6 → 0.0.71-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/api-CH2FNa6S.cjs +1 -0
  3. package/dist/api-CgqRvuKL.js +38 -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/actions.mdx +38 -18
  10. package/dist/docs/api/authentication.md +75 -48
  11. package/dist/docs/api/authorization-and-scopes.md +63 -47
  12. package/dist/docs/api/error-handling.md +70 -54
  13. package/dist/docs/api/index.md +41 -22
  14. package/dist/docs/api/pagination.md +30 -20
  15. package/dist/docs/api-keys.mdx +11 -6
  16. package/dist/docs/chrome-extension.md +22 -5
  17. package/dist/docs/conditions.mdx +32 -19
  18. package/dist/docs/content/asset-library.mdx +65 -34
  19. package/dist/docs/content/content-entries.mdx +8 -2
  20. package/dist/docs/content/layouts.mdx +45 -29
  21. package/dist/docs/content/pages.mdx +29 -14
  22. package/dist/docs/content/sections.mdx +19 -8
  23. package/dist/docs/delivery-api/experience-content.md +10 -5
  24. package/dist/docs/delivery-api/index.md +19 -12
  25. package/dist/docs/delivery-api/preview-caching-crawling.md +27 -14
  26. package/dist/docs/delivery-api/project-config.md +6 -4
  27. package/dist/docs/experiences.mdx +26 -10
  28. package/dist/docs/features.mdx +66 -25
  29. package/dist/docs/fields.mdx +42 -27
  30. package/dist/docs/flow-builder/building-flows.md +38 -20
  31. package/dist/docs/flow-builder/condition-builder.md +32 -21
  32. package/dist/docs/flow-builder/keyboard-shortcuts.md +18 -18
  33. package/dist/docs/flow-builder/nodes.md +26 -13
  34. package/dist/docs/flow-builder/overview.mdx +28 -15
  35. package/dist/docs/gtm-template.md +35 -26
  36. package/dist/docs/guides/build-a-personalized-page.mdx +36 -18
  37. package/dist/docs/image-optimisation-service.md +33 -17
  38. package/dist/docs/index.md +28 -17
  39. package/dist/docs/mcp.md +46 -38
  40. package/dist/docs/pickedql/data-sources.md +65 -43
  41. package/dist/docs/pickedql/dimensions.md +27 -12
  42. package/dist/docs/pickedql/examples.md +38 -26
  43. package/dist/docs/pickedql/filtering.md +10 -6
  44. package/dist/docs/pickedql/index.md +32 -14
  45. package/dist/docs/pickedql/schema.md +85 -76
  46. package/dist/docs/pickedql/virtual-functions.md +62 -36
  47. package/dist/docs/projects.mdx +14 -7
  48. package/dist/docs/reports.mdx +5 -3
  49. package/dist/docs/sdk/actions.md +33 -17
  50. package/dist/docs/sdk/conditions.md +8 -5
  51. package/dist/docs/sdk/content-security-policy.md +5 -4
  52. package/dist/docs/sdk/experiences.md +29 -11
  53. package/dist/docs/sdk/feature-allocation.md +79 -47
  54. package/dist/docs/sdk/features.md +130 -76
  55. package/dist/docs/sdk/fields.md +66 -42
  56. package/dist/docs/sdk/index.md +22 -13
  57. package/dist/docs/sdk/plugins/create-experience.md +18 -15
  58. package/dist/docs/sdk/plugins/dynamic-experiences.md +42 -16
  59. package/dist/docs/sdk/predicates.md +53 -37
  60. package/dist/docs/sdk/ssr/index.md +25 -8
  61. package/dist/docs/sdk/ssr/rendering-and-hydration.md +37 -16
  62. package/dist/docs/sdk/ssr/serialize.md +38 -20
  63. package/dist/docs/sdk/ssr/slots-and-placements.md +28 -10
  64. package/dist/docs/sdk/steps.md +100 -55
  65. package/dist/docs/steps.mdx +20 -11
  66. package/dist/docs/team-management.md +17 -11
  67. package/dist/docs/vite-plugin.md +26 -19
  68. package/dist/docs/workspace-versioning.mdx +14 -8
  69. package/dist/docs/workspaces.mdx +55 -24
  70. package/dist/{dom-BwmTHeT7.js → dom-BqcToNL0.js} +1 -3
  71. package/dist/{dom-CDrHc_j9.cjs → dom-DlLH3FxV.cjs} +1 -1
  72. package/dist/esm/form/index.js +2 -4
  73. package/dist/esm/index.js +50 -45
  74. package/dist/esm/plugins/dom-observer/index.js +1 -1
  75. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  76. package/dist/esm/preview/index.js +1 -1
  77. package/dist/esm/server/index.js +12 -10
  78. package/dist/{experience-instance-DOJShRk5.js → experience-instance-DGk11d8a.js} +3 -3
  79. package/dist/experience-instance-Dej3P1sE.cjs +1 -0
  80. package/dist/{plugin-BxMR7fOV.js → plugin-D2UqFikQ.js} +2 -2
  81. package/dist/plugin-DCW-tnjO.cjs +1 -0
  82. package/dist/{preview-channel-DW9J8t4B.js → preview-channel-CSr96XAo.js} +1 -1
  83. package/dist/{protocol-DSqOlW2r.js → protocol-C5KkkiZc.js} +1 -1
  84. package/dist/types/client.d.ts +7 -6
  85. package/dist/types/entities/action/action.d.ts +5 -4
  86. package/dist/types/entities/action/action.d.ts.map +1 -1
  87. package/dist/types/entities/component/component.d.ts +6 -5
  88. package/dist/types/entities/component/component.d.ts.map +1 -1
  89. package/dist/types/entities/condition/condition.d.ts +5 -5
  90. package/dist/types/entities/content/content.d.ts +4 -3
  91. package/dist/types/entities/experience/experience.d.ts +11 -10
  92. package/dist/types/entities/experience/experience.d.ts.map +1 -1
  93. package/dist/types/entities/experience-instance/experience-instance.d.ts +10 -9
  94. package/dist/types/entities/feature/feature.d.ts +3 -2
  95. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  96. package/dist/types/entities/field/field.d.ts +7 -6
  97. package/dist/types/entities/framework/framework.d.ts +7 -6
  98. package/dist/types/entities/framework/framework.d.ts.map +1 -1
  99. package/dist/types/entities/history/history.d.ts +6 -6
  100. package/dist/types/entities/plugin/plugin.d.ts +3 -2
  101. package/dist/types/entities/plugin/plugin.d.ts.map +1 -1
  102. package/dist/types/entities/predicate/predicate.d.ts +4 -3
  103. package/dist/types/entities/predicate/predicate.d.ts.map +1 -1
  104. package/dist/types/entities/response/response-validation.d.ts +3 -2
  105. package/dist/types/entities/response/response.d.ts +12 -11
  106. package/dist/types/entities/session/session.d.ts +2 -1
  107. package/dist/types/entities/step/step.d.ts +6 -5
  108. package/dist/types/exports.d.ts +2 -1
  109. package/dist/types/exports.d.ts.map +1 -1
  110. package/dist/types/form/build-responses.d.ts +2 -1
  111. package/dist/types/form/create-field-evaluator.d.ts +4 -3
  112. package/dist/types/form/create-field-validation.d.ts +5 -4
  113. package/dist/types/form/create-form.d.ts +7 -6
  114. package/dist/types/form/create-submit-pipeline.d.ts +7 -6
  115. package/dist/types/form/get-step-fields.d.ts +3 -2
  116. package/dist/types/lifecycle/condition-selection.d.ts +14 -4
  117. package/dist/types/lifecycle/lifecycle.d.ts +4 -3
  118. package/dist/types/lifecycle/rules-logic.d.ts +4 -3
  119. package/dist/types/lifecycle/state-snapshot.d.ts +4 -3
  120. package/dist/types/picked.d.ts +64 -62
  121. package/dist/types/picked.d.ts.map +1 -1
  122. package/dist/types/ping.d.ts +1 -1
  123. package/dist/types/ping.d.ts.map +1 -1
  124. package/dist/types/plugins/create-experience/create-experience.d.ts +9 -8
  125. package/dist/types/plugins/create-experience/create-experience.d.ts.map +1 -1
  126. package/dist/types/plugins/create-experience/plugin.d.ts +3 -2
  127. package/dist/types/plugins/create-experience/plugin.d.ts.map +1 -1
  128. package/dist/types/plugins/create-experience/serialization.d.ts.map +1 -1
  129. package/dist/types/plugins/dom-observer/plugin.d.ts +1 -1
  130. package/dist/types/plugins/dynamic-experiences/plugin.d.ts +3 -3
  131. package/dist/types/plugins/dynamic-experiences/types.d.ts +8 -5
  132. package/dist/types/plugins/experience-browser-storage/plugin.d.ts +1 -1
  133. package/dist/types/plugins/experience-browser-storage/plugin.d.ts.map +1 -1
  134. package/dist/types/plugins/history-browser-storage/plugin.d.ts +1 -1
  135. package/dist/types/plugins/history-browser-storage/plugin.d.ts.map +1 -1
  136. package/dist/types/plugins/responses-browser-storage/plugin.d.ts +1 -1
  137. package/dist/types/plugins/responses-browser-storage/plugin.d.ts.map +1 -1
  138. package/dist/types/plugins/responses-indexeddb/plugin.d.ts +1 -1
  139. package/dist/types/plugins/single-page-app/plugin.d.ts +1 -1
  140. package/dist/types/plugins/single-page-app/plugin.d.ts.map +1 -1
  141. package/dist/types/plugins/web-components/plugin.d.ts +2 -1
  142. package/dist/types/plugins/window-object/plugin.d.ts +2 -2
  143. package/dist/types/preview-channel.d.ts +5 -1
  144. package/dist/types/preview-receiver/index.d.ts +18 -1
  145. package/dist/types/preview-receiver/protocol.d.ts +10 -1
  146. package/dist/types/server/index.d.ts +3 -3
  147. package/dist/types/server/serialize.d.ts +2 -2
  148. package/dist/types/utils/dom.d.ts +2 -1
  149. package/dist/types/utils/events.d.ts +6 -6
  150. package/dist/types/utils/events.d.ts.map +1 -1
  151. package/dist/types/utils/global-obj.d.ts +1 -1
  152. package/dist/types/utils/global-obj.d.ts.map +1 -1
  153. package/dist/types/utils/id.d.ts.map +1 -1
  154. package/dist/types/utils/logger.d.ts +2 -1
  155. package/dist/types/utils/logger.d.ts.map +1 -1
  156. package/dist/types/utils/node-tree/get-next-node-from-action.d.ts +2 -1
  157. package/dist/types/utils/node-tree/get-next-node-from-condition.d.ts +2 -1
  158. package/dist/types/utils/node-tree/get-next-node.d.ts +1 -1
  159. package/dist/types/utils/node-tree/get-next-node.d.ts.map +1 -1
  160. package/dist/types/utils/node-tree/get-origin-node.d.ts +1 -1
  161. package/dist/types/utils/node-tree/get-origin-node.d.ts.map +1 -1
  162. package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts +1 -1
  163. package/dist/types/utils/node-tree/min-max-steps-to-leaf.d.ts.map +1 -1
  164. package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts +1 -1
  165. package/dist/types/utils/node-tree/min-max-steps-to-origin.d.ts.map +1 -1
  166. package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts +1 -1
  167. package/dist/types/utils/node-tree/next-reachable-step-nodes.d.ts.map +1 -1
  168. package/dist/types/utils/nodes.d.ts +1 -1
  169. package/dist/types/utils/picked-config.d.ts +2 -1
  170. package/dist/types/utils/rules-logic.d.ts +6 -2
  171. package/dist/types/utils/storage.d.ts +1 -1
  172. package/dist/types/utils/storage.d.ts.map +1 -1
  173. package/package.json +22 -23
  174. package/dist/api-CoUkIxIr.cjs +0 -1
  175. package/dist/api-ahdSBQ3Y.js +0 -40
  176. package/dist/experience-instance-DurC3pE-.cjs +0 -1
  177. package/dist/plugin-CASdGtTX.cjs +0 -1
@@ -4,9 +4,11 @@ layout: docs
4
4
 
5
5
  # Server Snapshot
6
6
 
7
- `serialize()` evaluates which experiences match a URL and fetches their content on the server, returning a plain-JSON snapshot you pass to the client.
7
+ `serialize()` finds which experiences match a URL and reads their content on the
8
+ server. It returns a snapshot as plain JSON, and you pass that snapshot to the
9
+ client.
8
10
 
9
- ## serialize(client, params)
11
+ ## The `serialize(client, params)` function
10
12
 
11
13
  ```ts
12
14
  import { createClient } from '@picked/client'
@@ -20,7 +22,14 @@ const snapshot = await serialize(client, {
20
22
  })
21
23
  ```
22
24
 
23
- `serialize()` registers the project config on the client synchronously, evaluates each experience's condition against `attributes.location`, fetches content for the experiences whose steps reference layout content, dedupes the content blobs by id, and returns a `PickedSnapshot`. It expects a per-request client; concurrent calls on a single client are not supported.
25
+ `serialize()` does this. It registers the project options on the client at once. It
26
+ evaluates the condition of each experience against `attributes.location`. It reads
27
+ the content for each experience whose steps point at layout content. It removes the
28
+ duplicate content blobs, which it finds by their IDs. It then returns a
29
+ `PickedSnapshot`.
30
+
31
+ Give `serialize()` one client for each request. It does not support two calls at the
32
+ same time on one client.
24
33
 
25
34
  ```ts
26
35
  serialize(client: Picked, params: SerializeParams): Promise<PickedSnapshot>
@@ -30,13 +39,15 @@ serialize(client: Picked, params: SerializeParams): Promise<PickedSnapshot>
30
39
 
31
40
  | Parameter | Type | Default | Description |
32
41
  | --- | --- | --- | --- |
33
- | `projectConfig` | `ProjectConfig` | required | The published or preview config to register and select against. |
34
- | `attributes.location` | `Partial<SerializeLocation>` | none | URL fields injected into the selection context, such as `pathname`. |
35
- | `getContent` | `SerializeGetContent` | runtime API | A custom content fetcher used in place of the runtime API. |
36
- | `previewToken` | `string` | none | Forwarded to the content fetch so preview content resolves during preview SSR. |
37
- | `timeoutMs` | `number` | `10000` | Per-content-fetch timeout. On timeout the blob is skipped and the render proceeds. |
42
+ | `projectConfig` | `ProjectConfig` | required | The published options, or the preview options, to register and choose against. |
43
+ | `attributes.location` | `Partial<SerializeLocation>` | none | The URL fields that go into the selection context, such as `pathname`. |
44
+ | `getContent` | `SerializeGetContent` | runtime API | Your own function to read content, in place of the runtime API. |
45
+ | `previewToken` | `string` | none | The SDK sends this with the content request, so that preview content resolves during a preview render. |
46
+ | `timeoutMs` | `number` | `10000` | The limit for each content request. After the limit, the SDK skips that blob and continues. |
38
47
 
39
- `SerializeLocation` carries the plain URL fields `href`, `pathname`, `search`, `hostname`, `origin`, `protocol`, `port`, `host`, and `hash`. Pass only the ones your conditions read.
48
+ `SerializeLocation` carries the URL fields `href`, `pathname`, `search`, `hostname`,
49
+ `origin`, `protocol`, `port`, `host`, and `hash`. Pass only the fields that your
50
+ conditions read.
40
51
 
41
52
  ## The snapshot
42
53
 
@@ -51,16 +62,18 @@ interface PickedSnapshot {
51
62
 
52
63
  | Field | Description |
53
64
  | --- | --- |
54
- | `versionId` | The project version the snapshot was selected against, or `null` when unknown (for example on preview). |
55
- | `projectConfig` | The config the server selected with. It travels so the client renders against the exact same config. |
56
- | `content` | The deduped content blobs for the selected experiences. |
57
- | `conditionResults` | The memoized condition results, keyed by rule hash, so the client reuses the server's selection. |
65
+ | `versionId` | The project version that the snapshot was chosen against. It is `null` when the version is not known, for example in a preview. |
66
+ | `projectConfig` | The options that the server chose with. They travel to the client, so that the client renders against exactly the same options. |
67
+ | `content` | The content blobs for the chosen experiences, with the duplicates removed. |
68
+ | `conditionResults` | The condition results that the server kept, under a key made from the rule hash. The client then reuses the choice of the server. |
58
69
 
59
- The snapshot is plain JSON. Serialize it into your HTML response with `JSON.stringify` and read it back on the client.
70
+ The snapshot is plain JSON. Write it into your HTML response with `JSON.stringify`
71
+ and read it back on the client.
60
72
 
61
- ## Passing the snapshot to the client
73
+ ## Pass the snapshot to the client
62
74
 
63
- Embed the snapshot in your HTML payload, then hand it to the provider (React) or context (Svelte):
75
+ Embed the snapshot in your HTML payload. Then give it to the provider in React, or to
76
+ the context in Svelte:
64
77
 
65
78
  ```ts
66
79
  const html = `
@@ -69,11 +82,14 @@ const html = `
69
82
  `
70
83
  ```
71
84
 
72
- On the client, read `window.__PICKED__` and pass it as the `snapshot` prop. See [Rendering & Hydration](/sdk/ssr/rendering-and-hydration).
85
+ On the client, read `window.__PICKED__` and pass it as the `snapshot` prop. See
86
+ [Rendering and Hydration](/sdk/ssr/rendering-and-hydration).
73
87
 
74
- ## Preview rendering
88
+ ## Render a preview
75
89
 
76
- Pass `previewToken` so unpublished content resolves during preview SSR. The server has no access to the request's query string, so the token cannot be recovered any other way:
90
+ Pass `previewToken`, so that unpublished content resolves during a preview render.
91
+ The server cannot read the query string of the request, so there is no other way to
92
+ give it the token:
77
93
 
78
94
  ```ts
79
95
  const snapshot = await serialize(client, {
@@ -83,4 +99,6 @@ const snapshot = await serialize(client, {
83
99
  })
84
100
  ```
85
101
 
86
- Use the same token when you fetch `projectConfig` from the [Delivery API](/delivery-api), so the config and the content come from the same preview surface.
102
+ Use the same token when you read `projectConfig` from the
103
+ [Delivery API](/delivery-api). The options and the content then come from the same
104
+ preview.
@@ -2,34 +2,52 @@
2
2
  layout: docs
3
3
  ---
4
4
 
5
- # Slots & Placements
5
+ # Slots and Placements
6
6
 
7
- A slot is a named mount point. `<ExperienceSlot name="page" />` renders every experience that targets that slot and whose condition matches.
7
+ A slot is a named mount point. `<ExperienceSlot name="page" />` renders every
8
+ experience that points at that slot and whose condition matches.
8
9
 
9
10
  ## What a slot is
10
11
 
11
- A slot's `name` maps to the `.picked-slot-<name>` selector. `<ExperienceSlot name="page" />` renders every experience whose target selector is `.picked-slot-page` and whose condition is active. The slot reads each experience's active state from the seeded snapshot, so it renders synchronously during server-side rendering and the hydration first paint.
12
+ The `name` of a slot points at the `.picked-slot-<name>` selector. So
13
+ `<ExperienceSlot name="page" />` renders every experience with the target selector
14
+ `.picked-slot-page`, when the condition of that experience is active.
15
+
16
+ The slot reads the active state of each experience from the seeded snapshot. It
17
+ therefore renders at once, both in server-side rendering and at the first paint of
18
+ the hydration.
12
19
 
13
20
  ```tsx
14
21
  <ExperienceSlot name="page" />
15
22
  ```
16
23
 
17
- ## Targeting an experience to a slot
24
+ ## Point an experience at a slot
18
25
 
19
- Set the experience's target selector to `.picked-slot-<name>`. An experience targeting `.picked-slot-page` renders into `<ExperienceSlot name="page" />`. The slot name and the selector suffix must match.
26
+ Set the target selector of the experience to `.picked-slot-<name>`. An experience
27
+ that points at `.picked-slot-page` renders into `<ExperienceSlot name="page" />`. The
28
+ name of the slot and the end of the selector must match.
20
29
 
21
30
  ## Placement order
22
31
 
23
- When several experiences target the same slot, they render in placement precedence:
32
+ When more than one experience points at the same slot, they render in this placement
33
+ order:
24
34
 
25
35
  ```
26
36
  replace -> before -> prepend -> append -> after
27
37
  ```
28
38
 
29
- `replace` wins. If an active experience uses the `replace` placement, it renders alone and replaces every other match in the slot. Otherwise every active experience renders, in placement order.
39
+ `replace` wins. An active experience with the `replace` placement renders alone, and
40
+ it replaces every other match in the slot. In every other case each active experience
41
+ renders, in placement order.
30
42
 
31
- ## Multiple experiences in one slot
43
+ ## More than one experience in one slot
32
44
 
33
- The slot collects each experience that targets its selector, drops any whose condition is not active, and sorts the rest by placement. If a `replace` experience is among the active ones, only that experience renders. With no active `replace`, the slot renders all active experiences in `before, prepend, append, after` order.
45
+ The slot collects each experience that points at its selector. It drops each one
46
+ whose condition is not active. It then sorts the rest into placement order. When a `replace`
47
+ experience is among the active ones, only that experience renders. With no active
48
+ `replace`, the slot renders every active experience in the order `before`, `prepend`,
49
+ `append`, `after`.
34
50
 
35
- This lets one Page take over a slot with `replace`, while other slots can stack several experiences by using the non-`replace` placements. Conditions decide which experiences are active; see [Conditions](/conditions).
51
+ One Page can therefore take a whole slot with `replace`, while another slot holds
52
+ several experiences that use the other placements. A condition decides which
53
+ experiences are active. See [Conditions](/conditions).
@@ -4,23 +4,33 @@ layout: docs
4
4
 
5
5
  # Steps
6
6
 
7
- Render steps with `<StepRouter>` and `<Step>` from `@picked/react` or `@picked/svelte`, and access the active step through `useStep()` / `getStep()`.
7
+ Render your steps with `<StepRouter>` and `<Step>` from `@picked/react` or
8
+ `@picked/svelte`. Reach the active step through `useStep()` or `getStep()`.
8
9
 
9
10
  ## How steps work
10
11
 
11
- When an experience starts, its steps are connected in a directed node graph. The SDK traverses this graph to determine which step to display:
12
-
13
- 1. **Initialization**: When the experience opens, the SDK starts at the origin node and traverses the graph to find the first step
14
- 2. **Step Entry**: The active step is set and a `step_enter` event is emitted on the event bus
15
- 3. **Field Evaluation**: The step's fields are evaluated — fields with conditions are shown or hidden based on the current state
16
- 4. **User Input**: The user interacts with the step's fields. Client-side validation runs on blur and on submit
17
- 5. **Step Submission**: When the user submits, a `step_submit` event fires and field responses are committed to the client state
18
- 6. **Navigation**: The SDK follows the edge from the current step node to the next, traversing any conditionals or actions before arriving at the next step
19
- 7. **Completion**: When no further steps exist, the experience flow is complete
12
+ When an experience starts, its steps form a directed graph of nodes. The SDK walks
13
+ this graph to find which step to show:
14
+
15
+ 1. **Initialization**: when the experience opens, the SDK starts at the origin node.
16
+ It walks the graph to find the first step.
17
+ 2. **Step entry**: the SDK sets the active step. It sends a `step_enter` event on the
18
+ event bus.
19
+ 3. **Field evaluation**: the SDK evaluates the fields of the step. It shows or hides
20
+ a field with a condition, and the current state decides which.
21
+ 4. **User input**: the user fills in the fields of the step. Validation runs in the
22
+ browser on blur and on submit.
23
+ 5. **Step submission**: when the user submits, a `step_submit` event fires. The SDK
24
+ commits the field responses to the client state.
25
+ 6. **Navigation**: the SDK follows the edge from the current step node to the next
26
+ one. It walks through each condition and action on the way.
27
+ 7. **Completion**: when no step remains, the experience flow is complete.
20
28
 
21
29
  ## Step routing
22
30
 
23
- `StepRouter` listens to the experience's current step state and activates the matching `Step` by its `id`. Each `Step` registers itself on mount and unregisters on unmount, so lazy-rendered tabs or drawers work automatically.
31
+ `StepRouter` watches the current step state of the experience. It activates the
32
+ `Step` whose `id` matches. Each `Step` registers itself on mount and unregisters on
33
+ unmount, so a tab or a drawer that renders lazily works with no extra code.
24
34
 
25
35
  :::code-group
26
36
 
@@ -69,12 +79,12 @@ function MyExperience() {
69
79
  :::
70
80
 
71
81
  :::note
72
- All Steps must be wrapped in a router, even if it is a single Step
82
+ A router must hold every Step, even when there is only one Step.
73
83
  :::
74
84
 
75
85
  ### Lazy-loaded steps
76
86
 
77
- For large experiences, steps can be lazily loaded using dynamic imports:
87
+ For a large experience, load a step lazily with a dynamic import:
78
88
 
79
89
  :::code-group
80
90
 
@@ -98,11 +108,13 @@ const WelcomeScreen = lazy(() => import('./WelcomeScreen'))
98
108
 
99
109
  :::
100
110
 
101
- The `component` prop accepts a `Promise<Component>` — the step's content is not loaded until it becomes active.
111
+ The `component` prop takes a `Promise<Component>`. The SDK does not load the content
112
+ of the step until that step becomes active.
102
113
 
103
- ## Accessing step context
114
+ ## Reach the step context
104
115
 
105
- Inside a `Step` component (or any component rendered within one), call `getStep()` / `useStep()` to access the step's reactive context:
116
+ Inside a `Step` component, or inside any component that a `Step` renders, call
117
+ `getStep()` or `useStep()` to reach the reactive context of the step:
106
118
 
107
119
  :::code-group
108
120
 
@@ -161,24 +173,29 @@ function ContactForm() {
161
173
 
162
174
  :::
163
175
 
164
- `getStep()` (Svelte) / `useStep()` (React) returns a `StepContext` with the following properties:
176
+ `getStep()` in Svelte, and `useStep()` in React, return a `StepContext` with these
177
+ properties:
165
178
 
166
179
  | Property | Type | Description |
167
180
  | ---------- | --------------------------------------------- | ------------------------------------------------------------------------------ |
168
- | `step` | `Store<ExperienceStep>` | Reactive store containing the step's `id` and `label` |
169
- | `fields` | `Store<FormField[]>` | Reactive store of fields with their current values, errors, and event handlers |
170
- | `isActive` | `Store<boolean>` (Svelte) / `boolean` (React) | Whether this step is currently active in the router |
171
- | `form` | Object | Form submission handlers (see [Forms](#forms)) |
181
+ | `step` | `Store<ExperienceStep>` | A reactive store that holds the `id` and the `label` of the step |
182
+ | `fields` | `Store<FormField[]>` | A reactive store of the fields, with their current values, errors, and handlers |
183
+ | `isActive` | `Store<boolean>` (Svelte) / `boolean` (React) | Whether the router holds this step as the active one |
184
+ | `form` | Object | The handlers for a form submission. See [Forms](#forms). |
172
185
 
173
186
  ## Forms
174
187
 
175
- When a step has fields, `getStep()` / `useStep()` provides a `form` object for handling submission, validation, and navigation.
188
+ When a step has fields, `getStep()` and `useStep()` give you a `form` object. Use it
189
+ for the submission, the validation, and the move to the next step.
176
190
 
177
- Both accept an optional callbacks object — `getStep({ onSuccess })` / `useStep({ onSuccess })` — which is the recommended way to react to submission. The available slots are `onBeforeSubmit`, `onSuccess` and `onError`, all covered below.
191
+ Both take an optional object of callbacks, as in `getStep({ onSuccess })` or
192
+ `useStep({ onSuccess })`. This is the recommended way to act on a submission. The
193
+ slots are `onBeforeSubmit`, `onSuccess`, and `onError`. This page covers each one
194
+ below.
178
195
 
179
- ### Handling submission
196
+ ### Handle a submission
180
197
 
181
- Bind `form.props` to your `<form>` element's `onsubmit` handler:
198
+ Bind `form.props` to the `onsubmit` handler of your `<form>` element:
182
199
 
183
200
  :::code-group
184
201
 
@@ -242,19 +259,22 @@ function StepForm() {
242
259
 
243
260
  :::
244
261
 
245
- Submitting the form:
262
+ When you submit the form, the SDK does this:
246
263
 
247
- 1. Collects field values from the HTML form
248
- 2. Runs `onBeforeSubmit` transform callbacks in series — a throw here aborts the submit before anything is committed
249
- 3. Validates all required fields and field validation rules
250
- 4. Commits the responses to the experience state
251
- 5. Fires `form_submit_success` or `form_submit_error` on the event bus
252
- 6. Runs your `onSuccess` or `onError` callbacks
253
- 7. Navigates to the next step (unless `stopNavigation` is called, and regardless of whether a callback in step 6 failed)
264
+ 1. It collects the field values from the HTML form.
265
+ 2. It runs each `onBeforeSubmit` callback, one after another. A throw here aborts the
266
+ submit and commits nothing.
267
+ 3. It checks each required field and each validation rule.
268
+ 4. It commits the responses to the experience state.
269
+ 5. It fires `form_submit_success` or `form_submit_error` on the event bus.
270
+ 6. It runs your `onSuccess` or `onError` callback.
271
+ 7. It moves to the next step. A call to `stopNavigation` stops this. A callback that
272
+ failed in step 6 does not stop it.
254
273
 
255
- ### Transforming data before submission
274
+ ### Transform data before a submission
256
275
 
257
- Use `onBeforeSubmit` to intercept and transform field data before it is committed. Callbacks run in series, each receiving the output of the previous:
276
+ Use `onBeforeSubmit` to intercept and transform the field data before the SDK stores it. The callbacks run one after another. Each one takes the output of the one before
277
+ it:
258
278
 
259
279
  :::code-group
260
280
 
@@ -264,7 +284,7 @@ Use `onBeforeSubmit` to intercept and transform field data before it is committe
264
284
 
265
285
  const { form } = getStep({
266
286
  onBeforeSubmit: async (data) => {
267
- // Normalize email to lowercase before committing
287
+ // Normalize the email address to lowercase first.
268
288
  return {
269
289
  ...data,
270
290
  'email-address': String(data['email-address']).toLowerCase(),
@@ -282,7 +302,7 @@ import { useStep } from '@picked/react/hooks'
282
302
  function ContactStep() {
283
303
  const { form } = useStep({
284
304
  onBeforeSubmit: async (data) => {
285
- // Normalize email to lowercase before committing
305
+ // Normalize the email address to lowercase first.
286
306
  return {
287
307
  ...data,
288
308
  'email-address': String(data['email-address']).toLowerCase(),
@@ -297,12 +317,16 @@ function ContactStep() {
297
317
  :::
298
318
 
299
319
  :::warning
300
- If an `onBeforeSubmit` callback throws, the submit is aborted before anything is committed and the step does not advance. This is deliberate — committing a half-transformed payload would persist wrong-but-plausible data. Use it for transforms and validation, and put side effects such as analytics or backend calls in `onSuccess` instead.
320
+ When an `onBeforeSubmit` callback throws, the SDK aborts the submit before it commits
321
+ anything, and the step does not advance. This is deliberate. A payload that is only
322
+ part transformed would store data that looks correct but is wrong. Use
323
+ `onBeforeSubmit` for a transform and for a validation check. Put a side effect, such as an
324
+ analytics call or a backend call, in `onSuccess`.
301
325
  :::
302
326
 
303
327
  ### Success and error callbacks
304
328
 
305
- Pass `onSuccess` and `onError` to react to submission outcomes:
329
+ Pass `onSuccess` and `onError` to act on the outcome of a submission:
306
330
 
307
331
  :::code-group
308
332
 
@@ -342,13 +366,20 @@ function ContactStep() {
342
366
 
343
367
  :::
344
368
 
345
- Callbacks are scoped to the component that passes them, and are cleaned up automatically when that component unmounts — no manual teardown is needed. Passing a new inline function on a re-render replaces the previous one rather than adding another, so a single submit runs each callback exactly once.
369
+ A callback belongs to the component that passes it. The SDK releases the callback
370
+ when that component unmounts, so you write no teardown code. A new inline function on
371
+ a re-render replaces the earlier one and does not add a second one. One submit
372
+ therefore runs each callback exactly one time.
346
373
 
347
- A throwing or rejecting `onSuccess`/`onError` callback cannot block the flow: the step still advances, and the failure is reported on the client event bus as `form_submit_callback_error` so it stays observable.
374
+ An `onSuccess` or `onError` callback that throws or rejects cannot stop the flow. The
375
+ step still advances, and the SDK reports the failure on the client event bus as
376
+ `form_submit_callback_error`, so the failure stays observable.
348
377
 
349
- #### Registering callbacks imperatively
378
+ #### Register a callback in code
350
379
 
351
- `form.onSuccess`, `form.onError` and `form.onBeforeSubmit` are also available as an escape hatch, for cases where a callback has to be attached conditionally. They return nothing — cleanup is automatic, as above.
380
+ `form.onSuccess`, `form.onError`, and `form.onBeforeSubmit` are an escape hatch, for
381
+ a callback that you must attach only when a condition holds. They return nothing. The
382
+ SDK releases them for you, as above.
352
383
 
353
384
  ```tsx [React]
354
385
  function ContactStep({ trackConversions }: { trackConversions: boolean }) {
@@ -363,12 +394,17 @@ function ContactStep({ trackConversions }: { trackConversions: boolean }) {
363
394
  ```
364
395
 
365
396
  :::warning
366
- Register from the component body, not from an event handler. A registration made inside a click handler is added each time the handler runs and is only released when the component unmounts, so it accumulates. Prefer the options object above wherever you can.
397
+ Register from the body of the component, and not from an event handler. A
398
+ registration inside a click handler is added each time the handler runs, and the SDK
399
+ releases it only when the component unmounts. The registrations therefore accumulate.
400
+ Use the options object above where you can.
367
401
  :::
368
402
 
369
- ### Preventing navigation on success
403
+ ### Stop the move to the next step
370
404
 
371
- By default, the experience advances to the next step after a successful submission. Call `stopNavigation()` inside `onSuccess` to prevent this, for example when you need to handle navigation manually:
405
+ By default the experience advances to the next step after a successful submission.
406
+ Call `stopNavigation()` inside `onSuccess` to stop this. Do it when you must choose
407
+ the next step yourself:
372
408
 
373
409
  :::code-group
374
410
 
@@ -383,7 +419,7 @@ By default, the experience advances to the next step after a successful submissi
383
419
  onSuccess: ({ data, stopNavigation }) => {
384
420
  stopNavigation()
385
421
 
386
- // Navigate to step based on submitted data
422
+ // Choose the next step from the data that the user sent.
387
423
  if (data['plan-type'] === 'enterprise') {
388
424
  experience.goToStepById('enterprise-contact')
389
425
  } else {
@@ -407,7 +443,7 @@ function PlanStep() {
407
443
  onSuccess: ({ data, stopNavigation }) => {
408
444
  stopNavigation()
409
445
 
410
- // Decide the next step based on submitted data
446
+ // Choose the next step from the data that the user sent.
411
447
  if (data['plan-type'] === 'enterprise') {
412
448
  experience.goToStepById('enterprise-contact')
413
449
  } else {
@@ -422,13 +458,22 @@ function PlanStep() {
422
458
 
423
459
  :::
424
460
 
425
- Navigate by step `id` with `goToStepById('<step-id>')`, or by the step's semantic `key` with `goToStepByKey('<step-key>')`. Both jump straight to the matching step node; if no step matches, the call is a no-op.
461
+ Move by step `id` with `goToStepById('<step-id>')`, or by the `key` of the step with
462
+ `goToStepByKey('<step-key>')`. Both jump directly to the step node that matches. When
463
+ no step matches, the call does nothing.
426
464
 
427
465
  ## Best practices
428
466
 
429
- - **Give steps meaningful labels**: The `label` is used in analytics events and the dashboard — prefer descriptive names like `"Contact Details"` over `"Step 2"`
430
- - **Keep steps focused**: Each step should collect a small, related set of information. Prefer more steps over longer forms to reduce abandonment
431
- - **Use conditionals for branching**: Avoid duplicating steps — model branching logic with `nextConditional()` so shared steps can be reused
432
- - **Use `stopNavigation` sparingly**: Automatic navigation after submit is the expected behavior. Only disable it when you need explicit control over the next destination
433
- - **Validate eagerly on blur, not on change**: The SDK follows this pattern automatically — fields show errors after the user leaves them, not as they type
434
- - **Lazy-load large step components**: Use the `component` prop on `<Step>` with a dynamic import for content that is only shown to a subset of users
467
+ - **Give each step a meaningful label**: analytics events and the dashboard both show
468
+ the `label`. Use a name that says what the step does, such as `"Contact Details"`
469
+ and not `"Step 2"`.
470
+ - **Keep each step small**: each step must collect one small set of related
471
+ information. More steps with fewer fields each reduce the number of users who leave.
472
+ - **Use a condition to take a branch**: do not make a second copy of a step. Write the
473
+ branch with `nextConditional()`, so that you can reuse a shared step.
474
+ - **Use `stopNavigation` only when you must**: an automatic move after a submit is
475
+ what a user expects. Stop it only when you must choose the next step yourself.
476
+ - **Check a field on blur, not on change**: the SDK does this for you. A field shows
477
+ an error after the user leaves it, and not while the user types.
478
+ - **Load a large step component lazily**: use the `component` prop on `<Step>` with a
479
+ dynamic import, for content that only some users see.
@@ -6,7 +6,8 @@ import { Screenshot } from '../../components/Screenshot'
6
6
 
7
7
  # Steps
8
8
 
9
- A step is a visual screen or card shown to visitors during an experience. Each step holds one or more fields that collect data or display content.
9
+ A step is one screen or card that a visitor sees during an experience. Each step
10
+ holds one or more fields. A field collects data or shows content.
10
11
 
11
12
  <Screenshot
12
13
  route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/steps"
@@ -18,27 +19,35 @@ A step is a visual screen or card shown to visitors during an experience. Each s
18
19
  1. Open your project.
19
20
  2. Click **Steps** in the sidebar.
20
21
  3. Click **New Step**.
21
- 4. Enter a name and key.
22
+ 4. Enter a name and a key.
22
23
  5. Click **Create**.
23
24
 
24
- The key is a unique identifier used in your application. It's set once and can't be changed later.
25
+ The key is a unique identifier for your application. You set it once, and you cannot
26
+ change it later.
25
27
 
26
- ## Configure a step
28
+ ## Set up a step
27
29
 
28
- **Name**: The display name shown in the dashboard. Not visible to visitors.
30
+ **Name**: The name that the dashboard shows. Your visitors do not see it.
29
31
 
30
- **Key**: The programmatic identifier used by your application to reference this step. Not visible to visitors.
32
+ **Key**: The identifier that your application uses for this step. Your
33
+ visitors do not see it.
31
34
 
32
- **Layout Name**: The name of the component layout used to render this step. For example, `card`, `modal`, or `fullscreen`.
35
+ **Layout Name**: The name of the component layout that renders this step. Examples
36
+ are `card`, `modal`, and `fullscreen`.
33
37
 
34
- A step can also carry a **Step Layout**, a reusable CMS schema that backs the step with a Content Entry. See [Page & Step Layouts](/content/layouts).
38
+ A step can also carry a **Step Layout**. A step layout is a reusable CMS schema, and
39
+ it gives the step a Content Entry. See [Page and Step Layouts](/content/layouts).
35
40
 
36
41
  ## Assign fields
37
42
 
38
- In the flow builder, drag a field from the entity browser onto a step node. The field appears in the step's **Assigned Fields** list in the inspector.
43
+ In the flow builder, drag a field from the entity browser onto a step node. The field
44
+ appears in the **Assigned Fields** list of the step, in the inspector.
39
45
 
40
- You can also view and manage assigned fields by selecting the step node and expanding the **Assigned Fields** section. Click any field to open its inspector.
46
+ You can also choose the step node and expand the **Assigned Fields** section to see
47
+ and control the assigned fields. Click a field to open its inspector.
41
48
 
42
49
  ## Reuse steps
43
50
 
44
- Steps are project-level entities. You can drag the same step into multiple experience flows. Changes to a step's configuration apply everywhere it's used.
51
+ A step is a project-level entity. You can drag the same step into more than one
52
+ experience flow. A change to the options of a step applies at every place that uses
53
+ it.
@@ -4,13 +4,16 @@ layout: docs
4
4
 
5
5
  # Team management
6
6
 
7
- Manage who has access to your organization and what they can do. Organizations are the top-level container for projects and members.
7
+ Control who can reach your organization and what each person can do. An organization
8
+ is the top-level container for your projects and members.
8
9
 
9
10
  ## Organizations
10
11
 
11
- An organization groups your projects, members, and settings. You can belong to multiple organizations.
12
+ An organization groups your projects, your members, and your settings. You can belong
13
+ to more than one organization.
12
14
 
13
- Switch between organizations using the organization selector in the top of the sidebar.
15
+ To move between organizations, use the organization control at the top of the
16
+ sidebar.
14
17
 
15
18
  ## Invite members
16
19
 
@@ -18,23 +21,26 @@ Switch between organizations using the organization selector in the top of the s
18
21
  2. Click **Members**.
19
22
  3. Click **Invite**.
20
23
  4. Enter an email address.
21
- 5. Select a role.
24
+ 5. Choose a role.
22
25
  6. Click **Send Invite**.
23
26
 
24
- The invited user receives an email with a link to join your organization. They need to create a Picked account if they don't have one.
27
+ The person that you invite receives an email with a link to join your organization.
28
+ A person without a Picked account must create one first.
25
29
 
26
30
  ## Remove members
27
31
 
28
32
  1. Open your organization.
29
33
  2. Click **Members**.
30
- 3. Find the member you want to remove.
31
- 4. Click **Remove** next to their name.
32
- 5. Confirm the removal.
34
+ 3. Find the member that you want to remove.
35
+ 4. Click **Remove** next to the name of the member.
36
+ 5. Confirm.
33
37
 
34
- The member immediately loses access to all projects in the organization.
38
+ The member loses access to every project in the organization immediately.
35
39
 
36
40
  ## Roles and permissions
37
41
 
38
- Members are assigned roles that control what they can access. Roles determine whether a member can create projects, edit experiences, manage other members, or view reports.
42
+ Each member holds a role, and the role controls what the member can reach. A role
43
+ decides whether a member can create a project, edit an experience, control other
44
+ members, or read a report.
39
45
 
40
- Assign the minimum role needed for each member's responsibilities.
46
+ Give each member the smallest role that the work of that member needs.