@case-framework/survey-assistant 0.8.0 → 0.9.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.
Files changed (114) hide show
  1. package/dist/{authoring-references-DbuOKerg.mjs → authoring-references-CmwLc_C8.mjs} +40 -32
  2. package/dist/authoring-references-CmwLc_C8.mjs.map +1 -0
  3. package/dist/capabilities-D55dq-fO.mjs +582 -0
  4. package/dist/capabilities-D55dq-fO.mjs.map +1 -0
  5. package/dist/capabilities-default.d.mts +4 -12
  6. package/dist/capabilities-default.d.mts.map +1 -1
  7. package/dist/capabilities-default.mjs +2 -2
  8. package/dist/capabilities-z95e1mti.d.mts +117 -0
  9. package/dist/capabilities-z95e1mti.d.mts.map +1 -0
  10. package/dist/{constants-B6HzpEsx.d.mts → constants-HL_klgJl.d.mts} +4 -4
  11. package/dist/{constants-B6HzpEsx.d.mts.map → constants-HL_klgJl.d.mts.map} +1 -1
  12. package/dist/{controller-proxy-DEeFl9IA.mjs → controller-proxy-BQ1YsVxJ.mjs} +4 -3
  13. package/dist/controller-proxy-BQ1YsVxJ.mjs.map +1 -0
  14. package/dist/{controller-proxy-D7uQfm5f.d.mts → controller-proxy-Cmzdr9tV.d.mts} +4 -4
  15. package/dist/{controller-proxy-D7uQfm5f.d.mts.map → controller-proxy-Cmzdr9tV.d.mts.map} +1 -1
  16. package/dist/default-D22AKTqE.mjs +233 -0
  17. package/dist/default-D22AKTqE.mjs.map +1 -0
  18. package/dist/{digest-C39WyAWG.d.mts → digest-FnTTDBq2.d.mts} +2 -2
  19. package/dist/digest-FnTTDBq2.d.mts.map +1 -0
  20. package/dist/digest.d.mts +1 -1
  21. package/dist/engine-D950TfvC.mjs +3098 -0
  22. package/dist/engine-D950TfvC.mjs.map +1 -0
  23. package/dist/engine.d.mts +4 -3
  24. package/dist/engine.mjs +3 -3
  25. package/dist/{index-WB5kfnz0.d.mts → index-B8TLBd1G.d.mts} +8 -6
  26. package/dist/index-B8TLBd1G.d.mts.map +1 -0
  27. package/dist/index-CaX3hZmr.d.mts +23813 -0
  28. package/dist/index-CaX3hZmr.d.mts.map +1 -0
  29. package/dist/{index-r5riv4md.d.mts → index-CeUlGkoU.d.mts} +27 -6
  30. package/dist/index-CeUlGkoU.d.mts.map +1 -0
  31. package/dist/{index-D7wT6P3t.d.mts → index-DYHdvSE8.d.mts} +83 -41
  32. package/dist/index-DYHdvSE8.d.mts.map +1 -0
  33. package/dist/lifecycle-DFzxVxpq.d.mts +54346 -0
  34. package/dist/lifecycle-DFzxVxpq.d.mts.map +1 -0
  35. package/dist/{memory-thread-repository-DAnOGIB3.mjs → memory-thread-repository-DmLadygY.mjs} +15 -9
  36. package/dist/{memory-thread-repository-DAnOGIB3.mjs.map → memory-thread-repository-DmLadygY.mjs.map} +1 -1
  37. package/dist/protocol-BQ7uIDYT.mjs +569 -0
  38. package/dist/protocol-BQ7uIDYT.mjs.map +1 -0
  39. package/dist/protocol.d.mts +4 -4
  40. package/dist/protocol.mjs +3 -2
  41. package/dist/{react-C_GY-Fsc.mjs → react-DybJ5pWw.mjs} +17 -11
  42. package/dist/react-DybJ5pWw.mjs.map +1 -0
  43. package/dist/react-integration.d.mts +1 -1
  44. package/dist/react-integration.mjs +1 -1
  45. package/dist/react.d.mts +2 -2
  46. package/dist/react.mjs +2 -2
  47. package/dist/references/assistant-operations.md +190 -215
  48. package/dist/references/core-rules.md +2 -2
  49. package/dist/references/element-types.md +189 -0
  50. package/dist/references/expressions.md +215 -238
  51. package/dist/references/follow-ups.md +151 -0
  52. package/dist/references/localization.md +26 -12
  53. package/dist/references/response-variables.md +3 -3
  54. package/dist/references/rich-text-content.md +22 -43
  55. package/dist/references/source-material-surveys.md +20 -25
  56. package/dist/references/survey-data-model.md +36 -117
  57. package/dist/{request-context-Dg4jSwN1.mjs → request-context-KVh8VjgH.mjs} +4 -4
  58. package/dist/request-context-KVh8VjgH.mjs.map +1 -0
  59. package/dist/server-agent.d.mts +28 -7
  60. package/dist/server-agent.d.mts.map +1 -1
  61. package/dist/server-agent.mjs +33 -25
  62. package/dist/server-agent.mjs.map +1 -1
  63. package/dist/server-runtime.d.mts +6 -5
  64. package/dist/server-runtime.d.mts.map +1 -1
  65. package/dist/server-runtime.mjs +3 -3
  66. package/dist/server-tasks.d.mts +1 -1
  67. package/dist/server-tasks.mjs +1 -1
  68. package/dist/server-tools.d.mts +1 -1
  69. package/dist/server-tools.mjs +1 -1
  70. package/dist/server.d.mts +7 -6
  71. package/dist/server.d.mts.map +1 -1
  72. package/dist/server.mjs +3 -3
  73. package/dist/{tasks-BZEiwgxA.mjs → tasks-Coh5N027.mjs} +16 -15
  74. package/dist/tasks-Coh5N027.mjs.map +1 -0
  75. package/dist/{tasks-B9h3LPam.d.mts → tasks-wb9M-bak.d.mts} +11 -11
  76. package/dist/{tasks-B9h3LPam.d.mts.map → tasks-wb9M-bak.d.mts.map} +1 -1
  77. package/dist/{thread-handlers-Bef0td9N.d.mts → thread-handlers-C3Iya92v.d.mts} +6 -5
  78. package/dist/thread-handlers-C3Iya92v.d.mts.map +1 -0
  79. package/dist/{tools-CEsv-WoO.mjs → tools-D0KBDolR.mjs} +435 -408
  80. package/dist/tools-D0KBDolR.mjs.map +1 -0
  81. package/dist/turn-survey-draft-B34lggSG.d.mts +185 -0
  82. package/dist/turn-survey-draft-B34lggSG.d.mts.map +1 -0
  83. package/dist/ui.d.mts +3 -3
  84. package/dist/ui.d.mts.map +1 -1
  85. package/dist/ui.mjs +35 -23
  86. package/dist/ui.mjs.map +1 -1
  87. package/package.json +7 -7
  88. package/dist/authoring-references-DbuOKerg.mjs.map +0 -1
  89. package/dist/capabilities-CmeeAjEc.d.mts +0 -192
  90. package/dist/capabilities-CmeeAjEc.d.mts.map +0 -1
  91. package/dist/capabilities-DCMA71PP.mjs +0 -58
  92. package/dist/capabilities-DCMA71PP.mjs.map +0 -1
  93. package/dist/controller-proxy-DEeFl9IA.mjs.map +0 -1
  94. package/dist/default-fhSon2RM.mjs +0 -760
  95. package/dist/default-fhSon2RM.mjs.map +0 -1
  96. package/dist/digest-C39WyAWG.d.mts.map +0 -1
  97. package/dist/engine-nXoCWYpQ.mjs +0 -6888
  98. package/dist/engine-nXoCWYpQ.mjs.map +0 -1
  99. package/dist/index-Ciq7vHIs.d.mts +0 -716
  100. package/dist/index-Ciq7vHIs.d.mts.map +0 -1
  101. package/dist/index-D7wT6P3t.d.mts.map +0 -1
  102. package/dist/index-WB5kfnz0.d.mts.map +0 -1
  103. package/dist/index-r5riv4md.d.mts.map +0 -1
  104. package/dist/lifecycle-Dm8u7QFh.d.mts +0 -545
  105. package/dist/lifecycle-Dm8u7QFh.d.mts.map +0 -1
  106. package/dist/protocol-CF4Bh_Ch.mjs +0 -1255
  107. package/dist/protocol-CF4Bh_Ch.mjs.map +0 -1
  108. package/dist/react-C_GY-Fsc.mjs.map +0 -1
  109. package/dist/references/embedded-forms.md +0 -146
  110. package/dist/references/item-types.md +0 -641
  111. package/dist/request-context-Dg4jSwN1.mjs.map +0 -1
  112. package/dist/tasks-BZEiwgxA.mjs.map +0 -1
  113. package/dist/thread-handlers-Bef0td9N.d.mts.map +0 -1
  114. package/dist/tools-CEsv-WoO.mjs.map +0 -1
