@case-framework/survey-assistant 0.7.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 (142) hide show
  1. package/README.md +58 -3
  2. package/dist/{attachments-B0hg3vpT.mjs → attachments-BuNni5vB.mjs} +1 -1
  3. package/dist/{attachments-B0hg3vpT.mjs.map → attachments-BuNni5vB.mjs.map} +1 -1
  4. package/dist/{authoring-references-BJQ_1nMX.mjs → authoring-references-CmwLc_C8.mjs} +49 -32
  5. package/dist/authoring-references-CmwLc_C8.mjs.map +1 -0
  6. package/dist/capabilities-D55dq-fO.mjs +582 -0
  7. package/dist/capabilities-D55dq-fO.mjs.map +1 -0
  8. package/dist/capabilities-default.d.mts +5 -12
  9. package/dist/capabilities-default.d.mts.map +1 -1
  10. package/dist/capabilities-default.mjs +2 -411
  11. package/dist/capabilities-z95e1mti.d.mts +117 -0
  12. package/dist/capabilities-z95e1mti.d.mts.map +1 -0
  13. package/dist/{constants-0UHGFcRJ.mjs → constants-Ct-vv9cu.mjs} +1 -1
  14. package/dist/{constants-0UHGFcRJ.mjs.map → constants-Ct-vv9cu.mjs.map} +1 -1
  15. package/dist/{constants-Be91Gay9.d.mts → constants-HL_klgJl.d.mts} +7 -28
  16. package/dist/constants-HL_klgJl.d.mts.map +1 -0
  17. package/dist/{controller-proxy-BH05e-C2.mjs → controller-proxy-BQ1YsVxJ.mjs} +12 -21
  18. package/dist/controller-proxy-BQ1YsVxJ.mjs.map +1 -0
  19. package/dist/controller-proxy-Cmzdr9tV.d.mts +65 -0
  20. package/dist/controller-proxy-Cmzdr9tV.d.mts.map +1 -0
  21. package/dist/default-D22AKTqE.mjs +233 -0
  22. package/dist/default-D22AKTqE.mjs.map +1 -0
  23. package/dist/{digest-SJmoYVaK.d.mts → digest-FnTTDBq2.d.mts} +2 -3
  24. package/dist/digest-FnTTDBq2.d.mts.map +1 -0
  25. package/dist/digest.d.mts +1 -1
  26. package/dist/digest.mjs.map +1 -1
  27. package/dist/engine-D950TfvC.mjs +3098 -0
  28. package/dist/engine-D950TfvC.mjs.map +1 -0
  29. package/dist/engine.d.mts +4 -3
  30. package/dist/engine.mjs +3 -3
  31. package/dist/{fair-guidance-CRYCRxQm.mjs → fair-guidance-BmO4PswW.mjs} +1 -1
  32. package/dist/{fair-guidance-CRYCRxQm.mjs.map → fair-guidance-BmO4PswW.mjs.map} +1 -1
  33. package/dist/{index-l_EK4yK7.d.mts → index-B8TLBd1G.d.mts} +18 -72
  34. package/dist/index-B8TLBd1G.d.mts.map +1 -0
  35. package/dist/index-CaX3hZmr.d.mts +23813 -0
  36. package/dist/index-CaX3hZmr.d.mts.map +1 -0
  37. package/dist/{index-x0qprHgK.d.mts → index-CeUlGkoU.d.mts} +58 -28
  38. package/dist/index-CeUlGkoU.d.mts.map +1 -0
  39. package/dist/{index-nhrgbgO5.d.mts → index-DYHdvSE8.d.mts} +347 -88
  40. package/dist/index-DYHdvSE8.d.mts.map +1 -0
  41. package/dist/lifecycle-DFzxVxpq.d.mts +54346 -0
  42. package/dist/lifecycle-DFzxVxpq.d.mts.map +1 -0
  43. package/dist/{memory-thread-repository-KWHQWFEW.mjs → memory-thread-repository-DmLadygY.mjs} +60 -30
  44. package/dist/memory-thread-repository-DmLadygY.mjs.map +1 -0
  45. package/dist/protocol-BQ7uIDYT.mjs +569 -0
  46. package/dist/protocol-BQ7uIDYT.mjs.map +1 -0
  47. package/dist/protocol.d.mts +4 -4
  48. package/dist/protocol.mjs +4 -3
  49. package/dist/{react-B3ybjJ-X.mjs → react-DybJ5pWw.mjs} +159 -46
  50. package/dist/react-DybJ5pWw.mjs.map +1 -0
  51. package/dist/react-integration.d.mts +2 -2
  52. package/dist/react-integration.mjs +2 -2
  53. package/dist/react.d.mts +3 -3
  54. package/dist/react.mjs +3 -3
  55. package/dist/references/assistant-operations.md +224 -180
  56. package/dist/references/core-rules.md +6 -3
  57. package/dist/references/element-types.md +189 -0
  58. package/dist/references/expressions.md +307 -168
  59. package/dist/references/fair-by-design.md +9 -9
  60. package/dist/references/follow-ups.md +151 -0
  61. package/dist/references/localization.md +26 -12
  62. package/dist/references/response-variables.md +38 -0
  63. package/dist/references/rich-text-content.md +22 -43
  64. package/dist/references/source-material-surveys.md +20 -24
  65. package/dist/references/survey-data-model.md +36 -102
  66. package/dist/{request-context-CG4SWijt.mjs → request-context-KVh8VjgH.mjs} +4 -10
  67. package/dist/request-context-KVh8VjgH.mjs.map +1 -0
  68. package/dist/{request-guardrails-TmgQtqp4.mjs → request-guardrails-C0ugBOp0.mjs} +6 -5
  69. package/dist/request-guardrails-C0ugBOp0.mjs.map +1 -0
  70. package/dist/server-agent.d.mts +62 -43
  71. package/dist/server-agent.d.mts.map +1 -1
  72. package/dist/server-agent.mjs +73 -40
  73. package/dist/server-agent.mjs.map +1 -1
  74. package/dist/server-runtime.d.mts +24 -42
  75. package/dist/server-runtime.d.mts.map +1 -1
  76. package/dist/server-runtime.mjs +12 -12
  77. package/dist/server-runtime.mjs.map +1 -1
  78. package/dist/server-tasks.d.mts +2 -2
  79. package/dist/server-tasks.mjs +2 -313
  80. package/dist/server-tools.d.mts +1 -1
  81. package/dist/server-tools.mjs +1 -1
  82. package/dist/server.d.mts +14 -36
  83. package/dist/server.d.mts.map +1 -1
  84. package/dist/server.mjs +5 -5
  85. package/dist/server.mjs.map +1 -1
  86. package/dist/storage-postgres.d.mts +9 -25
  87. package/dist/storage-postgres.d.mts.map +1 -1
  88. package/dist/storage-postgres.mjs +5 -4
  89. package/dist/storage-postgres.mjs.map +1 -1
  90. package/dist/tasks-Coh5N027.mjs +397 -0
  91. package/dist/tasks-Coh5N027.mjs.map +1 -0
  92. package/dist/{tasks-CYqrcaRO.d.mts → tasks-wb9M-bak.d.mts} +25 -41
  93. package/dist/tasks-wb9M-bak.d.mts.map +1 -0
  94. package/dist/thread-documents-DrHbaXqP.d.mts +119 -0
  95. package/dist/thread-documents-DrHbaXqP.d.mts.map +1 -0
  96. package/dist/{thread-handlers-DBAaU7BA.d.mts → thread-handlers-C3Iya92v.d.mts} +9 -40
  97. package/dist/thread-handlers-C3Iya92v.d.mts.map +1 -0
  98. package/dist/{tools-BvSovehT.mjs → tools-D0KBDolR.mjs} +677 -459
  99. package/dist/tools-D0KBDolR.mjs.map +1 -0
  100. package/dist/turn-survey-draft-B34lggSG.d.mts +185 -0
  101. package/dist/turn-survey-draft-B34lggSG.d.mts.map +1 -0
  102. package/dist/ui.css +1 -1
  103. package/dist/ui.d.mts +15 -54
  104. package/dist/ui.d.mts.map +1 -1
  105. package/dist/ui.mjs +203 -156
  106. package/dist/ui.mjs.map +1 -1
  107. package/docs/integration.md +11 -1
  108. package/package.json +41 -36
  109. package/dist/authoring-references-BJQ_1nMX.mjs.map +0 -1
  110. package/dist/capabilities-1KNnNO1_.mjs +0 -73
  111. package/dist/capabilities-1KNnNO1_.mjs.map +0 -1
  112. package/dist/capabilities-DVTfogjv.d.mts +0 -193
  113. package/dist/capabilities-DVTfogjv.d.mts.map +0 -1
  114. package/dist/capabilities-default.mjs.map +0 -1
  115. package/dist/constants-Be91Gay9.d.mts.map +0 -1
  116. package/dist/controller-proxy-BH05e-C2.mjs.map +0 -1
  117. package/dist/controller-proxy-Bd0ZHBPF.d.mts +0 -85
  118. package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +0 -1
  119. package/dist/digest-SJmoYVaK.d.mts.map +0 -1
  120. package/dist/engine-Bo85Oyj0.mjs +0 -6723
  121. package/dist/engine-Bo85Oyj0.mjs.map +0 -1
  122. package/dist/index-Tnqv-yKW.d.mts +0 -777
  123. package/dist/index-Tnqv-yKW.d.mts.map +0 -1
  124. package/dist/index-l_EK4yK7.d.mts.map +0 -1
  125. package/dist/index-nhrgbgO5.d.mts.map +0 -1
  126. package/dist/index-x0qprHgK.d.mts.map +0 -1
  127. package/dist/lifecycle-Bx0aq_4S.d.mts +0 -529
  128. package/dist/lifecycle-Bx0aq_4S.d.mts.map +0 -1
  129. package/dist/memory-thread-repository-KWHQWFEW.mjs.map +0 -1
  130. package/dist/protocol-BhzQbx81.mjs +0 -1173
  131. package/dist/protocol-BhzQbx81.mjs.map +0 -1
  132. package/dist/react-B3ybjJ-X.mjs.map +0 -1
  133. package/dist/references/embedded-forms.md +0 -126
  134. package/dist/references/item-types.md +0 -407
  135. package/dist/request-context-CG4SWijt.mjs.map +0 -1
  136. package/dist/request-guardrails-TmgQtqp4.mjs.map +0 -1
  137. package/dist/server-tasks.mjs.map +0 -1
  138. package/dist/tasks-CYqrcaRO.d.mts.map +0 -1
  139. package/dist/thread-documents-rmd6nyX9.d.mts +0 -107
  140. package/dist/thread-documents-rmd6nyX9.d.mts.map +0 -1
  141. package/dist/thread-handlers-DBAaU7BA.d.mts.map +0 -1
  142. package/dist/tools-BvSovehT.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
