@picked/client 0.0.55 → 0.0.56-next.2

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 (65) hide show
  1. package/dist/{api-B1yTKITq.js → api-DHSz_ruC.js} +1 -1
  2. package/dist/api-DVSjNleu.cjs +1 -0
  3. package/dist/cjs/form/index.js +1 -1
  4. package/dist/cjs/index.js +1 -1
  5. package/dist/cjs/plugins/create-experience/index.js +1 -1
  6. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  7. package/dist/docs/actions.md +58 -0
  8. package/dist/docs/api/index.md +32 -0
  9. package/dist/docs/api-keys.md +33 -0
  10. package/dist/docs/conditions.md +52 -0
  11. package/dist/docs/experiences.md +33 -0
  12. package/dist/docs/features.md +51 -0
  13. package/dist/docs/fields.md +70 -0
  14. package/dist/docs/flow-builder/building-flows.md +61 -0
  15. package/dist/docs/flow-builder/condition-builder.md +54 -0
  16. package/dist/docs/flow-builder/keyboard-shortcuts.md +66 -0
  17. package/dist/docs/flow-builder/nodes.md +43 -0
  18. package/dist/docs/flow-builder/overview.md +43 -0
  19. package/dist/docs/index.md +79 -0
  20. package/dist/docs/pickedql/data-sources.md +39 -0
  21. package/dist/docs/pickedql/dimensions.md +23 -0
  22. package/dist/docs/pickedql/examples.md +165 -0
  23. package/dist/docs/pickedql/filtering.md +31 -0
  24. package/dist/docs/pickedql/index.md +34 -0
  25. package/dist/docs/pickedql/schema.md +95 -0
  26. package/dist/docs/pickedql/virtual-functions.md +84 -0
  27. package/dist/docs/projects.md +30 -0
  28. package/dist/docs/reference/index.md +11 -0
  29. package/dist/docs/reports.md +11 -0
  30. package/dist/docs/sdk/actions.md +7 -0
  31. package/dist/docs/sdk/conditions.md +131 -0
  32. package/dist/docs/sdk/content-security-policy.md +31 -0
  33. package/dist/docs/sdk/experiences.md +41 -0
  34. package/dist/docs/sdk/features.md +213 -0
  35. package/dist/docs/sdk/fields.md +263 -0
  36. package/dist/docs/sdk/index.md +74 -0
  37. package/dist/docs/sdk/plugins/create-experience.md +133 -0
  38. package/dist/docs/sdk/plugins/dynamic-experiences.md +59 -0
  39. package/dist/docs/sdk/plugins/index.md +9 -0
  40. package/dist/docs/sdk/plugins/window-object.md +20 -0
  41. package/dist/docs/sdk/predicates.md +113 -0
  42. package/dist/docs/sdk/steps.md +411 -0
  43. package/dist/docs/steps.md +35 -0
  44. package/dist/docs/team-management.md +40 -0
  45. package/dist/docs/tooling/chrome-extension.md +29 -0
  46. package/dist/docs/tooling/gtm-template.md +69 -0
  47. package/dist/docs/tooling/index.md +19 -0
  48. package/dist/docs/tooling/mcp.md +120 -0
  49. package/dist/docs/tooling/vite-plugin.md +82 -0
  50. package/dist/docs/workspaces.md +35 -0
  51. package/dist/esm/form/index.js +2 -2
  52. package/dist/esm/index.js +1 -1
  53. package/dist/esm/plugins/create-experience/index.js +1 -1
  54. package/dist/esm/plugins/dynamic-experiences/index.js +11 -2
  55. package/dist/types/entities/step/step.d.ts +1 -1
  56. package/dist/types/entities/step/step.d.ts.map +1 -1
  57. package/dist/types/form/create-field-evaluator.d.ts +5 -5
  58. package/dist/types/form/create-field-evaluator.d.ts.map +1 -1
  59. package/dist/types/form/create-form.d.ts +7 -7
  60. package/dist/types/form/create-form.d.ts.map +1 -1
  61. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  62. package/dist/types/plugins/dynamic-experiences/types.d.ts +9 -1
  63. package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
  64. package/package.json +2 -2
  65. package/dist/api-DO5hnUsd.cjs +0 -1