@@ -10,16 +10,17 @@ Use `survey_expression` for expression work. It is the expression capability bun
10
10
 
11
11
  ## JsonExpression
12
12
 
13
- Expression JSON has four node types:
13
+ Expression JSON has five node types:
14
14
 
15
15
  ```json
16
16
  { "type": "const", "value": { "type": "boolean", "value": true } }
17
17
  { "type": "responseVariable", "variableRef": { "slotId": "<slotId>", "method": "get" } }
18
18
  { "type": "contextVariable", "contextType": "locale" }
19
19
  { "type": "function", "functionName": "and", "arguments": [] }
20
+ { "type": "ownerState", "state": "disabled", "targetRef": { "ownerId": "<ownerId>" } }
20
21
  ```
21
22
 
22
- Optional `editorConfig` can contain `usedTemplate`, `label`, and `description`.
23
+ Optional `editorConfig` contains only `usedTemplate`, `label`, and `description`; unknown keys are rejected. Missing argument-array operands are `null`, preserving their positions across JSON round trips. Required gaps are unfinished expressions, not false values. Optional single children such as a context key are omitted properties.
23
24
 
24
25
  ## Response Values And Variables
25
26
 
@@ -57,8 +58,7 @@ component's visibility. Repair rejected changes using the returned retry linkage
57
58
  Hidden answers remain stored for the session and return when shown again, but are unavailable
