@picked/client 0.0.70-next.5 → 0.0.70

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 (78) hide show
  1. package/dist/{api-DQ1DqG_F.js → api-CPy9G49C.js} +1 -1
  2. package/dist/api-Ce6xYI--.cjs +1 -0
  3. package/dist/cjs/index.js +2 -2
  4. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  5. package/dist/cjs/server/index.js +1 -1
  6. package/dist/docs/actions.mdx +38 -18
  7. package/dist/docs/api/authentication.md +75 -48
  8. package/dist/docs/api/authorization-and-scopes.md +63 -47
  9. package/dist/docs/api/error-handling.md +70 -54
  10. package/dist/docs/api/index.md +41 -22
  11. package/dist/docs/api/pagination.md +30 -20
  12. package/dist/docs/api-keys.mdx +11 -6
  13. package/dist/docs/chrome-extension.md +22 -5
  14. package/dist/docs/conditions.mdx +32 -19
  15. package/dist/docs/content/asset-library.mdx +65 -34
  16. package/dist/docs/content/content-entries.mdx +8 -2
  17. package/dist/docs/content/layouts.mdx +45 -29
  18. package/dist/docs/content/pages.mdx +29 -14
  19. package/dist/docs/content/sections.mdx +19 -8
  20. package/dist/docs/delivery-api/experience-content.md +10 -5
  21. package/dist/docs/delivery-api/index.md +19 -12
  22. package/dist/docs/delivery-api/preview-caching-crawling.md +27 -14
  23. package/dist/docs/delivery-api/project-config.md +6 -4
  24. package/dist/docs/experiences.mdx +26 -10
  25. package/dist/docs/features.mdx +75 -20
  26. package/dist/docs/fields.mdx +42 -27
  27. package/dist/docs/flow-builder/building-flows.md +38 -20
  28. package/dist/docs/flow-builder/condition-builder.md +32 -21
  29. package/dist/docs/flow-builder/keyboard-shortcuts.md +18 -18
  30. package/dist/docs/flow-builder/nodes.md +26 -13
  31. package/dist/docs/flow-builder/overview.mdx +28 -15
  32. package/dist/docs/gtm-template.md +35 -26
  33. package/dist/docs/guides/build-a-personalized-page.mdx +36 -18
  34. package/dist/docs/image-optimisation-service.md +33 -17
  35. package/dist/docs/index.md +28 -17
  36. package/dist/docs/mcp.md +46 -38
  37. package/dist/docs/pickedql/data-sources.md +65 -43
  38. package/dist/docs/pickedql/dimensions.md +27 -12
  39. package/dist/docs/pickedql/examples.md +38 -26
  40. package/dist/docs/pickedql/filtering.md +10 -6
  41. package/dist/docs/pickedql/index.md +32 -14
  42. package/dist/docs/pickedql/schema.md +85 -76
  43. package/dist/docs/pickedql/virtual-functions.md +62 -36
  44. package/dist/docs/projects.mdx +14 -7
  45. package/dist/docs/reports.mdx +5 -3
  46. package/dist/docs/sdk/actions.md +33 -17
  47. package/dist/docs/sdk/conditions.md +8 -5
  48. package/dist/docs/sdk/content-security-policy.md +5 -4
  49. package/dist/docs/sdk/experiences.md +29 -11
  50. package/dist/docs/sdk/feature-allocation.md +79 -47
  51. package/dist/docs/sdk/features.md +130 -76
  52. package/dist/docs/sdk/fields.md +66 -42
  53. package/dist/docs/sdk/index.md +22 -13
  54. package/dist/docs/sdk/plugins/create-experience.md +18 -15
  55. package/dist/docs/sdk/plugins/dynamic-experiences.md +42 -16
  56. package/dist/docs/sdk/predicates.md +53 -37
  57. package/dist/docs/sdk/ssr/index.md +25 -8
  58. package/dist/docs/sdk/ssr/rendering-and-hydration.md +37 -16
  59. package/dist/docs/sdk/ssr/serialize.md +38 -20
  60. package/dist/docs/sdk/ssr/slots-and-placements.md +28 -10
  61. package/dist/docs/sdk/steps.md +100 -55
  62. package/dist/docs/steps.mdx +20 -11
  63. package/dist/docs/team-management.md +17 -11
  64. package/dist/docs/vite-plugin.md +26 -19
  65. package/dist/docs/workspace-versioning.mdx +14 -8
  66. package/dist/docs/workspaces.mdx +55 -24
  67. package/dist/esm/index.js +257 -238
  68. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  69. package/dist/esm/server/index.js +1 -1
  70. package/dist/{plugin-EPx6sE_l.js → plugin-ChPkTj__.js} +58 -53
  71. package/dist/plugin-DHn11gn6.cjs +1 -0
  72. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  73. package/dist/types/plugins/dynamic-experiences/hash.d.ts +40 -5
  74. package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
  75. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  76. package/package.json +1 -1
  77. package/dist/api-B9koex39.cjs +0 -1
  78. package/dist/plugin-YbSSGrM7.cjs +0 -1
