@picked/client 0.0.1-dev.1786675074931 → 0.0.1-dev.1787537193674

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 (91) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/api-4YpcWP-x.js +33 -0
  3. package/dist/api-DRTduQVm.cjs +1 -0
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  6. package/dist/cjs/plugins/telemetry/index.js +1 -0
  7. package/dist/cjs/server/index.js +1 -1
  8. package/dist/docs/_root.css +101 -0
  9. package/dist/docs/explanation/feature-flag-allocation.md +43 -1
  10. package/dist/docs/how-to/analytics/index.md +2 -0
  11. package/dist/docs/how-to/authenticate-the-cli.md +15 -13
  12. package/dist/docs/how-to/create-and-revoke-an-api-key.md +7 -2
  13. package/dist/docs/how-to/debug-with-the-chrome-extension.md +1 -1
  14. package/dist/docs/how-to/install-the-sdk.md +30 -12
  15. package/dist/docs/how-to/install-the-vite-plugin.md +22 -9
  16. package/dist/docs/how-to/read-a-feature-flag.md +23 -11
  17. package/dist/docs/how-to/read-a-referenced-entry.md +4 -6
  18. package/dist/docs/how-to/register-a-component.md +2 -2
  19. package/dist/docs/how-to/register-an-action-handler.md +1 -1
  20. package/dist/docs/how-to/render-a-step-form.md +3 -4
  21. package/dist/docs/how-to/server-render-a-page.md +1 -1
  22. package/dist/docs/how-to/switch-workspaces.md +1 -2
  23. package/dist/docs/how-to/write-a-predicate.md +1 -1
  24. package/dist/docs/reference/dashboard/api-keys.mdx +5 -4
  25. package/dist/docs/reference/dashboard/features.mdx +45 -5
  26. package/dist/docs/reference/delivery-api/workspace-schema.md +12 -1
  27. package/dist/docs/reference/pickedql/dimensions.md +16 -0
  28. package/dist/docs/reference/pickedql/virtual-functions.md +16 -5
  29. package/dist/docs/reference/sdk/analytics-events.md +445 -0
  30. package/dist/docs/reference/sdk/content.md +7 -10
  31. package/dist/docs/reference/sdk/features.md +18 -7
  32. package/dist/docs/reference/sdk/plugins/dynamic-experiences.md +1 -1
  33. package/dist/docs/reference/sdk/plugins/window-object.md +1 -1
  34. package/dist/docs/reference/sdk/ssr/serialize.md +1 -1
  35. package/dist/docs/reference/sdk/steps.md +3 -4
  36. package/dist/docs/reference/sdk/type-generation.md +65 -26
  37. package/dist/docs/reference/tools/cli.md +19 -14
  38. package/dist/docs/reference/tools/vite-plugin.md +28 -6
  39. package/dist/docs/tutorials/first-experience.mdx +6 -7
  40. package/dist/docs/tutorials/first-server-rendered-page.mdx +3 -3
  41. package/dist/emit-D-tLiWL1.cjs +1 -0
  42. package/dist/emit-Ds2EG7CB.js +14 -0
  43. package/dist/esm/index.js +255 -232
  44. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  45. package/dist/esm/plugins/telemetry/index.js +94 -0
  46. package/dist/esm/server/index.js +1 -1
  47. package/dist/plugin-BPZV7SJg.cjs +1 -0
  48. package/dist/{plugin-ClKkYGTd.js → plugin-DTN7fsyp.js} +44 -29
  49. package/dist/{provider-D7msjLQ1.cjs → provider-BxhVIcsp.cjs} +1 -1
  50. package/dist/{provider-BEpf8z6A.js → provider-DLebj-AW.js} +1 -1
  51. package/dist/request-observer-BXDfOU0V.cjs +1 -0
  52. package/dist/request-observer-DtG0cRfz.js +19 -0
  53. package/dist/types/data-source/api.d.ts.map +1 -1
  54. package/dist/types/data-source/provider.d.ts +11 -1
  55. package/dist/types/data-source/provider.d.ts.map +1 -1
  56. package/dist/types/data-source/request-observer.d.ts +46 -0
  57. package/dist/types/data-source/request-observer.d.ts.map +1 -0
  58. package/dist/types/entities/feature/feature.d.ts +10 -3
  59. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  60. package/dist/types/index.d.ts +1 -1
  61. package/dist/types/index.d.ts.map +1 -1
  62. package/dist/types/lifecycle/lifecycle.d.ts.map +1 -1
  63. package/dist/types/picked.d.ts +12 -2
  64. package/dist/types/picked.d.ts.map +1 -1
  65. package/dist/types/plugins/dynamic-experiences/hash.d.ts +53 -1
  66. package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
  67. package/dist/types/plugins/dynamic-experiences/index.d.ts +1 -0
  68. package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
  69. package/dist/types/plugins/dynamic-experiences/plugin.d.ts +1 -1
  70. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  71. package/dist/types/plugins/dynamic-experiences/types.d.ts +3 -4
  72. package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
  73. package/dist/types/plugins/telemetry/emit.d.ts +18 -0
  74. package/dist/types/plugins/telemetry/emit.d.ts.map +1 -0
  75. package/dist/types/plugins/telemetry/index.d.ts +4 -0
  76. package/dist/types/plugins/telemetry/index.d.ts.map +1 -0
  77. package/dist/types/plugins/telemetry/plugin-summary.d.ts +10 -0
  78. package/dist/types/plugins/telemetry/plugin-summary.d.ts.map +1 -0
  79. package/dist/types/plugins/telemetry/plugin.d.ts +29 -0
  80. package/dist/types/plugins/telemetry/plugin.d.ts.map +1 -0
  81. package/dist/types/schema-registry.d.ts +16 -0
  82. package/dist/types/schema-registry.d.ts.map +1 -1
  83. package/dist/types/utils/user-timing.d.ts +18 -0
  84. package/dist/types/utils/user-timing.d.ts.map +1 -0
  85. package/dist/version-BlahSDlG.cjs +1 -0
  86. package/dist/{version-CTwGfdI4.js → version-BmBn0dXR.js} +1 -1
  87. package/package.json +16 -4
  88. package/dist/api-CSNjsqCv.js +0 -22
  89. package/dist/api-yuvsQhNy.cjs +0 -1
  90. package/dist/plugin-D4-caxZN.cjs +0 -1
  91. package/dist/version-CRxCADU7.cjs +0 -1
