@picked/client 0.0.72-next.1 → 0.0.72-next.3

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 (70) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/{api-B9DDtylO.cjs → api-B4qxWoQN.cjs} +1 -1
  3. package/dist/{api-BmzHzzpu.js → api-DNvaLWw_.js} +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  6. package/dist/cjs/server/index.js +1 -1
  7. package/dist/docs/explanation/content-references.md +99 -0
  8. package/dist/docs/explanation/how-picked-works.md +25 -3
  9. package/dist/docs/explanation/the-content-model.md +38 -28
  10. package/dist/docs/how-to/create-a-content-entry.md +48 -0
  11. package/dist/docs/how-to/define-a-page-layout.md +12 -5
  12. package/dist/docs/how-to/point-one-entry-at-another.md +61 -0
  13. package/dist/docs/how-to/read-a-referenced-entry.md +111 -0
  14. package/dist/docs/how-to/reuse-a-section.md +26 -10
  15. package/dist/docs/index.md +1 -1
  16. package/dist/docs/reference/dashboard/content/blocks.mdx +57 -0
  17. package/dist/docs/reference/dashboard/content/content-entries.mdx +61 -10
  18. package/dist/docs/reference/dashboard/content/models.mdx +108 -0
  19. package/dist/docs/reference/dashboard/content/pages.mdx +4 -3
  20. package/dist/docs/reference/dashboard/fields.mdx +1 -1
  21. package/dist/docs/reference/dashboard/steps.mdx +2 -2
  22. package/dist/docs/reference/delivery-api/experience-content.md +68 -13
  23. package/dist/docs/reference/glossary.md +13 -5
  24. package/dist/docs/reference/management-api/error-handling.md +2 -2
  25. package/dist/docs/reference/sdk/content.md +216 -0
  26. package/dist/docs/reference/sdk/plugins/dynamic-experiences.md +51 -3
  27. package/dist/docs/reference/sdk/ssr/serialize.md +6 -3
  28. package/dist/docs/reference/tools/vite-plugin.md +90 -8
  29. package/dist/docs/tutorials/first-api-call.md +1 -1
  30. package/dist/docs/tutorials/first-server-rendered-page.mdx +1 -1
  31. package/dist/esm/index.js +484 -429
  32. package/dist/esm/plugins/dynamic-experiences/index.js +4 -3
  33. package/dist/esm/server/index.js +43 -46
  34. package/dist/plugin-DkSWIASL.cjs +1 -0
  35. package/dist/plugin-DviILBha.js +153 -0
  36. package/dist/provider-Bskcgd6U.js +68 -0
  37. package/dist/provider-Bv5nM3So.cjs +1 -0
  38. package/dist/registry-BMXsAEbZ.cjs +1 -0
  39. package/dist/registry-DjBZ8upD.js +74 -0
  40. package/dist/types/data-source/api.d.ts +60 -0
  41. package/dist/types/data-source/api.d.ts.map +1 -0
  42. package/dist/types/data-source/provider.d.ts +90 -0
  43. package/dist/types/data-source/provider.d.ts.map +1 -0
  44. package/dist/types/data-source/registry.d.ts +26 -0
  45. package/dist/types/data-source/registry.d.ts.map +1 -0
  46. package/dist/types/data-source/virtual-data.d.ts +19 -0
  47. package/dist/types/data-source/virtual-data.d.ts.map +1 -0
  48. package/dist/types/entities/content/reference.d.ts +101 -0
  49. package/dist/types/entities/content/reference.d.ts.map +1 -0
  50. package/dist/types/exports.d.ts +2 -0
  51. package/dist/types/exports.d.ts.map +1 -1
  52. package/dist/types/index.d.ts +1 -0
  53. package/dist/types/index.d.ts.map +1 -1
  54. package/dist/types/picked.d.ts +65 -1
  55. package/dist/types/picked.d.ts.map +1 -1
  56. package/dist/types/plugins/dynamic-experiences/index.d.ts +1 -0
  57. package/dist/types/plugins/dynamic-experiences/index.d.ts.map +1 -1
  58. package/dist/types/plugins/dynamic-experiences/plugin.d.ts +9 -40
  59. package/dist/types/plugins/dynamic-experiences/plugin.d.ts.map +1 -1
  60. package/dist/types/plugins/dynamic-experiences/types.d.ts +25 -23
  61. package/dist/types/plugins/dynamic-experiences/types.d.ts.map +1 -1
  62. package/dist/types/server/index.d.ts +1 -1
  63. package/dist/types/server/index.d.ts.map +1 -1
  64. package/dist/types/server/serialize.d.ts +27 -34
  65. package/dist/types/server/serialize.d.ts.map +1 -1
  66. package/package.json +3 -3
  67. package/dist/docs/reference/dashboard/content/layouts.mdx +0 -62
  68. package/dist/docs/reference/dashboard/content/sections.mdx +0 -28
  69. package/dist/plugin-CZN0_kqv.js +0 -252
  70. package/dist/plugin-SFx511s_.cjs +0 -1
