@picked/client 0.0.1-dev.1777856271317 → 0.0.1-dev.1777867216429

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 (56) hide show
  1. package/dist/{api-DaHz97Mz.js → api-Xp22knXn.js} +1 -1
  2. package/dist/{api-dOWgI8zV.cjs → api-qBG8rsTJ.cjs} +1 -1
  3. package/dist/cjs/form/index.js +1 -1
  4. package/dist/cjs/index.js +2 -2
  5. package/dist/cjs/plugins/create-experience/index.js +1 -1
  6. package/dist/cjs/plugins/dynamic-experiences/index.js +1 -1
  7. package/dist/cjs/plugins/experience-browser-storage/index.js +1 -1
  8. package/dist/cjs/plugins/history-browser-storage/index.js +1 -1
  9. package/dist/cjs/plugins/responses-browser-storage/index.js +1 -1
  10. package/dist/cjs/server/index.js +1 -1
  11. package/dist/docs/api/authentication.md +376 -0
  12. package/dist/docs/api/authorization-and-scopes.md +262 -0
  13. package/dist/docs/api/error-handling.md +211 -0
  14. package/dist/docs/api/index.md +172 -11
  15. package/dist/docs/api/pagination.md +176 -0
  16. package/dist/docs/image-optimisation-service.md +72 -0
  17. package/dist/docs/sdk/plugins/dynamic-experiences.md +2 -2
  18. package/dist/docs/workspaces.mdx +60 -1
  19. package/dist/esm/form/index.js +40 -33
  20. package/dist/esm/index.js +295 -170
  21. package/dist/esm/plugins/create-experience/index.js +1 -1
  22. package/dist/esm/plugins/dynamic-experiences/index.js +1 -1
  23. package/dist/esm/plugins/experience-browser-storage/index.js +1 -1
  24. package/dist/esm/plugins/history-browser-storage/index.js +1 -1
  25. package/dist/esm/plugins/responses-browser-storage/index.js +1 -1
  26. package/dist/esm/server/index.js +1 -1
  27. package/dist/experience-instance-BWniatU5.cjs +1 -0
  28. package/dist/experience-instance-C4UJM092.js +506 -0
  29. package/dist/types/entities/experience-instance/experience-instance.d.ts.map +1 -1
  30. package/dist/types/entities/history/history.d.ts +9 -0
  31. package/dist/types/entities/history/history.d.ts.map +1 -1
  32. package/dist/types/form/create-form.d.ts +55 -0
  33. package/dist/types/form/create-form.d.ts.map +1 -1
  34. package/package.json +1 -11
  35. package/dist/cjs/plugins/dom-observer/index.js +0 -1
  36. package/dist/docs/reference/index.md +0 -11
  37. package/dist/docs/tooling/index.md +0 -19
  38. package/dist/esm/plugins/dom-observer/index.js +0 -107
  39. package/dist/experience-BhteyYBl.cjs +0 -1
  40. package/dist/experience-CoMdlvyn.js +0 -136
  41. package/dist/experience-instance-CZDcicPj.cjs +0 -1
  42. package/dist/experience-instance-IkIw31Cg.js +0 -387
  43. package/dist/nodes-ChVU1VZO.js +0 -101
  44. package/dist/nodes-DDDs-rC2.cjs +0 -1
  45. package/dist/types/plugins/dom-observer/index.d.ts +0 -3
  46. package/dist/types/plugins/dom-observer/index.d.ts.map +0 -1
  47. package/dist/types/plugins/dom-observer/plugin.d.ts +0 -51
  48. package/dist/types/plugins/dom-observer/plugin.d.ts.map +0 -1
  49. /package/dist/docs/{tooling/chrome-extension.md → chrome-extension.md} +0 -0
  50. /package/dist/docs/{tooling/gtm-template.md → gtm-template.md} +0 -0
  51. /package/dist/docs/{tooling/mcp.md → mcp.md} +0 -0
  52. /package/dist/docs/{tooling/vite-plugin.md → vite-plugin.md} +0 -0
  53. /package/dist/{id-Bbodazn8.cjs → id-Bv44ilEo.cjs} +0 -0
  54. /package/dist/{id-B-N2zNbS.js → id-CRqy981z.js} +0 -0
  55. /package/dist/{storage-hOHCCXvP.js → storage-6Xk0uAxB.js} +0 -0
  56. /package/dist/{storage-BXSlLTRa.cjs → storage-B6lQtc4-.cjs} +0 -0