@@ -65,6 +65,43 @@ your application in its decision. The value comes from the visitor ID, the featu
65
65
  ID, and the weight, and from nothing else. Define the feature flag in SDK code when the
66
66
  decision needs a cart, a quote, or a profile.
67
67
 
68
+ ## More than two groups
69
+
70
+ A Text feature flag has a table of variants, and each variant is one group. Picked
71
+ allocates a visitor to exactly one of them, and the control group is one of the rows.
72
+
73
+ The two types decide by different routes, and the difference matters if you compare them:
74
+
75
+ | Type | Picked compares | Against |
76
+ | --- | --- | --- |
77
+ | Boolean | The stable value of the visitor, out of 100 | The weight |
78
+ | Text | One stable value for each variant | The weight of that variant |
79
+
80
+ A Text feature flag gives each variant a score. Picked makes the score from the visitor
81
+ ID, the feature flag ID, and the key of the variant. The weight of the variant then scales
82
+ that score. The variant with the best score receives the visitor.
83
+
84
+ The weight of a variant therefore changes the score of that variant alone. No other
85
+ variant moves because of it.
86
+
87
+ A Boolean feature flag makes the same comparison it always has. A feature flag that you
88
+ made before variants existed therefore keeps every visitor where it put them.
89
+
90
+ Picked derives the stable value from the visitor ID and from the ID of the feature flag,
91
+ for both types. The two types are therefore independent of each other in the same way that
92
+ two feature flags are.
93
+
94
+ **The order of the rows has no effect.** Picked scores each variant from its key, and the
95
+ key of a variant never changes. Move a row in the dashboard and the
96
+ visitors stay where they are. The order that you see is the order that you chose to read
97
+ them in, and nothing more.
98
+
99
+ Rename a variant and nothing changes. The name travels no further than the dashboard.
100
+
101
+ Change the string that a variant serves and the visitors stay in the same group, but your
102
+ site and your reports receive the new string. A visitor with a decision in browser storage
103
+ keeps the old string until you increment the version.
104
+
68
105
  ## Each feature flag is independent
69
106
 
70
107
  Picked builds that value from the visitor ID together with the ID of the feature flag.
@@ -81,7 +118,8 @@ then never sees the mixed groups that tell one result from the other.
81
118
 
82
119
  ## The allocation ratchets
83
120
 
84
- Inside one feature flag, a larger weight only adds visitors. Everybody allocated at
121
+ The ratchet applies to both types of feature flag. Inside one feature flag, a larger
122
+ weight only adds visitors. Everybody allocated at
85
123
  weight 30 is still allocated at weight 50. The allocation is a **ratchet**: it turns one
86
124
  way, and a larger weight never moves a visitor back out.