- { "type": "responseVariable", "variableRef": "<itemId>...get...<slotId>" }
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
 
@@ -28,34 +29,37 @@ Response values are typed objects:
28
29
  - string: `{ "type": "string", "value": "abc" }`
29
30
  - number: `{ "type": "number", "value": 42 }`
30
31
  - boolean: `{ "type": "boolean", "value": true }`
31
- - date: `{ "type": "date", "value": 1767225600000 }`
32
+ - date: `{ "type": "date", "value": "2026-01-01" }`
32
33
  - duration: `{ "type": "duration", "value": 3, "unit": "days" }`
33
34
  - reference: `{ "type": "reference", "value": "<optionId>" }`
34
35
  - arrays use `string[]`, `number[]`, `date[]`, `duration[]`, `reference[]`.
35
36
 
36
- Response variable refs use this exact string format:
37
- `<itemId>...<method>...<slotId>`
37
+ Response variable refs are structured objects: `{ "slotId": "<persistent-slot-id>", "method": "get" }`. There is no item ID, dotted path, variable name, or encoded string in a reference.
38
38
 
39
39
  Methods:
40
40
 
41
- - `get`: returns the current slot value.
42
- - `isDefined`: returns boolean true when a response exists.
41
+ - `get`: returns the current active slot value.
42
+ - `isDefined`: returns boolean true when an active response exists.
43
43
 