@@ -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 flag is not deciding it
11
+ ## A registration is not a decision
10
12
 
11
- The SDK loads every flag in your project at startup, but it does not allocate visitors then. A 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 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 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 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 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 flags without deciding them
35
+ ## Check a feature flag without a decision
26
36
 
27
- `client.features.listStore` holds every registered flag. Each entry reports whether it has been decided yet, so you can inspect 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 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 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 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 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 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` | Flag ID |
119
- | `ab_name` | 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 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.
@@ -4,29 +4,40 @@ layout: docs
4
4
 
5
5
  # Feature flags
6
6
 
7
- Feature flags let you run A/B tests and roll changes out gradually. Manage them from the dashboard, or define them in SDK code when you need your own decision logic.
7
+ A feature flag lets you run an A/B test and roll a change out in stages. Control a
8
+ feature flag from the dashboard, or define it in SDK code when you need your own decision
9
+ rules.
8
10
 
9
11
  ## Where feature flags come from
10
12
 
11
- Most flags are managed from the dashboard. The [dynamic experiences plugin](/sdk/plugins/dynamic-experiences) fetches your project configuration at runtime and registers every flag automatically — you do not declare them in code.
13
+ You control most feature flags from the dashboard. The
14
+ [dynamic experiences plugin](/sdk/plugins/dynamic-experiences) reads the options of
15
+ your project at runtime and registers every feature flag for you. You declare no feature flag in
16
+ code.
12
17
 
13
- For a dashboard-managed flag, the SDK generates the definition:
18
+ For a feature flag that you control from the dashboard, the SDK makes the definition:
14
19
 
15
20
  | Field | Value |
16
21
  | --- | --- |
17
- | `id` | The flag's dashboard key |
18
- | `key` | `v{version}` — for example `v3` |
19
- | `name` | The flag's dashboard name |
22
+ | `id` | The dashboard key of the feature flag |
23
+ | `key` | `v{version}`, for example `v3` |
24
+ | `name` | The dashboard name of the feature flag |
20
25
  | `enabled` | The dashboard **Enabled** toggle |
21
- | `weight` | The dashboard **Weight**, 0–100 |
26
+ | `weight` | The dashboard **Weight**, 0 to 100 |
22
27
  | `storage` | `local`, or `session` in [preview mode](/sdk/feature-allocation#preview-mode) |
23
- | `decide` | Generated — allocates by comparing the visitor's stable value against `weight` |
28
+ | `decide` | Made by the SDK. It compares the stable value of the visitor for this feature flag against `weight`. |
24
29
 
25
- The generated `decide` derives from the visitor ID and the weight alone, so a dashboard-managed flag cannot base its decision on your application's data. Define the flag in SDK code when you need that.
30
+ The `decide` function that the SDK makes derives from the visitor ID, the ID of the
31
+ feature flag, and the weight. A feature flag that you control from the dashboard
32
+ therefore cannot use the data of your application. Define the feature flag in SDK code
33
+ when you need that.
26
34
 
27
- ## Defining flags in SDK code
35
+ ## Define a feature flag in SDK code
28
36
 
29
- Define flags when creating the Picked client by passing a `features` object. Use this when a decision depends on data only your application has. Each flag requires a unique identifier, a versioning key, a human-readable name, and a `decide` function:
37
+ Define your feature flags when you create the Picked client. Pass a `features` object. Use
38
+ this when a decision needs data that only your application holds. Each feature flag needs a
39
+ unique identifier, a version key, a name that a person can read, and a `decide`
40
+ function:
30
41
 
31
42
  ```ts