87
125
 
@@ -89,6 +127,10 @@ That property is what makes a staged release safe. A visitor who sees the change
89
127
  still sees it at 20%. A smaller weight is the reverse turn. It removes visitors from the
90
128
  top of the range, and every visitor below the new weight keeps what they had.
91
129
 
130
+ A Text feature flag has the same ratchet, one variant at a time. A larger weight on one variant only
131
+ adds visitors to that variant. A smaller weight only removes them. Two variants that you
132
+ do not change never exchange visitors with each other.
133
+
92
134
  ## A disabled feature flag records nothing
93
135
 
94
136
  A disabled feature flag allocates nobody, and the difference from a weight of 0
@@ -311,4 +311,6 @@ WHERE project_id = '{{ your project ID }}'
311
311
  - [About PickedQL](/explanation/how-pickedql-works) — what the layer expands for you.
312
312
  - [Virtual functions](/reference/pickedql/virtual-functions) — every function.
313
313
  - [Dimensions](/reference/pickedql/dimensions) — every dimension column.
314
+ - [Analytics events](/reference/sdk/analytics-events) — every `event_type` value that a
315
+ filter can use.
314
316
  - [Reports](/reference/dashboard/reports) — the same data as an embedded dashboard.
@@ -5,24 +5,24 @@ layout: docs
5
5
 
6
6
  # How to authenticate the CLI
7
7
 
8
- This guide shows you how to install `@picked/cli` and sign it in. Commands that read your
9
- organizations, projects, and workspaces then know who you are. It expects a Picked account
10
- and a project directory to work in.
8
+ This guide shows you how to install the `picked` command and sign it in. Commands that read
9
+ your organizations, projects, and workspaces then know who you are. It expects a Picked
10
+ account and a project directory to work in.
11
11
 
12
12
  ## 1. Install the CLI
13
13
 
14
14
  :::code-group
15
15
 
16
16
  ```bash [npm]
17
- npm install --save-dev @picked/cli
17
+ npm install picked
18
18
  ```
19
19
 
20
20
  ```bash [pnpm]
21
- pnpm add -D @picked/cli
21
+ pnpm add picked
22
22
  ```
23
23
 
24
24
  ```bash [yarn]
25
- yarn add -D @picked/cli
25
+ yarn add picked
26
26
  ```
27
27
 
28
28
  :::
