@picked/client 0.0.1-dev.1787019239195 → 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 (75) 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/explanation/feature-flag-allocation.md +43 -1
  9. package/dist/docs/how-to/install-the-sdk.md +2 -2
  10. package/dist/docs/how-to/read-a-feature-flag.md +22 -10
  11. package/dist/docs/how-to/read-a-referenced-entry.md +4 -6
  12. package/dist/docs/how-to/render-a-step-form.md +3 -4
  13. package/dist/docs/how-to/switch-workspaces.md +1 -2
  14. package/dist/docs/reference/dashboard/features.mdx +45 -5
  15. package/dist/docs/reference/delivery-api/workspace-schema.md +12 -1
  16. package/dist/docs/reference/pickedql/dimensions.md +6 -0
  17. package/dist/docs/reference/pickedql/virtual-functions.md +16 -5
  18. package/dist/docs/reference/sdk/content.md +6 -9
  19. package/dist/docs/reference/sdk/features.md +17 -6
  20. package/dist/docs/reference/sdk/steps.md +3 -4
  21. package/dist/docs/reference/sdk/type-generation.md +18 -4
  22. package/dist/docs/reference/tools/cli.md +3 -5
  23. package/dist/docs/reference/tools/vite-plugin.md +1 -5
  24. package/dist/docs/tutorials/first-experience.mdx +4 -5
  25. package/dist/emit-D-tLiWL1.cjs +1 -0
  26. package/dist/emit-Ds2EG7CB.js +14 -0
  27. package/dist/esm/index.js +255 -232
  28. package/dist/esm/plugins/dynamic-experiences/index.js +2 -2
  29. package/dist/esm/plugins/telemetry/index.js +94 -0
  30. package/dist/esm/server/index.js +1 -1
  31. package/dist/plugin-BPZV7SJg.cjs +1 -0
  32. package/dist/{plugin-Bb2fJVFr.js → plugin-DTN7fsyp.js} +44 -29
  33. package/dist/{provider-DZEDUlIk.cjs → provider-BxhVIcsp.cjs} +1 -1
  34. package/dist/{provider-B8vTLrFT.js → provider-DLebj-AW.js} +1 -1
  35. package/dist/request-observer-BXDfOU0V.cjs +1 -0
  36. package/dist/request-observer-DtG0cRfz.js +19 -0
  37. package/dist/types/data-source/api.d.ts.map +1 -1
  38. package/dist/types/data-source/provider.d.ts +11 -1
  39. package/dist/types/data-source/provider.d.ts.map +1 -1
  40. package/dist/types/data-source/request-observer.d.ts +46 -0
  41. package/dist/types/data-source/request-observer.d.ts.map +1 -0
  42. package/dist/types/entities/feature/feature.d.ts +10 -3
  43. package/dist/types/entities/feature/feature.d.ts.map +1 -1
  44. package/dist/types/index.d.ts +1 -1
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/types/lifecycle/lifecycle.d.ts.map +1 -1
  47. package/dist/types/picked.d.ts +12 -2
  48. package/dist/types/picked.d.ts.map +1 -1
  49. package/dist/types/plugins/dynamic-experiences/hash.d.ts +53 -1
  50. package/dist/types/plugins/dynamic-experiences/hash.d.ts.map +1 -1
  51. package/dist/types/plugins/dynamic-experiences/index.d.ts +1 -0
  52. package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
  53. package/dist/types/plugins/dynamic-experiences/plugin.d.ts +1 -1
  54. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  55. package/dist/types/plugins/dynamic-experiences/types.d.ts +3 -4
  56. package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
  57. package/dist/types/plugins/telemetry/emit.d.ts +18 -0
  58. package/dist/types/plugins/telemetry/emit.d.ts.map +1 -0
  59. package/dist/types/plugins/telemetry/index.d.ts +4 -0
  60. package/dist/types/plugins/telemetry/index.d.ts.map +1 -0
  61. package/dist/types/plugins/telemetry/plugin-summary.d.ts +10 -0
  62. package/dist/types/plugins/telemetry/plugin-summary.d.ts.map +1 -0
  63. package/dist/types/plugins/telemetry/plugin.d.ts +29 -0
  64. package/dist/types/plugins/telemetry/plugin.d.ts.map +1 -0
  65. package/dist/types/schema-registry.d.ts +16 -0
  66. package/dist/types/schema-registry.d.ts.map +1 -1
  67. package/dist/types/utils/user-timing.d.ts +18 -0
  68. package/dist/types/utils/user-timing.d.ts.map +1 -0
  69. package/dist/version-BlahSDlG.cjs +1 -0
  70. package/dist/{version-DjCWhJD5.js → version-BmBn0dXR.js} +1 -1
  71. package/package.json +16 -4
  72. package/dist/api-TWnRLh3X.cjs +0 -1
  73. package/dist/api-uttjU_Tg.js +0 -22
  74. package/dist/plugin-hFQIiHOB.cjs +0 -1
  75. package/dist/version-CfTz7TK0.cjs +0 -1
