@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,11 +4,13 @@ layout: docs
4
4
 
5
5
  # Actions
6
6
 
7
- Register action handlers on the Picked client so the SDK can invoke them when an experience flow reaches an action node.
7
+ Register an action handler on the Picked client. The SDK then invokes the handler
8
+ when an experience flow reaches an action node.
8
9
 
9
- ## Registering actions
10
+ ## Register an action
10
11
 
11
- Register actions when creating the Picked client. Each key in the `actions` object must match an action key defined in the dashboard:
12
+ Register your actions when you create the Picked client. Each key in the `actions`
13
+ object must match an action key from the dashboard:
12
14
 
13
15
  ```ts
14
16
  import { createClient } from '@picked/client'
@@ -26,41 +28,55 @@ const picked = createClient({
26
28
  })
27
29
  ```
28
30
 
29
- Each action is a function that receives optional parameters and usually runs a side effect. When the flow reaches an action node, the SDK calls the matching function and branches on whether it succeeds or throws (see [Success and failure branching](#success-and-failure-branching)).
31
+ Each action is a function. It takes optional parameters and it usually runs a side
32
+ effect. When the flow reaches an action node, the SDK calls the function that
33
+ matches. The SDK then takes a branch, which depends on whether the function succeeds
34
+ or throws. See [Success and failure branches](#success-and-failure-branches).
30
35
 
31
36
  ## How actions work
32
37
 
33
- 1. **Registration**: Actions are registered to the Picked client during initialization
34
- 2. **Flow traversal**: When the SDK traverses the experience flow and reaches an action node, it looks up the action by its key
35
- 3. **Execution**: The matching function is called and awaited. The SDK then continues to the next node based on the action's outcome
36
- 4. **Non-blocking**: Actions don't interrupt the visitor's experience. The current step stays on screen while an async action runs
38
+ 1. **Registration**: you register each action on the Picked client at startup.
39
+ 2. **Flow traversal**: the SDK walks the experience flow. When it reaches an action
40
+ node, it finds the action by its key.
41
+ 3. **Execution**: the SDK calls the function that matches and awaits it. The outcome
42
+ then decides which node comes next.
43
+ 4. **No block**: an action does not interrupt your visitor. The current step
44
+ stays on the screen while an async action runs.
37
45
 
38
- ## Success and failure branching
46
+ ## Success and failure branches
39
47
 
40
- An action node can have separate **success** and **failure** branches. The SDK selects the branch from the action's outcome:
48
+ An action node can have a **success** branch and a **failure** branch. The SDK
49
+ chooses the branch from the outcome of the action:
41
50
 
42
- - **Failure** — the action throws (or its promise rejects). The flow follows the failure (`onFail`) branch.
43
- - **Success** — the action resolves to any value (`void`, `true`, `false`, or a string). A resolved `false` is a valid response, not a failure. The flow follows the success (`onSuccess`) branch.
51
+ - **Failure** — the action throws, or its promise rejects. The flow takes the failure
52
+ branch, `onFail`.
53
+ - **Success** — the action resolves to any value: `void`, `true`, `false`, or a
54
+ string. A resolved `false` is a valid response and not a failure. The flow takes
55
+ the success branch, `onSuccess`.
44
56
 
45
- Reserve the failure branch for genuine errors — throw on the failure case, since a returned value never routes to `onFail`:
57
+ Keep the failure branch for a true error. Throw on the failure case, because a
58
+ returned value never takes the `onFail` branch:
46
59
 
47
60
  ```ts
48
61
  'check-eligibility': async () => {
49
62
  const response = await fetch('/api/eligibility')
50
- // Throw only when the request itself fails — that takes the failure branch.
63
+ // Throw only when the request itself fails. That takes the failure branch.
51
64
  if (!response.ok) throw new Error('Eligibility request failed')
52
65
  }
53
66
  ```
54
67
 
55
- A valid negative result is not a failure. An API that returns `200` with a "no" (for example `{ serviced: false }`) should record that value as a response and branch on it with a condition, rather than throw:
68
+ A valid negative result is not a failure. An API can return `200` with a "no", for
69
+ example `{ serviced: false }`. Record that value as a response and branch on it with
70
+ a condition. Do not throw:
56
71
 
57
72
  ```ts
58
73
  'check-serviced-area': async ({ client, responses }) => {
59
74
  const postcode = responses.find((r) => r.fieldKey === 'postcode')?.value
60
75
  const { serviced } = await fetch(`/api/serviced?postcode=${postcode}`).then((r) => r.json())
61
- // Commit the outcome as a response; a downstream condition routes the flow.
76
+ // Store the outcome as a response. A later condition then takes the branch.
62
77
  client.responses.register('is-serviced', serviced)
63
78
  }
64
79
  ```
65
80
 
66
- If an action node has only a single plain connection (no explicit success or failure branch), the flow continues through it on either outcome.
81
+ An action node can also have one plain connection, with no success or failure branch.
82
+ The flow then continues through it on either outcome.
@@ -4,11 +4,13 @@ layout: docs
4
4
 
5
5
  # Conditions
6
6
 
7
- Conditions use JSON Logic to evaluate client state. Use them to gate experience activation, branch step navigation, and control field visibility.
7
+ A condition uses JSON Logic to evaluate the client state. Use a condition to control
8
+ when an experience starts, to choose the next step, and to show or hide a field.
8
9
 
9
10
  ## Conditions in experiences
10
11
 
11
- A common pattern is using conditions between steps to branch the flow based on form responses:
12
+ A common pattern puts a condition between two steps. The flow then takes a branch
13
+ that the form responses decide:
12
14
 
13
15
  ```ts
14
16
  import { createExperience } from '@picked/client/plugins/create-experience'
@@ -47,12 +49,13 @@ isSigningUpForNewsletter.addFalse(userDoesNotWantNewsletter)
47
49
 
48
50
  ## Conditional fields
49
51
 
50
- Fields can be shown or hidden based on previous responses or [predicates](./predicates) using JSON Logic conditions:
52
+ A JSON Logic condition can show or hide a field. The condition can read an earlier
53
+ response or a [predicate](./predicates):
51
54
 
52
55
  ```ts
53
56
  import { createField } from '@picked/client/plugins/create-experience'
54
57
 
55
- // Show email field only if user selected "email" as a contact method
58
+ // Show the email field only when the user chose "email" as a contact method.
56
59
  export const emailField = createField({
57
60
  id: 'email-input',
58
61
  name: 'email-input',
@@ -70,7 +73,7 @@ export const emailField = createField({
70
73
  },
71
74
  })
72
75
 
73
- // Show field based on predicate
76
+ // Show the field when the predicate returns true.
74
77
  export const mailingAddressField = createField({
75
78
  id: 'mailing-address',
76
79
  name: 'mailing-address',
@@ -4,15 +4,16 @@ layout: docs
4
4
 
5
5
  # Content Security Policy
6
6
 
7
- The Picked SDK loads resources from the following domains. Add each to the matching CSP directive.
7
+ The Picked SDK loads resources from the domains below. Add each domain to the CSP
8
+ directive that the table gives.
8
9
 
9
10
  ## Required domains
10
11
 
11
12
  | Domain | Directive | Purpose |
12
13
  | --- | --- | --- |
13
- | `api.picked.app` | `connect-src` | API requests, project configuration, experiences, session tracking |
14
- | `evnts.pckd.dev` | `connect-src` | Analytics event collection (Snowplow) |
15
- | `img.picked-content.com` | `img-src` | Optimised images served via the [image optimisation service](../image-optimisation-service.md) |
14
+ | `api.picked.app` | `connect-src` | API requests, project options, experiences, and the record of each session |
15
+ | `evnts.pckd.dev` | `connect-src` | Analytics events, which Snowplow collects |
16
+ | `img.picked-content.com` | `img-src` | Optimised images, which the [image optimisation service](../image-optimisation-service.md) serves |
16
17
 
17
18
  ## Configuration
18
19
 
@@ -4,11 +4,18 @@ layout: docs
4
4
 
5
5
  # Experiences
6
6
 
7
- Define experiences in code with `createExperience()`, or load them from the dashboard at runtime with the [Dynamic Experiences Plugin](./plugins/dynamic-experiences).
7
+ Define an experience in code with `createExperience()`. You can also load an
8
+ experience from the dashboard at runtime with the
9
+ [Dynamic Experiences Plugin](./plugins/dynamic-experiences).
8
10
 
9
- The execution flow below targets a DOM selector and renders into it. A Page experience instead renders through a named slot: target it to `.picked-slot-<name>` and render `<ExperienceSlot name="<name>" />` on your server. Use the slot path for SEO-critical, server-rendered Pages; use the selector path for browser-rendered Flows. See [Server-Side Rendering](/sdk/ssr) and [Slots & Placements](/sdk/ssr/slots-and-placements).
11
+ The flow below points at a DOM selector and renders into it. A Page experience
12
+ renders through a named slot instead. Point it at `.picked-slot-<name>` and render
13
+ `<ExperienceSlot name="<name>" />` on your server. Use the slot path for a Page that
14
+ the server renders and that search engines must read. Use the selector path for a
15
+ Flow that the browser renders. See [Server-Side Rendering](/sdk/ssr) and
16
+ [Slots and Placements](/sdk/ssr/slots-and-placements).
10
17
 
11
- ## Creating experiences
18
+ ## Create an experience
12
19
 
13
20
  ```ts
14
21
  import { createExperience } from '@picked/client/plugins/create-experience'
@@ -32,15 +39,26 @@ export const productPageExperience = createExperience({
32
39
  })
33
40
  ```
34
41
 
35
- Register experiences using the [Create Experience Plugin](./plugins/create-experience) for code-defined experiences, or the [Dynamic Experiences Plugin](./plugins/dynamic-experiences) to fetch and manage them from the dashboard at runtime.
42
+ To register an experience that you define in code, use the
43
+ [Create Experience Plugin](./plugins/create-experience). To read an experience from
44
+ the dashboard at runtime and control it there, use the
45
+ [Dynamic Experiences Plugin](./plugins/dynamic-experiences).
36
46
 
37
47
  ## How experiences work
38
48
 
39
- When an experience is registered, the SDK follows this execution flow:
49
+ After you register an experience, the SDK follows this flow:
40
50
 
41
- 1. **Registration**: Experiences are registered to the Picked client using [createExperiencePlugin](./plugins/create-experience) for code-defined experiences, or [dynamicExperiencesPlugin](./plugins/dynamic-experiences) for dashboard-managed experiences
42
- 2. **Condition evaluation**: The SDK evaluates the experience's `condition` using the logic engine to determine if it should be shown
43
- 3. **Target resolution**: The SDK searches the DOM for elements matching the `targets` selectors (e.g., `body`, `.product-page`, `#checkout`)
44
- 4. **Initialization**: If the condition evaluates to `true` and at least one target element exists, the experience is initialized
45
- 5. **Component rendering**: The experience's component is rendered and injected into the DOM at the specified target location and placement (before, after, append, prepend, or replace)
46
- 6. **Flow execution**: If the experience contains steps, fields, and conditionals, users can progress through the multi-step flow with validation and branching logic
51
+ 1. **Registration**: You register the experience on the Picked client. Use
52
+ `createExperiencePlugin` for an experience that you define in code. Use
53
+ `dynamicExperiencesPlugin` for one that you control from the dashboard.
54
+ 2. **Condition evaluation**: The SDK evaluates the `condition` of the experience with
55
+ the logic engine. The result decides whether a visitor sees it.
56
+ 3. **Target resolution**: The SDK searches the DOM for an element that matches a
57
+ selector in `targets`. Examples are `body`, `.product-page`, and `#checkout`.
58
+ 4. **Initialization**: The SDK starts the experience when the condition is `true` and
59
+ at least one target element is there.
60
+ 5. **Component rendering**: The SDK renders the component of the experience. It puts
61
+ the component into the DOM at the target and the placement that you set. A
62
+ placement is `before`, `after`, `append`, `prepend`, or `replace`.
63
+ 6. **Flow execution**: An experience can hold steps, fields, and conditions. A user
64
+ then moves through the flow. The flow checks each answer and takes a branch.
@@ -4,87 +4,108 @@ layout: docs
4
4
 
5
5
  # Allocation and exposure
6
6
 
7
- Reading a feature flag's bucket is what allocates a visitor to it. This page covers when that happens, how to observe it, and how to make sure a visitor is allocated before you report on it.
7
+ A read of the bucket of a feature flag is what allocates a visitor to that feature flag. This
8
+ page covers when that happens and how to watch it. It also covers how to be sure that
9
+ Picked allocates a visitor before you report on that visitor.
8
10
 
9
- ## Registering a feature flag is not deciding it
11
+ ## A registration is not a decision
10
12
 
11
- The SDK loads every feature flag in your project at startup, but it does not allocate visitors then. A feature flag stays undecided until something reads its bucket. The first read runs the `decide` function, stores the result, and reports the decision.
13
+ The SDK loads every feature flag in your project at startup, but it allocates no visitor
14
+ then. A feature flag stays undecided until something reads its bucket. The first read runs the
15
+ `decide` function, stores the result, and reports the decision.
12
16
 
13
- That means you choose when a visitor is committed to a feature flag, simply by choosing when you read it.
17
+ You therefore choose when Picked commits a visitor to a feature flag. You choose it by the
18
+ point at which you read the feature flag.
14
19
 
15
- ## Reading a bucket records an exposure
20
+ ## A read of a bucket records an exposure
16
21
 
17
- Because the first read is the decision, reading a bucket is the moment the visitor enters the test. Read every feature flag at startup and every visitor counts as exposed to every test, including ones they never reached.
22
+ The first read is the decision, so a read of a bucket is the moment when the visitor
23
+ enters the test. If you read every feature flag at startup, every visitor counts as exposed
24
+ to every test. This includes a test that the visitor never reached.
18
25
 
19
26
  :::warning
20
- Avoid looping over every feature flag at startup to read its bucket. It commits every visitor to every test immediately and removes your ability to defer a decision until the data it depends on has arrived.
27
+ Do not loop over every feature flag at startup to read its bucket. That commits every visitor
28
+ to every test immediately. You then have no way to wait for the data that a decision
29
+ needs.
21
30
  :::
22
31
 
23
- Read a feature flag at the point the decision matters — after the cart loads, after the quote returns, after you know who the visitor is.
32
+ Read a feature flag at the point where the decision matters. That is after the cart loads,
33
+ after the quote returns, or after you know who the visitor is.
24
34
 
25
- ## Inspecting feature flags without deciding them
35
+ ## Check a feature flag without a decision
26
36
 
27
- `client.features.listStore` holds every registered feature flag. Each entry reports whether it has been decided yet, so you can inspect feature flags without committing anyone:
37
+ `client.features.listStore` holds every registered feature flag. Each entry reports whether
38
+ the feature flag is decided yet, so you can check a feature flag and commit nobody:
28
39
 
29
40
  ```ts
30
- for (const flag of client.features.listStore.getState()) {
31
- console.log(flag.id, flag.isResolved)
41
+ for (const feature flag of client.features.listStore.getState()) {
42
+ console.log(feature flag.id, feature flag.isResolved)
32
43
  }
33
44
  ```
34
45
 
35
- `getBucket()` behaves differently depending on that state:
46
+ `getBucket()` behaves differently for each state:
36
47
 
37
- - **Resolved** — returns `{ id, bucket }` synchronously. Reading it changes nothing.
38
- - **Not resolved** — returns a promise. Awaiting it runs `decide` and commits the visitor.
48
+ - **Resolved** — it returns `{ id, bucket }` at once. The read changes nothing.
49
+ - **Not resolved** — it returns a promise. An `await` runs `decide` and commits the
50
+ visitor.
39
51
 
40
- `await` works for both, so you can treat them uniformly when you do intend to decide.
52
+ `await` works for both states, so you can treat them the same way when you do intend
53
+ to decide.
41
54
 
42
55
  ## When `feature_decide` fires
43
56
 
44
- A `feature_decide` event is emitted the first time a feature flag is decided for a visitor — and only then.
57
+ The SDK sends a `feature_decide` event the first time that it decides a feature flag for a
58
+ visitor, and only then.
45
59
 
46
60
  :::warning
47
- On later page loads the decision is restored from browser storage, and **no event is emitted**. A returning visitor produces no `feature_decide` events at all, even though they are still allocated.
61
+ On a later page load the SDK restores the decision from browser storage, and it
62
+ sends **no event**. A visitor who returns produces no `feature_decide` event at all,
63
+ although Picked still allocates that visitor.
48
64
 
49
- An integration built only on `feature_decide` will therefore report nothing for returning visitors.
65
+ An integration that reads only `feature_decide` therefore reports nothing for a
66
+ visitor who returns.
50
67
  :::
51
68
 
52
- Use the right source for the question you are asking:
69
+ Use the correct source for your question:
53
70
 
54
71
  | Question | Use |
55
72
  | --- | --- |
56
- | What is this visitor allocated to right now? | `client.features.listStore` |
57
- | Was a new decision just made? | `feature_decide` on `client.eventBus` |
58
- | What decisions happened before I subscribed? | `client.events` |
73
+ | What is this visitor allocated to now? | `client.features.listStore` |
74
+ | Did the SDK make a new decision? | `feature_decide` on `client.eventBus` |
75
+ | Which decisions happened before I subscribed? | `client.events` |
59
76
 
60
- ## Observing decisions
77
+ ## Watch for decisions
61
78
 
62
- `listStore` updates whenever a feature flag resolves, whether it was restored from storage or decided just now. Watching it gives you the current picture at all times:
79
+ `listStore` updates each time that a feature flag resolves. It does this whether the SDK
80
+ restored the feature flag from storage or decided it now. A watch on `listStore` therefore
81
+ always gives you the current state:
63
82
 
64
83
  ```ts
65
84
  const buckets = new Map<string, string | boolean>()
66
85
 
67
- client.features.listStore.watch((flags) => {
68
- for (const flag of flags) {
69
- if (!flag.isResolved) continue
70
- buckets.set(flag.id, flag.getBucket().bucket)
86
+ client.features.listStore.watch((feature flags) => {
87
+ for (const feature flag of feature flags) {
88
+ if (!feature flag.isResolved) continue
89
+ buckets.set(feature flag.id, feature flag.getBucket().bucket)
71
90
  }
72
91
  })
73
92
  ```
74
93
 
75
- Use `feature_decide` when you need to know a decision was *made*, rather than what the current value is — for example to send your own exposure event:
94
+ Use `feature_decide` when you need to know that the SDK *made* a decision, and not
95
+ what the current value is. An example is your own exposure event:
76
96
 
77
97
  ```ts
78
98
  client.eventBus.watch((event) => {
79
99
  if (event.type !== 'feature_decide') return
80
100
  analytics.track('Experiment exposure', {
81
- flag: event.feature.id,
101
+ feature flag: event.feature.id,
82
102
  bucket: event.feature.bucket,
83
103
  })
84
104
  })
85
105
  ```
86
106
 
87
- If you subscribe after startup, earlier events are still available. `client.events` holds every event fired since the client initialised:
107
+ An earlier event is still available when you subscribe after startup. `client.events`
108
+ holds every event since the client started:
88
109
 
89
110
  ```ts
90
111
  const earlier = client.events
@@ -92,38 +113,49 @@ const earlier = client.events
92
113
  .filter((entry) => entry.payload.type === 'feature_decide')
93
114
  ```
94
115
 
95
- ## Deciding before you report
116
+ ## Decide before you report
96
117
 
97
- To guarantee a feature flag is decided before a downstream event fires, await it first:
118
+ To be sure that the SDK decides a feature flag before a later event fires, `await` the feature flag
119
+ first:
98
120
 
99
121
  ```ts
100
- const flag = client.features.getById('quote-experiment').getState()
122
+ const feature flag = client.features.getById('quote-experiment').getState()
101
123
 
102
- if (flag) {
103
- const { bucket } = await flag.getBucket()
124
+ if (feature flag) {
125
+ const { bucket } = await feature flag.getBucket()
104
126
  trackViewQuote({ experiment: bucket })
105
127
  }
106
128
  ```
107
129
 
108
- Awaiting `getBucket()` resolves the feature flag if it has not been decided, and returns the stored value if it has. Either way the bucket is known before the next line runs.
130
+ An `await` on `getBucket()` resolves the feature flag when it is undecided, and returns the
131
+ stored value when it is decided. In both cases the bucket is known before the next
132
+ line runs.
109
133
 
110
- ## What Picked already reports
134
+ ## What Picked reports for you
111
135
 
112
- Before building your own tracking, note what the SDK sends already.
136
+ Read this before you build a report of your own.
113
137
 
114
- Every Picked analytics event carries a `feature_list` context describing the feature flags this visitor has resolved:
138
+ Every Picked analytics event carries a `feature_list` context. The context describes
139
+ each feature flag that this visitor resolved:
115
140
 
116
141
  | Field | Description |
117
142
  | --- | --- |
118
- | `ab_feature` | Feature flag ID |
119
- | `ab_name` | Feature flag name |
120
- | `ab_key` | Version key, such as `v3` |
143
+ | `ab_feature` | The feature flag ID |
144
+ | `ab_name` | The feature flag name |
145
+ | `ab_key` | The version key, such as `v3` |
121
146
  | `ab_bucket` | The resolved bucket |
122
147
 
123
- Only resolved feature flags appear, so the context reflects what the visitor was actually exposed to at the time the event fired.
148
+ Only a resolved feature flag appears there. The context therefore shows what the visitor was
149
+ exposed to at the time when the event fired.
124
150
 
125
- If the [Sentry plugin](https://www.npmjs.com/package/@picked/sentry) is installed, each decision also tags the Sentry scope as `picked.feature.<flag-id>` with the bucket as the value, so errors can be filtered by variant.
151
+ The [Sentry plugin](https://www.npmjs.com/package/@picked/sentry) is optional. When
152
+ it is installed, each decision also tags the Sentry scope as
153
+ `picked.feature.<feature flag-id>`, with the bucket as the value. You can then filter your
154
+ errors by the variant.
126
155
 
127
156
  ## Preview mode
128
157
 
129
- In [preview mode](/workspaces#preview) decisions are stored in session storage rather than local storage. They do not persist beyond the tab, and a preview visitor may be allocated differently from a production visitor. Treat preview allocations as disposable.
158
+ In [preview mode](/workspaces#preview) the SDK stores a decision in session storage
159
+ and not in local storage. The decision does not stay after you close the tab, and
160
+ Picked can allocate a preview visitor differently from a production visitor. Treat a
161
+ preview allocation as disposable.