44
- `isDefined` means only "the respondent answered this slot." It never means a particular answer
44
+ `isDefined` means only "this slot currently has an active answer." It never means a particular answer
45
45
  such as Yes, selected, eligible, consented, or has children. When the user's condition names or
46
46
  implies a choice, inspect the source choice item's exact option ids and labels, then compare the
47
47
  `get` response with that option's reference using `eq` (single choice) or `list_contains`
48
48
  (multiple choice). Do not substitute `isDefined` merely because the exact option id is not yet
49
49
  known.
50
50
 
51
- Avoid accidental cycles in assistant-authored explicit item-level display-condition response
52
- dependencies, including direct self-reference and A depending on B while B depends on A. Shared
53
- survey integrity analysis reports these as advisory findings because responses can be populated
54
- before visibility is evaluated, including through prefills. Prefer an acyclic controlling source
55
- item unless the dependency is intentional. This analysis currently covers explicit item-level
56
- response edges; it does not model inherited group visibility or component-level dependency graphs.
57
- Component-level conditions may still use another response slot from the same item when that
58
- interaction is valid.
51
+ Visibility dependencies must be acyclic. The runtime rejects self-reference, mutual
52
+ question dependencies, parent groups controlled by their descendants, and cycles through
53
+ conditional components or computed scores. Prefills do not make these cycles valid.
54
+ Use an independent controlling answer or serialized custom context value. A component may
55
+ reference another slot in the same item only when its active value does not depend on that
56
+ 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
59
+ to expressions, validation, scoring and submission while hidden. `get` returns undefined and
60
+ `isDefined` returns false for an inactive answer. This includes hidden groups, matrix rows,
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
+ callbacks; supply serialized `customValue` context instead.
59
63
 