@@ -0,0 +1,176 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Pagination
6
+
7
+ The Picked API uses Relay-style cursor-based pagination for all list fields that may return large result sets. This guide explains the connection pattern and demonstrates how to paginate through results.
8
+
9
+ ## Connection pattern
10
+
11
+ Paginated fields return a **connection** object with the following shape:
12
+
13
+ ```graphql
14
+ type ExampleConnection {
15
+ edges: [ExampleEdge!]!
16
+ pageInfo: PageInfo!
17
+ totalCount: Int!
18
+ }
19
+
20
+ type ExampleEdge {
21
+ cursor: String!
22
+ node: Example!
23
+ }
24
+
25
+ type PageInfo {
26
+ hasNextPage: Boolean!
27
+ hasPreviousPage: Boolean!
28
+ startCursor: String
29
+ endCursor: String
30
+ }
31
+ ```
32
+
33
+ | Field | Description |
34
+ |-------|-------------|
35
+ | `edges` | An ordered list of edges, each containing a `cursor` and a `node` (the actual data object) |
36
+ | `pageInfo.hasNextPage` | `true` if more results exist after the last edge |
37
+ | `pageInfo.hasPreviousPage` | `true` if results exist before the first edge |
38
+ | `pageInfo.startCursor` | Cursor of the first edge in the current page |
39
+ | `pageInfo.endCursor` | Cursor of the last edge in the current page |
40
+ | `totalCount` | Total number of items across all pages |
41
+
42
+ ## Forward pagination with `first` and `after`
43
+
44
+ To paginate forward through results, use the `first` argument to specify the page size and the `after` argument to specify the cursor to start after.
45
+
46
+ ### First page
47
+
48
+ Omit the `after` argument to fetch the first page:
49
+
50
+ ```graphql
51
+ query {
52
+ organizations(input: { first: 10 }) {
53
+ edges {
54
+ cursor
55
+ node {
56
+ id
57
+ name
58
+ }
59
+ }
60
+ pageInfo {
61
+ hasNextPage
62
+ endCursor
63
+ }
64
+ totalCount
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### Next page
70
+
71
+ Pass the `endCursor` from the previous response as the `after` argument:
72
+
73
+ ```graphql
74
+ query {
75
+ organizations(input: { first: 10, after: "cursor_from_previous_page" }) {
76
+ edges {
77
+ cursor
78
+ node {
79
+ id
80
+ name
81
+ }
82
+ }
83
+ pageInfo {
84
+ hasNextPage
85
+ endCursor
86
+ }
87
+ totalCount
88
+ }
89
+ }
90
+ ```
91
+
92
+ ## Checking for more pages
93
+
94
+ Before requesting the next page, check the `hasNextPage` field:
95
+
96
+ - `hasNextPage: true` — more results are available; pass `endCursor` to the next request's `after` argument
97
+ - `hasNextPage: false` — all results have been fetched; stop paginating
98
+
99
+ ## Full paginated query example
100
+
101
+ The following example fetches all members of an organization, page by page:
102
+
103
+ ```graphql
104
+ # Page 1
105
+ query {
106
+ organization(input: { slug: "my-company" }) {
107
+ members(first: 20) {
108
+ edges {
109
+ cursor
110
+ node {
111
+ id
112
+ userEmail
113
+ role
114
+ joinedAt
115
+ }
116
+ }
117
+ pageInfo {
118
+ hasNextPage
119
+ endCursor
120
+ }
121
+ totalCount
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ If `pageInfo.hasNextPage` is `true`, fetch the next page:
128
+
129
+ ```graphql
130
+ # Page 2
131
+ query {
132
+ organization(input: { slug: "my-company" }) {
133
+ members(first: 20, after: "cursor_from_page_1") {
134
+ edges {
135
+ cursor
136
+ node {
137
+ id
138
+ userEmail
139
+ role
140
+ joinedAt
141
+ }
142
+ }
143
+ pageInfo {
144
+ hasNextPage
145
+ endCursor
146
+ }
147
+ totalCount
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ Continue until `hasNextPage` returns `false`.
154
+
155
+ ## Paginated fields
156
+
157
+ The following fields in the schema return connection types and support `first`/`after` pagination:
158
+
159
+ | Parent Type | Field | Connection Type |
160
+ |------------|-------|----------------|
161
+ | `Query` | `organizations` | `OrganizationConnection` |
162
+ | `User` | `organizations` | `OrganizationConnection` |
163
+ | `User` | `invitations` | `ViewerInvitationConnection` |
164
+ | `Organization` | `members` | `OrganizationMemberConnection` |
165
+ | `Organization` | `invitations` | `OrganizationInvitationConnection` |
166
+ | `Organization` | `projects` | `ProjectConnection` |
167
+ | `Project` | `reports` | `ProjectReportConnection` |
168
+
169
+ All of these fields accept the same `first` and `after` arguments and return the standard `edges`, `pageInfo`, and `totalCount` structure.
170
+
171
+ ## Tips
172
+
173
+ - **Default page size** — if `first` is omitted, the API applies a default limit. Explicitly setting `first` ensures predictable page sizes.
174
+ - **Cursors are opaque** — cursor strings should be treated as opaque tokens. Do not parse, construct, or modify them. Pass them exactly as received from the API.
175
+ - **Stable ordering** — results within a connection are returned in a stable order. The same cursor always refers to the same position in the result set.
176
+ - **Combine with other fields** — pagination arguments can be used alongside other query fields. For example, fetching an organization and its first page of members in a single request is common and efficient.
@@ -0,0 +1,72 @@
1
+ ---
2
+ layout: docs
3
+ ---
4
+
5
+ # Image optimisation service
6
+
7
+ The Picked image optimisation service is a hosted endpoint at `https://img.picked.app`. It fetches images from allowed source domains, resizes them, re-encodes to modern formats (WebP or AVIF), and caches the result.
8
+
9
+ ## Endpoint
10
+
11
+ ```
12
+ GET https://img.picked.app/_next/image?url={source}&w={width}&q={quality}
13
+ ```
14
+
15
+ | Parameter | Description |
16
+ |-----------|-------------|
17
+ | `url` | URL-encoded URL of the source image. Must point to an allowed source domain (see below). |
18
+ | `w` | Target width in pixels. Must match one of the supported widths. |
19
+ | `q` | Target quality (`25`, `50`, `75`, or `100`). |
20
+
21
+ The service performs content negotiation: if the requesting browser advertises `image/avif` or `image/webp` in its `Accept` header, the response uses that format. Otherwise the response uses the source format.
22
+
23
+ ## Allowed source domains
24
+
25
+ The service only fetches from these hostnames:
26
+
27
+ - `**.picked.app`
28
+
29
+ A request whose `url` parameter resolves to any other hostname returns `400 Bad Request`.
30
+
31
+ ## Supported widths
32
+
33
+ ```
34
+ 640, 750, 828, 1080, 1200, 1920, 2048, 3840
35
+ ```
36
+
37
+ Requests with any other width return `400 Bad Request`. Pick the smallest width that covers your target rendering size at the user's device pixel ratio.
38
+
39
+ ## Supported qualities
40
+
41
+ ```
42
+ 25, 50, 75, 100
43
+ ```
44
+
45
+ Lower values reduce bandwidth significantly. `75` is a good default for most images; use `25` or `50` for thumbnails on slow networks.
46
+
47
+ ## Responsive images
48
+
49
+ Combine the endpoint with the standard `srcset` attribute to let browsers pick the right width per viewport:
50
+
51
+ ```html
52
+ <img
53
+ src="https://img.picked.app/_next/image?url=https%3A%2F%2Fcdn.picked.app%2Fhero.png&w=1080&q=75"
54
+ srcset="
55
+ https://img.picked.app/_next/image?url=https%3A%2F%2Fcdn.picked.app%2Fhero.png&w=640&q=75 640w,
56
+ https://img.picked.app/_next/image?url=https%3A%2F%2Fcdn.picked.app%2Fhero.png&w=1080&q=75 1080w,
57
+ https://img.picked.app/_next/image?url=https%3A%2F%2Fcdn.picked.app%2Fhero.png&w=1920&q=75 1920w
58
+ "
59
+ sizes="(max-width: 768px) 100vw, 720px"
60
+ alt="..."
61
+ />
62
+ ```
63
+
64
+ ## Caching
65
+
66
+ Optimised responses are cached by the upstream CDN. The minimum cache TTL is 60 seconds; the actual lifetime depends on the CDN's eviction policy. If you change the source image at the same URL, expect the optimised variant to remain cached for at least that interval.
67
+
68
+ ## Security
69
+
70
+ - SVG sources are not optimised. Requests for SVG return the original bytes if the source is allowed; the service never executes embedded scripts.
71
+ - Optimised responses carry a strict `Content-Security-Policy` (`default-src *; script-src 'none'; sandbox;`) so even a compromised source cannot run scripts in a viewing browser.
72
+ - The response uses `Content-Disposition: inline`, never `attachment`.
@@ -35,14 +35,14 @@ When the client starts, the plugin resolves your project configuration using a t
35
35
 
36
36
  1. **Preview mode** — When you [enter preview mode from the dashboard](/workspaces#preview), the plugin fetches fresh config from the API. Feature decisions use session storage so they are not persisted beyond the current tab.
37
37
 
38
- 2. **Build-time config** — If `projectConfig` was passed as a plugin option, or the [Vite Plugin](/tooling/vite-plugin) injected config at build time, the plugin uses that config with no network request.
38
+ 2. **Build-time config** — If `projectConfig` was passed as a plugin option, or the [Vite Plugin](/vite-plugin) injected config at build time, the plugin uses that config with no network request.
39
39
 
40
40
  3. **Runtime fetch** — Otherwise, the plugin fetches config from the API when the client starts.
41
41
 
42
42
  Once resolved, the plugin registers all features, conditions, fields, steps, and experiences with the client. Experiences then initialize according to their [conditions](/sdk/conditions) and [target selectors](/sdk/experiences).
43
43
 
44
44
  :::note
45
- The [Vite Plugin](/tooling/vite-plugin) eliminates the runtime API request in production by embedding your project config at build time. This is the recommended setup for Vite-based applications.
45
+ The [Vite Plugin](/vite-plugin) eliminates the runtime API request in production by embedding your project config at build time. This is the recommended setup for Vite-based applications.
46
46
  :::
47
47
 
48
48
  ## Feature Allocation
@@ -18,6 +18,12 @@ A workspace is a version of your project's configuration. Use workspaces to stag
18
18
 
19
19
  Each project has a workspace that holds your experiences, features, and entity configurations. When you make changes in the dashboard — editing a flow, updating a feature, or modifying an entity — those changes exist in the workspace until you publish.
20
20
 
21
+ A workspace is in one of three states:
22
+
23
+ - **Live** — the workspace the SDK serves to visitors. There is exactly one live workspace per project.
24
+ - **Draft** — a workspace not currently serving traffic. You can edit, preview, and publish a draft to make it live.
25
+ - **Archived** — a draft you have set aside. Archived workspaces stay editable but are filtered out of the default workspaces list.
26
+
21
27
  ## Preview
22
28
 
23
29
  Preview your workspace to see how changes look before publishing.
@@ -34,10 +40,63 @@ Your site opens in a new tab with the unpublished workspace applied. Preview use
34
40
  Publishing creates an immutable snapshot of your workspace. Once published, the SDK serves the new configuration to visitors.
35
41
 
36
42
  1. Click **Publish** in the workspace toolbar.
37
- 2. Confirm in the publish dialog.
43
+ 2. Review the change preview in the publish modal.
44
+ 3. Click **Publish** to confirm.
38
45
 
39
46
  The publish button is only enabled when there are unpublished changes. A status badge in the toolbar shows whether the workspace has unpublished changes or is up to date.
40
47
 
48
+ ## Change previews
49
+
50
+ The publish modal shows a side-by-side change preview comparing the current workspace against the last published snapshot. Use it to verify exactly what your visitors will see after publishing.
51
+
52
+ <Screenshot
53
+ route="/{orgSlug}/{projectId}/workspaces/{workspaceId}?action=publish"
54
+ alt="The publish modal showing a change preview with added experiences, steps, and conditions"
55
+ selector='[role="dialog"]'
56
+ wait='[role="dialog"]'
57
+ hide={['[data-testid="publish-diff-replaces-version"]']}
58
+ renderWidth={720}
59
+ renderHeight={579}
60
+ />
61
+
62
+ The preview groups changes by entity type — experiences, features, steps, fields, actions, and conditions. Each row shows whether the entity was **added**, **removed**, or **modified**. For modifications, expand the row to see which properties changed.
63
+
64
+ When the change preview includes breaking changes — for example, removing a field that other steps still reference — a banner highlights the breaking entries above the diff so you can reconsider before publishing.
65
+
66
+ The first publish of a workspace has no previous snapshot to compare against, so the publish modal shows the workspace's contents as the initial publish without a diff.
67
+
41
68
  ## Unpublished changes
42
69
 
43
70
  The workspace tracks whether any changes have been made since the last publish. If you create or update an experience, feature, or entity after the last publish, the toolbar shows a warning badge indicating unpublished changes.
71
+
72
+ ## Workspace settings
73
+
74
+ The workspace settings page (**Settings** in the workspace nav) covers renaming, archiving, and deleting a workspace.
75
+
76
+ ### Rename
77
+
78
+ Change the workspace name in the **Name** field and click **Save**. Names are unique within a project — duplicates are rejected with a validation error.
79
+
80
+ ### Archive
81
+
82
+ Archiving moves a draft workspace out of the default workspaces list without deleting it. Use it for drafts you want to set aside for later without losing the work.
83
+
84
+ 1. Open the workspace settings page.
85
+ 2. Click **Archive** in the page header.
86
+ 3. Confirm in the modal.
87
+
88
+ Live workspaces cannot be archived directly — make a different workspace live first, then archive the previous one.
89
+
90
+ ### Unarchive
91
+
92
+ Restore an archived workspace to draft status from the same page. The settings page detects the archived state and switches the toolbar button to **Unarchive**.
93
+
94
+ ### Delete
95
+
96
+ Deleting a workspace permanently removes it and its entities. This is destructive and cannot be undone — prefer archive when in doubt.
97
+
98
+ 1. Open the workspace settings page.
99
+ 2. In the **Danger zone** card at the bottom, click **Delete**.
100
+ 3. Confirm in the modal.
101
+
102
+ Live workspaces cannot be deleted. Archive or publish a different workspace first to free the workspace for deletion.
@@ -389,7 +389,32 @@ function y(e) {
389
389
  if (t instanceof HTMLInputElement) return t.type === "checkbox" ? t.checked : t.value;
390
390
  if (t instanceof HTMLSelectElement || t instanceof HTMLTextAreaElement) return t.value;
391
391
  }
392
- function b(e, t, r, i) {
392
+ function b(e, t, n) {
393
+ let r = e.currentTarget;
394
+ if (t.multiple) {
395
+ if (r instanceof HTMLInputElement && r.type === "checkbox") return x(e, r, n);
396
+ if (r instanceof HTMLSelectElement && r.multiple) return Array.from(r.selectedOptions, (e) => e.value);
397
+ }
398
+ return y(e);
399
+ }
400
+ function x(e, t, n) {
401
+ if (e.type === "input") return;
402
+ let r = Array.isArray(n) ? n.map(String) : [];
403
+ return e.type === "change" ? S(r, String(t.value)) : r;
404
+ }
405
+ function S(e, t) {
406
+ return e.includes(t) ? e.filter((e) => e !== t) : [...e, t];
407
+ }
408
+ function C(e, t, n) {
409
+ return (r) => {
410
+ let i = b(r, e, t);
411
+ i !== void 0 && n({
412
+ fieldKey: e.key,
413
+ value: i
414
+ });
415
+ };
416
+ }
417
+ function w(e, t, r, i) {
393
418
  let { client: s, experienceInstance: c } = e, { currentStepStore: l, goToNextStep: u } = c, d = n(l, c, (e, t) => ({
394
419
  stepId: e?.id,
395
420
  experienceId: t.id
@@ -404,49 +429,31 @@ function b(e, t, r, i) {
404
429
  },
405
430
  target: f
406
431
  });
407
- let { fieldInputEvent: h, fieldChangeEvent: _, fieldBlurEvent: b, touchedStore: x, fieldErrorsStore: S, formErrorsStore: C } = g(s, p, f), w = n({
432
+ let { fieldInputEvent: h, fieldChangeEvent: _, fieldBlurEvent: y, touchedStore: b, fieldErrorsStore: x, formErrorsStore: S } = g(s, p, f), w = n({
408
433
  fields: p,
409
434
  responses: s.responses.listStore,
410
435
  staged: f,
411
- fieldErrors: S,
412
- touched: x,
436
+ fieldErrors: x,
437
+ touched: b,
413
438
  experienceAndStepId: d
414
439
  }, ({ fields: e, responses: t, staged: n, fieldErrors: r, touched: i, experienceAndStepId: a }) => e.map((e) => {
415
- let o = (t) => {
416
- let n = y(t);
417
- n !== void 0 && h({
418
- fieldKey: e.key,
419
- value: n
420
- });
421
- }, s = (t) => {
422
- let n = y(t);
423
- n !== void 0 && _({
424
- fieldKey: e.key,
425
- value: n
426
- });
427
- }, c = (t) => {
428
- let n = y(t);
429
- n !== void 0 && b({
430
- fieldKey: e.key,
431
- value: n
432
- });
433
- }, l = n[e.key] ?? t.find((t) => t.fieldKey === e.key)?.value ?? e.defaultValue, u = r[e.key], d = e.type === "number" ? "number" : "text", f = e.type === "boolean" ? "checkbox" : d, { experienceId: p, stepId: m } = a, g = `${p}-${m ?? "step"}-${e.id}`;
440
+ let o = n[e.key] ?? t.find((t) => t.fieldKey === e.key)?.value ?? e.defaultValue, s = C(e, o, h), c = C(e, o, _), l = C(e, o, y), u = r[e.key], d = e.type === "number" ? "number" : "text", f = e.type === "boolean" ? "checkbox" : d, { experienceId: p, stepId: m } = a, g = `${p}-${m ?? "step"}-${e.id}`;
434
441
  return {
435
442
  ...e,
436
- value: l,
443
+ value: o,
437
444
  error: u,
438
445
  touched: i[e.key] ?? !1,
439
446
  props: {
440
447
  id: g,
441
448
  name: e.key,
442
449
  key: e.key,
443
- value: l,
450
+ value: o,
444
451
  type: f,
445
452
  required: e.required,
446
453
  placeholder: e.placeholder,
447
- oninput: o,
448
- onchange: s,
449
- onblur: c
454
+ oninput: s,
455
+ onchange: c,
456
+ onblur: l
450
457
  }
451
458
  };
452
459
  })), { submitEvent: T, isFormSubmitting: E, onBeforeSubmit: D, onSuccess: O, onError: k, submitStarted: A, validationErrorsReceived: j } = v({
@@ -461,20 +468,20 @@ function b(e, t, r, i) {
461
468
  return o({
462
469
  clock: A,
463
470
  fn: () => ({}),
464
- target: x
471
+ target: b
465
472
  }), o({
466
473
  clock: A,
467
474
  fn: () => ({}),
468
- target: S
475
+ target: x
469
476
  }), o({
470
477
  clock: j,
471
478
  source: p,
472
479
  fn: (e, t) => Object.fromEntries(t.map((t) => [e.find((e) => e.key === t.fieldKey)?.key ?? t.fieldKey, t])),
473
- target: S
480
+ target: x
474
481
  }), {
475
482
  form: {
476
483
  props: { onsubmit: T },
477
- errors: C,
484
+ errors: S,
478
485
  submitting: E,
479
486
  onBeforeSubmit: D,
480
487
  onSuccess: O,
@@ -484,4 +491,4 @@ function b(e, t, r, i) {
484
491
  };
485
492
  }
486
493
  //#endregion
487
- export { l as buildResponsesFromFormData, m as createFieldEvaluator, g as createFieldValidation, b as createForm, v as createSubmitPipeline, y as getEventValue, p as getStepFields };
494
+ export { l as buildResponsesFromFormData, m as createFieldEvaluator, g as createFieldValidation, w as createForm, v as createSubmitPipeline, y as getEventValue, p as getStepFields };