@@ -0,0 +1,108 @@
1
+ ---
2
+ type: reference
3
+ layout: docs
4
+ ---
5
+
6
+ import { Screenshot } from '../../../../../components/Screenshot'
7
+
8
+ # Models
9
+
10
+ A Model is a reusable schema. It sets which fields a piece of content can hold, and it holds no data itself.
11
+
12
+ <Screenshot
13
+ route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/models"
14
+ alt="The models list in the schema builder on the Picked dashboard"
15
+ />
16
+
17
+ ## Where a Model appears
18
+
19
+ **CMS** in the sidebar holds **Models**, and that list shows every Model in the
20
+ workspace. The list shows the **Key** below the name, because the key is the value that
21
+ a schema holds.
22
+
23
+ Two shortcuts open a filtered subset of the same list. **Flows** holds **Step Layouts**,
24
+ and **Pages** holds **Page Layouts**.
25
+
26
+ ## Category
27
+
28
+ A category gives a Model a role. It decides where the Model appears, and what renders an
29
+ entry that comes from it.
30
+
31
+ | Category | It gives its shape to | What renders it |
32
+ | --- | --- | --- |
33
+ | **Page layout** | A standalone [Page](/reference/dashboard/content/pages). | The SDK renders the entry on your server. |
34
+ | **Step layout** | One [Step](/reference/dashboard/steps) inside a Flow. | The Step renders the entry in the browser. |
35
+ | **None (general content)** | Content with no layout role. | Nothing renders it directly. A [reference field](#reference-fields) reaches it. |
36
+
37
+ A Model with no category holds content that other content points at, such as a product
38
+ or a category. It appears under **CMS** only.
39
+
40
+ :::warning
41
+ The category is fixed when you create the Model. You cannot change it later.
42
+ :::
43
+
44
+ ## Field types
45
+
46
+ A Model is built from fields. Each field has a type. The type decides the editor for the
47
+ field, and the shape of the value that the field holds.
48
+
49
+ | Type | Holds | Use it for |
50
+ | --- | --- | --- |
51
+ | **Single line text** | A short string. | A title, a label, or a short headline. |
52
+ | **Multi-line text** | A longer plain string. | A description, or a paragraph with no format marks. |
53
+ | **Markdown** | A Markdown string. | Body copy with format marks. |
54
+ | **Slug** | A URL-safe string. | A part of a path, or an identifier. |
55
+ | **Number** | A numeric value. | A price, a count, or a rating. |
56
+ | **Boolean** | A true or false value. | A toggle or a flag. |
57
+ | **Date** | A date value. | A publish date or a deadline. |
58
+ | **Color** | A color value. | A theme accent or a swatch. |
59
+ | **JSON** | Any JSON value. | Structured data with no editor of its own. |
60
+ | **Group** | An object of fields inside the Model. | A set of related fields, held together. |
61
+ | **Content block** | A [Block](/reference/dashboard/content/blocks) schema inside the Model. | A shared schema fragment that you reuse. |
62
+ | **Page Sections** | A repeatable Content block field, limited to the Section category. | The section list of a page. |
63
+ | **Reference** | A pointer at another [content entry](/reference/dashboard/content/content-entries). | A product that points at its category. |
64
+ | **Asset** | A link to an [Asset Library](/reference/dashboard/content/asset-library) item. | An image, a video, or a document. |
65
+ | **HTML** | A link to a `.html` file in your library. | HTML that you wrote before, held inside the content. |
66
+
67
+ ## Reference fields
68
+
69
+ A **Reference** field points at another content entry. The entry that you point at keeps
70
+ its own data, and many fields can point at the same entry.
71
+
72
+ | Option | Values | What it does |
73
+ | --- | --- | --- |
74
+ | **Target Model** | A Model, or **Any Model** | Limits which entries an editor can choose. |
75
+ | **Repeatable** | On or off | On gives an ordered list of entries. Off gives one entry. |
76
+
77
+ **Target Model** holds the `key` of the Model, and not its id. A duplicate of the
78
+ workspace therefore keeps the constraint.
79
+
80
+ A reference stays inside one workspace. A field cannot point at an entry in another
81
+ workspace, and Picked reports such a target as unknown.
82
+
83
+ ## Repeatable and grouped fields
84
+
85
+ You can make any field **repeatable**. It then shows as a sortable list of values, and
86
+ not as one value. Use it for a gallery, for a list of features, or for any collection.
87
+
88
+ A **Group** field holds a set of fields under one key, and it gives a structured object.
89
+ Make a group repeatable to describe a list of objects, such as a list of feature cards.
90
+
91
+ ## Delete rules
92
+
93
+ Picked does not delete a Model while something still needs it.
94
+
95
+ | Condition | Result |
96
+ | --- | --- |
97
+ | Content entries still belong to the Model | The delete stops. |
98
+ | A Reference field still holds the Model as its **Target Model** | The delete stops. |
99
+
100
+ ## Related
101
+
102
+ - [About the content model](/explanation/the-content-model) — why a schema change does not
103
+ migrate your data.
104
+ - [About content references](/explanation/content-references) — why a reference is a pointer.
105
+ - [How to define a page layout](/how-to/define-a-page-layout)
106
+ - [How to create a content entry](/how-to/create-a-content-entry)
107
+ - [How to point one entry at another](/how-to/point-one-entry-at-another)
108
+ - [Blocks](/reference/dashboard/content/blocks)
@@ -24,12 +24,13 @@ choose, and the preview panel shows the page while it fills in.
24
24
  ## Change or remove the layout
25
25
 
26
26
  Use the change-layout control on the **Settings** tab to move the page to another
27
- Page Layout. When you unlink a layout, Picked separates the Content Entry from the
28
- page. It deletes no entry, but the page then has no content to render until you
29
- attach a layout again.
27
+ [Page Layout](/reference/dashboard/content/models#category). When you unlink a layout,
28
+ Picked separates the content entry from the page. It deletes no entry, but the page then
29
+ has no content to render until you attach a layout again.
30
30
 
31
31
  ## Related
32
32
 
33
33
  - [About pages and flows](/explanation/pages-and-flows) — when to choose a Page.
34
34
  - [How to fill and publish a page](/how-to/fill-and-publish-a-page)
35
35
  - [How to server-render a page](/how-to/server-render-a-page)
36
+ - [Models](/reference/dashboard/content/models) — the schema behind a Page.
@@ -12,7 +12,7 @@ information such as a name, an email address, a preference, or a choice.
12
12
 
13
13
  This page covers the experience field, which collects visitor input. An experience
14
14
  field is not the same as a content field. A content field is part of the schema of a
15
- CMS layout. See [Page and Step Layouts](/reference/dashboard/content/layouts) for those.
15
+ CMS layout. See [Models](/reference/dashboard/content/models) for those.
16
16
 
17
17
  <Screenshot
18
18
  route="/{orgSlug}/{projectId}/workspaces/{workspaceId}/fields"
@@ -26,7 +26,7 @@ visitors do not see it.
26
26
  are `card`, `modal`, and `fullscreen`.
27
27
 
28
28
  A step can also carry a **Step Layout**. A step layout is a reusable CMS schema, and
29
- it gives the step a Content Entry. See [Page and Step Layouts](/reference/dashboard/content/layouts).
29
+ it gives the step a Content Entry. See [Models](/reference/dashboard/content/models).
30
30
 
31
31
  ## Related
32
32
 
@@ -34,4 +34,4 @@ it gives the step a Content Entry. See [Page and Step Layouts](/reference/dashbo
34
34
 
35
35
  - [How to build a flow](/how-to/build-a-flow) — add a step and assign fields to it.
36
36
  - [How to reuse a step, a field, or an action](/how-to/reuse-a-step-field-or-action)
37
- - [Page and step layouts](/reference/dashboard/content/layouts) — the step layout schema.
37
+ - [Models](/reference/dashboard/content/models) — the step layout schema.
@@ -5,7 +5,7 @@ layout: docs
5
5
 
6
6
  # Experience Content Endpoint
7
7
 
8
- Fetch the Content Entries that back a single experience's steps.
8
+ Fetch the content entries that back the steps of a single experience.
9
9
 
10
10
  ## Request
11
11
 
@@ -29,7 +29,17 @@ GET /api/v1/projects/:projectId/experiences/:id/content
29
29
  {
30
30
  "id": "entry_789",
31
31
  "contentModelKey": "page-hero",
32
- "data": { "title": "Spring sale", "body": "..." }
32
+ "data": {
33
+ "title": "Spring sale",
34
+ "category": { "contentEntryId": "entry_42", "contentModelKey": "category" }
35
+ }
36
+ }
37
+ ],
38
+ "references": [
39
+ {
40
+ "id": "entry_42",
41
+ "contentModelKey": "category",
42
+ "data": { "name": "Outerwear" }
33
43
  }
34
44
  ]
35
45
  }
@@ -39,14 +49,59 @@ GET /api/v1/projects/:projectId/experiences/:id/content
39
49
  | --- | --- | --- |
40
50
  | `experienceId` | `string` | The experience the content belongs to. |
41
51
  | `versionId` | `string \| null` | The published version id, or `null` on preview. |
42
- | `content[].id` | `string` | The Content Entry id. |
43
- | `content[].contentModelKey` | `string \| null` | The layout's Key, or `null` when it cannot be resolved. |
44
- | `content[].data` | `object` | The entry data. It matches the layout schema. |
52
+ | `content[]` | `array` | The entries that the steps of the experience point at. |
53
+ | `content[].id` | `string` | The content entry id. |
54
+ | `content[].contentModelKey` | `string \| null` | The Key of the Model, or `null` when it cannot be resolved. |
55
+ | `content[].data` | `object` | The entry data. It matches the schema of the Model. |
56
+ | `references[]` | `array` | Every entry that a `reference` field reaches, in the same blob shape as `content[]`. |
57
+
58
+ The published response and the preview response hold the same fields, and the same
59
+ values, for the same content.
60
+
61
+ ## A reference field points at a separate blob
62
+
63
+ A [Reference field](/reference/dashboard/content/models#reference-fields) arrives as a
64
+ pointer, and not as the entry:
65
+
66
+ ```json
67
+ { "contentEntryId": "entry_42", "contentModelKey": "category" }
68
+ ```
69
+
70
+ A repeatable Reference field arrives as an array of such pointers.
71
+
72
+ The entry itself arrives in `references[]` under its own `id`. Look the pointer up
73
+ there. The SDK does this for you, through
74
+ [`useReference` and `getReference`](/reference/sdk/content).
75
+
76
+ `references[]` holds each entry one time, for any number of fields that point at it. It
77
+ omits the entries that are in `content[]` already.
78
+
79
+ ### What `references[]` omits
80
+
81
+ Picked resolves a chain of references to a maximum depth of 5. It counts the depth from
82
+ the entries in `content[]`. An entry is absent from `references[]` when Picked cannot
83
+ resolve it:
84
+
85
+ | Cause | Result |
86
+ | --- | --- |
87
+ | The target was deleted | The pointer stays. The entry is absent. |
88
+ | The target is in another workspace | The pointer stays. The entry is absent. |
89
+ | The chain makes a cycle | The pointer stays. The entry is absent. |
90
+ | The chain is deeper than 5 | The pointer stays, with its `contentModelKey`. The entry is absent. |
91
+
92
+ A pointer with no entry in `references[]` means that there is no such entry. Picked sends
93
+ no empty blob for it.
94
+
95
+ :::warning
96
+ A reference pointer also carries a `data` field with the content of the target inlined.
97
+ This is for the SDK versions that came before `references[]`. Read `references[]`
98
+ instead. A later release removes the inline copy.
99
+ :::
45
100
 
46
101
  ## An HTML asset comes back in the response
47
102
 
48
- A layout can have an [HTML asset](/reference/dashboard/content/asset-library) field. The value of that
49
- field arrives inside the response. The endpoint changes the reference to
103
+ A Model can have an [HTML asset](/reference/dashboard/content/asset-library) field. The
104
+ value of that field arrives inside the response. The endpoint changes the reference to
50
105
  `{ id, content }`. There, `content` holds the HTML that you uploaded, as a string. It
51
106
  is `null` when the endpoint cannot resolve it:
52
107
 
@@ -82,16 +137,16 @@ curl https://api.picked.app/api/v1/projects/proj_123/experiences/exp_123/content
82
137
  "versionId": "ver_456",
83
138
  "content": [
84
139
  { "id": "entry_789", "contentModelKey": "page-hero", "data": { "title": "Spring sale" } }
85
- ]
140
+ ],
141
+ "references": []
86
142
  }