58
59
  to expressions, validation, scoring and submission while hidden. `get` returns undefined and
59
60
  `isDefined` returns false for an inactive answer. This includes hidden groups, matrix rows,
60
- form groups, hidden choice options and unselected embedded fields. Display conditions with
61
- undefined or non-boolean results hide their target. Visibility cannot use `customExpression`
61
+ semantic sections, hidden choice options and unselected follow-up inputs. With no visibility condition the target is visible. An authored visibility expression returning missing or false makes it inactive; a defined non-boolean result is inactive with a diagnostic. Visibility cannot use `customExpression`
62
62
  callbacks; supply serialized `customValue` context instead.
63
63
 
64
64
  Never invent response refs. Use the current context response-slots scope or item details.
@@ -87,15 +87,30 @@ Always check the response slot value type before choosing a function:
87
87
  - Multiple-choice choice items return `reference[]`; check selected options with `list_contains` and a `reference` const.
88
88
  - `list_contains` must receive `string[]` + `string` or `reference[]` + `reference`. Never use it with a single `reference` response.
89
89
 
90
- Context variable types are `locale`, `participantFlag`, `customValue`, and `customExpression`.
90
+ Context variable types are `locale`, `currentDate`, `participantFlag`, and `customValue`.
91
+ `{ "type": "contextVariable", "contextType": "currentDate" }` returns the participant's current
92
+ day as a full `date`. It takes no key; the host fixes it when the session starts and records it with
93
+ the response, so submission validation and export replay see the same day. Opaque callbacks (`customExpression`) are not part of the grammar; use recorded custom values so player, submission validation and export replay agree.
91
94
 
92
95
  ## Functions
93
96
 
94
97
  Boolean: `and`, `or`, `not`.
95
98
  List containment: `list_contains`.
99
+ List counting: `list_count(list)` returns the number of entries in any array value;
100
+ `list_count(list, filter)` counts only entries that also appear in `filter`, a list of the same
101
+ type. A missing list (for example an unanswered multiple choice) counts as `0`; use `isDefined`
102
+ to tell unanswered from none selected. For "at least two of these options selected":
103
+ `gte(list_count(<reference[] ref>, const(reference[], ["<optionId>", ...])), const(number, 2))`.
104
+ Filter option ids must be declared option identities, as with `list_contains`.
96
105
  Equality/comparison: `eq`, `gt`, `gte`, `lt`, `lte`, `in_range`.
97
106
  Numeric aggregation: `sum`, `min`, `max`.
98
107
  String equality: `str_eq`.
108
+ Conditional value: `if(condition, then, else)` returns `then` when the boolean condition is true
109
+ and `else` when it is false; only the chosen branch is evaluated. `else` is optional (missing when
110
+ omitted), and a missing condition returns missing rather than `else`. The result type is the
111
+ branches' type: both branches must have the same type, and the usage checks it like any other
112
+ result (e.g. a score uses number branches, a visibility condition boolean branches). Nest `if`
113
+ calls for more than two cases, e.g. `if(c1, a, if(c2, b, c))`.
99
114
  Calendar dates use `YYYY`, `YYYY-MM`, or `YYYY-MM-DD`; numeric timestamps and date-time strings are invalid. Preserve the answer's precision.