@@ -44,8 +44,7 @@ https://api.picked.app/…/oauth/authorize?…
44
44
  Logged in as you@example.com (https://api.picked.app)
45
45
  ```
46
46
 
47
- The CLI stores the session in `~/.picked/auth/<origin>.json`, outside any repository, with
48
- file mode `600`. It refreshes the session as it nears expiry.
47
+ The CLI stores the session in `~/.picked/auth/<origin>.json`. It refreshes the session as it nears expiry.
49
48
 
50
49
  ## 3. Confirm the session
51
50
 
@@ -70,15 +69,18 @@ Logged out of https://api.picked.app.
70
69
 
71
70
  ## Run the CLI in CI
72
71
 
73
- CI has no browser, so give the CLI a token instead of a session. `PICKED_TOKEN` takes
74
- precedence over any stored session:
72
+ `picked generate types` reads the workspace schema, not your account, so CI needs no
73
+ session for it. A workspace you read with `source: 'preview'` needs that workspace's token.
74
+ Run `picked workspace use` once on your machine, then give CI the two values it wrote:
75
75
 
76
76
  ```bash
77
- PICKED_TOKEN="$PICKED_ACCESS_TOKEN" picked generate types --check
77
+ PICKED_PROJECT_ID=your-project-id \
78
+ PICKED_PREVIEW_TOKEN=$YOUR_CI_SECRET \
79
+ picked generate types --check
78
80
  ```
79
81
 
80
- If an API deployment does not offer OAuth client registration, `picked login` fails and
81
- tells you to set `PICKED_TOKEN`. It falls back to no other flow.
82
+ `picked workspace list` and `picked workspace use` read your organizations and projects, so
83
+ those two do need a session. Run them on your machine, where a browser can sign you in.
82
84
 
83
85
  :::note
84
86
  This is a developer session for local tooling. It is not the same as an
@@ -5,8 +5,13 @@ layout: docs
5
5
 
6
6
  # How to create and revoke an API key
7
7
 
8
- This guide shows you how to give your code access to the Picked API. Use a key for a
9
- server-side integration or for a CI/CD workflow.
8
+ This guide shows you how to give your code access to the Picked API. Use a key where your
9
+ own code calls the API, such as a server-side integration.
10
+
11
+ An API key does not sign the [CLI](/reference/tools/cli) in. The CLI reads a session, which
12
+ `picked login` writes. See
13
+ [How to authenticate the CLI](/how-to/authenticate-the-cli) for the commands that CI can
14
+ run without one.
10
15
 
11
16
  ## Create a key
12
17
 
@@ -16,7 +16,7 @@ Install it from the
16
16
  ## 2. Add the devtools plugin
17
17
 
18
18
  ```typescript
19
- import { createClient } from '@picked/client'
19
+ import { createClient } from 'picked'
20
20
  import { svelteDevtoolsPlugin } from '@picked/svelte/plugins/devtools'
21
21
 
22
22
  const picked = createClient({
@@ -13,28 +13,41 @@ ID from the dashboard.
13
13
  :::code-group
14
14
 
15
15
  ```bash [React]
16
- npm install @picked/client @picked/react effector effector-react
16
+ npm install picked @picked/react
17
17
  ```
18
18
 
19
19
  ```bash [Svelte]
20
- npm install @picked/client @picked/svelte effector
20
+ npm install picked @picked/svelte
21
21
  ```
22
22
 
23
23
  :::
24
24
 
25
- :::note
26
- `picked` is a shorter name for the same client. `npm install picked` gives you
27
- `createClient` from `picked` and the `picked.config` helpers from `picked/config`. It also
28
- gives you the `picked` command. npm adds `@picked/client` with it, which is where the
29
- generated types attach.
30
- :::
25
+ `picked` gives you the client, the `picked.config` helpers, and the `picked` command. Your
26
+ package manager adds `@picked/client` with it, which carries the runtime and holds the
27
+ generated types.
28
+
29
+ ## 2. Add `picked.config.ts`
30
+
31
+ Create this file in the root of your project. The `picked` command and the
32
+ [Vite plugin](/reference/tools/vite-plugin) read your project identity from it:
33
+
34
+ ```typescript
35
+ import { defineConfig } from 'picked/config'
36
+
37
+ export default defineConfig({
38
+ projectId: 'your-project-id',
39
+ })
40
+ ```
31
41
 
32
- ## 2. Create and start the client
42
+ [Type generation](/reference/sdk/type-generation) reads this file, and gives your fields,
43
+ features and steps the types the workspace declares.
44
+
45
+ ## 3. Create and start the client
33
46
 
34
47
  :::code-group
35
48
 
36
49
  ```tsx [React]
37
- import { createClient } from '@picked/client'
50
+ import { createClient } from 'picked'
38
51
  import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
39
52
  import { reactFrameworkPlugin } from '@picked/react/plugins/framework'
40
53
 
@@ -47,7 +60,7 @@ picked.start()
47
60
  ```
48
61
 
49
62
  ```ts [Svelte]
50
- import { createClient } from '@picked/client'
63
+ import { createClient } from 'picked'
51
64
  import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
52
65
  import { svelteFrameworkPlugin } from '@picked/svelte/plugins/framework'
53
66
 
@@ -61,9 +74,13 @@ picked.start()
61
74
 
62
75
  :::
63
76
 
77
+ `picked` carries the client's root entry. Each deeper import path, such as
78
+ `@picked/client/plugins/dynamic-experiences` and `@picked/client/server`, keeps its own
79
+ name.
80
+
64
81
  `start()` is idempotent. A second call changes nothing on a client that started already.
65
82
 
66
- ## 3. Add the optional plugins
83
+ ## 4. Add the optional plugins
67
84
 
68
85
  - `windowObjectPlugin()` puts the client on `window.Picked`, which helps when you debug.
69
86
  - `singlePageAppPlugin()` starts the client again on a history event.
@@ -72,4 +89,5 @@ picked.start()
72
89
 
73
90
  - [Add Picked to your application](/tutorials/first-experience) — the whole path.
74
91
  - [SDK installation](/reference/sdk) — every client option.
92
+ - [Type generation](/reference/sdk/type-generation) — types from your workspace.
75
93
  - [How to register a component](/how-to/register-a-component).
@@ -13,37 +13,49 @@ API request at runtime.
13
13
  :::code-group
14
14
 
15
15
  ```bash [npm]
16
- npm install @picked/vite-plugin-picked
16
+ npm install @picked/vite-plugin-picked picked
17
17
  ```
18
18
 
19
19
  ```bash [pnpm]
20
- pnpm add @picked/vite-plugin-picked
20
+ pnpm add @picked/vite-plugin-picked picked
21
21
  ```
22
22
 
23
23
  ```bash [yarn]
24
- yarn add @picked/vite-plugin-picked
24
+ yarn add @picked/vite-plugin-picked picked
25
25
  ```
26
26
 
27
27
  :::
28
28
 
29
- ## 2. Add it to your config
29
+ ## 2. Name your project
30
+
31
+ Create `picked.config.ts` in the root of your project. The plugin reads your project
32
+ identity from this file:
33
+
34
+ ```typescript
35
+ import { defineConfig } from 'picked/config'
36
+
37
+ export default defineConfig({
38
+ projectId: 'your-project-id',
39
+ })
40
+ ```
41
+
42
+ ## 3. Add the plugin to your Vite config
30
43
 
31
44
  ```typescript
32
45
  import { defineConfig } from 'vite'
33
46
  import { picked } from '@picked/vite-plugin-picked'
34
47
 
35
48
  export default defineConfig({
36
- plugins: [
37
- picked({
38
- projectId: 'your-project-id',
39
- }),
40
- ],
49
+ plugins: [picked()],
41
50
  })
42
51
  ```
43
52
 
44
53
  When Vite starts, the plugin reads your project configuration and embeds them in your bundle.
45
54
  The SDK then reads the embedded configuration and makes no network request.
46
55
 
56
+ The plugin also writes `picked-env.d.ts`, which types your fields, features and steps
57
+ against the workspace. See [Type generation](/reference/sdk/type-generation).
58
+
47
59
  :::note
48
60
  When a preview token is there at runtime, the SDK ignores the embedded configuration and reads
49
61
  fresh data from the API.
@@ -52,5 +64,6 @@ fresh data from the API.
52
64
  ## Related
53
65
 
54
66
  - [Vite plugin](/reference/tools/vite-plugin) — every option.
67
+ - [Type generation](/reference/sdk/type-generation) — the `picked.config.ts` options.
55
68
  - [About how Picked works](/explanation/how-picked-works) — where build-time configuration sit
56
69
  in the startup order.
@@ -22,8 +22,8 @@ decision.
22
22
  | --- | --- | --- |
23
23
  | `useFeature` / `getFeature` | `() => Promise<bucket>` | Only when you call the function that it returns |
24
24
  | `useFeatureResult` / `getFeatureResult` | `{ id, bucket }`, and `undefined` until it resolves | Yes, as soon as the component renders |
25
- | `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined`, the dashboard **Enabled** feature flag | No |
26
- | `client.features.listStore` | Every registered feature flag, each with an `isResolved` feature flag | No |
25
+ | `useFeatureEnabled` / `getFeatureEnabled` | `boolean \| undefined`, the dashboard **Enabled** setting | No |
26
+ | `client.features.listStore` | Every registered feature flag, each with an `isResolved` property | No |
27
27
  | `client.features.getById(id)` | A store that holds one feature flag | No |
28
28
 
29
29
  Some decisions must wait for data that you do not have yet, such as a cart, a quote, or
@@ -32,6 +32,9 @@ function they return after the data arrives.
32
32
 
33
33
  ## 2. Read the bucket
34
34
 
35
+ The bucket is what your site receives. A Boolean feature flag gives `true` or `false`. A
36
+ Text feature flag gives the value of one variant, or `false` for the control group.
37
+
35
38
  :::code-group
36
39
 
37
40
  ```tsx [React]
@@ -39,12 +42,13 @@ import { useFeatureResult } from '@picked/react/hooks'
39
42
 
40
43
  function MyComponent() {
41
44
  // { id, bucket }, or undefined until the feature flag resolves.
42
- const result = useFeatureResult('my-feature')
45
+ const result = useFeatureResult('hero')
43
46
 
44
47
  return (
45
48
  <div>
46
- {result?.bucket === 'control' && <p>Control</p>}
47
- {result?.bucket === 'variant-a' && <p>Variant A</p>}
49
+ {result?.bucket === false && <p>Control</p>}
50
+ {result?.bucket === 'hero-a' && <p>Hero A</p>}
51
+ {result?.bucket === 'hero-b' && <p>Hero B</p>}
48
52
  </div>
49
53
  )
50
54
  }
@@ -55,13 +59,15 @@ function MyComponent() {
55
59
  import { getFeatureResult } from '@picked/svelte/hooks'
56
60
 
57
61
  // A store that holds { id, bucket }, or undefined until the feature flag resolves.
58
- const result = getFeatureResult('my-feature')
62
+ const result = getFeatureResult('hero')
59
63
  </script>
60
64
 
61
- {#if $result?.bucket === 'control'}
65
+ {#if $result?.bucket === false}
62
66
  <p>Control</p>
63
- {:else if $result?.bucket === 'variant-a'}
64
- <p>Variant A</p>
67
+ {:else if $result?.bucket === 'hero-a'}
68
+ <p>Hero A</p>
69
+ {:else if $result?.bucket === 'hero-b'}
70
+ <p>Hero B</p>
65
71
  {/if}
66
72
  ```
67
73
 
@@ -72,12 +78,18 @@ function MyComponent() {
72
78
  renders, and they commit the visitor to a bucket at that moment.
73
79
  :::
74
80
 
81
+ :::tip
82
+ Run [type generation](/reference/sdk/type-generation) and `bucket` narrows to the values
83
+ that this Text feature flag declares. Your editor then shows each value, and it reports a
84
+ comparison against a value that the feature flag cannot serve.
85
+ :::
86
+
75
87
  ## 3. Read it without a framework
76
88
 
77
89
  The event bus works with any framework, and with none:
78
90
 
79
91
  ```ts
80
- import { createClient } from '@picked/client'
92
+ import { createClient } from 'picked'
81
93
 
82
94
  const client = createClient({
83
95
  /* Put your client options here. */
@@ -85,7 +97,7 @@ const client = createClient({
85
97
 
86
98
  client.eventBus.watch((event) => {
87
99
  if (event.type === 'feature_decide' && event.feature.id === 'my-feature') {
88
- console.log(event.feature) // { id: 'my-feature', key: 'v1', bucket: 'control' }
100
+ console.log(event.feature) // { id: 'my-feature', key: 'v1', bucket: 'hero-a' }
89
101
  }
90
102
  })
91
103
  ```
@@ -20,12 +20,11 @@ Pass the raw field value to `useReference()` in React, or to `getReference()` in
20
20
  :::code-group
21
21
 
22
22
  ```tsx [React]
23
- import { useUnit } from 'effector-react'
24
- import { useReference, useStepContent } from '@picked/react/hooks'
23
+ import { useReference, useStepContent, useStoreGlobal } from '@picked/react/hooks'
25
24
 
26
25
  export function ProductCategory() {
27
26
  const content = useStepContent()
28
- const category = useUnit(useReference(content?.category))
27
+ const category = useStoreGlobal(useReference(content?.category))
29
28
 
30
29
  if (!category) return null
31
30
 
@@ -58,12 +57,11 @@ Use `useReferences()` or `getReferences()`. They give a list in field order.
58
57
  :::code-group
59
58
 
60
59
  ```tsx [React]
61
- import { useUnit } from 'effector-react'
62
- import { useReferences, useStepContent } from '@picked/react/hooks'
60
+ import { useReferences, useStepContent, useStoreGlobal } from '@picked/react/hooks'
63
61
 
64
62
  export function ProductCategories() {
65
63
  const content = useStepContent()
66
- const categories = useUnit(useReferences(content?.categories))
64
+ const categories = useStoreGlobal(useReferences(content?.categories))
67
65
 
68
66
  return (
69
67
  <ul>
@@ -17,7 +17,7 @@ the dashboard.
17
17
  :::code-group
18
18
 
19
19
  ```tsx [React]
20
- import { createClient } from '@picked/client'
20
+ import { createClient } from 'picked'
21
21
  import { adaptReactComponent } from '@picked/react'
22
22
  import { MyWidget } from './MyWidget'
23
23
 
@@ -30,7 +30,7 @@ const picked = createClient({
30
30
  ```
31
31
 
32
32
  ```ts [Svelte]
33
- import { createClient } from '@picked/client'
33
+ import { createClient } from 'picked'
34
34
  import { adaptSvelteComponent } from '@picked/svelte'
35
35
  import MyWidget from './MyWidget.svelte'
36
36
 
@@ -15,7 +15,7 @@ Pass an `actions` object to `createClient()`. Each key must match an action key
15
15
  dashboard:
16
16
 
17
17
  ```ts
18
- import { createClient } from '@picked/client'
18
+ import { createClient } from 'picked'
19
19
 
20
20
  const picked = createClient({
21
21
  projectId: 'your-project-id',
@@ -59,13 +59,12 @@ Bind `form.props` to your `<form>` element and map over the fields.
59
59
  :::code-group
60
60
 
61
61
  ```tsx [React]
62
- import { useStep } from '@picked/react/hooks'
63
- import { useUnit } from 'effector-react'
62
+ import { useStep, useStoreGlobal } from '@picked/react/hooks'
64
63
 
65
64
  function StepForm() {
66
65
  const { fields, form } = useStep()
67
- const currentFields = useUnit(fields)
68
- const isSubmitting = useUnit(form.submitting)
66
+ const currentFields = useStoreGlobal(fields)
67
+ const isSubmitting = useStoreGlobal(form.submitting)
69
68
 
70
69
  return (
71
70
  <form {...form.props}>
@@ -13,7 +13,7 @@ browser. You need a published Page and a server that renders React or Svelte.
13
13
  Create one client for each request, then call `serialize()`:
14
14
 
15
15
  ```ts
16
- import { createClient } from '@picked/client'
16
+ import { createClient } from 'picked'
17
17
  import { serialize } from '@picked/client/server'
18
18
 
19
19
  const client = createClient(options)
@@ -22,7 +22,7 @@ picked login
22
22
  ```
23
23
 
24
24
  This opens your browser for a one-time OAuth sign-in and stores a session under
25
- `~/.picked/`. Confirm it with `picked whoami`. For CI, set `PICKED_TOKEN` instead — see
25
+ `~/.picked/`. Confirm it with `picked whoami`. See
26
26
  [CLI authentication](/how-to/authenticate-the-cli).
27
27
 
28
28
  ## 2. Find a workspace
@@ -99,7 +99,6 @@ Active selection: none (using the config default).
99
99
  workspace in CI with no selection file, set the environment directly:
100
100
 
101
101
  ```bash
102
- PICKED_TOKEN="$TOKEN" \
103
102
  PICKED_PROJECT_ID=proj_a1b2 \
104
103
  PICKED_PREVIEW_TOKEN="$DRAFT_TOKEN" \
105
104
  picked generate types --check
@@ -13,7 +13,7 @@ such as a cart total or an account level.
13
13
  Pass a `predicates` object to `createClient()`:
14
14
 
15
15
  ```ts
16
- import { createClient } from '@picked/client'
16
+ import { createClient } from 'picked'
17
17
 
18
18
  const client = createClient({
19
19
  projectId: 'your-project-id',
@@ -7,8 +7,9 @@ import { Screenshot } from '../../../../components/Screenshot'
7
7
 
8
8
  # API keys
9
9
 
10
- An API key lets your code reach the Picked API. Use one for a server-side integration
11
- or for a CI/CD workflow.
10
+ An API key lets your code reach the Picked API. Use one where your own code calls the API,
11
+ such as a server-side integration. It does not sign the
12
+ [CLI](/reference/tools/cli) in.
12
13
 
13
14
  <Screenshot
14
15
  route="/{orgSlug}/api-keys"
@@ -17,11 +18,11 @@ or for a CI/CD workflow.
17
18
 
18
19
  ## Use an API key
19
20
 
20
- Put your API key in the `Authorization` header of each request to the
21
+ Put your API key in the `x-api-key` header of each request to the
21
22
  [Picked API](/tutorials/first-api-call).
22
23
 
23
24
  ```
24
- Authorization: Bearer your-api-key
25
+ x-api-key: your-api-key
25
26
  ```
26
27
 
27
28
  ## Scopes
@@ -21,6 +21,19 @@ Each feature flag has a unique key. Your application uses this key to check whet
21
21
  Picked allocates a visitor to the feature flag. You set the key when you create the
22
22
  feature flag, and you cannot change it later.
23
23
 
24
+ ## Types
25
+
26
+ Each feature flag has a type. You set the type when you create the feature flag, and you
27
+ cannot change it later.
28
+
29
+ | Type | What your site receives | Groups |
30
+ | --- | --- | --- |
31
+ | **Boolean** | `true` or `false` | Two |
32
+ | **Text** | A string that you write, or `false` for the control group | Two or more |
33
+
34
+ Choose **Boolean** for one change against no change. Choose **Text** when you must test
35
+ several changes at the same time, and each change needs its own group.
36
+
24
37
  ## Set the allocation
25
38
 
26
39
  Set the allocation from the detail page of the feature flag:
@@ -28,19 +41,46 @@ Set the allocation from the detail page of the feature flag:
28
41
  | Control | What it does |
29
42
  | --- | --- |
30
43
  | **Enabled** | Whether Picked allocates a visitor to the feature flag. A disabled feature flag allocates nobody, and it records nothing. See [Disable a feature flag](#disable-a-feature-flag). |
31
- | **Weight** | The percentage of visitors that Picked allocates to the feature flag, from 0 to 100. |
44
+ | **Weight** | The percentage of visitors that Picked allocates to a Boolean feature flag, from 0 to 100. |
45
+ | **Variants** | The groups of a Text feature flag, and the traffic that each one receives. See [Variants](#variants). |
32
46
  | **Description** | An internal note about what the feature flag tests. |
33
47
 
34
- A feature flag has one weight. For each visitor it gives one of two outcomes:
48
+ A Boolean feature flag has one weight. For each visitor it gives one of two outcomes:
35
49
  allocated, or not allocated. A weight of 30 allocates 30% of your visitors. The other
36
50
  70% stay unallocated. A weight of 0 allocates nobody. A weight of 100 allocates
37
51
  everybody.
38
52
 
53
+ ## Variants
54
+
55
+ A Text feature flag has a table of variants. Each row is one group of visitors:
56
+
57
+ | Column | What it does |
58
+ | --- | --- |
59
+ | **Name** | A label for the variant. It appears in your reports. You can change it at any time. |
60
+ | **Value** | The string that your site receives for a visitor in this variant. |
61
+ | **Weight** | The share of traffic for this variant, against the other rows. |
62
+
63
+ The first row is **Control**. Picked always sends `false` to a visitor in the control
64
+ group, and you cannot remove the row, move it, or change its value. It takes a weight like
65
+ any other row.
66
+
67
+ The weights are shares, and not percentages. Picked reduces them to their smallest whole
68
+ numbers, so `10 : 10 : 20` and `1 : 1 : 2` are the same split. The percentage beside each
69
+ row shows the traffic that the variant receives.
70
+
71
+ Choose **Even split** to give every variant the same weight.
72
+
73
+ :::info
74
+ The order of the rows is the order that you read them in. It has no effect on the
75
+ allocation, so you can move a row and no visitor changes group.
76
+ :::
77
+
39
78
  ## How Picked allocates a visitor
40
79
 
41
- The allocation is deterministic. It derives from the visitor ID, the ID of the feature
42
- flag, and the weight. Each feature flag gets its own value, so tests that run at the
43
- same time are independent. See
80
+ The allocation is deterministic. It derives from the visitor ID and from the ID of the
81
+ feature flag. A Boolean feature flag then compares that value against the weight, and a
82
+ Text feature flag compares it against the range of each variant. Each feature flag gets its
83
+ own value, so tests that run at the same time are independent. See
44
84
  [About feature flag allocation](/explanation/feature-flag-allocation).
45
85
 
46
86
  ## Disable a feature flag
@@ -28,6 +28,7 @@ The response is a [JSON Schema draft 2020-12](https://json-schema.org/draft/2020
28
28
  | `x-picked-source` | `"published"` or `"preview"` — mirrors the surface you requested. |
29
29
  | `x-picked-version-id` | Published workspace version id, or `null` on preview. |
30
30
  | `x-picked-step-content-models` | Map of step key → content model key, which gives each layout its type. |
31
+ | `x-picked-feature-values` | Map of feature key → the values that the feature can serve. Picked adds this field only when the workspace has a Text feature flag. |
31
32
  | `$defs.contentModels` | JSON Schema definitions for each content model. |
32
33
  | `$defs.contentBlocks` | JSON Schema definitions for reusable content blocks. |
33
34
  | `$defs.fields` | Object schema for project field values. |
@@ -41,6 +42,13 @@ On the published surface, the schema reflects your last published workspace. On
41
42
 
42
43
  When no published schema exists, the published endpoint returns `404` and tells you to publish the workspace.
43
44
 
45
+ A Boolean feature flag serves `true` or `false`, and it contributes no entry to
46
+ `x-picked-feature-values`. A workspace of Boolean feature flags therefore omits the field.
47
+ A schema that Picked published before Text feature flags existed also omits it.
48
+
49
+ Picked sorts the values of each Text feature flag by the key of the variant. The document
50
+ therefore does not change when you rename a variant or move a row.
51
+
44
52
  ## Example
45
53
 
46
54
  ```bash
@@ -57,11 +65,14 @@ curl https://api.picked.app/api/v1/projects/proj_123/schema
57
65
  "x-picked-step-content-models": {
58
66
  "intro": "welcome-page"
59
67
  },
68
+ "x-picked-feature-values": {
69
+ "hero": [false, "hero-a", "hero-b"]
70
+ },
60
71
  "$defs": {
61
72
  "contentModels": {},
62
73
  "contentBlocks": {},
63
74
  "fields": { "type": "object", "additionalProperties": false, "properties": {} },
64
- "featureKeys": { "type": "string", "enum": ["default"] },
75
+ "featureKeys": { "type": "string", "enum": ["default", "hero"] },
65
76
  "experienceKeys": { "type": "string", "enum": ["welcome-survey"] },
66
77
  "stepKeys": { "type": "string", "enum": ["intro"] }
67
78
  }