@@ -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,6 +78,12 @@ 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:
@@ -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>
@@ -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}>
@@ -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
@@ -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
  }
@@ -22,6 +22,12 @@ point at one. You write no join.
22
22
  | `experiences` | `experience_title` |
23
23
  | `projects` | `project_name`, `project_timezone` |
24
24
 
25
+ :::warning
26
+ `dim_variant` describes whether the visitor is a known user. It does **not** hold the variants
27
+ of your feature flags. To count the visitors in one variant, use
28
+ [`feature_users`](/reference/pickedql/virtual-functions#feature-functions-on-features-and-on-events).
29
+ :::
30
+
25
31
  The `steps`, `experiences`, and `projects` sources are reference tables, and they come
26
32
  from the operational database. PickedQL joins them onto `events` and `fact_events`
27
33
  when you point at one of their analyst-facing columns. Each one **also** needs its own
@@ -213,15 +213,26 @@ Parameterized functions that accept the feature name, bucket, and an optional fe
213
213
  | `feature_sessions('name', 'bucket' [, 'key'])` | Unique sessions in a feature bucket |
214
214
  | `feature_events('name', 'bucket' [, 'key'])` | Unique events in a feature bucket |
215
215
 
216
+ The bucket is what the SDK sent to the site of the visitor. A Boolean feature flag gives
217
+ `'true'` or `'false'`. A Text feature flag gives the value of one variant, or `'false'` for
218
+ the control group. The key is the version of the feature flag, such as `'v1'`.
219
+
216
220
  ```sql
217
- -- Compare feature buckets
221
+ -- Compare the two buckets of a Boolean feature flag.
222
+ SELECT
223
+ feature_users('homepage_test', 'false') AS control,
224
+ feature_users('homepage_test', 'true') AS variant
225
+ FROM features
226
+
227
+ -- Compare the variants of a Text feature flag.
218
228
  SELECT
219
- feature_users('homepage_test', 'control') AS control,
220
- feature_users('homepage_test', 'variant') AS variant
229
+ feature_users('hero', 'false') AS control,
230
+ feature_users('hero', 'hero-a') AS hero_a,
231
+ feature_users('hero', 'hero-b') AS hero_b
221
232
  FROM features
222
233
 
223
- -- With feature key filter
224
- SELECT feature_users('homepage_test', 'control', 'v1') FROM features
234
+ -- Count one version of the feature flag alone.
235
+ SELECT feature_users('homepage_test', 'false', 'v1') FROM features
225
236
  ```
226
237
 
227
238
  ## Other (`metrics` only)
@@ -68,11 +68,10 @@ changes. `getStepContent()` returns a store, which you read with the `$` prefix.
68
68
  :::code-group
69
69
 
70
70
  ```tsx [React]
71
- import { useUnit } from 'effector-react'
72
- import { useContent } from '@picked/react/hooks'
71
+ import { useContent, useStoreGlobal } from '@picked/react/hooks'
73
72
 
74
73
  export function Banner() {
75
- const banner = useUnit(useContent('entry_42'))
74
+ const banner = useStoreGlobal(useContent('entry_42'))
76
75
 
77
76
  return <h1>{String(banner?.data.headline ?? '')}</h1>
78
77
  }
@@ -99,12 +98,11 @@ index.
99
98
  :::code-group
100
99
 
101
100
  ```tsx [React]
102
- import { useUnit } from 'effector-react'
103
- import { useReference, useStepContent } from '@picked/react/hooks'
101
+ import { useReference, useStepContent, useStoreGlobal } from '@picked/react/hooks'
104
102
 
105
103
  export function ProductCategory() {
106
104
  const content = useStepContent()
107
- const category = useUnit(useReference(content?.category))
105
+ const category = useStoreGlobal(useReference(content?.category))
108
106
 
109
107
  return <span>{String(category?.data.name ?? '')}</span>
110
108
  }
@@ -129,12 +127,11 @@ a list in field order.
129
127
  :::code-group
130
128
 
131
129
  ```tsx [React]
132
- import { useUnit } from 'effector-react'
133
- import { useReferences, useStepContent } from '@picked/react/hooks'
130
+ import { useReferences, useStepContent, useStoreGlobal } from '@picked/react/hooks'
134
131
 
135
132
  export function ProductCategories() {
136
133
  const content = useStepContent()
137
- const categories = useUnit(useReferences(content?.categories))
134
+ const categories = useStoreGlobal(useReferences(content?.categories))
138
135
 
139
136
  return (
140
137
  <ul>
@@ -26,12 +26,22 @@ For a feature flag that you control from the dashboard, the SDK makes the defini
26
26
  | `enabled` | The dashboard **Enabled** toggle |
27
27
  | `weight` | The dashboard **Weight**, 0 to 100 |
28
28
  | `storage` | `local`, or `session` in [preview mode](/explanation/feature-flag-allocation#preview-mode) |
29
- | `decide` | Made by the SDK. It compares the stable value of the visitor for this feature flag against `weight`. |
29
+ | `decide` | Made by the SDK. See the two routes below. |
30
30
 
31
- The `decide` function that the SDK makes derives from the visitor ID, the ID of the
32
- feature flag, and the weight. A feature flag that you control from the dashboard
33
- therefore cannot use the data of your application. Define the feature flag in SDK code
34
- when you need that.
31
+ The `decide` function that the SDK makes derives from the visitor ID and from the ID of
32
+ the feature flag. A feature flag that you control from the dashboard therefore cannot use
33
+ the data of your application. Define the feature flag in SDK code when you need that.
34
+
35
+ Which route `decide` takes depends on the type of the feature flag:
36
+
37
+ | Dashboard type | The bucket | How the SDK decides |
38
+ | --- | --- | --- |
39
+ | Boolean | `true` or `false` | It compares the stable value of the visitor against `weight`. |
40
+ | Text | The value of one variant, or `false` | It compares the stable value of the visitor against the range that Picked gives to each variant. |
41
+
42
+ The SDK sorts the variants by their key before it allocates, so the order of the payload
43
+ has no effect. See
44
+ [More than two groups](/explanation/feature-flag-allocation#more-than-two-groups).
35
45
 
36
46
  ## Define a feature flag in SDK code
37
47
 
@@ -95,7 +105,8 @@ You can give more than one override value:
95
105
  https://example.com?picked-feature=my-feature:true&picked-feature=homepage-variant:variant-a
96
106
  ```
97
107
 
98
- The format is `?picked-feature=<feature-id>:<bucket-value>`.
108
+ The format is `?picked-feature=<feature-id>:<bucket-value>`. For a Text feature flag,
109
+ give the value that the variant serves.
99
110
 
100
111
  This is useful for QA, because you can test one bucket value and change no code. The
101
112
  SDK keeps the override value in session storage. The browser clears it when you close
@@ -16,13 +16,12 @@ Inside a `Step` component, or inside any component that a `Step` renders, call
16
16
  :::code-group
17
17
 
18
18
  ```tsx [React]
19
- import { useStep } from '@picked/react/hooks'
20
- import { useUnit } from 'effector-react'
19
+ import { useStep, useStoreGlobal } from '@picked/react/hooks'
21
20
 
22
21
  function ContactForm() {
23
22
  const { step, fields } = useStep()
24
- const currentStep = useUnit(step)
25
- const currentFields = useUnit(fields)
23
+ const currentStep = useStoreGlobal(step)
24
+ const currentFields = useStoreGlobal(fields)
26
25
 
27
26
  return (
28
27
  <>
@@ -65,9 +65,7 @@ If you use Vite, add `@picked/vite-plugin-picked`. Type generation runs automati
65
65
  import { picked } from '@picked/vite-plugin-picked'
66
66
 
67
67
  export default defineConfig({
68
- plugins: [
69
- picked({ disableLauncher: true }),
70
- ],
68
+ plugins: [picked()],
71
69
  })
72
70
  ```
73
71
 
@@ -126,7 +124,7 @@ The generated `picked-env.d.ts` file looks like:
126
124
 
127
125
  export interface WelcomePage { /* ... */ }
128
126
  export interface FieldValues { /* ... */ }
129
- export type FeatureKey = 'default' | 'winter-sale'
127
+ export type FeatureKey = 'default' | 'hero' | 'winter-sale'
130
128
 
131
129
  declare module '@picked/client' {
132
130
  interface PickedSchemaRegistry {
@@ -134,12 +132,28 @@ declare module '@picked/client' {
134
132
  stepContentModels: { intro: 'welcome-page' }
135
133
  fieldValues: FieldValues
136
134
  featureKeys: FeatureKey
135
+ featureValues: { hero: false | 'hero-a' | 'hero-b' }
137
136
  }
138
137
  }
139
138
  ```
140
139
 
141
140
  Commit this file to the version control system, or regenerate it in CI after you publish a workspace change.
142
141
 
142
+ ### Feature flag values
143
+
144
+ A Text feature flag declares the values that it can serve, so `useFeatureResult` narrows
145
+ the bucket to those values:
146
+
147
+ ```typescript
148
+ const result = useFeatureResult('hero')
149
+ ```
150
+
151
+ The `bucket` property then has the type `false | 'hero-a' | 'hero-b'`.
152
+
153
+ `featureValues` holds the Text feature flags alone. A Boolean feature flag, and a feature
154
+ flag that your application defines, both keep the general `boolean | string` type. The
155
+ `FeatureValueFor<'hero'>` type gives the same union on its own.
156
+
143
157
  ## Keys the application defines
144
158
 
145
159
  An application can define a feature, an action, a field, a step, or an experience of its
@@ -30,7 +30,7 @@ package.
30
30
  | --- | --- |
31
31
  | `picked login` | Sign in with your browser (OAuth) to the API origin of the current directory. Stores a session under `~/.picked/auth/`. |
32
32
  | `picked logout` | Clear the stored session for the current directory's API origin. |
33
- | `picked whoami` | Print the identity signed in to the API origin of the current directory, or report that `PICKED_TOKEN` is in use. |
33
+ | `picked whoami` | Print the identity signed in to the API origin of the current directory. |
34
34
  | `picked status` | Print the resolved selection and the committed `picked.config` default. |
35
35
  | `picked workspace list` | List each org, project, and workspace that you can read. Alias `ls`. `--json` gives machine output. |
36
36
  | `picked workspace use` | Set the active project or workspace. Alias `picked use`. Interactive, or `--project` and `--workspace`. |
@@ -107,9 +107,8 @@ a session for another API.
107
107
  | --- | --- |
108
108
  | `~/.picked/auth/<origin>.json` | The OAuth session for one API origin — access token, refresh token, expiry — with the CLI's registered client id for that origin. File mode `600`. Refreshed as it nears expiry. |
109
109
 
110
- `PICKED_TOKEN` supersedes each stored session and needs no browser, which is what a CI run
111
- uses. An API deployment with no OAuth client registration rejects `picked login`, and has
112
- no other flow.
110
+ An API deployment with no OAuth client registration rejects `picked login`, and has no
111
+ other flow.
113
112
 
114
113
  A CLI session is a developer credential. It is not an
115
114
  [API key](/reference/dashboard/api-keys), which authenticates server-to-server calls, and
@@ -120,7 +119,6 @@ not [dashboard sign-in](/reference/management-api/authentication).
120
119
  | Variable | Effect |
121
120
  | --- | --- |
122
121
  | `PICKED_API_URL` | The API origin that the CLI reads. Supersedes `picked.config` `apiUrl` and the `https://api.picked.app` default. |
123
- | `PICKED_TOKEN` | An access token in place of the stored session, for a CI or headless run. `picked login` is then not needed. |
124
122
  | `PICKED_PROJECT_ID` | Supersedes the resolved project id, above the active selection and the committed configuration. |
125
123
  | `PICKED_PREVIEW_TOKEN` | Supersedes the preview token. Applies only when the resolved source is `preview`. |
126
124
 
@@ -124,11 +124,7 @@ These two options moved to [`picked.config.ts`](/reference/sdk/type-generation),
124
124
 
125
125
  The plugin still reads an option for a field that `picked.config.ts` leaves out, so a
126
126
  project that has yet to move needs no change. It stops when the two values are different,
127
- rather than choose one for you:
128
-
129
- ```
130
- picked plugin option `projectId` is "option-proj", and picked.config says "file-proj".
131
- ```
127
+ rather than choose one for you.
132
128
 
133
129
  ### generateTypes
134
130
 
@@ -102,11 +102,11 @@ Install the client and the adapter for your framework.
102
102
  :::code-group
103
103
 
104
104
  ```bash [React]
105
- npm install picked @picked/react effector effector-react
105
+ npm install picked @picked/react
106
106
  ```
107
107
 
108
108
  ```bash [Svelte]
109
- npm install picked @picked/svelte effector
109
+ npm install picked @picked/svelte
110
110
  ```
111
111
 
112
112
  :::
@@ -118,12 +118,11 @@ puts each one in a form.
118
118
  :::code-group
119
119
 
120
120
  ```tsx [React]
121
- import { useStep } from '@picked/react/hooks'
122
- import { useUnit } from 'effector-react'
121
+ import { useStep, useStoreGlobal } from '@picked/react/hooks'
123
122
 
124
123
  export function ColorSurvey() {
125
124
  const { fields, form } = useStep()
126
- const currentFields = useUnit(fields)
125
+ const currentFields = useStoreGlobal(fields)
127
126
 
128
127
  return (
129
128
  <form {...form.props}>
@@ -0,0 +1 @@
1
+ let e=require("@picked/telemetry");async function t(t,n,r){let i=(0,e.createTelemetryEmitter)({endpoint:t,resource:n,headers:{"Picked-Telemetry":`sdk`},maxBatchSize:r.length||1});for(let e of r)i.record(e);await i.flush()}exports.flushSpans=t;
@@ -0,0 +1,14 @@
1
+ import { createTelemetryEmitter as e } from "@picked/telemetry";
2
+ //#region src/plugins/telemetry/emit.ts
3
+ async function t(t, n, r) {
4
+ let i = e({
5
+ endpoint: t,
6
+ resource: n,
7
+ headers: { "Picked-Telemetry": "sdk" },
8
+ maxBatchSize: r.length || 1
9
+ });
10
+ for (let e of r) i.record(e);
11
+ await i.flush();
12
+ }
13
+ //#endregion
14
+ export { t as flushSpans };