100
115
 
101
116
  Date comparisons: `date_eq`, `date_gt`, `date_gte`, `date_lt`, `date_lte` (two date arguments).
@@ -104,18 +119,34 @@ Equality includes precision; ordering requires matching precision and returns un
104
119
  `date_add_days`, `date_add_months`, `date_add_years` take a date and a signed integer number.
105
120
  Offsets retain precision; month/year offsets clamp to the last day of the destination month.
106
121
  Nonzero day offsets require full dates, nonzero month offsets require at least month precision.
107
- `date_diff(a, b)` returns signed `a - b`, in days for full dates, months for month values,
108
- or years for year values. Both arguments must have matching precision. Missing arguments' values
109
- propagate undefined; do not use timestamps or divide by seconds to calculate calendar differences.
110
-
111
- Form and form-matrix `minDate` / `maxDate` rules can use `valueExpression` instead of `value`.
112
- Use a date response reference or date arithmetic expression. For example, a latest date seven days
113
- after another answer uses `date_add_days(responseVariable(...), const(number, 7))`.
114
- Use the form-field or matrix configuration operation to set these field rules. The normal expression
115
- operations remain appropriate for item validations, prefills, and display conditions.
116
- Bounds are inclusive: minimum uses the start of its calendar period, maximum uses its end.
117
- The entire answer period must fit within the bound. An unanswered optional date remains valid;
118
- a supplied answer with an unresolved bound fails validation until the source is available.
122
+ `date_diff(a, b, unit)` returns the signed number of completed units in `a - b`. `unit` is a
123
+ required string const: `"years"`, `"months"`, or `"days"`. Mixed precisions are compared at the
124
+ coarser one (a year-only birth date gives calendar-year differences), and a unit finer than that
125
+ precision returns undefined. Missing values propagate undefined; do not use timestamps or divide
126
+ days to calculate ages. Age in years:
127
+ `date_diff(contextVariable(currentDate), responseVariable(<birth date>), const(string, "years"))`.
128
+ For an "at least N years old" check, use `gte(date_diff(...), const(number, N))`, which works
129
+ for every birth-date precision. The anniversary form
130
+ `date_lte(date_add_years(<birth date>, N), currentDate)` only works for full `YYYY-MM-DD` birth
131
+ dates: for a year or month birth date it compares mismatched precisions and returns undefined.
132
+ For full dates the two agree except on February 29 birthdays, which reach an anniversary on
133
+ February 28 with the anniversary form and on March 1 with `date_diff`.
134
+
135
+ Number and date inputs (and form-matrix number/date fields) take `min` / `max` bounds that are a
136
+ literal or `{expression, fallback?}`. Use a response reference or arithmetic expression of the
137
+ input's type. For example, a latest date seven days after another answer uses
138
+ `date_add_days(responseVariable(...), const(number, 7))`. When the expression has no value (for
139
+ example the referenced answer is empty), the bound does not apply, unless it has a `fallback`.
140
+ Slider bounds from an expression require a `fallback`.
141
+ Use set-config-expression with path `/min/expression` or `/max/expression` relative to the element
142
+ config; an existing literal bound becomes the fallback. clear-config-expression on the same path
143
+ restores the fallback, or removes a bound without one. The normal expression operations remain
144
+ appropriate for item validations, prefills, and display conditions.
145
+ Date bounds are inclusive: minimum uses the start of its calendar period, maximum uses its end.
146
+ The entire answer period must fit within the bound. An unanswered optional date remains valid.
147
+ A bound whose expression has no value does not apply, unless it has a `fallback`; then the
148
+ fallback applies. Fixed bounds and fallbacks must leave at least one valid answer at the input's
149
+ precision.
119
150
 
120
151
  Example: show an item when option "yes" is selected on choice item q1:
121
152
 
@@ -169,51 +200,72 @@ Example: multiple-select contains option "apple" when the inspected response slo
169
200
 
170
201
  ## New Items With Dependencies
171
202
 
172
- For a newly created source and dependent, use this operation order and expression placement.
173
- This pattern is item-type independent: `displayConditions` is a sibling of `config` on the
174
- raw item, never inside `config` or `translations`.
203
+ For a newly created source and dependent, use this operation order and expression placement. This pattern is element-type independent: `visibility` is on its owner, beside `config`, never inside translations. A flow item condition applies to its complete body; an element condition applies only to that element. Replace `<root-group-id>` with the inspected root group ID.
175
204
 
