@sonata-innovations/fiber-types 2.1.0 → 2.3.0

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.
@@ -0,0 +1,256 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/sonata-innovations/fiber-docs/main/schema/flow-data-schema.json",
4
+ "title": "Fiber FlowData",
5
+ "description": "Schema for FlowData JSON — the output structure produced by FBRE (render engine) when a form is completed.",
6
+ "type": "object",
7
+ "required": ["uuid", "metadata", "screens"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "uuid": {
11
+ "type": "string",
12
+ "description": "UUID of the flow this submission belongs to."
13
+ },
14
+ "metadata": {
15
+ "type": "object",
16
+ "description": "Metadata from the original flow, passed through as-is.",
17
+ "additionalProperties": { "type": "string" }
18
+ },
19
+ "screens": {
20
+ "type": "array",
21
+ "description": "Ordered list of screens that were visible at completion time.",
22
+ "items": { "$ref": "#/$defs/ScreenData" }
23
+ },
24
+ "calculations": {
25
+ "type": "array",
26
+ "description": "Evaluated calculation results. Present only when the flow defines calculations.",
27
+ "items": { "$ref": "#/$defs/CalculationData" }
28
+ }
29
+ },
30
+
31
+ "$defs": {
32
+ "ScreenData": {
33
+ "type": "object",
34
+ "description": "Submitted data for a single screen.",
35
+ "required": ["uuid", "components"],
36
+ "additionalProperties": false,
37
+ "properties": {
38
+ "uuid": {
39
+ "type": "string",
40
+ "description": "UUID of the screen."
41
+ },
42
+ "label": {
43
+ "type": "string",
44
+ "description": "Screen label. Present only when the screen has a non-empty label."
45
+ },
46
+ "components": {
47
+ "type": "array",
48
+ "description": "Submitted data for each data-bearing component on this screen.",
49
+ "items": { "$ref": "#/$defs/ComponentData" }
50
+ }
51
+ }
52
+ },
53
+
54
+ "ComponentData": {
55
+ "type": "object",
56
+ "description": "Submitted data for a single component. The 'value' key is absent on container entries (group, repeater) and on a confirm that was ticked then unticked.",
57
+ "required": ["uuid", "type"],
58
+ "additionalProperties": false,
59
+ "properties": {
60
+ "uuid": {
61
+ "type": "string",
62
+ "description": "UUID of the component."
63
+ },
64
+ "label": {
65
+ "type": "string",
66
+ "description": "Component label. Present only when the component has a non-empty label."
67
+ },
68
+ "type": {
69
+ "type": "string",
70
+ "description": "Component type key. Display-only types (header, text, divider, callout, table) are never present in FlowData.",
71
+ "enum": [
72
+ "inputText",
73
+ "inputTextArea",
74
+ "inputNumber",
75
+ "dropDown",
76
+ "dropDownMulti",
77
+ "checkbox",
78
+ "radio",
79
+ "rating",
80
+ "slider",
81
+ "toggleSwitch",
82
+ "fileUpload",
83
+ "group",
84
+ "date",
85
+ "time",
86
+ "dateTime",
87
+ "dateRange",
88
+ "timeRange",
89
+ "dateTimeRange",
90
+ "yesNo",
91
+ "confirm",
92
+ "colorPicker",
93
+ "cardSelect",
94
+ "signature",
95
+ "computed",
96
+ "repeater"
97
+ ]
98
+ },
99
+ "value": {
100
+ "description": "The collected value. Type varies by component — see documentation for per-type details. Absent on containers (group, repeater) and on an unticked confirm.",
101
+ "oneOf": [
102
+ { "type": "string" },
103
+ { "type": "number" },
104
+ { "type": "boolean" },
105
+ { "type": "null" },
106
+ {
107
+ "type": "array",
108
+ "items": { "type": "string" }
109
+ },
110
+ {
111
+ "type": "array",
112
+ "items": { "type": "number" }
113
+ },
114
+ { "$ref": "#/$defs/FileUploadData" },
115
+ { "$ref": "#/$defs/RangeValue" }
116
+ ]
117
+ },
118
+ "components": {
119
+ "type": "array",
120
+ "description": "Container iterations. Present on group and repeater components. Outer array = iterations (a group has exactly one; a repeater has one per iteration), inner array = child components per iteration.",
121
+ "items": {
122
+ "type": "array",
123
+ "items": { "$ref": "#/$defs/ComponentData" }
124
+ }
125
+ }
126
+ }
127
+ },
128
+
129
+ "FileUploadData": {
130
+ "description": "Uploaded file data. Discriminated union on the 'mode' field.",
131
+ "oneOf": [
132
+ { "$ref": "#/$defs/FileUploadBase64Data" },
133
+ { "$ref": "#/$defs/FileUploadS3Data" }
134
+ ]
135
+ },
136
+
137
+ "FileUploadBase64Data": {
138
+ "type": "object",
139
+ "description": "File uploaded as inline Base64 data. This is the default mode when no server-side storage is configured. FBRE emits this shape without a 'mode' field; the optional 'lastModifiedDate' passes through the deprecated File API property only when the browser provides it.",
140
+ "required": [
141
+ "name",
142
+ "type",
143
+ "size",
144
+ "lastModified",
145
+ "data"
146
+ ],
147
+ "additionalProperties": false,
148
+ "properties": {
149
+ "mode": {
150
+ "type": "string",
151
+ "description": "Optional discriminant allowed by the TypeScript type. Never emitted by FBRE.",
152
+ "const": "base64"
153
+ },
154
+ "name": {
155
+ "type": "string",
156
+ "description": "Original file name."
157
+ },
158
+ "type": {
159
+ "type": "string",
160
+ "description": "MIME type of the file (e.g. 'application/pdf', 'image/png')."
161
+ },
162
+ "size": {
163
+ "type": "number",
164
+ "description": "File size in bytes."
165
+ },
166
+ "lastModified": {
167
+ "type": "number",
168
+ "description": "Last modified timestamp (Unix milliseconds)."
169
+ },
170
+ "lastModifiedDate": {
171
+ "type": "string",
172
+ "description": "Deprecated File API 'lastModifiedDate' value as an ISO-8601 string, present only when the browser provides the property."
173
+ },
174
+ "data": {
175
+ "type": "string",
176
+ "description": "Base64-encoded file content (data URL format)."
177
+ }
178
+ }
179
+ },
180
+
181
+ "FileUploadS3Data": {
182
+ "type": "object",
183
+ "description": "File uploaded to S3 or compatible object storage. Contains a reference to the stored file rather than inline data.",
184
+ "required": ["mode", "fileId", "name", "type", "size"],
185
+ "additionalProperties": false,
186
+ "properties": {
187
+ "mode": {
188
+ "type": "string",
189
+ "description": "Upload mode. Always 's3' for server-stored files.",
190
+ "const": "s3"
191
+ },
192
+ "fileId": {
193
+ "type": "string",
194
+ "description": "Server-assigned file identifier for retrieval."
195
+ },
196
+ "name": {
197
+ "type": "string",
198
+ "description": "Original file name."
199
+ },
200
+ "type": {
201
+ "type": "string",
202
+ "description": "MIME type of the file."
203
+ },
204
+ "size": {
205
+ "type": "number",
206
+ "description": "File size in bytes."
207
+ }
208
+ }
209
+ },
210
+
211
+ "CalculationData": {
212
+ "type": "object",
213
+ "description": "Evaluated result of a flow-level calculation.",
214
+ "required": ["uuid", "label", "value"],
215
+ "additionalProperties": false,
216
+ "properties": {
217
+ "uuid": {
218
+ "type": "string",
219
+ "description": "UUID of the calculation."
220
+ },
221
+ "label": {
222
+ "type": "string",
223
+ "description": "Human-readable label for the calculation."
224
+ },
225
+ "value": {
226
+ "description": "Numeric result of the calculation, or null if the formula could not be evaluated.",
227
+ "oneOf": [
228
+ { "type": "number" },
229
+ { "type": "null" }
230
+ ]
231
+ },
232
+ "formattedValue": {
233
+ "type": "string",
234
+ "description": "Pre-formatted display string (e.g. \"$1,234.56\", \"75.00%\"). Omitted when value is null."
235
+ }
236
+ }
237
+ },
238
+
239
+ "RangeValue": {
240
+ "type": "object",
241
+ "description": "A start/end pair used by range components (dateRange, timeRange, dateTimeRange).",
242
+ "required": ["start", "end"],
243
+ "additionalProperties": false,
244
+ "properties": {
245
+ "start": {
246
+ "type": "string",
247
+ "description": "Start value. Format depends on component type (date, time, or datetime)."
248
+ },
249
+ "end": {
250
+ "type": "string",
251
+ "description": "End value. Same format as start."
252
+ }
253
+ }
254
+ }
255
+ }
256
+ }
@@ -0,0 +1,371 @@
1
+ ---
2
+ title: FlowData Schema
3
+ applies-to:
4
+ - "@sonata-innovations/fiber-types@^2.2"
5
+ - "@sonata-innovations/fiber-fbre@^3.3"
6
+ read-when: "Consuming FlowData output from FBRE: structure, per-type value shapes, exclusion rules, containers, calculations."
7
+ ---
8
+
9
+ <!-- Generated from the Fiber repo's docs/ tree by project/scripts/sync-package-docs.mjs. Do not edit here. -->
10
+ # Fiber FlowData Schema
11
+
12
+ > Canonical source: [`flow-data-schema.json`](flow-data-schema.json)
13
+
14
+ FlowData is the output structure produced by FBRE (the render engine) when a form is completed. It contains the collected values for every visible, data-bearing component. The parent application receives FlowData via the `onFlowComplete` callback or the server-driven session completion endpoint.
15
+
16
+ > **In-memory vs serialized:** the object handed to `onFlowComplete` is a plain JavaScript object and may carry keys whose value is `undefined` (e.g. `value` on containers or an unticked `confirm`); once serialized with `JSON.stringify`, those keys are absent from the JSON entirely.
17
+
18
+ ---
19
+
20
+ ## FlowData (root)
21
+
22
+ | Field | Type | Required | Description |
23
+ |-------|------|----------|-------------|
24
+ | `uuid` | `string` | Yes | UUID of the flow this submission belongs to |
25
+ | `metadata` | `Record<string, string>` | Yes | Metadata from the original flow, passed through as-is |
26
+ | `screens` | [ScreenData](#screendata)[] | Yes | Ordered list of screens that were visible at completion time |
27
+ | `calculations` | [CalculationData](#calculationdata)[] | No | Evaluated calculation results. Present only when the flow defines calculations |
28
+
29
+ ---
30
+
31
+ ## ScreenData
32
+
33
+ Submitted data for a single screen.
34
+
35
+ | Field | Type | Required | Description |
36
+ |-------|------|----------|-------------|
37
+ | `uuid` | `string` | Yes | UUID of the screen |
38
+ | `label` | `string` | No | Screen label. Present only when the screen has a non-empty label |
39
+ | `components` | [ComponentData](#componentdata)[] | Yes | Submitted data for each data-bearing component on this screen |
40
+
41
+ ---
42
+
43
+ ## ComponentData
44
+
45
+ Submitted data for a single component.
46
+
47
+ | Field | Type | Required | Description |
48
+ |-------|------|----------|-------------|
49
+ | `uuid` | `string` | Yes | UUID of the component |
50
+ | `label` | `string` | No | Component label. Present only when the component has a non-empty label |
51
+ | `type` | `string` | Yes | Component type key (see [Value Types by Component](#value-types-by-component)) |
52
+ | `value` | *varies* | No | The collected value. Type depends on component type. Absent on containers (`group`, `repeater`) and on a ticked-then-unticked `confirm` (see [Assembly Rules](#assembly-rules)) |
53
+ | `components` | [ComponentData](#componentdata)[][] | No | Container iterations. Present on `group` and `repeater` components |
54
+
55
+ ---
56
+
57
+ ## Value Types by Component
58
+
59
+ The `value` field type depends on the component `type`. Display-only types (`header`, `text`, `divider`, `callout`, `table`) are never present in FlowData. Note that `computed` is **not** display-only — it appears in FlowData with its derived value.
60
+
61
+ | Component Type | Value Type | Example |
62
+ |----------------|-----------|---------|
63
+ | `inputText` | `string` | `"John Doe"` |
64
+ | `inputTextArea` | `string` | `"Long form text..."` |
65
+ | `inputNumber` | `number` | `42` |
66
+ | `dropDown` | `string` \| `number` | `"option-a"` |
67
+ | `dropDownMulti` | `string[]` \| `number[]` | `["opt-1", "opt-2"]` |
68
+ | `checkbox` | `string[]` \| `number[]` | `["check-a", "check-b"]` |
69
+ | `radio` | `string` \| `number` | `"radio-b"` |
70
+ | `rating` | `number` | `4` |
71
+ | `slider` | `number` | `75` |
72
+ | `toggleSwitch` | `boolean` | `true` |
73
+ | `fileUpload` | [FileUploadData](#fileuploaddata) | *(see below)* |
74
+ | `group` | *(no `value` key)* | children in `components` |
75
+ | `repeater` | *(no `value` key)* | iterations in `components` |
76
+ | `date` | `string` | `"2026-02-23"` |
77
+ | `time` | `string` | `"14:30"` |
78
+ | `dateTime` | `string` | `"2026-02-23T14:30"` |
79
+ | `dateRange` | [RangeValue](#rangevalue) | `{ "start": "2026-02-23", "end": "2026-02-28" }` |
80
+ | `timeRange` | [RangeValue](#rangevalue) | `{ "start": "09:00", "end": "17:00" }` |
81
+ | `dateTimeRange` | [RangeValue](#rangevalue) | `{ "start": "2026-02-23T09:00", "end": "2026-02-28T17:00" }` |
82
+ | `yesNo` | `string` | `"yes"` or `"no"` |
83
+ | `confirm` | `boolean` \| `null` \| *(absent)* | `true` when ticked; `null` when never touched; key absent when ticked then unticked |
84
+ | `cardSelect` | `string` \| `number` | `"premium-plan"` |
85
+ | `signature` | `string` | Typed name (`"Jane Smith"`) or drawn signature as a `data:image/png;base64,...` URL |
86
+ | `computed` | `number` \| `null` | `128.5` — derived value; `null` when the formula could not be evaluated |
87
+ | `colorPicker` | `string` | `"#FF5733"` |
88
+
89
+ > **Note:** `dropDown`, `radio`, `dropDownMulti`, `checkbox`, and `cardSelect` values match the `value` field of the selected `Option`(s) in the flow. When options use numeric values, the FlowData value will be `number` or `number[]` accordingly.
90
+
91
+ ---
92
+
93
+ ## Container Components (group and repeater)
94
+
95
+ Both container types — `group` and `repeater` — use the `components` field (a 2D array) to hold their children's data. Containers never have a `value` key: FBRE never assigns them a value, so after JSON serialization the key is absent (in the in-memory object it is `value: undefined`).
96
+
97
+ **Structure:** `components[iteration][child]`
98
+
99
+ - **Outer array** — one entry per iteration. A `group` has exactly one iteration; a `repeater` has one inner array per iteration the user added.
100
+ - **Inner array** — the child `ComponentData` entries for that iteration.
101
+
102
+ ```
103
+ container.components = [
104
+ [ /* iteration 0 */ child1, child2, child3 ],
105
+ [ /* iteration 1 */ child1, child2, child3 ],
106
+ ...
107
+ ]
108
+ ```
109
+
110
+ Display-only children (`header`, `text`, `divider`, `callout`, `table`) inside containers are excluded, just like at the screen level. Children hidden by conditions within an iteration are also excluded.
111
+
112
+ Nesting is expanded recursively: a `group` nested inside a repeater iteration carries its own `components` 2D array, following the same shape as a top-level container.
113
+
114
+ ---
115
+
116
+ ## FileUploadData
117
+
118
+ When no server-side storage is configured, files are returned as inline Base64; when S3 storage is configured, a reference to the stored file is returned instead. The two shapes are distinguished by the `mode` field: S3 references always carry `mode: "s3"`, while inline Base64 objects are emitted **without** a `mode` field.
119
+
120
+ ### FileUploadBase64Data
121
+
122
+ Inline Base64-encoded file. This is the wire format FBRE actually emits — the `mode` field is not emitted (the TypeScript type allows an optional `"base64"` discriminant, but FBRE never sets it).
123
+
124
+ | Field | Type | Required | Description |
125
+ |-------|------|----------|-------------|
126
+ | `mode` | `"base64"` | No | Never emitted by FBRE; allowed by the TypeScript type as an optional discriminant |
127
+ | `name` | `string` | Yes | Original file name |
128
+ | `type` | `string` | Yes | MIME type (e.g. `"application/pdf"`, `"image/png"`) |
129
+ | `size` | `number` | Yes | File size in bytes |
130
+ | `lastModified` | `number` | Yes | Last modified timestamp (Unix milliseconds) |
131
+ | `lastModifiedDate` | `string` | No | Legacy `File.lastModifiedDate` as an ISO-8601 string, emitted only when the browser provides the property (deprecated File API) |
132
+ | `data` | `string` | Yes | Base64-encoded file content (data URL format) |
133
+
134
+ ### FileUploadS3Data
135
+
136
+ Server-stored file reference.
137
+
138
+ | Field | Type | Required | Description |
139
+ |-------|------|----------|-------------|
140
+ | `mode` | `"s3"` | Yes | Upload mode. Always `"s3"` for server-stored files |
141
+ | `fileId` | `string` | Yes | Server-assigned file identifier for retrieval |
142
+ | `name` | `string` | Yes | Original file name |
143
+ | `type` | `string` | Yes | MIME type of the file |
144
+ | `size` | `number` | Yes | File size in bytes |
145
+
146
+ ---
147
+
148
+ ## CalculationData
149
+
150
+ Evaluated result of a flow-level calculation.
151
+
152
+ | Field | Type | Required | Description |
153
+ |-------|------|----------|-------------|
154
+ | `uuid` | `string` | Yes | UUID of the calculation |
155
+ | `label` | `string` | Yes | Human-readable label for the calculation |
156
+ | `value` | `number \| null` | Yes | Numeric result, or `null` if the formula could not be evaluated |
157
+ | `formattedValue` | `string` | No | Pre-formatted display string (e.g. `"$1,234.56"`, `"75.00%"`). Omitted when `value` is `null` |
158
+
159
+ ---
160
+
161
+ ## RangeValue
162
+
163
+ A start/end pair used by range components (`dateRange`, `timeRange`, `dateTimeRange`).
164
+
165
+ | Field | Type | Required | Description |
166
+ |-------|------|----------|-------------|
167
+ | `start` | `string` | Yes | Start value. Format matches the component type |
168
+ | `end` | `string` | Yes | End value. Same format as `start` |
169
+
170
+ ### Value Formats by Range Type
171
+
172
+ | Component Type | `start` / `end` Format | Example |
173
+ |----------------|----------------------|---------|
174
+ | `dateRange` | `YYYY-MM-DD` | `"2026-02-23"` |
175
+ | `timeRange` | `HH:MM` (24h) | `"09:00"` |
176
+ | `dateTimeRange` | `YYYY-MM-DDTHH:MM` | `"2026-02-23T09:00"` |
177
+
178
+ ---
179
+
180
+ ## Assembly Rules
181
+
182
+ FlowData is assembled from the runtime state when the form is completed. The following rules govern what is included and excluded:
183
+
184
+ - **Display components excluded** — `header`, `text`, `divider`, `callout`, and `table` components are never included. They have no user-collected data. (`computed` is not in this set — it is included with its derived value.)
185
+ - **Hidden screens excluded** — Screens that are hidden by conditions at completion time are omitted entirely.
186
+ - **Hidden components excluded** — Components hidden by conditions at completion time are omitted, both at the screen level and inside container iterations.
187
+ - **Labels included only when non-empty** — The `label` field on `ScreenData` and `ComponentData` is present only when the source had a non-empty label string.
188
+ - **`components` field only on containers** — The `components` 2D array is present only on `group` and `repeater` entries. All other component types omit it.
189
+ - **`value` present on non-containers, with two exceptions** — Every non-container `ComponentData` entry has a `value` field (unanswered fields are `null`), except: containers (`group`, `repeater`) never have a `value` key, and a `confirm` that was ticked and then unticked has its `value` set to `undefined` in memory — so the key is absent after JSON serialization. A never-touched `confirm` is `null`.
190
+ - **In-memory vs serialized** — The object passed to `onFlowComplete` may carry `value: undefined` keys (containers, unticked `confirm`); serialized JSON omits those keys.
191
+ - **Screen order preserved** — Screens appear in the same order as the flow definition (minus hidden ones).
192
+ - **Component order preserved** — Components appear in the same order as they were defined on each screen.
193
+ - **Calculations included when present** — If the flow defines calculations (`flow.calculations`), the `calculations` array is included with each calculation's UUID, label, evaluated value, and (when the value is non-null) a pre-formatted display string. Omitted entirely when the flow has no calculations.
194
+ - **Nested containers expand recursively** — A `group` nested inside a repeater iteration carries its own `components` 2D array (see [Container Components](#container-components-group-and-repeater)).
195
+
196
+ ---
197
+
198
+ ## Complete Example
199
+
200
+ A realistic multi-screen FlowData submission:
201
+
202
+ ```json
203
+ {
204
+ "uuid": "flow-abc-123",
205
+ "metadata": {
206
+ "name": "Employee Onboarding",
207
+ "description": "New hire information form"
208
+ },
209
+ "screens": [
210
+ {
211
+ "uuid": "screen-1",
212
+ "label": "Personal Info",
213
+ "components": [
214
+ {
215
+ "uuid": "comp-name",
216
+ "label": "Full Name",
217
+ "type": "inputText",
218
+ "value": "Jane Smith"
219
+ },
220
+ {
221
+ "uuid": "comp-dept",
222
+ "label": "Department",
223
+ "type": "dropDown",
224
+ "value": "engineering"
225
+ },
226
+ {
227
+ "uuid": "comp-skills",
228
+ "label": "Skills",
229
+ "type": "checkbox",
230
+ "value": ["javascript", "python", "go"]
231
+ },
232
+ {
233
+ "uuid": "comp-notifications",
234
+ "label": "Enable Notifications",
235
+ "type": "toggleSwitch",
236
+ "value": true
237
+ },
238
+ {
239
+ "uuid": "comp-start-date",
240
+ "label": "Start Date",
241
+ "type": "date",
242
+ "value": "2026-03-15"
243
+ },
244
+ {
245
+ "uuid": "comp-work-hours",
246
+ "label": "Work Hours",
247
+ "type": "timeRange",
248
+ "value": { "start": "09:00", "end": "17:00" }
249
+ },
250
+ {
251
+ "uuid": "comp-rating",
252
+ "label": "Self-Assessed Skill Level",
253
+ "type": "rating",
254
+ "value": 4
255
+ },
256
+ {
257
+ "uuid": "comp-experience",
258
+ "label": "Years of Experience",
259
+ "type": "slider",
260
+ "value": 8
261
+ },
262
+ {
263
+ "uuid": "comp-brand-color",
264
+ "label": "Preferred Theme Color",
265
+ "type": "colorPicker",
266
+ "value": "#1976d2"
267
+ },
268
+ {
269
+ "uuid": "comp-terms",
270
+ "label": "I accept the terms",
271
+ "type": "confirm",
272
+ "value": true
273
+ }
274
+ ]
275
+ },
276
+ {
277
+ "uuid": "screen-2",
278
+ "label": "Emergency Contacts",
279
+ "components": [
280
+ {
281
+ "uuid": "comp-has-contacts",
282
+ "label": "Do you have an emergency contact?",
283
+ "type": "yesNo",
284
+ "value": "yes"
285
+ },
286
+ {
287
+ "uuid": "comp-contacts",
288
+ "label": "Contacts",
289
+ "type": "repeater",
290
+ "components": [
291
+ [
292
+ {
293
+ "uuid": "comp-contact-name-0",
294
+ "label": "Contact Name",
295
+ "type": "inputText",
296
+ "value": "John Smith"
297
+ },
298
+ {
299
+ "uuid": "comp-contact-phone-0",
300
+ "label": "Phone",
301
+ "type": "inputText",
302
+ "value": "+1-555-0100"
303
+ }
304
+ ],
305
+ [
306
+ {
307
+ "uuid": "comp-contact-name-1",
308
+ "label": "Contact Name",
309
+ "type": "inputText",
310
+ "value": "Mary Smith"
311
+ },
312
+ {
313
+ "uuid": "comp-contact-phone-1",
314
+ "label": "Phone",
315
+ "type": "inputText",
316
+ "value": "+1-555-0101"
317
+ }
318
+ ]
319
+ ]
320
+ }
321
+ ]
322
+ },
323
+ {
324
+ "uuid": "screen-3",
325
+ "label": "Documents",
326
+ "components": [
327
+ {
328
+ "uuid": "comp-resume",
329
+ "label": "Resume",
330
+ "type": "fileUpload",
331
+ "value": {
332
+ "mode": "s3",
333
+ "fileId": "file-xyz-789",
334
+ "name": "jane-smith-resume.pdf",
335
+ "type": "application/pdf",
336
+ "size": 245760
337
+ }
338
+ },
339
+ {
340
+ "uuid": "comp-signature",
341
+ "label": "Signature",
342
+ "type": "signature",
343
+ "value": "data:image/png;base64,iVBORw0KGgo..."
344
+ },
345
+ {
346
+ "uuid": "comp-score",
347
+ "label": "Experience Score",
348
+ "type": "computed",
349
+ "value": 32
350
+ }
351
+ ]
352
+ }
353
+ ],
354
+ "calculations": [
355
+ {
356
+ "uuid": "calc-total-exp",
357
+ "label": "Total Experience Score",
358
+ "value": 32,
359
+ "formattedValue": "32.00"
360
+ },
361
+ {
362
+ "uuid": "calc-bonus",
363
+ "label": "Signing Bonus",
364
+ "value": 5000,
365
+ "formattedValue": "$5000.00"
366
+ }
367
+ ]
368
+ }
369
+ ```
370
+
371
+ Note the `repeater` entry above: it has no `value` key — containers carry their data in the `components` 2D array only. A non-repeatable `group` looks the same but always has exactly one inner array.