60
64
  Never invent response refs. Use the current context response-slots scope or item details.
61
65
  This rule is item-type independent: preserve every item id, slot id, component/validation key,
@@ -73,27 +77,76 @@ in its `create-item.item` object in one `survey_change` call. When a genuine dep
73
77
  separate calls, inspect the updated request-scoped draft and use `survey_expression` against the
74
78
  newly available ids and response slots.
75
79
 
76
- Each response slot defined by a source item's config is available as
77
- `<sourceItemId>...get...<slotId>`, with a boolean presence check at
78
- `<sourceItemId>...isDefined...<slotId>`. Use the slot's actual value type to choose the
80
+ Each declared response slot is available as `{ "slotId": "<slotId>", "method": "get" }`, with a presence check using the same slot ID and `method: "isDefined"`. Use the slot's actual value type to choose the
79
81
  function and constant. For example, a single-choice `reference` slot uses `eq` with a
80
82
  `reference` const; this is only one typed case, not a restriction on source item types.
81
83
  Always check the response slot value type before choosing a function:
82
84
 
85
+ - Consent items return `boolean` in their persistent `config.slotId`; compare with `eq` and boolean `true` for agreement or `false` for refusal. Unanswered is neither. `isDefined` means either decision was recorded. Display conditions hide dependent content while retaining session answers; hidden items are omitted from submission.
83
86
  - Single-choice choice items return `reference`; compare them with `eq` and a `reference` const.
84
87
  - Multiple-choice choice items return `reference[]`; check selected options with `list_contains` and a `reference` const.
85
88
  - `list_contains` must receive `string[]` + `string` or `reference[]` + `reference`. Never use it with a single `reference` response.
86
89
 
87
- 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.
88
94
 
89
95
  ## Functions
90
96
 
91
97
  Boolean: `and`, `or`, `not`.
92
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`.
93
105
  Equality/comparison: `eq`, `gt`, `gte`, `lt`, `lte`, `in_range`.
94
106
  Numeric aggregation: `sum`, `min`, `max`.
95
107
  String equality: `str_eq`.
96
- Date equality: `date_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))`.
114
+ Calendar dates use `YYYY`, `YYYY-MM`, or `YYYY-MM-DD`; numeric timestamps and date-time strings are invalid. Preserve the answer's precision.
115
+
116
+ Date comparisons: `date_eq`, `date_gt`, `date_gte`, `date_lt`, `date_lte` (two date arguments).
117
+ Equality includes precision; ordering requires matching precision and returns undefined otherwise.
118
+ `date_min` / `date_max` take one or more dates of matching precision and return a date.
119
+ `date_add_days`, `date_add_months`, `date_add_years` take a date and a signed integer number.
120
+ Offsets retain precision; month/year offsets clamp to the last day of the destination month.
121
+ Nonzero day offsets require full dates, nonzero month offsets require at least month precision.
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.
97
150
 
98
151
  Example: show an item when option "yes" is selected on choice item q1:
99
152
 
@@ -102,8 +155,20 @@ Example: show an item when option "yes" is selected on choice item q1:
102
155
  "type": "function",
103
156
  "functionName": "eq",
104
157
  "arguments": [
105
- { "type": "responseVariable", "variableRef": "q1...get...q1" },
106
- { "type": "const", "value": { "type": "reference", "value": "yes" } }
158
+ {
159
+ "type": "responseVariable",
160
+ "variableRef": {
161
+ "slotId": "q1",
162
+ "method": "get"
163
+ }
164
+ },
165
+ {
166
+ "type": "const",
167
+ "value": {
168
+ "type": "reference",
169
+ "value": "yes"
170
+ }
171
+ }
107
172
  ]
108
173
  }
109
174
  ```