176
205
  ```json
177
206
  [
178
207
  {
179
208
  "kind": "create-item",
180
209
  "target": {
181
- "parentItemId": "<parent-group-id>"
210
+ "parentItemId": "<root-group-id>"
182
211
  },
183
212
  "item": {
184
- "id": "routing_choice",
185
- "itemType": "choiceItem",
213
+ "id": "routing",
214
+ "itemType": "content",
186
215
  "config": {
187
- "id": "routing_choice",
188
- "maxSelection": 1,
189
- "shuffleOptions": false,
190
- "options": [
191
- {
192
- "id": "option_a",
193
- "code": "option_a"
194
- },
195
- {
196
- "id": "option_b",
197
- "code": "option_b"
198
- }
199
- ],
200
- "variableName": "routing_choice"
216
+ "body": {
217
+ "blocks": [
218
+ {
219
+ "kind": "layout",
220
+ "id": "routing-layout",
221
+ "layout": {
222
+ "mode": "automatic",
223
+ "maxColumns": 1
224
+ },
225
+ "elements": [
226
+ {
227
+ "kind": "element",
228
+ "id": "routing-choice",
229
+ "elementType": "choice",
230
+ "config": {
231
+ "slotId": "routing-slot",
232
+ "variableName": "routing_choice",
233
+ "presentation": "radio",
234
+ "options": [
235
+ {
236
+ "id": "routing-a",
237
+ "code": "A"
238
+ },
239
+ {
240
+ "id": "routing-b",
241
+ "code": "B"
242
+ }
243
+ ]
244
+ }
245
+ }
246
+ ]
247
+ }
248
+ ]
249
+ }
201
250
  }
202
251
  },
203
252
  "translations": [
204
253
  {
254
+ "ownerId": "routing",
255
+ "role": "title",
205
256
  "locale": "en",
206
- "contentKey": "title",
207
257
  "plainText": "Choose an option"
208
258
  },
209
259
  {
260
+ "ownerId": "routing-a",
261
+ "role": "label",
210
262
  "locale": "en",
211
- "contentKey": "options.option_a.label",
212
263
  "plainText": "Option A"
213
264
  },
214
265
  {
266
+ "ownerId": "routing-b",
267
+ "role": "label",
215
268
  "locale": "en",
216
- "contentKey": "options.option_b.label",
217
269
  "plainText": "Option B"
218
270
  }
219
271
  ]
@@ -221,259 +273,184 @@ raw item, never inside `config` or `translations`.
221
273
  {
222
274
  "kind": "create-item",
223
275
  "target": {
224
- "parentItemId": "<parent-group-id>"
276
+ "parentItemId": "<root-group-id>"
225
277
  },
226
278
  "item": {
227
- "id": "follow_up_a",
228
- "itemType": "choiceItem",
279
+ "id": "routing-followup-a",
280
+ "itemType": "content",
229
281
  "config": {
230
- "id": "follow_up_a",
231
- "maxSelection": 1,
232
- "shuffleOptions": false,
233
- "options": [
234
- {
235
- "id": "yes",
236
- "code": "yes"
237
- },
238
- {
239
- "id": "no",
240
- "code": "no"
241
- }
242
- ],
243
- "variableName": "follow_up_a"
244
- },
245
- "displayConditions": {
246
- "root": {
247
- "type": "function",
248
- "functionName": "eq",
249
- "arguments": [
282
+ "body": {
283
+ "blocks": [
250
284
  {
251
- "type": "responseVariable",
252
- "variableRef": {
253
- "slotId": "routing_choice",
254
- "method": "get"
255
- }
256
- },
257
- {
258
- "type": "const",
259
- "value": {
260
- "type": "reference",
261
- "value": "option_a"
262
- }
285
+ "kind": "layout",
286
+ "id": "routing-followup-a-layout",
287
+ "layout": {
288
+ "mode": "automatic",
289
+ "maxColumns": 1
290
+ },
291
+ "elements": [
292
+ {
293
+ "kind": "element",
294
+ "id": "routing-followup-a-text",
295
+ "elementType": "text",
296
+ "config": {
297
+ "slotId": "routing-followup-a-slot",
298
+ "variableName": "followup_a",
299
+ "presentation": "long"
300
+ }
301
+ }
302
+ ]
263
303
  }
264
304
  ]
265
305
  }
306
+ },
307
+ "visibility": {
308
+ "type": "function",
309
+ "functionName": "eq",
310
+ "arguments": [
311
+ {
312
+ "type": "responseVariable",
313
+ "variableRef": {
314
+ "slotId": "routing-slot",
315
+ "method": "get"
316
+ }
317
+ },
318
+ {
319
+ "type": "const",
320
+ "value": {
321
+ "type": "reference",
322
+ "value": "routing-a"
323
+ }
324
+ }
325
+ ]
266
326
  }
267
327
  },
268
328
  "translations": [
269
329
  {
330
+ "ownerId": "routing-followup-a",
331
+ "role": "title",
270
332
  "locale": "en",
271
- "contentKey": "title",
272
- "plainText": "Follow-up for option A"
273
- },
274
- {
275
- "locale": "en",
276
- "contentKey": "options.yes.label",
277
- "plainText": "Yes"
278
- },
279
- {
280
- "locale": "en",
281
- "contentKey": "options.no.label",
282
- "plainText": "No"
333
+ "plainText": "Details for option A"
283
334
  }
284
335
  ]
285
336
  },
286
337
  {
287
338
  "kind": "create-item",
288
339
  "target": {
289
- "parentItemId": "<parent-group-id>"
340
+ "parentItemId": "<root-group-id>"
290
341
  },
291
342
  "item": {
292
- "id": "follow_up_b",
293
- "itemType": "choiceItem",
343
+ "id": "routing-followup-b",
344
+ "itemType": "content",
294
345
  "config": {
295
- "id": "follow_up_b",
296
- "maxSelection": 1,
297
- "shuffleOptions": false,
298
- "options": [
299
- {
300
- "id": "yes",
301
- "code": "yes"
302
- },
303
- {
304
- "id": "no",
305
- "code": "no"
306
- }
307
- ],
308
- "variableName": "follow_up_b"
309
- },
310
- "displayConditions": {
311
- "root": {
312
- "type": "function",
313
- "functionName": "eq",
314
- "arguments": [
315
- {
316
- "type": "responseVariable",
317
- "variableRef": {
318
- "slotId": "routing_choice",
319
- "method": "get"
320
- }
321
- },
346
+ "body": {
347
+ "blocks": [
322
348
  {
323
- "type": "const",
324
- "value": {
325
- "type": "reference",
326
- "value": "option_b"
327
- }
349
+ "kind": "layout",
350
+ "id": "routing-followup-b-layout",
351
+ "layout": {
352
+ "mode": "automatic",
353
+ "maxColumns": 1
354
+ },
355
+ "elements": [
356
+ {
357
+ "kind": "element",
358
+ "id": "routing-followup-b-text",
359
+ "elementType": "text",
360
+ "config": {
361
+ "slotId": "routing-followup-b-slot",
362
+ "variableName": "followup_b",
363
+ "presentation": "long"
364
+ }
365
+ }
366
+ ]
328
367
  }
329
368
  ]
330
369
  }
370
+ },
371
+ "visibility": {
372
+ "type": "function",
373
+ "functionName": "eq",
374
+ "arguments": [
375
+ {
376
+ "type": "responseVariable",
377
+ "variableRef": {
378
+ "slotId": "routing-slot",
379
+ "method": "get"
380
+ }
381
+ },
382
+ {
383
+ "type": "const",
384
+ "value": {
385
+ "type": "reference",
386
+ "value": "routing-b"
387
+ }
388
+ }
389
+ ]
331
390
  }
332
391
  },
333
392
  "translations": [
334
393
  {
394
+ "ownerId": "routing-followup-b",
395
+ "role": "title",
335
396
  "locale": "en",
336
- "contentKey": "title",
337
- "plainText": "Follow-up for option B"
338
- },
339
- {
340
- "locale": "en",
341
- "contentKey": "options.yes.label",
342
- "plainText": "Yes"
343
- },
344
- {
345
- "locale": "en",
346
- "contentKey": "options.no.label",
347
- "plainText": "No"
397
+ "plainText": "Details for option B"
348
398
  }
349
399
  ]
350
400
  }
351
401
  ]
352
402
  ```