87
143
  ```
88
144
 
89
- See [Project Config](/reference/delivery-api/project-config) for the endpoint that links a step
90
- to an entry. See [Asset Library](/reference/dashboard/content/asset-library) for an HTML asset. See
91
- [Server Snapshot](/reference/sdk/ssr/serialize) for how the SDK reads this content.
92
-
93
145
  ## Related
94
146
 
95
147
  - [About the two APIs](/explanation/the-two-apis)
96
- - [Project config](/reference/delivery-api/project-config)
148
+ - [About content references](/explanation/content-references) — why a reference is a pointer.
149
+ - [Content](/reference/sdk/content) — how the SDK resolves a pointer.
150
+ - [Project config](/reference/delivery-api/project-config) — the endpoint that links a step to an entry.
151
+ - [Server Snapshot](/reference/sdk/ssr/serialize) — how the SDK reads this content.
97
152
  - [Preview, cache, and crawlers](/reference/delivery-api/preview-caching-crawling)
@@ -74,14 +74,21 @@ documentation uses, and it names the others.
74
74
 
75
75
  | Term | What it is |
76
76
  | --- | --- |
77
- | **Page layout** | A reusable schema that sets which fields a Page can hold. The API type is `ContentModel`, and the dashboard list page is titled **Models**. |
78
- | **Step layout** | The same, for one Step inside a Flow. |
79
- | **Section** | A reusable schema fragment that you put inside one or more layouts. The API type is `ContentBlock`, and the dashboard list page is titled **Blocks**. |
80
- | **Content entry** | One instance of a layout, filled with real values. The dashboard list page is titled **Content**. |
77
+ | **Model** | A reusable schema that sets which fields a piece of content can hold. The API type is `ContentModel`. The dashboard lists them under **CMS**, at **Models**. |
78
+ | **Block** | A reusable schema fragment that you put inside one or more Models. The API type is `ContentBlock`. The dashboard lists them under **CMS**, at **Blocks**. |
79
+ | **Content entry** | One instance of a Model, filled with real values. The dashboard lists them under **CMS**, at **Content entries**. |
80
+ | **Category** | The role of a Model or a Block. It is fixed when you create the record. |
81
+ | **Page layout** | A Model with the Page layout category. It sets which fields a Page can hold. |
82
+ | **Step layout** | A Model with the Step layout category. It sets which fields one Step inside a Flow can hold. |
83
+ | **Section** | A Block with the Section category. A **Page Sections** field accepts it. |
84
+ | **General content** | A Model or a Block with no category. It has no layout role, and nothing renders its entries directly. |
85
+ | **Reference** | A field that points one content entry at another. The pointer holds the id of the target, and not a copy of it. |
86
+ | **Target Model** | The Model whose entries a Reference field accepts. It holds a Key. **Any Model** sets no limit. |
87
+ | **Entry title** | The name that the entries list shows. It is the first single-line text field of the entry, and it falls back to the entry id. |
81
88
  | **Asset** | A file that your content points at: an image, a video, a document, or HTML. |
82
89
  | **Asset library** | The project-level store that holds your assets. |
83
90
  | **Design system** | A set of shared design decisions for a project. |
84
- | **Content schema** | The set of fields that a layout or a section defines. |
91
+ | **Content schema** | The set of fields that a Model or a Block defines. |
85
92
  | **Asset version** | One file that somebody uploaded for an asset. The library keeps the earlier versions. |
86
93
  | **Validation error** | A report that one field in a content entry does not match its schema. It carries the path to the exact field. |
87
94
 
@@ -96,6 +103,7 @@ documentation uses, and it names the others.
96
103
  | **Slot** | A named mount point, rendered by `<ExperienceSlot name="…" />`. It maps to the `.picked-slot-<name>` selector. |
97
104
  | **Placement** | Where an experience renders against its target: `replace`, `before`, `prepend`, `append`, or `after`. |
98
105
  | **Target selector** | The selector that an experience renders into. |
106
+ | **Content index** | The store of every content record that the client holds, keyed by id. `useContent(id)` and `useReference()` read it. |
99
107
  | **Snapshot** | The plain JSON that `serialize()` returns on your server, and that seeds the client in the browser. |
100
108
  | **Precondition** | A gate that decides whether Picked starts at all. |
101
109
  | **Visitor** | The person on your site. Picked gives each one a stable ID. A visitor is not a user. |
@@ -46,7 +46,7 @@ operation, or MFA. They match the `AuthErrorCode` enum in the schema.
46
46
  | Code | Description |
47
47
  |------|-------------|
48
48
  | `INVALID_CREDENTIALS` | The email address and the password do not match. |
49
- | `EMAIL_NOT_CONFIRMED` | The account is there, but nobody verified the email address. See [Authentication](/reference/management-api/authentication#verify-an-email-address). |
49
+ | `EMAIL_NOT_CONFIRMED` | The account is there, but nobody verified the email address. See [Authentication](/reference/management-api/authentication#the-mutations). |
50
50
  | `USER_NOT_FOUND` | There is no account with that email address. |
51
51
  | `USER_ALREADY_EXISTS` | An account with that email address is there already. The API returns this when you create an account. |
52
52
  | `WEAK_PASSWORD` | The password is not strong enough. |
@@ -199,7 +199,7 @@ if (result.errors) {
199
199
  ### Refresh a token that expired
200
200
 
201
201
  A request can return `UNAUTHORIZED` while the caller holds a refresh token. Try a
202
- [session refresh](/reference/management-api/authentication#refresh-a-session) first. Do not treat it as a
202
+ [session refresh](/reference/management-api/authentication#the-mutations) first. Do not treat it as a
203
203
  failure to sign in:
204
204
 
205
205
  ```javascript