32
43
  import { createClient } from '@picked/client'
@@ -35,20 +46,20 @@ const client = createClient({
35
46
  projectId: 'your-project-id',
36
47
  features: {
37
48
  'summer-sale': {
38
- key: 'summer-sale-v1', // Version key - change to invalidate cache
39
- name: 'Summer Sale Experiment', // Human-readable name for analytics
49
+ key: 'summer-sale-v1', // The version key. Change it to invalidate the cache.
50
+ name: 'Summer Sale Experiment', // A name for analytics that a person can read.
40
51
  decide: async () => {
41
- // Your decision logic here
52
+ // Put your decision rules here.
42
53
  const isEligible = await checkUserEligibility()
43
- return isEligible // Returns boolean
54
+ return isEligible // Returns a boolean.
44
55
  },
45
- storage: 'local', // Optional: 'local' (default) or 'session'
56
+ storage: 'local', // Optional: 'local' is the default, or 'session'.
46
57
  },
47
58
  'homepage-variant': {
48
59
  key: 'homepage-test-v2',
49
60
  name: 'Homepage A/B Test',
50
61
  decide: async () => {
51
- // Return string for multi-variant tests
62
+ // Return a string for a test of more than two variants.
52
63
  return Math.random() < 0.5 ? 'variant-a' : 'variant-b'
53
64
  },
54
65
  },
@@ -56,7 +67,8 @@ const client = createClient({
56
67
  })
57
68
  ```
58
69
 
59
- Feature flags can also be defined as factory functions for lazy initialization:
70
+ A feature flag can also be a factory function, which starts the feature flag only when it is
71
+ first needed:
60
72
 
61
73
  ```ts
62
74
  features: {
@@ -70,25 +82,33 @@ features: {
70
82
 
71
83
  ## How feature flags work
72
84
 
73
- When a feature flag is registered, the SDK follows this execution order:
85
+ After you register a feature flag, the SDK follows this order:
74
86
 
75
- 1. **Registration** — flags are registered with the client at startup. Registering does not allocate anyone.
76
- 2. **Cache check** — the SDK looks in browser storage for a decision from a previous visit, keyed by the flag `id` and `key`. If one exists the flag starts out already resolved, which is why a returning visitor is allocated before anything reads the flag.
77
- 3. **Lazy evaluation** — with no cached decision, `decide` runs the first time something reads the bucket.
78
- 4. **Storage** — the resolved bucket is written to browser storage.
79
- 5. **Event emission** — a `feature_decide` event fires, but only for a decision made during this page view. A decision restored in step 2 emits nothing.
87
+ 1. **Registration** — the client registers each feature flag at startup. A registration
88
+ allocates nobody.
89
+ 2. **Cache check** — the SDK looks in browser storage for a decision from an earlier
90
+ visit. The cache key holds the feature flag `id` and `key`. When an entry is there, the
91
+ feature flag starts in a resolved state. This is why Picked allocates a visitor who
92
+ returns before anything reads the feature flag.
93
+ 3. **Lazy evaluation** — with no cached decision, `decide` runs the first time that
94
+ something reads the bucket.
95
+ 4. **Storage** — the SDK writes the resolved bucket to browser storage.
96
+ 5. **Event emission** — the SDK sends a `feature_decide` event, but only for a
97
+ decision that it makes during this page view. A decision that step 2 restores
98
+ sends nothing.
80
99
 
81
- See [Allocation and exposure](/sdk/feature-allocation) for what this means when tracking exposures.
100
+ See [Allocation and exposure](/sdk/feature-allocation) for what this means when you
101
+ track an exposure.
82
102
 
83
- ## URL-based overrides
103
+ ## Overrides in the URL
84
104
 
85
- You can override feature flag decisions via URL search parameters for testing purposes:
105
+ You can override a feature flag decision from the URL search parameters, for a test:
86
106
 
87
107
  ```
88
108
  https://example.com?picked-feature=my-feature:true
89
109
  ```
90
110
 
91
- Multiple overrides can be specified:
111
+ You can give more than one override value:
92
112
 
93
113
  ```
94
114
  https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
@@ -96,11 +116,15 @@ https://example.com?picked-feature=my-feature:true&picked-feature=homepage-varia
96
116
 
97
117
  The format is `?picked-feature=<feature-id>:<bucket-value>`.
98
118
 
99
- This is useful for QA testing specific bucket values without modifying code. The override is persisted in session storage and will be cleared when the browser tab is closed.
119
+ This is useful for QA, because you can test one bucket value and change no code. The
120
+ SDK keeps the override value in session storage. The browser clears it when you close
121
+ the tab.
100
122
 
101
- ## Accessing feature flags
123
+ ## Read a feature flag
102
124
 
103
- Once feature flags are defined, you can read their resolved bucket values in your application code. The SDK provides framework hooks and a framework-agnostic event bus.
125
+ After you define your feature flags, your application code can read the resolved
126
+ bucket value of each one. The SDK gives you a hook for each framework, and an event
127
+ bus that works with any framework.
104
128
 
105
129
  :::code-group
106
130
 
@@ -108,7 +132,7 @@ Once feature flags are defined, you can read their resolved bucket values in you
108
132
  import { createClient } from '@picked/client'
109
133
 
110
134
  const client = createClient({
111
- /* Create client options here */
135
+ /* Put your client options here. */
112
136
  })
113
137
 
114
138
  client.eventBus.watch((event) => {
@@ -122,7 +146,7 @@ client.eventBus.watch((event) => {
122
146
  <script lang="ts">
123
147
  import { getFeatureResult } from '@picked/svelte/hooks'
124
148
 
125
- // A store holding { id, bucket }, or undefined until the flag resolves
149
+ // A store that holds { id, bucket }, or undefined until the feature flag resolves.
126
150
  const result = getFeatureResult('my-feature')
127
151
  </script>
128
152
 
@@ -137,7 +161,7 @@ client.eventBus.watch((event) => {
137
161
  import { useFeatureResult } from '@picked/react/hooks'
138
162
 
139
163
  function MyComponent() {
140
- // { id, bucket }, or undefined until the flag resolves
164
+ // { id, bucket }, or undefined until the feature flag resolves.
141
165
  const result = useFeatureResult('my-feature')
142
166
 
143
167
  return (
@@ -151,41 +175,46 @@ function MyComponent() {
151
175
 
152
176
  :::
153
177
 
154
- ### Choosing an accessor
178
+ ### Choose an accessor
155
179
 
156
- Accessors differ in what they return, and in whether reading one resolves the flag. Resolving is what commits a visitor to a bucket and reports the decision — see [Allocation and exposure](/sdk/feature-allocation).
180
+ Each accessor returns a different value. They also differ in whether a read resolves
181
+ the feature flag. A read that resolves the feature flag commits the visitor to a bucket and reports
182
+ the decision. See [Allocation and exposure](/sdk/feature-allocation).
157
183
 
158
- | API | Returns | Resolves the flag? |
184
+ | API | Returns | Does a read resolve the feature flag? |
159
185
  | --- | --- | --- |
160
- | `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the returned function |
161
- | `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, `undefined` until resolved | Yes, as soon as the component renders |
162
- | `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined` — the dashboard **Enabled** flag | No |
163
- | `client.features.listStore` | Every registered flag, each with an `isResolved` flag | No |
164
- | `client.features.getById(id)` | A store holding a single flag | No |
186
+ | `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the function that it returns |
187
+ | `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, and `undefined` until it resolves | Yes, as soon as the component renders |
188
+ | `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined`, the dashboard **Enabled** toggle | No |
189
+ | `client.features.listStore` | Every registered feature flag, each with an `isResolved` feature flag | No |
190
+ | `client.features.getById(id)` | A store that holds one feature flag | No |
165
191
 
166
192
  :::warning
167
- `useFeatureResult` and `getFeatureResult` resolve the flag as soon as the component renders, committing the visitor to a bucket at that moment.
193
+ `useFeatureResult` and `getFeatureResult` resolve the feature flag as soon as the component
194
+ renders. They commit the visitor to a bucket at that moment.
168
195
 
169
- If the decision should wait for data you do not have yet — a cart, a quote, a signed-in profile — use `useFeature` or `getFeature` and call the function they return once that data arrives.
196
+ Some decisions must wait for data that you do not have yet, such as a cart, a quote,
197
+ or a profile of a signed-in user. For those, use `useFeature` or `getFeature`. Call
198
+ the function that they return after the data arrives.
170
199
  :::
171
200
 
172
- ## Using feature flags in experience conditions
201
+ ## Use a feature flag in an experience condition
173
202
 
174
- Feature flags can be used with experience conditions via JSON logic:
203
+ You can use a feature flag in an experience condition through JSON logic:
175
204
 
176
205
  ```ts
177
206
  const myExperience = createExperience({
178
- // ... other config
207
+ // The other options go here.
179
208
  condition: {
180
209
  and: [
181
210
  {
182
211
  '==': [
183
212
  {
184
213
  invoke: {
185
- var: 'features.summer-sale', // Reference the flag by ID
214
+ var: 'features.summer-sale', // Point at the feature flag by its ID.
186
215
  },
187
216
  },
188
- true, // Check if bucket equals true
217
+ true, // Check that the bucket equals true.
189
218
  ],
190
219
  },
191
220
  ],
@@ -193,13 +222,15 @@ const myExperience = createExperience({
193
222
  })
194
223
  ```
195
224
 
196
- The ID in `features.<id>` is the flag's **Key** from the dashboard — the same value shown on the flag detail page.
225
+ The ID in `features.<id>` is the **Key** of the feature flag from the dashboard. It is the
226
+ same value that the detail page of the feature flag shows.
197
227
 
198
- Evaluating a condition reads the bucket, so a flag referenced by an experience condition is allocated at the moment that condition runs.
228
+ An evaluation of a condition reads the bucket. Picked therefore allocates a visitor
229
+ to a feature flag when the condition that points at it runs.
199
230
 
200
- ## Listening to feature flag decisions
231
+ ## Watch for feature flag decisions
201
232
 
202
- Subscribe to the `feature_decide` event to track when flags are resolved:
233
+ Subscribe to the `feature_decide` event to track when a feature flag resolves:
203
234
 
204
235
  ```ts
205
236
  client.eventBus.watch((event) => {
@@ -207,9 +238,9 @@ client.eventBus.watch((event) => {
207
238
 
208
239
  const { feature } = event
209
240
  console.log(`Flag "${feature.id}" resolved to bucket:`, feature.bucket)
210
- // Example output: Flag "summer-sale" resolved to bucket: true
241
+ // Example output: the feature flag resolved to the bucket true.
211
242
 
212
- // Send to analytics
243
+ // Send the decision to your analytics.
213
244
  analytics.track('Feature Decision', {
214
245
  featureId: feature.id,
215
246
  bucket: feature.bucket,
@@ -217,50 +248,73 @@ client.eventBus.watch((event) => {
217
248
  })
218
249
  ```
219
250
 
220
- ## Versioning and cache invalidation
251
+ ## Versions and cache invalidation
221
252
 
222
- Feature flag decisions are cached in browser storage (localStorage, or sessionStorage in preview) under a key derived from the flag ID and version, so a visitor keeps the same decision across page loads.
253
+ The SDK caches a feature flag decision in browser storage. It uses `localStorage`, or
254
+ `sessionStorage` in a preview. The cache key derives from the feature flag ID and the version,
255
+ so a visitor keeps the same decision across page loads.
223
256
 
224
257
  :::warning
225
- Bucket values that are strings, such as `'variant-a'`, are not yet restored correctly from storage — the visitor is treated as unallocated on their next page load. Boolean bucket values, which includes every flag managed from the dashboard, are unaffected. Tracked in PICK-1574.
258
+ The SDK does not yet restore a string bucket value, such as `'variant-a'`, correctly
259
+ from storage. It treats the visitor as unallocated on the next page load. A boolean
260
+ bucket value is correct, and that includes every feature flag that you control from the
261
+ dashboard. See PICK-1574.
226
262
  :::
227
263
 
228
- ### How versioning works
264
+ ### How versions work
229
265
 
230
- When you create a feature flag in the dashboard, it starts at version 1. The SDK uses the version to build the cache key — for example, a flag at version 3 caches under `v3`.
266
+ A feature flag that you create in the dashboard starts at version 1. The SDK uses the
267
+ version to build the cache key. A feature flag at version 3 caches under `v3`.
231
268
 
232
- The version affects **caching only**. It is not part of the allocation calculation, which for dashboard-managed flags derives from the visitor ID and the flag's weight.
269
+ The version affects **the cache only**. It is not part of the allocation. For a feature flag
270
+ that you control from the dashboard, the allocation derives from the visitor ID and
271
+ the weight of the feature flag.
233
272
 
234
- ### What happens when the version is incremented
273
+ ### What happens when you increment the version
235
274
 
236
- When you increment a flag's version in the dashboard:
275
+ When you increment the version of a feature flag in the dashboard:
237
276
 
238
- 1. **Cache keys change.** The old cache entry (e.g., `v3`) no longer matches the new key (`v4`), so the SDK treats the flag as undecided.
239
- 2. **The `decide` function runs again.** On the next page load, the SDK calls `decide()` fresh and caches the new result.
240
- 3. **Visitors are re-evaluated, not reshuffled.** For dashboard-managed flags allocation is deterministic, so re-evaluating the same visitor against an unchanged weight produces the same bucket.
277
+ 1. **The cache key changes.** The old entry, `v3`, no longer matches the new key,
278
+ `v4`. The SDK therefore treats the feature flag as undecided.
279
+ 2. **The `decide` function runs again.** On the next page load, the SDK calls
280
+ `decide()` again and caches the new result.
281
+ 3. **Picked evaluates each visitor again and moves nobody.** For a feature flag that you
282
+ control from the dashboard the allocation is deterministic. The same visitor
283
+ against an unchanged weight gives the same bucket.
241
284
 
242
285
  :::note
243
- Incrementing the version clears cached decisions and runs allocation again. It does not randomise visitors — with an unchanged weight, everyone lands where they were. Increment it after changing a weight, so visitors who already hold a cached decision are re-evaluated against the new one.
286
+ The new version clears the cached decisions and runs the allocation again. It moves
287
+ no visitor between groups. With an unchanged weight, everybody lands where they were.
288
+ Increment the version after you change a weight, so that Picked evaluates a visitor
289
+ with a cached decision against the new weight.
244
290
  :::
245
291
 
246
- ### SDK `key` vs dashboard version
292
+ ### The SDK `key` and the dashboard version
247
293
 
248
- If you define feature flags statically in the SDK (not loaded from the dashboard), use the `key` field for manual versioning:
294
+ For a feature flag that you define in SDK code, use the `key` field to set the version
295
+ yourself. This applies when you do not read the feature flag from the dashboard:
249
296
 
250
297
  ```ts
251
298
  features: {
252
299
  'summer-sale': {
253
- key: 'summer-sale-v2', // Change to invalidate cache
254
- // ...
300
+ key: 'summer-sale-v2', // Change this to invalidate the cache.
301
+ // The other options go here.
255
302
  },
256
303
  }
257
304
  ```
258
305
 
259
- For flags managed through the dashboard, versioning is handled automatically — the dashboard appends the version number to the cache key used by the SDK.
306
+ For a feature flag that you control from the dashboard, Picked sets the version for you. The
307
+ dashboard adds the version number to the end of the cache key that the SDK uses.
260
308
 
261
309
  ## Best practices
262
310
 
263
- - **Use `session` storage for temporary tests**: Set `storage: 'session'` for short-lived experiments that shouldn't persist across browser sessions
264
- - **Keep `decide` functions fast**: Anything awaiting the flag — an experience condition, a component reading the result — waits for `decide` to finish, so avoid slow network calls inside it
265
- - **Allocate when the decision matters**: Defer reading a flag until the data it depends on has arrived, rather than reading every flag at startup. See [Allocation and exposure](/sdk/feature-allocation)
266
- - **Return consistent types**: Don't mix boolean and string returns for the same flag - pick one type and stick with it
311
+ - **Use `session` storage for a short test**: set `storage: 'session'` for an
312
+ experiment that must not stay across browser sessions.
313
+ - **Keep each `decide` function fast**: everything that waits for the feature flag waits for
314
+ `decide` to finish. This includes an experience condition and a component that
315
+ reads the result. Do not make a slow network call inside it.
316
+ - **Allocate when the decision matters**: read a feature flag only after the data that it
317
+ needs arrives. Do not read every feature flag at startup. See
318
+ [Allocation and exposure](/sdk/feature-allocation).
319
+ - **Return one type**: do not return a boolean from a feature flag one time and a string the
320
+ next. Choose one type and keep it.