353
403
 
354
- Use the same placement on other interactive item types; only their `itemType`, `config`, and
355
- translations change. The validator makes response slots from earlier creates available to later
356
- creates in this one operations array.
357
-
358
- ## survey_expression Mutations
404
+ The validator makes response slots from earlier creates available to later creates in this one operations array. This example creates separate conditional questions. For content directly below a radio/checkbox option, use an option-inline follow-up instead; it already has an activation gate.
359
405
 
360
- The shapes below are the canonical mutations. Send the complete array once as `mutations`; the
361
- server validates every entry against these canonical shapes before changing the request-scoped
362
- draft.
406
+ ## Disabled state and applicability
363
407
 
364
- Item visibility:
408
+ `ownerState` with `state: "disabled"` is the stored form of `isDisabled(owner)`. It reads the configured and inherited disabling of an inspected structural owner. It always returns boolean. It does not report visibility, answer presence or whether a choice cap permits a change. Answer and state dependencies share one cycle-checked graph. A parent reading its own child's disabled state is cyclic because the child inherits from that parent.
365
409
 
366
- ```json
367
- {
368
- "kind": "set-item-display-condition",
369
- "itemId": "followup_yes",
370
- "expression": {
371
- "type": "function",
372
- "functionName": "eq",
373
- "arguments": [
374
- {
375
- "type": "responseVariable",
376
- "variableRef": {
377
- "slotId": "q1",
378
- "method": "get"
379
- }
380
- },
381
- {
382
- "type": "const",
383
- "value": {
384
- "type": "reference",
385
- "value": "yes"
386
- }
387
- }
388
- ]
389
- }
390
- }
391
- ```
410
+ Active disabled answers are retained, readable in expressions and submitted. Participant writes are rejected while the whole input or an ancestor is disabled; initialization has its separate engine-owned path. Validation cannot block on targets the participant cannot change. Disabled checkbox options are locked; single-choice replacement can leave a disabled selection for an enabled destination. Hidden answers remain unavailable instead. Use visibility for “not applicable” and disabled for locking, not as a way of removing answers from data.
392
411
 