@@ -115,209 +180,277 @@ Example: multiple-select contains option "apple" when the inspected response slo
115
180
  "type": "function",
116
181
  "functionName": "list_contains",
117
182
  "arguments": [
118
- { "type": "responseVariable", "variableRef": "q2...get...q2" },
119
- { "type": "const", "value": { "type": "reference", "value": "apple" } }
183
+ {
184
+ "type": "responseVariable",
185
+ "variableRef": {
186
+ "slotId": "q2",
187
+ "method": "get"
188
+ }
189
+ },
190
+ {
191
+ "type": "const",
192
+ "value": {
193
+ "type": "reference",
194
+ "value": "apple"
195
+ }
196
+ }
120
197
  ]
121
198
  }
122
199
  ```
123
200
 
124
201
  ## New Items With Dependencies
125
202
 
126
- For a newly created source and dependent, use this operation order and expression placement.
127
- This pattern is item-type independent: `displayConditions` is a sibling of `config` on the
128
- 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.
129
204
 
130
205
  ```json
131
206
  [
132
207
  {
133
208
  "kind": "create-item",
134
- "target": { "parentItemId": "<parent-group-id-or-key>" },
209
+ "target": {
210
+ "parentItemId": "<root-group-id>"
211
+ },
135
212
  "item": {
136
- "id": "routing_choice",
137
- "key": "routing_choice",
138
- "itemType": "choiceItem",
213
+ "id": "routing",
214
+ "itemType": "content",
139
215
  "config": {
140
- "id": "routing_choice",
141
- "maxSelection": 1,
142
- "shuffleOptions": false,
143
- "options": [
144
- { "id": "option_a", "key": "option_a" },
145
- { "id": "option_b", "key": "option_b" }
146
- ]
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
+ }
147
250
  }
148
251
  },
149
252
  "translations": [
150
- { "locale": "en", "contentKey": "title", "plainText": "Choose an option" },
151
- { "locale": "en", "contentKey": "options.option_a.label", "plainText": "Option A" },
152
- { "locale": "en", "contentKey": "options.option_b.label", "plainText": "Option B" }
253
+ {
254
+ "ownerId": "routing",
255
+ "role": "title",
256
+ "locale": "en",
257
+ "plainText": "Choose an option"
258
+ },
259
+ {
260
+ "ownerId": "routing-a",
261
+ "role": "label",
262
+ "locale": "en",
263
+ "plainText": "Option A"
264
+ },
265
+ {
266
+ "ownerId": "routing-b",
267
+ "role": "label",
268
+ "locale": "en",
269
+ "plainText": "Option B"
270
+ }
153
271
  ]
154
272
  },