@@ -0,0 +1,263 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Fields
6
+
7
+ Fields are used to store data for a Picked instance. Fields can be added to steps or forms and rendered as inputs, or have their values directly set.
8
+
9
+ ## Defining Fields
10
+
11
+ Define fields using `createField()` from the `@picked/client/plugins/create-experience` package. Each field requires a unique identifier, name, type, label, and cardinality:
12
+
13
+ ```ts
14
+ import { createField } from '@picked/client/plugins/create-experience'
15
+
16
+ // Required text input
17
+ export const emailField = createField({
18
+ id: 'email-address',
19
+ name: 'email-address',
20
+ type: 'text',
21
+ label: 'Email Address',
22
+ multiple: false,
23
+ required: true,
24
+ placeholder: 'you@example.com',
25
+ })
26
+
27
+ // Multi-select dropdown with static options
28
+ export const communicationMethods = createField({
29
+ id: 'communication-methods',
30
+ name: 'communication-methods',
31
+ type: 'text',
32
+ label: 'Preferred contact methods',
33
+ multiple: true,
34
+ required: true,
35
+ options: [
36
+ {
37
+ id: 'email',
38
+ label: 'Email',
39
+ value: 'email',
40
+ },
41
+ {
42
+ id: 'phone',
43
+ label: 'Phone',
44
+ value: 'phone',
45
+ },
46
+ {
47
+ id: 'sms',
48
+ label: 'SMS',
49
+ value: 'sms',
50
+ },
51
+ ],
52
+ })
53
+ ```
54
+
55
+ ## How Fields Work
56
+
57
+ When a field is registered and used in an experience:
58
+
59
+ 1. **Registration**: Fields are registered to the experience during step creation via `.addField()`
60
+ 2. **Condition Evaluation**: If the field has a `condition`, it's evaluated against the current context to determine visibility
61
+ 3. **Rendering**: Visible fields are rendered in the experience UI with their label, placeholder, and options
62
+ 4. **User Input**: User enters or selects a value, triggering client-side validation if specified
63
+ 5. **Storage**: Valid responses are stored in the experience state under `responses[field.name]`
64
+ 6. **Reference**: Field values can be referenced in subsequent field conditions, step conditions, or experience logic via `{ var: 'responses.field-name' }`
65
+
66
+ ## Field Types
67
+
68
+ Fields support four primitive types, `text`, `number`, `boolean` and `object`:
69
+
70
+ ```ts
71
+ // Single text input
72
+ const nameField = createField({
73
+ type: 'text',
74
+ multiple: false,
75
+ // ...
76
+ })
77
+ ```
78
+
79
+ Provide an `options` array to allow multiple select:
80
+
81
+ ```ts
82
+ const nameField = createField({
83
+ type: 'text',
84
+ multiple: true,
85
+ options: [
86
+ {
87
+ id: 'option-one',
88
+ label: 'Option One',
89
+ value: 'product-id-one',
90
+ },
91
+ {
92
+ id: 'option-two',
93
+ label: 'Option Two',
94
+ value: 'product-id-two',
95
+ },
96
+ {
97
+ id: 'option-three',
98
+ label: 'Option Three',
99
+ value: 'product-id-three',
100
+ },
101
+ ],
102
+ })
103
+ ```
104
+
105
+ ## Conditions
106
+
107
+ Fields and field options are compatible with condition logic:
108
+
109
+ ```ts
110
+ const conditionalField = createField({
111
+ id: 'conditional-field',
112
+ type: 'text',
113
+ condition: {
114
+ invoke: {
115
+ var: 'responses.has-answered-question',
116
+ },
117
+ },
118
+ options: [
119
+ {
120
+ id: 'conditional-option',
121
+ label: 'Conditional Label',
122
+ condition: {
123
+ invoke: {
124
+ var: 'responses.eligible-for-option',
125
+ },
126
+ },
127
+ },
128
+ ],
129
+ })
130
+ ```
131
+
132
+ ## Field Validation
133
+
134
+ Validate user input using JSON Logic rules. The `$input` variable contains the current field value:
135
+
136
+ ```ts
137
+ const quantityField = createField({
138
+ id: 'quantity',
139
+ name: 'quantity',
140
+ type: 'number',
141
+ label: 'Quantity',
142
+ multiple: false,
143
+ required: true,
144
+ validation: {
145
+ catch: {
146
+ or: [
147
+ {
148
+ and: [
149
+ {
150
+ // Input value is above 0
151
+ '>': [
152
+ {
153
+ var: '$input',
154
+ },
155
+ 0,
156
+ ],
157
+ },
158
+ {
159
+ // Input value is equal or below 999
160
+ '<=': [
161
+ {
162
+ var: '$input',
163
+ },
164
+ 999,
165
+ ],
166
+ },
167
+ ],
168
+ },
169
+ {
170
+ throw: 'Please enter a value between 1 and 999',
171
+ },
172
+ ],
173
+ },
174
+ },
175
+ })
176
+ ```
177
+
178
+ :::tip
179
+ The `catch`/`throw` pattern in JSON Logic validates that the condition evaluates to `true`. If it doesn't, the `throw` message is displayed to the user.
180
+ :::
181
+
182
+ ## Field Policies
183
+
184
+ Control how field data is tracked and stored using privacy policies:
185
+
186
+ ```ts
187
+ const sensitiveField = createField({
188
+ id: 'ssn',
189
+ name: 'ssn',
190
+ type: 'text',
191
+ label: 'Social Security Number',
192
+ multiple: false,
193
+ required: true,
194
+ policies: {
195
+ doNotTrackValue: true, // Don't send value to analytics
196
+ doNotStoreValue: true, // Don't persist value to backend
197
+ },
198
+ })
199
+ ```
200
+
201
+ **Privacy policies**:
202
+
203
+ - `doNotTrackValue`: Excludes field value from analytics events (only field ID is tracked)
204
+ - `doNotStoreValue`: Prevents field value from being persisted to backend storage
205
+
206
+ Use these policies for sensitive data like SSNs, credit cards, or passwords that should only exist client-side.
207
+
208
+ ## Field Metadata
209
+
210
+ Enhance user experience with additional descriptive text:
211
+
212
+ ```ts
213
+ const passwordField = createField({
214
+ id: 'password',
215
+ name: 'password',
216
+ type: 'text',
217
+ label: 'Password',
218
+ multiple: false,
219
+ required: true,
220
+ placeholder: 'Enter a strong password',
221
+ description: 'Your password must be at least 8 characters',
222
+ helpText: 'Use a mix of letters, numbers, and symbols for better security',
223
+ })
224
+ ```
225
+
226
+ **Metadata fields**:
227
+
228
+ - `placeholder`: Hint text displayed in empty input fields
229
+ - `description`: Brief explanation shown below the label
230
+ - `helpText`: Additional guidance or requirements displayed near the field
231
+
232
+ ## Accessing Field Values
233
+
234
+ Field responses are stored in the experience state and can be accessed via the `responses` object:
235
+
236
+ :::code-group
237
+
238
+ ```ts [In Conditions]
239
+ // Reference in subsequent field conditions
240
+ const sensitiveField = createField({
241
+ condition: {
242
+ '==': [
243
+ {
244
+ var: 'responses.field-name',
245
+ },
246
+ 'expected-value',
247
+ ],
248
+ },
249
+ })
250
+ ```
251
+
252
+ :::
253
+
254
+ ## Best Practices
255
+
256
+ - **Use semantic IDs and names**: Name fields descriptively (e.g., `email-address` not `field1`) for easier debugging and analytics
257
+ - **Match type to data**: Use `number` for numeric values, not `text`, to enable proper validation and comparison
258
+ - **Set appropriate `required` flags**: Only mark fields as required when truly necessary to reduce form abandonment
259
+ - **Keep validation messages clear**: Error messages should tell users exactly how to fix the input
260
+ - **Use conditional fields for progressive disclosure**: Show fields only when relevant based on prior responses
261
+ - **Apply privacy policies to sensitive data**: Use `doNotTrackValue` and `doNotStoreValue` for PII, passwords, or financial data
262
+ - **Provide helpful metadata**: Add `placeholder` and `description` to guide users, especially for complex or formatted inputs
263
+ - **Keep option lists scannable**: For more than 5 options, consider providing search/filter functionality or breaking into multiple steps
@@ -0,0 +1,74 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Picked SDK
6
+
7
+ The Picked SDK enables you to add dynamic, personalized experiences to your web applications with minimal code.
8
+
9
+ :::tip
10
+ The SDK is framework-agnostic and works with React, Svelte, Vue, and vanilla JavaScript.
11
+ :::
12
+
13
+ ## Quick Start
14
+
15
+ :::code-group
16
+
17
+ ```typescript [Browser]
18
+ import { createClient } from '@picked/client'
19
+ import { windowObjectPlugin } from '@picked/client/plugins/window-object'
20
+ import { singlePageAppPlugin } from '@picked/client/plugins/single-page-app'
21
+
22
+ const picked = createClient({
23
+ organizationId: 'your-organization-id',
24
+ projectId: 'your-project-id',
25
+ plugins: [windowObjectPlugin(), singlePageAppPlugin()],
26
+ })
27
+
28
+ picked.init()
29
+ ```
30
+
31
+ ```ts [Server]
32
+ Coming Soon...
33
+ ```
34
+
35
+ :::
36
+
37
+ :::tip
38
+ If you’re running Picked in a browser environment, access the Picked client in the console with `window.Picked`
39
+ :::
40
+
41
+ ## Precondition & Condition
42
+
43
+ | Category | Question | Description |
44
+ | ------------ | ---------------------- | --------------------------------------------------------------------------------------------- |
45
+ | Precondition | Should Picked start? | Use for environment checks, page filtering, or feature flags that completely disable the SDK. |
46
+ | Condition | Is this user eligible? | Use for user segmentation, A/B test enrollment, or targeting specific user groups. |
47
+
48
+ ```ts
49
+ const picked = createClient({
50
+ precondition: {
51
+ and: [
52
+ {
53
+ or: [
54
+ {
55
+ invoke: {
56
+ var: 'features.aa-test',
57
+ },
58
+ },
59
+ true,
60
+ ],
61
+ },
62
+ { invoke: { var: 'predicates.shopify-is-product-page' } },
63
+ ],
64
+ },
65
+ condition: {
66
+ invoke: {
67
+ var: 'features.convincely-enabled',
68
+ },
69
+ },
70
+ // rest of picked config
71
+ })
72
+ ```
73
+
74
+ See [conditions](./conditions.md) for more detail
@@ -0,0 +1,133 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Create Experience Plugin
6
+
7
+ A plugin to register an array of experiences to the Picked client.
8
+
9
+ ## Setup Plugin
10
+
11
+ :::code-group
12
+
13
+ ```ts [app.ts]
14
+ import { createClient } from '@picked/client'
15
+ import { createExperiencePlugin } from '@picked/client/plugins/create-experience'
16
+ import { funnelExperience } from '../experiences/funnel-experience'
17
+ import { widgetExperience } from '../experiences/widget-experience'
18
+
19
+ const client = createClient({
20
+ organizationId: 'your-org-id',
21
+ projectId: 'your-project-id',
22
+ plugins: createExperiencePlugin({
23
+ experiences: [funnelExperience, widgetExperience],
24
+ }),
25
+ })
26
+ ```
27
+
28
+ :::
29
+
30
+ ## Defining Steps
31
+
32
+ Steps are defined using `createStep()`. They are added directly to an experience using `experience.addStep()`.
33
+
34
+ ```ts
35
+ import { createExperience, createStep } from '@picked/client/plugins/create-experience'
36
+
37
+ // Define standalone steps
38
+ const welcomeStep = createStep({
39
+ id: 'welcome',
40
+ name: 'Welcome',
41
+ })
42
+
43
+ const contactStep = createStep({
44
+ id: 'contact',
45
+ name: 'Contact Details',
46
+ })
47
+
48
+ // Define the experience and add steps to build the flow
49
+ const onboardingExperience = createExperience({
50
+ id: 'onboarding',
51
+ name: 'User Onboarding',
52
+ targets: [{ selector: 'body', placement: 'after' }],
53
+ })
54
+
55
+ onboardingExperience.addStep(welcomeStep)
56
+ onboardingExperience.addStep(contactStep)
57
+ ```
58
+
59
+ Each step requires a `name` that identifies it in analytics events. An `id` is optional — one will be generated automatically if omitted.
60
+
61
+ Steps can be used on multiple experiences.
62
+
63
+ ### Adding Fields to Steps
64
+
65
+ Fields are defined with `createField()`.
66
+
67
+ ```ts
68
+ import { createStep, createField } from '@picked/client/plugins/create-experience'
69
+
70
+ const emailField = createField({
71
+ id: 'email-address',
72
+ name: 'email-address',
73
+ type: 'text',
74
+ label: 'Email Address',
75
+ multiple: false,
76
+ required: true,
77
+ placeholder: 'you@example.com',
78
+ })
79
+ ```
80
+
81
+ Fields are added to a step with `step.addField()`, or an array of `fieldIds` when creating a step:
82
+
83
+ ```ts
84
+ const contactStep = createStep({
85
+ id: 'contact',
86
+ label: 'Contact Details',
87
+ })
88
+
89
+ contactStep.addField(emailField)
90
+
91
+ //Or
92
+
93
+ const contactStep = createStep({
94
+ id: 'contact',
95
+ label: 'Contact Details',
96
+ fieldIds: [contactStep.id],
97
+ })
98
+ ```
99
+
100
+ ## Using conditions in an experience
101
+
102
+ [Conditions](../conditions) are used to define when an experience will become active for the user.
103
+
104
+ ```ts
105
+ import { createExperience, createStep } from '@picked/client/plugins/create-experience'
106
+
107
+ const productPageFunnel = createExperience({
108
+ id: 'product-page-funnel',
109
+ name: 'Product Page Funnel',
110
+ condition: {
111
+ and: [
112
+ {
113
+ '==': [
114
+ {
115
+ var: 'location.pathname',
116
+ },
117
+ '/product/:id',
118
+ ],
119
+ },
120
+ {
121
+ var: 'actions.isSaleActive',
122
+ },
123
+ ],
124
+ },
125
+ targets: [{ selector: 'body', placement: 'after' }],
126
+ })
127
+ ```
128
+
129
+ Navigation between steps is handled with [conditions](../conditions). Use the `create`
130
+
131
+ ```ts
132
+ import { createExperience, createStep } from '@picked/client/plugins/create-experience'
133
+ ```
@@ -0,0 +1,59 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Dynamic Experiences Plugin
6
+
7
+ A plugin that fetches your project configuration from the Picked API at runtime, letting you manage experiences, features, steps, fields, and conditions entirely from the [dashboard](/experiences).
8
+
9
+ ## Setup Plugin
10
+
11
+ :::code-group
12
+
13
+ ```ts [app.ts]
14
+ import { createClient } from '@picked/client'
15
+ import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
16
+
17
+ const client = createClient({
18
+ organizationId: 'your-org-id',
19
+ projectId: 'your-project-id',
20
+ plugins: [dynamicExperiencesPlugin()],
21
+ })
22
+
23
+ client.start()
24
+ ```
25
+
26
+ :::
27
+
28
+ :::tip
29
+ Unlike [createExperiencePlugin](./create-experience.md), you do not define experiences, steps, or fields in code. All configuration is managed from the [Picked dashboard](/experiences).
30
+ :::
31
+
32
+ ## How It Works
33
+
34
+ When the client starts, the plugin resolves your project configuration using a three-tier strategy:
35
+
36
+ 1. **Preview mode** — When you [enter preview mode from the dashboard](/workspaces#preview), the plugin fetches fresh config from the API. Feature decisions use session storage so they are not persisted beyond the current tab.
37
+
38
+ 2. **Build-time config** — If `projectConfig` was passed as a plugin option, or the [Vite Plugin](/tooling/vite-plugin) injected config at build time, the plugin uses that config with no network request.
39
+
40
+ 3. **Runtime fetch** — Otherwise, the plugin fetches config from the API when the client starts.
41
+
42
+ Once resolved, the plugin registers all features, conditions, fields, steps, and experiences with the client. Experiences then initialize according to their [conditions](/sdk/conditions) and [target selectors](/sdk/experiences).
43
+
44
+ :::note
45
+ The [Vite Plugin](/tooling/vite-plugin) eliminates the runtime API request in production by embedding your project config at build time. This is the recommended setup for Vite-based applications.
46
+ :::
47
+
48
+ ## Feature Allocation
49
+
50
+ Each [feature](/sdk/features) in your project has an `enabled` state and a `weight` (0–100) that controls the percentage of traffic allocated to it. The plugin uses a deterministic hash of the user's session ID to produce a stable allocation value. A user whose value falls below the feature weight is allocated to that feature — the same user always gets the same result.
51
+
52
+ Feature decisions are cached in `localStorage` by default, or `sessionStorage` when in [preview mode](#preview-mode).
53
+
54
+ ## Preview Mode
55
+
56
+ When you [enter preview mode from the dashboard](/workspaces#preview), the SDK receives a preview token and activates preview mode:
57
+
58
+ - The plugin fetches fresh config from the API using the token, bypassing any build-time or cached config
59
+ - Feature decisions use session storage, so closing the tab resets them
@@ -0,0 +1,9 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Plugins
6
+
7
+ [Create Experience](./create-experience.md) — Define experiences, steps, and fields in code.
8
+
9
+ [Dynamic Experiences](./dynamic-experiences.md) — Fetch and register experiences from the Picked API at runtime.
@@ -0,0 +1,20 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Window Object
6
+
7
+ A plugin to add the Picked client to the window object
8
+
9
+ ## Setup Plugin
10
+
11
+ ```ts
12
+ import { createClient } from '@picked/client'
13
+ import { windowObjectPlugin } from '@picked/client/plugins/window-object'
14
+
15
+ const client = createClient({
16
+ organizationId: 'your-org-id',
17
+ projectId: 'your-project-id',
18
+ plugins: [windowObjectPlugin()],
19
+ })
20
+ ```
@@ -0,0 +1,113 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Predicates
6
+
7
+ Predicates enable reusable condition logic based on current application state. Each predicate is a pure function that receives a state snapshot containing responses, fields, browser context, and optional arguments.
8
+
9
+ ## Defining Predicates
10
+
11
+ Define predicates when creating the Picked client by passing a `predicates` object. Each predicate requires a unique identifier and a function that receives a state snapshot:
12
+
13
+ ```ts
14
+ import { createClient } from '@picked/client'
15
+
16
+ const client = createClient({
17
+ organizationId: 'your-org-id',
18
+ projectId: 'your-project-id',
19
+ predicates: {
20
+ 'is-example-page': ({ location }) => {
21
+ return location?.pathname === '/example'
22
+ },
23
+ 'get-account-tier': async () => {
24
+ const data = await fetch('/api/get-account-tier').then((r) => r.json())
25
+ return data.result // returns "trial"
26
+ },
27
+ },
28
+ })
29
+ ```
30
+
31
+ ## How Predicates Work
32
+
33
+ When a predicate is registered and used in conditions, the SDK follows this execution flow:
34
+
35
+ 1. **Registration**: Predicates are registered to the Picked client during initialization
36
+ 2. **State Snapshot Creation**: When invoked, predicates receive a frozen state snapshot containing current responses, fields, and browser context
37
+ 3. **Condition Evaluation**: Experience conditions invoke predicates with a fresh snapshot whenever called
38
+ 4. **Return Flexibility**: Predicates can return boolean, string, string array, undefined, or promises that resolve to these types
39
+
40
+ ## Using Predicates in Conditions
41
+
42
+ Predicates are most commonly used within condition logic via the `invoke` operator:
43
+
44
+ ```ts
45
+ const myExperience = createExperience({
46
+ // ... other config
47
+ condition: {
48
+ and: [
49
+ {
50
+ '==': [
51
+ {
52
+ invoke: {
53
+ var: 'predicates.is-example-page', // Reference predicate that calls user endpoint
54
+ },
55
+ },
56
+ true, // Check if return value is true
57
+ ],
58
+ },
59
+ {
60
+ '==': [
61
+ {
62
+ invoke: {
63
+ var: 'predicates.account-tier',
64
+ },
65
+ },
66
+ '/trial', // If string matches, evaluate to true
67
+ ],
68
+ },
69
+ ],
70
+ },
71
+ })
72
+ ```
73
+
74
+ :::tip
75
+ Predicates can also be used in `validation` logic
76
+ :::
77
+
78
+ ## Using the State Snapshot
79
+
80
+ Each predicate receives a `StateSnapshot` object with the following properties:
81
+
82
+ ```ts
83
+ interface StateSnapshot {
84
+ client: Picked // The Picked client instance
85
+ responses?: Array<{
86
+ // All user responses
87
+ fieldId: string
88
+ value: FieldValue | undefined
89
+ }>
90
+ args?: unknown[]
91
+ fields?: Field[] // All registered fields
92
+ location?: Location // Browser location (URL, pathname, search, etc.)
93
+ document?: Document // Browser document object
94
+ sessionStorage?: Storage // Browser sessionStorage API
95
+ localStorage?: Storage // Browser localStorage API
96
+ }
97
+ ```
98
+
99
+ :::warning
100
+ In server-side rendering or non-browser environments, `location`, `document`, `sessionStorage`,
101
+ and `localStorage` will be `undefined`.
102
+ :::
103
+
104
+ ## Best Practices
105
+
106
+ - **Keep predicates pure**: Predicates should be deterministic and side-effect free. Given the same snapshot, they should always return the same result
107
+ - **Handle missing data gracefully**: Always check if responses or fields exist before accessing their values. Return `undefined` when data is unavailable rather than throwing errors
108
+ - **Avoid heavy computations**: Predicates may be invoked frequently. Keep logic lightweight and fast
109
+ - **Use async sparingly**: While async predicates are supported, they can delay experience rendering. Prefer synchronous logic when possible
110
+ - **Return consistent types**: Don't mix return types (e.g., sometimes boolean, sometimes string) for the same predicate
111
+ - **Use meaningful names**: Predicate IDs should clearly describe what they evaluate (e.g., `is-premium-user`, `has-items-in-cart`)
112
+ - **Consider caching expensive operations**: If a predicate needs to perform expensive operations, consider caching the result in `localStorage` or `sessionStorage` within the predicate logic
113
+ - **Don't mutate the snapshot**: The state snapshot is frozen. Any attempt to modify it will throw an error in strict mode