393
- Component visibility:
394
- `set-component-display-condition` with `itemId`, `componentId`, and a boolean `expression`.
395
- For choice options the component id is usually the option id. For form fields or field groups, inspect item details/raw JSON first.
412
+ “Selected and enabled” is an authoring shortcut expanded into `and(selectionTest, not(ownerState(...)))`. Use eq for single choice, list_contains for checkbox selection. It is not a new persisted function. Disabled ancestors also make this false, including intentionally read-only prefilled items. Do not use the shortcut where the condition should still honor such answers. Mutual selected-and-enabled disabling may create a cycle; ordinary selection-based exclusivity avoids adding that state loop.
396
413
 
397
- Component disabled state:
398
- `set-component-disabled-condition` with `itemId`, `componentId`, and a boolean `expression`.
414
+ ## Creation and expression mutations
399
415
 
400
- Validation expressions:
401
- `set-validation-expression` with `itemId`, `validationKey`, and a boolean `expression`.
402
- Add a matching validation message translation when respondents need visible feedback.
416
+ Create source owners before their dependents. New owners may carry `visibility`, `disabled`, prefills and validation expressions in their creation payloads. For existing owners, use `survey_expression` with these mutations:
403
417
 
404
- Prefills:
405
- `set-prefill` with `itemId` and a full prefill object. `when`, when present, must return boolean.
406
- Prefill source can be `static`, `expression`, `templateValue`, or `previousResponse`.
418
+ - `set-condition`: `ownerId`, `property` (`visibility` or `disabled`), boolean `expression`.
419
+ - `clear-condition`: the same owner and property; absent visibility means visible and absent disabling means enabled, subject to ancestors.
420
+ - `set-config-expression`: `elementId`, inspected config-relative JSON Pointer `path`, and `expression`. The exact field must be declared as an expression by the installed element schema, including custom definitions.
421
+ - `clear-config-expression`: the same element and path; schema-required fields cannot be cleared.
422
+ - `set-validation-rule`: `itemId` and complete `rule` (`id`, nonempty `targets` slot IDs, boolean `expression`, optional boolean `when`, optional `phase: page|submit`). Its localized message belongs to that rule ID's `message` role.
423
+ - `remove-validation-rule`: `itemId`, `ruleId`.
424
+ - `set-prefill`: `elementId`, `slotId`, complete `prefill`.
425
+ - `clear-prefill`: `elementId`, `slotId`, `prefillId`.
426
+ - `set-template-value`: `templateKey`, complete `templateValue`.
427
+ - `clear-template-value`: `templateKey`.
407
428
 
408
- Template values:
409
- `set-template-value` with `templateKey` and `templateValue`.
410
- The template expression should match `returnType`; `date2string` also needs `dateFormat`.
429
+ Inspect exact owner IDs and expression locations. Conditions live directly on their annotated owner; prefills live at `element.prefills[slotId]`; validation rules live in `contentItem.config.validationRules`. Never reconstruct a location from a display label or old component path. Rules target slots owned by their item and cannot block when no target is active and changeable.
411
430
 