155
273
  {
156
274
  "kind": "create-item",
157
- "target": { "parentItemId": "<parent-group-id-or-key>" },
275
+ "target": {
276
+ "parentItemId": "<root-group-id>"
277
+ },
158
278
  "item": {
159
- "id": "follow_up_a",
160
- "key": "follow_up_a",
161
- "itemType": "choiceItem",
279
+ "id": "routing-followup-a",
280
+ "itemType": "content",
162
281
  "config": {
163
- "id": "follow_up_a",
164
- "maxSelection": 1,
165
- "shuffleOptions": false,
166
- "options": [
167
- { "id": "yes", "key": "yes" },
168
- { "id": "no", "key": "no" }
169
- ]
170
- },
171
- "displayConditions": {
172
- "root": {
173
- "type": "function",
174
- "functionName": "eq",
175
- "arguments": [
176
- { "type": "responseVariable", "variableRef": "routing_choice...get...routing_choice" },
177
- { "type": "const", "value": { "type": "reference", "value": "option_a" } }
282
+ "body": {
283
+ "blocks": [
284
+ {
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
+ ]
303
+ }
178
304
  ]
179
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
+ ]
180
326
  }
181
327
  },
182
328
  "translations": [
183
- { "locale": "en", "contentKey": "title", "plainText": "Follow-up for option A" },
184
- { "locale": "en", "contentKey": "options.yes.label", "plainText": "Yes" },
185
- { "locale": "en", "contentKey": "options.no.label", "plainText": "No" }
329
+ {
330
+ "ownerId": "routing-followup-a",
331
+ "role": "title",
332
+ "locale": "en",
333
+ "plainText": "Details for option A"
334
+ }
186
335
  ]
187
336
  },
188
337
  {
189
338
  "kind": "create-item",
190
- "target": { "parentItemId": "<parent-group-id-or-key>" },
339
+ "target": {
340
+ "parentItemId": "<root-group-id>"
341
+ },
191
342
  "item": {
192
- "id": "follow_up_b",
193
- "key": "follow_up_b",
194
- "itemType": "choiceItem",
343
+ "id": "routing-followup-b",
344
+ "itemType": "content",
195
345
  "config": {
196
- "id": "follow_up_b",
197
- "maxSelection": 1,
198
- "shuffleOptions": false,
199
- "options": [
200
- { "id": "yes", "key": "yes" },
201
- { "id": "no", "key": "no" }
202
- ]
203
- },
204
- "displayConditions": {
205
- "root": {
206
- "type": "function",
207
- "functionName": "eq",
208
- "arguments": [
209
- { "type": "responseVariable", "variableRef": "routing_choice...get...routing_choice" },
210
- { "type": "const", "value": { "type": "reference", "value": "option_b" } }
346
+ "body": {
347
+ "blocks": [
348
+ {
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
+ ]
367
+ }
211
368
  ]
212
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
+ ]
213
390
  }
214
391
  },
215
392
  "translations": [
216
- { "locale": "en", "contentKey": "title", "plainText": "Follow-up for option B" },
217
- { "locale": "en", "contentKey": "options.yes.label", "plainText": "Yes" },
218
- { "locale": "en", "contentKey": "options.no.label", "plainText": "No" }
393
+ {
394
+ "ownerId": "routing-followup-b",
395
+ "role": "title",
396
+ "locale": "en",
397
+ "plainText": "Details for option B"
398
+ }
219
399
  ]
220
400
  }
221
401
  ]
222
402
  ```
223
403
 
224
- Use the same placement on other interactive item types; only their `itemType`, `config`, and
225
- translations change. The validator makes response slots from earlier creates available to later
226
- creates in this one operations array.
227
-
228
- ## 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.
229
405
 
230
- The shapes below are the canonical mutations. Send the complete array once as `mutations`; the
231
- server validates every entry against these canonical shapes before changing the request-scoped
232
- draft.
233
-
234
- Item visibility:
235
-
236
- ```json
237
- {
238
- "kind": "set-item-display-condition",
239
- "itemId": "followup_yes",
240
- "expression": {
241
- "type": "function",
242
- "functionName": "eq",
243
- "arguments": [
244
- { "type": "responseVariable", "variableRef": "q1...get...q1" },
245
- { "type": "const", "value": { "type": "reference", "value": "yes" } }
246
- ]
247
- }
248
- }
249
- ```
406
+ ## Disabled state and applicability
250
407
 
251
- Component visibility:
252
- `set-component-display-condition` with `itemId`, `componentId`, and a boolean `expression`.
253
- For choice options the component id is usually the option id. For form fields or field groups, inspect item details/raw JSON first.
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.
254
409
 
255
- Component disabled state:
256
- `set-component-disabled-condition` with `itemId`, `componentId`, and a boolean `expression`.
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.
257
411
 
258
- Validation expressions:
259
- `set-validation-expression` with `itemId`, `validationKey`, and a boolean `expression`.
260
- Add a matching validation message translation when respondents need visible feedback.
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.
261
413
 
262
- Prefills:
263
- `set-prefill` with `itemId` and a full prefill object. `when`, when present, must return boolean.
264
- Prefill source can be `static`, `expression`, `templateValue`, or `previousResponse`.
414
+ ## Creation and expression mutations
265
415
 
266
- Template values:
267
- `set-template-value` with `templateKey` and `templateValue`.
268
- The template expression should match `returnType`; `date2string` also needs `dateFormat`.
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:
269
417
 
270
- Clear operations:
271
- `clear-item-display-condition`, `clear-component-display-condition`,
272
- `clear-component-disabled-condition`, `clear-validation-expression`,
273
- `clear-prefill`, and `clear-template-value`.
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`.
274
428
 
275
- ## Storage Locations
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.
276
430
 
277
- Item display condition:
278
- `/surveyItems/<itemIndex>/displayConditions/root`
279
-
280
- Component display condition:
281
- `/surveyItems/<itemIndex>/displayConditions/components/<componentId>`
282
-
283
- Component disabled condition:
284
- `/surveyItems/<itemIndex>/disabledConditions/components/<componentId>`
285
-
286
- Item validation expression:
287
- `/surveyItems/<itemIndex>/validations/<validationKey>`
288
-
289
- Prefills:
290
- `/surveyItems/<itemIndex>/prefills`
291
-
292
- Template values:
293
- `/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.
294
432
 
295
433
  ## Prefills
296
434
 
297
- 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.
298
438
 
299
439
  ```json
300
440
  {
301
- "id": "prefill-1",
302
- "target": { "type": "itemResponse" },
303
- "when": { "type": "const", "value": { "type": "boolean", "value": true } },
304
- "apply": "ifEmpty",
305
- "source": { "type": "static", "value": { "type": "string", "value": "x" } }
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" } }
447
+ }
306
448
  }