@@ -0,0 +1,216 @@
1
+ ---
2
+ type: reference
3
+ layout: docs
4
+ ---
5
+
6
+ # Content
7
+
8
+ The client holds every content record in one index, keyed by the id. The hooks on this
9
+ page read that index.
10
+
11
+ ## The content record
12
+
13
+ ```ts
14
+ interface Content {
15
+ id: string
16
+ contentModelKey?: string | null
17
+ data: Record<string, unknown>
18
+ }
19
+ ```
20
+
21
+ | Property | Type | Description |
22
+ | --- | --- | --- |
23
+ | `id` | `string` | The content entry id. |
24
+ | `contentModelKey` | `string \| null` | The Key of the [Model](/reference/dashboard/content/models) that the entry belongs to. |
25
+ | `data` | `Record<string, unknown>` | The field values, keyed by the field key from the schema. |
26
+
27
+ The client registers the `content` array and the `references` array of the
28
+ [experience content response](/reference/delivery-api/experience-content) into the same
29
+ index. Your code can therefore read a referenced entry by its own id.
30
+
31
+ ## Read the content of a step
32
+
33
+ `useStepContent()` in React, and `getStepContent()` in Svelte, give the `data` of the
34
+ entry that the current step points at.
35
+
36
+ :::code-group
37
+
38
+ ```tsx [React]
39
+ import { useStepContent } from '@picked/react/hooks'
40
+
41
+ export function Hero() {
42
+ const content = useStepContent()
43
+
44
+ return <h1>{String(content?.headline ?? '')}</h1>
45
+ }
46
+ ```
47
+
48
+ ```svelte [Svelte]
49
+ <script lang="ts">
50
+ import { getStepContent } from '@picked/svelte/hooks'
51
+
52
+ const content = getStepContent()
53
+ </script>
54
+
55
+ <h1>{$content?.headline ?? ''}</h1>
56
+ ```
57
+
58
+ :::
59
+
60
+ `useStepContent()` returns the data, and React re-renders the component when the data
61
+ changes. `getStepContent()` returns a store, which you read with the `$` prefix.
62
+
63
+ ## Read a record by id
64
+
65
+ `useContent(id)` and `getContent(id)` return a store of one record. The store holds
66
+ `null` until the client registers that record.
67
+
68
+ :::code-group
69
+
70
+ ```tsx [React]
71
+ import { useUnit } from 'effector-react'
72
+ import { useContent } from '@picked/react/hooks'
73
+
74
+ export function Banner() {
75
+ const banner = useUnit(useContent('entry_42'))
76
+
77
+ return <h1>{String(banner?.data.headline ?? '')}</h1>
78
+ }
79
+ ```
80
+
81
+ ```svelte [Svelte]
82
+ <script lang="ts">
83
+ import { getContent } from '@picked/svelte/hooks'
84
+
85
+ const banner = getContent('entry_42')
86
+ </script>
87
+
88
+ <h1>{$banner?.data.headline ?? ''}</h1>
89
+ ```
90
+
91
+ :::
92
+
93
+ ## Resolve a reference field
94
+
95
+ A [Reference field](/reference/dashboard/content/models#reference-fields) holds a pointer,
96
+ and not the record. `useReference()` and `getReference()` resolve the pointer against the
97
+ index.
98
+
99
+ :::code-group
100
+
101
+ ```tsx [React]
102
+ import { useUnit } from 'effector-react'
103
+ import { useReference, useStepContent } from '@picked/react/hooks'
104
+
105
+ export function ProductCategory() {
106
+ const content = useStepContent()
107
+ const category = useUnit(useReference(content?.category))
108
+
109
+ return <span>{String(category?.data.name ?? '')}</span>
110
+ }
111
+ ```
112
+
113
+ ```svelte [Svelte]
114
+ <script lang="ts">
115
+ import { getStepContent, getReference } from '@picked/svelte/hooks'
116
+
117
+ const content = getStepContent()
118
+ const category = getReference(content.map((c) => c?.category ?? null, { skipVoid: false }))
119
+ </script>
120
+
121
+ <span>{$category?.data.name ?? ''}</span>
122
+ ```
123
+
124
+ :::
125
+
126
+ For a repeatable Reference field, use `useReferences()` and `getReferences()`. They give
127
+ a list in field order.
128
+
129
+ :::code-group
130
+
131
+ ```tsx [React]
132
+ import { useUnit } from 'effector-react'
133
+ import { useReferences, useStepContent } from '@picked/react/hooks'
134
+
135
+ export function ProductCategories() {
136
+ const content = useStepContent()
137
+ const categories = useUnit(useReferences(content?.categories))
138
+
139
+ return (
140
+ <ul>
141
+ {categories.map((category) => (
142
+ <li key={category.id}>{String(category.data.name ?? '')}</li>
143
+ ))}
144
+ </ul>
145
+ )
146
+ }
147
+ ```
148
+
149
+ ```svelte [Svelte]
150
+ <script lang="ts">
151
+ import { getStepContent, getReferences } from '@picked/svelte/hooks'
152
+
153
+ const content = getStepContent()
154
+ const categories = getReferences(content.map((c) => c?.categories ?? null, { skipVoid: false }))
155
+ </script>
156
+
157
+ <ul>
158
+ {#each $categories as category (category.id)}
159
+ <li>{category.data.name ?? ''}</li>
160
+ {/each}
161
+ </ul>
162
+ ```
163
+
164
+ :::
165
+
166
+ The list holds no empty places. A pointer that Picked cannot resolve is absent from the
167
+ list, so each item is a record.
168
+
169
+ :::note
170
+ The Svelte helpers accept a store, and the examples pass one. Content arrives after the
171
+ component starts. A plain value therefore holds the result at "not set".
172
+ :::
173
+
174
+ ## Reference hooks
175
+
176
+ | Hook | Returns | When the target is absent |
177
+ | --- | --- | --- |
178
+ | `useReference(value)` | `Store<Content \| null>` | `null` |
179
+ | `useReferences(value)` | `Store<Content[]>` | The item is absent from the list. |
180
+ | `getReference(value)` | `Store<Content \| null>` | `null` |
181
+ | `getReferences(value)` | `Store<Content[]>` | The item is absent from the list. |
182
+
183
+ Each one accepts the raw field value, or a store of it. A single value that goes to
184
+ `useReferences()` or `getReferences()` gives a list of one.
185
+
186
+ A target is absent for three reasons. Somebody deleted the entry, the entry belongs to
187
+ another workspace, or the chain of references is deeper than the limit. See
188
+ [About content references](/explanation/content-references).
189
+
190
+ ## Read a pointer directly
191
+
192
+ `@picked/client` gives you the readers behind the hooks, for the code that runs outside
193
+ React and Svelte.
194
+
195
+ | Export | Returns | Description |
196
+ | --- | --- | --- |
197
+ | `isContentReference(value)` | `boolean` | Whether the value is a pointer. |
198
+ | `readReferenceId(value)` | `string \| null` | The target id of a single pointer. |
199
+ | `readReferenceIds(value)` | `string[]` | Every target id, for a repeatable field. |
200
+ | `deriveReference(contents, value)` | `Store<Content \| null>` | The store behind `useReference` and `getReference`. |
201
+ | `deriveReferences(contents, value)` | `Store<Content[]>` | The store behind `useReferences` and `getReferences`. |
202
+
203
+ ```ts
204
+ import { createClient, readReferenceIds } from '@picked/client'
205
+
206
+ const client = createClient({ projectId: 'proj_123' })
207
+ const content = client.contents.getById('entry_789').getState()
208
+ const categoryIds = readReferenceIds(content?.data.categories)
209
+ ```
210
+
211
+ ## Related
212
+
213
+ - [About content references](/explanation/content-references) — why a reference is a pointer.
214
+ - [How to read a referenced entry](/how-to/read-a-referenced-entry)
215
+ - [Experience content endpoint](/reference/delivery-api/experience-content) — the wire shape.
216
+ - [Server snapshot](/reference/sdk/ssr/serialize) — content on a server render.
@@ -9,16 +9,64 @@ This plugin reads the configuration of your project from the Picked API at runti
9
9
  therefore control your experiences, feature flags, steps, fields, and conditions from
10
10
  the [dashboard](/reference/dashboard/experiences) alone.
11
11
 
12
+ The plugin reads the configuration of your project and the CMS content of your
13
+ experiences from one source. It does not mix them. Picked gives a configuration and its
14
+ content the same version.
15
+
16
+ ## Options
17
+
18
+ | Option | Type | Default | Description |
19
+ | --- | --- | --- | --- |
20
+ | `mode` | `'bundle' \| 'api'` | see below | Where to read the configuration and the content from. |
21
+ | `apiUrl` | `string` | `https://api.picked.app` | Your own API URL, for a self-hosted or local API. |
22
+ | `projectConfig` | `ProjectConfig` | none | A configuration that you give. The plugin makes no request for it. |
23
+ | `getContent` | `function` | none | A function that you give. The plugin calls it for each experience that uses CMS content. |
24
+
25
+ ### mode
26
+
27
+ | Value | Source | Use it when |
28
+ | --- | --- | --- |
29
+ | `'bundle'` | The data that the [Vite plugin](/reference/tools/vite-plugin) puts in your build | You want no API request when the page loads. |
30
+ | `'api'` | The Picked API, read when the client starts | You want a workspace publish to reach users without a new deployment. |
31
+
32
+ :::code-group
33
+
34
+ ```ts [app.ts]
35
+ import { createClient } from '@picked/client'
36
+ import { dynamicExperiencesPlugin } from '@picked/client/plugins/dynamic-experiences'
37
+
38
+ const client = createClient({
39
+ projectId: 'your-project-id',
40
+ plugins: [dynamicExperiencesPlugin({ mode: 'bundle' })],
41
+ })
42
+
43
+ client.start()
44
+ ```
45
+
46
+ :::
47
+
48
+ If you do not set `mode`, the plugin uses the build data when the Vite plugin gave it
49
+ some. In every other case it uses the API.
50
+
51
+ ### projectConfig and getContent
52
+
53
+ These two replace the source for you. Keep the configuration and the content at the same
54
+ version when you use them. The plugin cannot make that check for you.
55
+
12
56
  ## Preview mode
13
57
 
14
- After you [enter preview mode from the dashboard](/reference/dashboard/workspaces#preview), the SDK
58
+ After you [enter preview mode from the dashboard](/how-to/preview-a-workspace), the SDK
15
59
  receives a preview token and starts preview mode:
16
60
 
17
- - The plugin reads fresh options from the API with that token. It bypasses the
18
- build-time configuration and the cached options.
61
+ - The plugin reads fresh options from the API with that token, for any value of `mode`.
62
+ It does not read the build-time configuration, a `projectConfig` that you give, or the
63
+ cached options.
19
64
  - A feature flag decision uses session storage. The decision resets when you close
20
65
  the tab.
21
66
 
67
+ A `getContent` function keeps its place in a preview session, and it receives the token.
68
+ You therefore decide what a preview means for your own content.
69
+
22
70
  ## Related
23
71
 
24
72
  - [About how Picked works](/explanation/how-picked-works) — how the plugin resolves your
@@ -26,8 +26,10 @@ const snapshot = await serialize(client, {
26
26
  `serialize()` does this. It registers the project configuration on the client at once. It
27
27
  evaluates the condition of each experience against `attributes.location`. It reads
28
28
  the content for each experience whose steps point at layout content. It removes the
29
- duplicate content blobs, which it finds by their IDs. It then returns a
30
- `PickedSnapshot`.
29
+ duplicate content blobs, which it finds by their IDs. It then returns a `PickedSnapshot`.
30
+
31
+ The content that it reads holds the entries of the steps. It also holds the entries that
32
+ a [reference field](/reference/dashboard/content/models#reference-fields) reaches.
31
33
 
32
34
  Give `serialize()` one client for each request. It does not support two calls at the
33
35
  same time on one client.
@@ -65,7 +67,7 @@ interface PickedSnapshot {
65
67
  | --- | --- |
66
68
  | `versionId` | The project version that the snapshot was chosen against. It is `null` when the version is not known, for example in a preview. |
67
69
  | `projectConfig` | The options that the server chose with. They travel to the client, so that the client renders against exactly the same options. |
68
- | `content` | The content blobs for the chosen experiences, with the duplicates removed. |
70
+ | `content` | The content blobs for the chosen experiences, with the duplicates removed. It holds the entries of the steps, and every entry that a reference field reaches. The server render and the first paint in the browser therefore resolve the same pointers. |
69
71
  | `conditionResults` | The condition results that the server kept, under a key made from the rule hash. The client then reuses the choice of the server. |
70
72
 
71
73
  The snapshot is plain JSON. Write it into your HTML response with `JSON.stringify`
@@ -75,3 +77,4 @@ and read it back on the client.
75
77
 
76
78
  - [How to server-render a page](/how-to/server-render-a-page)
77
79
  - [How to render an unpublished preview](/how-to/render-an-unpublished-preview)
80
+ - [Content](/reference/sdk/content) — how to read a content record in a component.