412
- Clear operations:
413
- `clear-item-display-condition`, `clear-component-display-condition`,
414
- `clear-component-disabled-condition`, `clear-validation-expression`,
415
- `clear-prefill`, and `clear-template-value`.
416
-
417
- ## Storage Locations
418
-
419
- Item display condition:
420
- `/surveyItems/<itemIndex>/displayConditions/root`
421
-
422
- Component display condition:
423
- `/surveyItems/<itemIndex>/displayConditions/components/<componentId>`
424
-
425
- Component disabled condition:
426
- `/surveyItems/<itemIndex>/disabledConditions/components/<componentId>`
427
-
428
- Item validation expression:
429
- `/surveyItems/<itemIndex>/validations/<validationKey>`
430
-
431
- Prefills:
432
- `/surveyItems/<itemIndex>/prefills`
433
-
434
- Template values:
435
- `/templateValues/<templateValueKey>`
431
+ Page rules that read another item's answers can prevent reaching those answers on a later page. Inspect the shared `cross-item-page-rule` advisory; use submit-phase validation or an explicit unanswered-dependency condition when appropriate. Do not silently change an existing rule's phase.
436
432
 
437
433
  ## Prefills
438
434
 
439
- Prefill shape:
435
+ Prefills initialize **once** when creating a new session, including hidden inputs, disabled inputs and closed follow-ups. They do not reapply while answering, after clearing a field, or on resume. A resumed session restores its versioned snapshot without new prefill lookups. Hiding/disabling retains initialized and participant-entered answers. Never use repeated prefilling to enforce exclusivity.
436
+
437
+ A prefill has `id`, optional boolean `when`, optional `apply: "ifEmpty"`, and `source`. Its target is the surrounding element's slot key, not an itemResponse/field/embeddedField descriptor.
440
438
 
441
439
  ```json
442
440
  {
443
- "id": "prefill-1",
444
- "target": {
445
- "type": "itemResponse"
446
- },
447
- "when": {
448
- "type": "const",
449
- "value": {
450
- "type": "boolean",
451
- "value": true
452
- }
453
- },
454
- "apply": "ifEmpty",
455
- "source": {
456
- "type": "static",
457
- "value": {
458
- "type": "string",
459
- "value": "x"
460
- }
441
+ "kind": "set-prefill",
442
+ "elementId": "name-input",
443
+ "slotId": "name-slot",
444
+ "prefill": {
445
+ "id": "name-initial",
446
+ "source": { "type": "static", "value": { "type": "string", "value": "Example" } }
461
447
  }
462
448
  }
463
449
  ```
464
450
 
465
- Targets:
466
-
467
- - `{ "type": "itemResponse" }`
468
- - `{ "type": "field", "fieldId": "<fieldId>" }`
469
- - `{ "type": "embeddedField", "optionId": "<optionId>", "fieldId": "<fieldId>" }`
470
-
471
- Sources:
451
+ Sources are static typed value, expression, templateValue key, or previousResponse with `ref: {surveyKey?, slotId}` and optional `ifUnsupported: "skip"`. Inspect host capabilities before depending on previous responses. Values must match the target slot's domain; scores/computed slots are not prefill targets. Prefill dependencies also participate in cycle checking.
472
452
 
473
- - `static`: direct ResponseValue.
474
- - `expression`: JsonExpression.
475
- - `templateValue`: `{ "key": "<templateValueKey>" }`.
476
- - `previousResponse`: `{ "ref": { "surveyKey"?, "itemId", "target"? }, "ifUnsupported": "skip" }`.
453
+ Do not assume a host provides previous responses. Survey Mode currently does not; it rejects publication with a required unsupported source. Use `ifUnsupported: "skip"` only when omitting that initial value is intentional. Invalid derived prefill values are skipped with a located diagnostic and later candidates may apply; do not treat that as permission to author incompatible values.
477
454
 
478
455
  ## Template Values
479
456
 
@@ -493,7 +470,7 @@ Template value shape:
493
470
  }
494
471
  ```
495
472
 
496
- Date formatting template values use `type: "date2string"`, `returnType: "string"`, and `dateFormat`.
473
+ Date formatting template values use `type: "formatDate"`, `returnType: "string"`, and `dateFormat`.
497
474
 
498
475
  ## Bounded context and atomic batches
499
476