307
449
  ```
308
450
 
309
- Targets:
310
-
311
- - `{ "type": "itemResponse" }`
312
- - `{ "type": "field", "fieldId": "<fieldId>" }`
313
- - `{ "type": "embeddedField", "optionId": "<optionId>", "fieldId": "<fieldId>" }`
314
-
315
- 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.
316
452
 
317
- - `static`: direct ResponseValue.
318
- - `expression`: JsonExpression.
319
- - `templateValue`: `{ "key": "<templateValueKey>" }`.
320
- - `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.
321
454
 
322
455
  ## Template Values
323
456
 
@@ -327,14 +460,20 @@ Template value shape:
327
460
  {
328
461
  "type": "default",
329
462
  "returnType": "string",
330
- "expression": { "type": "const", "value": { "type": "string", "value": "x" } }
463
+ "expression": {
464
+ "type": "const",
465
+ "value": {
466
+ "type": "string",
467
+ "value": "x"
468
+ }
469
+ }
331
470
  }
332
471
  ```
333
472
 
334
- Date formatting template values use `type: "date2string"`, `returnType: "string"`, and `dateFormat`.
473
+ Date formatting template values use `type: "formatDate"`, `returnType: "string"`, and `dateFormat`.
335
474
 
336
475
  ## Bounded context and atomic batches
337
476
 
338
- `survey_expression` action `context` filters response slots and expression locations with `itemIds` (ids, keys, or fullKeys). Follow `nextCursor` with the same filter and `limit` until absent; restart after a draft mutation. The returned model text contains the actual page and distinguishes filtered totals from the survey-wide response-slot count. For a controlling question outside the filter, omit itemIds and cursor to retrieve the survey-wide catalog. Unresolved identifiers are reported explicitly, not as evidence that an item has no response.
477
+ `survey_expression` action `context` filters response slots and expression locations with `itemIds` (exact persistent item IDs). Follow `nextCursor` with the same filter and `limit` until absent; restart after a draft mutation. The returned model text contains the actual page and distinguishes filtered totals from the survey-wide response-slot count. For a controlling question outside the filter, omit itemIds and cursor to retrieve the survey-wide catalog. Unresolved identifiers are reported explicitly, not as evidence that an item has no response.
339
478
 
340
479
  A `prepare` mutation array is executed in order within one atomic delta. Later mutations see earlier additions, replacements, and removals, including new containers and shifted prefill indexes. If any mutation fails, none of the delta is staged. Retry all requested mutations from that rejected delta; `pendingRepairs` tracks every remaining operation and surface. For missing or invalid targets, use the indicated `retryOfChangeIds` with the complete corrected input. If an expression location is already empty, inspect and acknowledge that rejected request with a linked `survey_change` using `operations: []`; other identifiable pending edits remain required. Clearing a condition is not a repair of a requested condition change.