@case-framework/survey-assistant 0.7.0 → 0.8.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 (130) 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-DbuOKerg.mjs} +12 -3
  5. package/dist/authoring-references-DbuOKerg.mjs.map +1 -0
  6. package/dist/{capabilities-DVTfogjv.d.mts → capabilities-CmeeAjEc.d.mts} +3 -4
  7. package/dist/capabilities-CmeeAjEc.d.mts.map +1 -0
  8. package/dist/{capabilities-1KNnNO1_.mjs → capabilities-DCMA71PP.mjs} +4 -19
  9. package/dist/capabilities-DCMA71PP.mjs.map +1 -0
  10. package/dist/capabilities-default.d.mts +13 -12
  11. package/dist/capabilities-default.d.mts.map +1 -1
  12. package/dist/capabilities-default.mjs +2 -411
  13. package/dist/{constants-Be91Gay9.d.mts → constants-B6HzpEsx.d.mts} +7 -28
  14. package/dist/constants-B6HzpEsx.d.mts.map +1 -0
  15. package/dist/{constants-0UHGFcRJ.mjs → constants-Ct-vv9cu.mjs} +1 -1
  16. package/dist/{constants-0UHGFcRJ.mjs.map → constants-Ct-vv9cu.mjs.map} +1 -1
  17. package/dist/controller-proxy-D7uQfm5f.d.mts +65 -0
  18. package/dist/controller-proxy-D7uQfm5f.d.mts.map +1 -0
  19. package/dist/{controller-proxy-BH05e-C2.mjs → controller-proxy-DEeFl9IA.mjs} +10 -20
  20. package/dist/controller-proxy-DEeFl9IA.mjs.map +1 -0
  21. package/dist/default-fhSon2RM.mjs +760 -0
  22. package/dist/default-fhSon2RM.mjs.map +1 -0
  23. package/dist/{digest-SJmoYVaK.d.mts → digest-C39WyAWG.d.mts} +2 -3
  24. package/dist/digest-C39WyAWG.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-Bo85Oyj0.mjs → engine-nXoCWYpQ.mjs} +755 -590
  28. package/dist/engine-nXoCWYpQ.mjs.map +1 -0
  29. package/dist/engine.d.mts +3 -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-Tnqv-yKW.d.mts → index-Ciq7vHIs.d.mts} +80 -141
  34. package/dist/index-Ciq7vHIs.d.mts.map +1 -0
  35. package/dist/{index-nhrgbgO5.d.mts → index-D7wT6P3t.d.mts} +277 -60
  36. package/dist/index-D7wT6P3t.d.mts.map +1 -0
  37. package/dist/{index-l_EK4yK7.d.mts → index-WB5kfnz0.d.mts} +15 -71
  38. package/dist/index-WB5kfnz0.d.mts.map +1 -0
  39. package/dist/{index-x0qprHgK.d.mts → index-r5riv4md.d.mts} +33 -24
  40. package/dist/index-r5riv4md.d.mts.map +1 -0
  41. package/dist/{lifecycle-Bx0aq_4S.d.mts → lifecycle-Dm8u7QFh.d.mts} +34 -18
  42. package/dist/lifecycle-Dm8u7QFh.d.mts.map +1 -0
  43. package/dist/{memory-thread-repository-KWHQWFEW.mjs → memory-thread-repository-DAnOGIB3.mjs} +51 -27
  44. package/dist/memory-thread-repository-DAnOGIB3.mjs.map +1 -0
  45. package/dist/{protocol-BhzQbx81.mjs → protocol-CF4Bh_Ch.mjs} +118 -36
  46. package/dist/protocol-CF4Bh_Ch.mjs.map +1 -0
  47. package/dist/protocol.d.mts +4 -4
  48. package/dist/protocol.mjs +3 -3
  49. package/dist/{react-B3ybjJ-X.mjs → react-C_GY-Fsc.mjs} +146 -39
  50. package/dist/react-C_GY-Fsc.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 +100 -31
  56. package/dist/references/core-rules.md +6 -3
  57. package/dist/references/embedded-forms.md +34 -14
  58. package/dist/references/expressions.md +220 -58
  59. package/dist/references/fair-by-design.md +9 -9
  60. package/dist/references/item-types.md +305 -71
  61. package/dist/references/response-variables.md +38 -0
  62. package/dist/references/source-material-surveys.md +4 -3
  63. package/dist/references/survey-data-model.md +23 -8
  64. package/dist/{request-context-CG4SWijt.mjs → request-context-Dg4jSwN1.mjs} +4 -10
  65. package/dist/request-context-Dg4jSwN1.mjs.map +1 -0
  66. package/dist/{request-guardrails-TmgQtqp4.mjs → request-guardrails-C0ugBOp0.mjs} +6 -5
  67. package/dist/request-guardrails-C0ugBOp0.mjs.map +1 -0
  68. package/dist/server-agent.d.mts +37 -39
  69. package/dist/server-agent.d.mts.map +1 -1
  70. package/dist/server-agent.mjs +45 -20
  71. package/dist/server-agent.mjs.map +1 -1
  72. package/dist/server-runtime.d.mts +23 -42
  73. package/dist/server-runtime.d.mts.map +1 -1
  74. package/dist/server-runtime.mjs +12 -12
  75. package/dist/server-runtime.mjs.map +1 -1
  76. package/dist/server-tasks.d.mts +2 -2
  77. package/dist/server-tasks.mjs +2 -313
  78. package/dist/server-tools.d.mts +1 -1
  79. package/dist/server-tools.mjs +1 -1
  80. package/dist/server.d.mts +13 -36
  81. package/dist/server.d.mts.map +1 -1
  82. package/dist/server.mjs +5 -5
  83. package/dist/server.mjs.map +1 -1
  84. package/dist/storage-postgres.d.mts +9 -25
  85. package/dist/storage-postgres.d.mts.map +1 -1
  86. package/dist/storage-postgres.mjs +5 -4
  87. package/dist/storage-postgres.mjs.map +1 -1
  88. package/dist/{tasks-CYqrcaRO.d.mts → tasks-B9h3LPam.d.mts} +18 -34
  89. package/dist/tasks-B9h3LPam.d.mts.map +1 -0
  90. package/dist/tasks-BZEiwgxA.mjs +396 -0
  91. package/dist/tasks-BZEiwgxA.mjs.map +1 -0
  92. package/dist/thread-documents-DrHbaXqP.d.mts +119 -0
  93. package/dist/thread-documents-DrHbaXqP.d.mts.map +1 -0
  94. package/dist/{thread-handlers-DBAaU7BA.d.mts → thread-handlers-Bef0td9N.d.mts} +8 -40
  95. package/dist/thread-handlers-Bef0td9N.d.mts.map +1 -0
  96. package/dist/{tools-BvSovehT.mjs → tools-CEsv-WoO.mjs} +296 -105
  97. package/dist/tools-CEsv-WoO.mjs.map +1 -0
  98. package/dist/ui.css +1 -1
  99. package/dist/ui.d.mts +15 -54
  100. package/dist/ui.d.mts.map +1 -1
  101. package/dist/ui.mjs +172 -137
  102. package/dist/ui.mjs.map +1 -1
  103. package/docs/integration.md +11 -1
  104. package/package.json +40 -35
  105. package/dist/authoring-references-BJQ_1nMX.mjs.map +0 -1
  106. package/dist/capabilities-1KNnNO1_.mjs.map +0 -1
  107. package/dist/capabilities-DVTfogjv.d.mts.map +0 -1
  108. package/dist/capabilities-default.mjs.map +0 -1
  109. package/dist/constants-Be91Gay9.d.mts.map +0 -1
  110. package/dist/controller-proxy-BH05e-C2.mjs.map +0 -1
  111. package/dist/controller-proxy-Bd0ZHBPF.d.mts +0 -85
  112. package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +0 -1
  113. package/dist/digest-SJmoYVaK.d.mts.map +0 -1
  114. package/dist/engine-Bo85Oyj0.mjs.map +0 -1
  115. package/dist/index-Tnqv-yKW.d.mts.map +0 -1
  116. package/dist/index-l_EK4yK7.d.mts.map +0 -1
  117. package/dist/index-nhrgbgO5.d.mts.map +0 -1
  118. package/dist/index-x0qprHgK.d.mts.map +0 -1
  119. package/dist/lifecycle-Bx0aq_4S.d.mts.map +0 -1
  120. package/dist/memory-thread-repository-KWHQWFEW.mjs.map +0 -1
  121. package/dist/protocol-BhzQbx81.mjs.map +0 -1
  122. package/dist/react-B3ybjJ-X.mjs.map +0 -1
  123. package/dist/request-context-CG4SWijt.mjs.map +0 -1
  124. package/dist/request-guardrails-TmgQtqp4.mjs.map +0 -1
  125. package/dist/server-tasks.mjs.map +0 -1
  126. package/dist/tasks-CYqrcaRO.d.mts.map +0 -1
  127. package/dist/thread-documents-rmd6nyX9.d.mts +0 -107
  128. package/dist/thread-documents-rmd6nyX9.d.mts.map +0 -1
  129. package/dist/thread-handlers-DBAaU7BA.d.mts.map +0 -1
  130. package/dist/tools-BvSovehT.mjs.map +0 -1
@@ -1,86 +1,116 @@
1
- import { At as surveyContentSchema, I as rawSurveyDocumentSchema, f as surveyChangeSetSchema, l as surveyAssistantTurnFinalSchema, yt as surveyOperationSchema } from "./protocol-BhzQbx81.mjs";
1
+ import { At as surveyContentSchema, R as rawSurveyDocumentSchema, bt as surveyOperationSchema, f as surveyChangeSetSchema, l as surveyAssistantTurnFinalSchema } from "./protocol-CF4Bh_Ch.mjs";
2
2
  import { stableStringify } from "./digest.mjs";
3
- import { n as assertSurveyAssistantCapabilities } from "./capabilities-1KNnNO1_.mjs";
3
+ import { n as assertSurveyAssistantCapabilities } from "./capabilities-DCMA71PP.mjs";
4
4
  import { z } from "zod";
5
- import { ContentType, Expression, ExpressionType, FunctionExpressionNames, JsonPatchError, ReservedSurveyItemTypes, SurveyItemTranslations, SurveyTranslations, ValueReference, ValueReferenceMethod, ValueType, analyzeSurveyExpressionReferences, applyJsonPatch, findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, generateId, getContentPlainText, isBlockingSurveyExpressionReferenceDiagnostic, isContentEmpty, parseJsonPointer, structuredCloneMethod } from "@case-framework/survey-core";
6
- import { getFormAuthoringMode, getFormAuthoringMode as getFormAuthoringMode$1, hasSingleFormField, isCompatibleSimpleFieldUpsert, isCompatibleSimpleFieldUpsert as isCompatibleSimpleFieldUpsert$1, isValidSimpleFormConfig, isValidSimpleFormConfig as isValidSimpleFormConfig$1, normalizeFormAuthoringConfig, normalizeFormAuthoringConfig as normalizeFormAuthoringConfig$1, transitionFormAuthoringMode, transitionFormAuthoringMode as transitionFormAuthoringMode$1 } from "@case-framework/survey-items/form";
7
- import { getChoiceOptionLabelContentKey, normalizeChoiceOptionKey } from "@case-framework/survey-items/choice";
8
- //#region src/engine/context.ts
9
- const isRecord$7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
10
- const asString$1 = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
11
- const asArray$1 = (value) => Array.isArray(value) ? value : [];
12
- const getChildIds = (item) => {
13
- return asArray$1((isRecord$7(item.config) ? item.config : void 0)?.items).filter((entry) => typeof entry === "string");
14
- };
15
- const getFieldValueType = (field) => {
16
- switch (field.type) {
17
- case "number":
18
- case "slider": return ValueType.number;
19
- case "datepicker": return ValueType.date;
20
- case "switch": return ValueType.boolean;
21
- default: return ValueType.string;
5
+ import { ActiveResponsePlan, ContentType, DATE_FUNCTION_SIGNATURES, Expression, ExpressionType, FunctionExpressionNames, JsonPatchError, ReservedSurveyItemTypes, Survey, SurveyItemCore, SurveyItemTranslations, SurveyTranslations, ValueReference, ValueType, analyzeSurveyExpressionReferences, applyJsonPatch, findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories, generateId, getContentPlainText, getDefaultProjectionIssues, isBlockingSurveyExpressionReferenceDiagnostic, isContentEmpty, parseJsonPointer, structuredCloneMethod, suggestResponseVariableName } from "@case-framework/survey-core";
6
+ import { ChoiceItemCore, getChoiceOptionLabelContentKey, normalizeChoiceOptionKey } from "@case-framework/survey-items/choice";
7
+ import { FormItemCore, getFormAuthoringMode, getFormAuthoringMode as getFormAuthoringMode$1, hasSingleFormField, isCompatibleSimpleFieldUpsert, isCompatibleSimpleFieldUpsert as isCompatibleSimpleFieldUpsert$1, isValidSimpleFormConfig, isValidSimpleFormConfig as isValidSimpleFormConfig$1, normalizeFormAuthoringConfig, normalizeFormAuthoringConfig as normalizeFormAuthoringConfig$1, normalizeFormItemConfig, transitionFormAuthoringMode, transitionFormAuthoringMode as transitionFormAuthoringMode$1 } from "@case-framework/survey-items/form";
8
+ import { ConsentItemCore } from "@case-framework/survey-items/consent";
9
+ import { ChoiceMatrixCore, getChoiceMatrixTranslationKeys, normalizeChoiceMatrixConfig } from "@case-framework/survey-items/choice-matrix";
10
+ import { FormMatrixCore, cleanFormMatrixTranslations, normalizeFormMatrixConfig } from "@case-framework/survey-items/form-matrix";
11
+ import { allocateCopiedResponseNames, previewResponseSettings } from "@case-framework/survey-core/editor";
12
+ //#region src/engine/survey-model.ts
13
+ /** A detached core view using trusted, server-safe slot declarations. No editor state. */
14
+ function createAssistantSurvey(raw, capabilities) {
15
+ const registry = {
16
+ choiceItem: ChoiceItemCore,
17
+ formItem: FormItemCore,
18
+ consentItem: ConsentItemCore,
19
+ choiceMatrix: ChoiceMatrixCore,
20
+ formMatrix: FormMatrixCore
21
+ };
22
+ for (const [itemType, adapter] of capabilities?.byItemType ?? []) if (adapter.itemCore) registry[itemType] = adapter.itemCore;
23
+ for (const item of raw.surveyItems) {
24
+ if (registry[item.itemType]) continue;
25
+ const adapter = capabilities?.byItemType.get(item.itemType);
26
+ registry[item.itemType] = class extends SurveyItemCore {
27
+ type = item.itemType;
28
+ parseConfig(config) {
29
+ return config;
30
+ }
31
+ isInteractive() {
32
+ return this.getResponseSlotDefinitions().length > 0;
33
+ }
34
+ getResponseSlotDefinitions() {
35
+ const current = {
36
+ ...this.rawItem,
37
+ config: this.config
38
+ };
39
+ if (adapter?.deriveResponseSlotDefinitions) return adapter.deriveResponseSlotDefinitions(current);
40
+ return Object.entries(adapter?.deriveResponseSlots?.(current) ?? {}).map(([slotId, valueType]) => ({
41
+ slotId,
42
+ variableName: slotId,
43
+ valueType
44
+ }));
45
+ }
46
+ };
22
47
  }
23
- };
24
- const addResponseSlot = (lookup, itemId, slotId, valueType) => {
25
- lookup[`${itemId}...${ValueReferenceMethod.get}...${slotId}`] = valueType;
26
- lookup[`${itemId}...${ValueReferenceMethod.isDefined}...${slotId}`] = ValueType.boolean;
27
- };
28
- const addFormResponseSlots = ({ config, itemId, lookup, slotPrefix }) => {
29
- if (!isRecord$7(config)) return;
30
- for (const group of asArray$1(config.fieldGroups).filter(isRecord$7)) for (const field of asArray$1(group.fields).filter(isRecord$7)) {
31
- const key = asString$1(field.key);
32
- if (!key) continue;
33
- addResponseSlot(lookup, itemId, slotPrefix ? `${slotPrefix}.${key}` : key, getFieldValueType(field));
48
+ return Survey.fromJson(raw, registry);
49
+ }
50
+ function getAssistantVisibilityIssues(survey) {
51
+ try {
52
+ new ActiveResponsePlan(survey);
53
+ return [];
54
+ } catch (error) {
55
+ return [{
56
+ severity: "error",
57
+ code: "invalid-survey-visibility",
58
+ message: error instanceof Error ? error.message : String(error)
59
+ }];
34
60
  }
35
- };
36
- const deriveResponseSlotTypes = (survey, trustedCapabilities) => {
37
- const lookup = {};
38
- for (const item of survey.surveyItems) {
39
- const deriveResponseSlots = trustedCapabilities?.byItemType.get(item.itemType)?.deriveResponseSlots;
40
- if (deriveResponseSlots) {
41
- for (const [slotId, valueType] of Object.entries(deriveResponseSlots(item))) addResponseSlot(lookup, item.id, slotId, valueType);
42
- continue;
43
- }
44
- if (item.itemType === "choiceItem") {
45
- const config = isRecord$7(item.config) ? item.config : {};
46
- const slotId = asString$1(config.id) ?? item.id;
47
- addResponseSlot(lookup, item.id, slotId, config.maxSelection === 1 ? ValueType.reference : ValueType.referenceArray);
48
- for (const option of asArray$1(config.options).filter(isRecord$7)) {
49
- const optionId = asString$1(option.id);
50
- if (optionId && option.embeddedForm) addFormResponseSlots({
51
- config: option.embeddedForm,
52
- itemId: item.id,
53
- lookup,
54
- slotPrefix: `embedded.${optionId}`
55
- });
56
- }
57
- } else if (item.itemType === "formItem") addFormResponseSlots({
58
- config: item.config,
59
- itemId: item.id,
60
- lookup
61
- });
61
+ }
62
+ function getAssistantResponseSchemaIssues(raw, capabilities) {
63
+ try {
64
+ if (raw.surveyItems.some((item) => "key" in item)) throw new Error("Item keys are not supported by survey schema version 2.");
65
+ const survey = createAssistantSurvey(raw, capabilities);
66
+ return [
67
+ ...getAssistantVisibilityIssues(survey),
68
+ ...survey.getResponseSlotRegistry().diagnostics.map((issue) => ({
69
+ severity: "error",
70
+ code: issue.code,
71
+ message: issue.message,
72
+ itemId: issue.itemId
73
+ })),
74
+ ...getDefaultProjectionIssues(survey).map((message) => ({
75
+ severity: "error",
76
+ code: "invalid-response-export",
77
+ message
78
+ }))
79
+ ];
80
+ } catch (error) {
81
+ return [{
82
+ severity: "error",
83
+ code: "invalid-response-schema",
84
+ message: error instanceof Error ? error.message : String(error)
85
+ }];
62
86
  }
63
- return lookup;
87
+ }
88
+ //#endregion
89
+ //#region src/engine/context.ts
90
+ const isRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
91
+ const asArray$1 = (value) => Array.isArray(value) ? value : [];
92
+ const getChildIds = (item) => {
93
+ const config = isRecord$6(item.config) ? item.config : void 0;
94
+ return asArray$1(config?.items).filter((entry) => typeof entry === "string");
64
95
  };
96
+ const deriveResponseSlotTypes = (survey, trustedCapabilities) => Object.fromEntries(createAssistantSurvey(survey, trustedCapabilities).getExpressionResponseSlots().map((slot) => [slot.ref, slot.valueType]));
65
97
  const buildDerivedContext = (snapshot, trustedCapabilities, requestedFocusItemId) => {
66
98
  const rawItems = snapshot.survey.surveyItems;
67
99
  const itemsById = new Map(rawItems.map((item) => [item.id, item]));
68
100
  const parentById = /* @__PURE__ */ new Map();
69
101
  for (const item of rawItems) for (const childId of getChildIds(item)) if (itemsById.has(childId) && !parentById.has(childId)) parentById.set(childId, item.id);
70
- const fullKeyCache = /* @__PURE__ */ new Map();
71
- const getFullKey = (itemId, stack = /* @__PURE__ */ new Set()) => {
72
- const cached = fullKeyCache.get(itemId);
73
- if (cached) return cached;
74
- const item = itemsById.get(itemId);
75
- if (!item) return itemId;
76
- if (stack.has(itemId)) return item.key;
77
- const parentId = parentById.get(itemId);
78
- if (!parentId) return item.key;
79
- stack.add(itemId);
80
- const fullKey = `${getFullKey(parentId, stack)}.${item.key}`;
81
- stack.delete(itemId);
82
- fullKeyCache.set(itemId, fullKey);
83
- return fullKey;
102
+ const model = createAssistantSurvey(snapshot.survey, trustedCapabilities);
103
+ const name = (id) => model.resolveItemEditorName(id, snapshot.ui.selectedLocale);
104
+ const getNamePath = (id) => {
105
+ const names = [name(id)];
106
+ const seen = /* @__PURE__ */ new Set([id]);
107
+ let parent = parentById.get(id);
108
+ while (parent && !seen.has(parent)) {
109
+ seen.add(parent);
110
+ names.unshift(name(parent));
111
+ parent = parentById.get(parent);
112
+ }
113
+ return names;
84
114
  };
85
115
  const outline = [];
86
116
  const visited = /* @__PURE__ */ new Set();
@@ -94,23 +124,23 @@ const buildDerivedContext = (snapshot, trustedCapabilities, requestedFocusItemId
94
124
  parentId: parentById.get(itemId),
95
125
  depth,
96
126
  itemType: item.itemType,
97
- key: item.key,
98
- fullKey: getFullKey(itemId),
127
+ editorName: name(item.id),
128
+ breadcrumb: getNamePath(itemId).join(" / "),
99
129
  itemLabel: item.metadata?.itemLabel,
100
130
  childCount: getChildIds(item).length
101
131
  });
102
132
  for (const childId of getChildIds(item)) visit(childId, depth + 1);
103
133
  };
104
- const explicitRoot = rawItems.find((item) => isRecord$7(item.config) && item.config.isRoot === true);
134
+ const explicitRoot = rawItems.find((item) => isRecord$6(item.config) && item.config.isRoot === true);
105
135
  if (explicitRoot) visit(explicitRoot.id, 0);
106
136
  for (const item of [...rawItems].sort((left, right) => left.id.localeCompare(right.id))) if (!visited.has(item.id)) visit(item.id, 0);
107
- const keyIndex = outline.map((node) => ({
137
+ const itemIndex = outline.map((node) => ({
108
138
  itemId: node.itemId,
109
139
  itemType: node.itemType,
110
- key: node.key,
111
- fullKey: node.fullKey,
140
+ editorName: node.editorName,
141
+ breadcrumb: node.breadcrumb,
112
142
  itemLabel: node.itemLabel,
113
- path: node.fullKey.split(".")
143
+ path: getNamePath(node.itemId)
114
144
  }));
115
145
  const translations = new SurveyTranslations(snapshot.survey.translations);
116
146
  const buildFocusNode = (itemId, budget) => {
@@ -125,10 +155,10 @@ const buildDerivedContext = (snapshot, trustedCapabilities, requestedFocusItemId
125
155
  return {
126
156
  itemId,
127
157
  itemType: item.itemType,
128
- itemKey: item.key,
129
- fullKey: getFullKey(itemId),
158
+ editorName: name(item.id),
159
+ breadcrumb: getNamePath(itemId).join(" / "),
130
160
  itemLabel: item.metadata?.itemLabel,
131
- siblingKeys: rawItems.filter((candidate) => candidate.id !== itemId && parentById.get(candidate.id) === parentById.get(itemId)).map((candidate) => candidate.key),
161
+ siblingNames: rawItems.filter((candidate) => candidate.id !== itemId && parentById.get(candidate.id) === parentById.get(itemId)).map((candidate) => name(candidate.id)),
132
162
  translations: translationSnippets,
133
163
  descendants: getChildIds(item).filter((childId) => itemsById.has(childId) && budget.remaining > 0).map((childId) => buildFocusNode(childId, budget))
134
164
  };
@@ -158,21 +188,22 @@ const buildDerivedContext = (snapshot, trustedCapabilities, requestedFocusItemId
158
188
  focusItem = buildFocusNode(focusItemId, budget);
159
189
  focusItemTreeTruncated = budget.remaining === 0;
160
190
  }
161
- const responseSlotTypes = deriveResponseSlotTypes(snapshot.survey, trustedCapabilities);
191
+ const responseSlotTypes = Object.fromEntries(model.getExpressionResponseSlots().map((slot) => [slot.ref, slot.valueType]));
162
192
  const locales = [snapshot.ui.selectedLocale, ...Object.keys(snapshot.survey.translations ?? {}).filter((locale) => locale !== snapshot.ui.selectedLocale)].filter((locale, index, values) => locale.length > 0 && values.indexOf(locale) === index);
163
193
  return {
164
194
  inspection: {
165
- surveyKey: explicitRoot?.key,
195
+ surveyKey: snapshot.survey.metadata?.surveyKey,
166
196
  locales,
167
197
  itemCount: rawItems.filter((item) => item.id !== explicitRoot?.id).length,
168
198
  outline,
169
199
  focus,
170
200
  focusItem,
171
- keyIndex,
172
- responseSlots: Object.keys(responseSlotTypes),
201
+ itemIndex,
202
+ responseSlots: model.getResponseSlotRegistry().definitions.map((slot) => slot.slotId),
173
203
  focusItemTreeTruncated
174
204
  },
175
- responseSlotTypes
205
+ responseSlotTypes,
206
+ responseVariables: model.getResponseSlotRegistry().definitions
176
207
  };
177
208
  };
178
209
  const createSurveyAssistantEngineContext = (document, trustedCapabilities, options) => {
@@ -181,7 +212,7 @@ const createSurveyAssistantEngineContext = (document, trustedCapabilities, optio
181
212
  trustedCapabilities,
182
213
  versionDigest: document.version.capabilityDigest
183
214
  });
184
- const { inspection, responseSlotTypes } = buildDerivedContext(document, trustedCapabilities, options?.focusItemId);
215
+ const { inspection, responseSlotTypes, responseVariables } = buildDerivedContext(document, trustedCapabilities, options?.focusItemId);
185
216
  return {
186
217
  document,
187
218
  version: document.version,
@@ -189,6 +220,7 @@ const createSurveyAssistantEngineContext = (document, trustedCapabilities, optio
189
220
  ui: document.ui,
190
221
  capabilities: document.capabilities,
191
222
  responseSlotTypes,
223
+ responseVariables,
192
224
  inspection,
193
225
  trustedCapabilities
194
226
  };
@@ -219,7 +251,7 @@ const validateArgumentCount = ({ actual, expected, functionName, issues, operati
219
251
  const inferExpressionReturnType = (expression, responseSlotTypes) => {
220
252
  if (!expression) return;
221
253
  if (expression.type === ExpressionType.Const) return expression.value?.type;
222
- if (expression.type === ExpressionType.ResponseVariable) return responseSlotTypes[expression.variableRef];
254
+ if (expression.type === ExpressionType.ResponseVariable) return responseSlotTypes[new ValueReference(expression.variableRef).toString()];
223
255
  if (expression.type === ExpressionType.ContextVariable) {
224
256
  if (expression.contextType === "locale") return ValueType.string;
225
257
  return expression.asType;
@@ -236,7 +268,17 @@ const inferExpressionReturnType = (expression, responseSlotTypes) => {
236
268
  case FunctionExpressionNames.in_range:
237
269
  case FunctionExpressionNames.list_contains:
238
270
  case FunctionExpressionNames.str_eq:
239
- case FunctionExpressionNames.date_eq: return ValueType.boolean;
271
+ case FunctionExpressionNames.date_eq:
272
+ case FunctionExpressionNames.date_gt:
273
+ case FunctionExpressionNames.date_gte:
274
+ case FunctionExpressionNames.date_lt:
275
+ case FunctionExpressionNames.date_lte: return ValueType.boolean;
276
+ case FunctionExpressionNames.date_add_days:
277
+ case FunctionExpressionNames.date_add_months:
278
+ case FunctionExpressionNames.date_add_years:
279
+ case FunctionExpressionNames.date_min:
280
+ case FunctionExpressionNames.date_max: return ValueType.date;
281
+ case FunctionExpressionNames.date_diff: return ValueType.number;
240
282
  case FunctionExpressionNames.sum:
241
283
  case FunctionExpressionNames.min:
242
284
  case FunctionExpressionNames.max: return ValueType.number;
@@ -283,6 +325,25 @@ const validateExpressionFunctionSignatures = ({ expression, issues, operationInd
283
325
  message: `${expression.functionName} arguments must have the same type, got ${formatValueType(argumentTypes[0])} and ${formatValueType(argumentTypes[1])}.`
284
326
  });
285
327
  };
328
+ const dateSignature = DATE_FUNCTION_SIGNATURES[expression.functionName];
329
+ if (dateSignature) {
330
+ if (dateSignature.variadic ? expression.arguments.length < 1 : expression.arguments.length !== dateSignature.args.length) addFunctionSignatureIssue({
331
+ issues,
332
+ operationIndex,
333
+ scope,
334
+ message: `${expression.functionName} has invalid argument count.`
335
+ });
336
+ argumentTypes.forEach((actual, i) => {
337
+ const expected = dateSignature.args[dateSignature.variadic ? 0 : i];
338
+ if (actual && actual !== expected) addFunctionSignatureIssue({
339
+ issues,
340
+ operationIndex,
341
+ scope,
342
+ message: `${expression.functionName} argument ${i + 1} must be ${expected}, got ${actual}.`
343
+ });
344
+ });
345
+ return;
346
+ }
286
347
  switch (expression.functionName) {
287
348
  case FunctionExpressionNames.and:
288
349
  case FunctionExpressionNames.or:
@@ -459,37 +520,14 @@ const validateExpression = ({ expression, expectedReturnType, issues, operationI
459
520
  };
460
521
  //#endregion
461
522
  //#region src/engine/expression-reference-integrity.ts
462
- const isRecord$6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
463
- const choiceAllowedValues = (survey, ref) => {
464
- let parsed;
465
- try {
466
- parsed = new ValueReference(ref);
467
- } catch {
468
- return;
469
- }
470
- if (parsed.name !== ValueReferenceMethod.get) return void 0;
471
- const item = survey.surveyItems.find((candidate) => candidate.id === parsed.itemId);
472
- if (item?.itemType !== "choiceItem" || !isRecord$6(item.config)) return void 0;
473
- const slotId = typeof item.config.id === "string" ? item.config.id : item.id;
474
- if (parsed.slotId !== slotId || !Array.isArray(item.config.options)) return void 0;
475
- return item.config.options.flatMap((option) => isRecord$6(option) && typeof option.id === "string" ? [option.id] : []);
476
- };
477
- const deriveSurveyExpressionResponseSlots = (survey, trustedCapabilities) => Object.entries(deriveResponseSlotTypes(survey, trustedCapabilities)).flatMap(([ref, valueType]) => {
478
- if (!valueType) return [];
479
- const allowedReferenceValues = valueType === ValueType.reference || valueType === ValueType.referenceArray ? choiceAllowedValues(survey, ref) : void 0;
480
- return [{
481
- ref,
482
- valueType,
483
- ...allowedReferenceValues ? { allowedReferenceValues } : {}
484
- }];
485
- });
523
+ const deriveSurveyExpressionResponseSlots = (survey, trustedCapabilities) => createAssistantSurvey(survey, trustedCapabilities).getExpressionResponseSlots();
486
524
  const analyzeSurveyAssistantExpressionReferences = (survey, trustedCapabilities) => analyzeSurveyExpressionReferences({
487
525
  survey,
488
526
  responseSlots: deriveSurveyExpressionResponseSlots(survey, trustedCapabilities)
489
527
  });
490
528
  const getSurveyAssistantExpressionDiagnosticGuidance = (diagnostic) => {
529
+ if (diagnostic.category === "display-dependency") return "Break the visibility dependency cycle using an independent controlling answer or serialized context value. Prefills do not make visibility cycles valid.";
491
530
  if (isBlockingSurveyExpressionReferenceDiagnostic(diagnostic)) return "Repair blocking expression diagnostics using the current response slots and option ids before claiming the repair is complete.";
492
- if (diagnostic.category === "display-dependency") return "Review advisory display dependency findings without automatic repair; they may be intentional when responses are populated before visibility is evaluated, including through prefills.";
493
531
  return "Review advisory expression diagnostics without automatic repair unless the user asks or the survey clearly shows they are accidental.";
494
532
  };
495
533
  //#endregion
@@ -499,14 +537,14 @@ const MAX_LIMIT = 50;
499
537
  const itemSummarySchema = z.object({
500
538
  itemId: z.string(),
501
539
  itemType: z.string(),
502
- key: z.string(),
503
- fullKey: z.string(),
540
+ editorName: z.string(),
541
+ breadcrumb: z.string(),
504
542
  itemLabel: z.string().optional(),
505
543
  parentId: z.string().optional(),
506
544
  depth: z.number().int().nonnegative().optional(),
507
545
  childCount: z.number().int().nonnegative().optional(),
508
546
  path: z.array(z.string()).optional(),
509
- siblingKeys: z.array(z.string()).optional()
547
+ siblingNames: z.array(z.string()).optional()
510
548
  });
511
549
  const translationEntrySchema = z.object({
512
550
  locale: z.string(),
@@ -516,20 +554,19 @@ const translationEntrySchema = z.object({
516
554
  const choiceOptionDetailsSchema = z.object({
517
555
  position: z.number().int().positive().describe("One-based position in the stored option order."),
518
556
  id: z.string(),
519
- key: z.string().optional(),
557
+ code: z.string().optional(),
520
558
  labelContentKey: z.string(),
521
559
  hasDisplayCondition: z.boolean().optional(),
522
560
  embeddedFormItemId: z.string().optional(),
523
561
  embeddedFormFieldGroupCount: z.number().int().nonnegative().optional(),
524
562
  embeddedFormAuthoringMode: z.enum(["simple", "custom"]).optional(),
525
563
  embeddedFormSimpleModeValid: z.boolean().optional(),
526
- embeddedFormTranslationPrefix: z.string().optional(),
527
- embeddedFormResponseSlotPrefix: z.string().optional()
564
+ embeddedFormTranslationPrefix: z.string().optional()
528
565
  });
529
566
  const formSelectOptionDetailsSchema = z.object({
530
567
  position: z.number().int().positive().describe("One-based position in the stored select-option order."),
531
568
  id: z.string(),
532
- value: z.string().optional(),
569
+ code: z.string().optional(),
533
570
  labelContentKey: z.string()
534
571
  });
535
572
  const formFieldGroupLayoutSchema = z.object({
@@ -542,7 +579,7 @@ const formFieldGroupLayoutSchema = z.object({
542
579
  const formFieldDetailsSchema = z.object({
543
580
  position: z.number().int().positive().describe("One-based position in this field group's stored field order."),
544
581
  id: z.string(),
545
- key: z.string(),
582
+ variableName: z.string(),
546
583
  type: z.string(),
547
584
  responseSlotId: z.string(),
548
585
  labelContentKey: z.string(),
@@ -597,12 +634,10 @@ const asArray = (value) => Array.isArray(value) ? value : [];
597
634
  const createContentKey = (...segments) => segments.filter(Boolean).join(".");
598
635
  const getChoiceOptionLabelContentKey$1 = (optionId) => createContentKey("options", optionId, "label");
599
636
  const getChoiceEmbeddedFormTranslationPrefix = (optionId) => createContentKey("embeddedForms", optionId);
600
- const getChoiceEmbeddedFormResponseSlotPrefix = (optionId) => createContentKey("embedded", optionId);
601
637
  const withFormTranslationNamespace = (contentKey, translationNamespace) => translationNamespace ? createContentKey(translationNamespace, contentKey) : contentKey;
602
638
  const getFormFieldGroupContentKey = (groupId, contentKey, translationNamespace) => withFormTranslationNamespace(createContentKey("fieldGroups", groupId, contentKey), translationNamespace);
603
639
  const getFormFieldContentKey = (fieldId, contentKey, translationNamespace) => withFormTranslationNamespace(createContentKey("fields", fieldId, contentKey), translationNamespace);
604
640
  const getFormFieldOptionLabelContentKey = (fieldId, optionId, translationNamespace) => withFormTranslationNamespace(createContentKey("fields", fieldId, "options", optionId, "label"), translationNamespace);
605
- const getFormFieldResponseSlotId = (fieldKey, responseSlotPrefix) => responseSlotPrefix ? createContentKey(responseSlotPrefix, fieldKey) : fieldKey;
606
641
  const getRoundedPositiveInt = (value, fallback = 1) => {
607
642
  if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
608
643
  return Math.max(1, Math.round(value));
@@ -617,9 +652,9 @@ const sliceWithCursor = (values, input) => {
617
652
  };
618
653
  };
619
654
  const mapOutlineById = (snapshot) => new Map(snapshot.inspection.outline.map((item) => [item.itemId, item]));
620
- const mapKeyIndexById = (snapshot) => new Map(snapshot.inspection.keyIndex.map((item) => [item.itemId, item]));
621
- /** The full-survey key index, regardless of the (possibly focus-scoped) outline. */
622
- const getKeyIndexEntries = (snapshot) => snapshot.inspection.keyIndex;
655
+ const mapItemIndexById = (snapshot) => new Map(snapshot.inspection.itemIndex.map((item) => [item.itemId, item]));
656
+ /** The full-survey item index, regardless of the (possibly focus-scoped) outline. */
657
+ const getItemIndexEntries = (snapshot) => snapshot.inspection.itemIndex;
623
658
  const getRawSurvey$1 = (snapshot) => snapshot.survey;
624
659
  const getRawSurveyItems = (snapshot) => {
625
660
  const rawItems = getRawSurvey$1(snapshot).surveyItems;
@@ -630,8 +665,8 @@ const buildSurveyTranslations = (snapshot) => new SurveyTranslations(getRawSurve
630
665
  const summarizeRawItem = (item) => ({
631
666
  itemId: item.id,
632
667
  itemType: item.itemType,
633
- key: item.key,
634
- fullKey: item.key,
668
+ editorName: item.metadata?.itemLabel?.trim() || item.id,
669
+ breadcrumb: item.id,
635
670
  itemLabel: item.metadata?.itemLabel
636
671
  });
637
672
  const buildItemTranslationEntries = (snapshot, itemId, input) => {
@@ -659,20 +694,20 @@ const buildItemTranslationEntries = (snapshot, itemId, input) => {
659
694
  const summarizeOutlineNode = (node) => ({
660
695
  itemId: node.itemId,
661
696
  itemType: node.itemType,
662
- key: node.key,
663
- fullKey: node.fullKey,
697
+ editorName: node.editorName,
698
+ breadcrumb: node.breadcrumb,
664
699
  itemLabel: node.itemLabel,
665
700
  parentId: node.parentId,
666
701
  depth: node.depth,
667
702
  childCount: node.childCount
668
703
  });
669
- const summarizeKeyIndexEntry = (entry, outlineById) => {
704
+ const summarizeItemIndexEntry = (entry, outlineById) => {
670
705
  const outlineNode = outlineById.get(entry.itemId);
671
706
  return {
672
707
  itemId: entry.itemId,
673
708
  itemType: entry.itemType,
674
- key: entry.key,
675
- fullKey: entry.fullKey,
709
+ editorName: entry.editorName,
710
+ breadcrumb: entry.breadcrumb,
676
711
  itemLabel: entry.itemLabel,
677
712
  parentId: outlineNode?.parentId,
678
713
  depth: outlineNode?.depth,
@@ -680,36 +715,7 @@ const summarizeKeyIndexEntry = (entry, outlineById) => {
680
715
  path: entry.path
681
716
  };
682
717
  };
683
- const getParentFullKey = (fullKey) => {
684
- const segments = fullKey.split(".");
685
- if (segments.length <= 1) return;
686
- return segments.slice(0, -1).join(".");
687
- };
688
- const summarizeKeyIndexEntries = (entries, outlineById) => {
689
- const byFullKey = new Map(entries.map((entry) => [entry.fullKey, entry]));
690
- const childCountByFullKey = /* @__PURE__ */ new Map();
691
- for (const entry of entries) {
692
- const parentFullKey = getParentFullKey(entry.fullKey);
693
- if (!parentFullKey) continue;
694
- childCountByFullKey.set(parentFullKey, (childCountByFullKey.get(parentFullKey) ?? 0) + 1);
695
- }
696
- return entries.map((entry) => {
697
- const outlineNode = outlineById.get(entry.itemId);
698
- const parentFullKey = getParentFullKey(entry.fullKey);
699
- const parentEntry = parentFullKey ? byFullKey.get(parentFullKey) : void 0;
700
- return {
701
- itemId: entry.itemId,
702
- itemType: entry.itemType,
703
- key: entry.key,
704
- fullKey: entry.fullKey,
705
- itemLabel: entry.itemLabel,
706
- parentId: outlineNode?.parentId ?? parentEntry?.itemId,
707
- depth: outlineNode?.depth ?? Math.max(0, entry.fullKey.split(".").length - 1),
708
- childCount: outlineNode?.childCount ?? childCountByFullKey.get(entry.fullKey) ?? 0,
709
- path: entry.path
710
- };
711
- });
712
- };
718
+ const summarizeItemIndexEntries = (entries, outlineById) => entries.map((entry) => summarizeItemIndexEntry(entry, outlineById));
713
719
  const buildChoiceConfigDetails = (rawItem) => {
714
720
  const config = isRecord$5(rawItem.config) ? rawItem.config : void 0;
715
721
  const rawOptions = asArray(config?.options);
@@ -726,8 +732,8 @@ const buildChoiceConfigDetails = (rawItem) => {
726
732
  id,
727
733
  labelContentKey: getChoiceOptionLabelContentKey$1(id)
728
734
  };
729
- const key = asString(option.key);
730
- if (key) details.key = key;
735
+ const key = asString(option.code);
736
+ if (key) details.code = key;
731
737
  const hasDisplayCondition = asBoolean(option.hasDisplayCondition);
732
738
  if (hasDisplayCondition !== void 0) details.hasDisplayCondition = hasDisplayCondition;
733
739
  const embeddedFormItemId = asString(option.embeddedFormItemId);
@@ -737,7 +743,6 @@ const buildChoiceConfigDetails = (rawItem) => {
737
743
  details.embeddedFormAuthoringMode = getFormAuthoringMode(embeddedForm);
738
744
  details.embeddedFormSimpleModeValid = isValidSimpleFormConfig(embeddedForm);
739
745
  details.embeddedFormTranslationPrefix = getChoiceEmbeddedFormTranslationPrefix(id);
740
- details.embeddedFormResponseSlotPrefix = getChoiceEmbeddedFormResponseSlotPrefix(id);
741
746
  }
742
747
  return details;
743
748
  }).filter((option) => Boolean(option));
@@ -755,9 +760,9 @@ const buildChoiceConfigDetails = (rawItem) => {
755
760
  listOrdering: { choiceOptions: config?.shuffleOptions === true ? "Stored configured order; positions are one-based. Respondents see a shuffled order, so this is not a stable respondent-visible order." : "Stored configured order; positions are one-based and match respondent-visible order." }
756
761
  };
757
762
  };
758
- const buildFormFieldDetails = (field, position, translationNamespace, responseSlotPrefix) => {
763
+ const buildFormFieldDetails = (field, position, translationNamespace) => {
759
764
  const id = asString(field.id);
760
- const key = asString(field.key);
765
+ const key = asString(field.variableName);
761
766
  const type = asString(field.type);
762
767
  if (!id || !key || !type) return;
763
768
  const controller = isRecord$5(field.controller) ? field.controller : void 0;
@@ -773,16 +778,16 @@ const buildFormFieldDetails = (field, position, translationNamespace, responseSl
773
778
  id: optionId,
774
779
  labelContentKey: getFormFieldOptionLabelContentKey(id, optionId, translationNamespace)
775
780
  };
776
- const value = asString(option.value);
777
- if (value) details.value = value;
781
+ const value = asString(option.code);
782
+ if (value) details.code = value;
778
783
  return details;
779
784
  }).filter((option) => Boolean(option)) : void 0;
780
785
  return {
781
786
  position,
782
787
  id,
783
- key,
788
+ variableName: key,
784
789
  type,
785
- responseSlotId: getFormFieldResponseSlotId(key, responseSlotPrefix),
790
+ responseSlotId: id,
786
791
  labelContentKey: getFormFieldContentKey(id, "label", translationNamespace),
787
792
  descriptionContentKey: getFormFieldContentKey(id, "description", translationNamespace),
788
793
  errorContentKey: getFormFieldContentKey(id, "error", translationNamespace),
@@ -797,7 +802,7 @@ const buildFormFieldDetails = (field, position, translationNamespace, responseSl
797
802
  selectOptions
798
803
  };
799
804
  };
800
- const buildFormConfigDetails = (rawItem, configOverride, translationNamespace, responseSlotPrefix) => {
805
+ const buildFormConfigDetails = (rawItem, configOverride, translationNamespace) => {
801
806
  const configCandidate = configOverride ?? rawItem.config;
802
807
  const config = isRecord$5(configCandidate) ? configCandidate : void 0;
803
808
  const rawGroups = asArray(config?.fieldGroups);
@@ -806,7 +811,7 @@ const buildFormConfigDetails = (rawItem, configOverride, translationNamespace, r
806
811
  if (!isRecord$5(group)) return;
807
812
  const id = asString(group.id);
808
813
  if (!id) return;
809
- const fields = asArray(group.fields).map((field, fieldIndex) => isRecord$5(field) ? buildFormFieldDetails(field, fieldIndex + 1, translationNamespace, responseSlotPrefix) : void 0).filter((field) => Boolean(field));
814
+ const fields = asArray(group.fields).map((field, fieldIndex) => isRecord$5(field) ? buildFormFieldDetails(field, fieldIndex + 1, translationNamespace) : void 0).filter((field) => Boolean(field));
810
815
  const layout = isRecord$5(group.layout) ? group.layout : void 0;
811
816
  return {
812
817
  position: groupIndex + 1,
@@ -855,23 +860,24 @@ const buildItemConfigDetails = (rawItem) => {
855
860
  };
856
861
  const buildEmbeddedFormDetails = (choiceOption, embeddedForm) => {
857
862
  if (!isRecord$5(embeddedForm)) return;
858
- return buildFormConfigDetails({
863
+ const syntheticRawItem = {
859
864
  id: choiceOption.embeddedFormItemId ?? choiceOption.id,
860
- key: choiceOption.key ?? choiceOption.id,
865
+ metadata: { itemLabel: choiceOption.code ?? choiceOption.id },
861
866
  itemType: "formItem",
862
867
  config: embeddedForm
863
- }, embeddedForm, getChoiceEmbeddedFormTranslationPrefix(choiceOption.id), getChoiceEmbeddedFormResponseSlotPrefix(choiceOption.id));
868
+ };
869
+ return buildFormConfigDetails(syntheticRawItem, embeddedForm, getChoiceEmbeddedFormTranslationPrefix(choiceOption.id));
864
870
  };
865
871
  const buildConfigSearchText = (rawItem) => {
866
872
  const details = buildItemConfigDetails(rawItem);
867
873
  if (!details) return [];
868
874
  const values = [...details.summary];
869
- for (const option of details.choiceOptions ?? []) values.push(option.id, option.key ?? "", option.labelContentKey, option.embeddedFormItemId ?? "", option.embeddedFormTranslationPrefix ?? "", option.embeddedFormResponseSlotPrefix ?? "");
875
+ for (const option of details.choiceOptions ?? []) values.push(option.id, option.code ?? "", option.labelContentKey, option.embeddedFormItemId ?? "", option.embeddedFormTranslationPrefix ?? "");
870
876
  for (const group of details.formFieldGroups ?? []) {
871
877
  values.push(group.id, group.titleContentKey, group.descriptionContentKey, String(group.layout.base), String(group.layout.xs), String(group.layout.sm), String(group.layout.md), String(group.layout.xl), group.hasDisplayCondition === true ? "hasDisplayCondition" : "");
872
878
  for (const field of group.fields) {
873
- values.push(field.id, field.key, field.type, field.responseSlotId, field.labelContentKey, field.descriptionContentKey, field.errorContentKey, field.placeholderContentKey ?? "", field.unitContentKey ?? "", field.checkedLabelContentKey ?? "", field.uncheckedLabelContentKey ?? "", field.controllerType ?? "", ...field.validationTypes ?? [], ...field.validationMessageContentKeys ?? []);
874
- for (const option of field.selectOptions ?? []) values.push(option.id, option.value ?? "", option.labelContentKey);
879
+ values.push(field.id, field.variableName, field.type, field.responseSlotId, field.labelContentKey, field.descriptionContentKey, field.errorContentKey, field.placeholderContentKey ?? "", field.unitContentKey ?? "", field.checkedLabelContentKey ?? "", field.uncheckedLabelContentKey ?? "", field.controllerType ?? "", ...field.validationTypes ?? [], ...field.validationMessageContentKeys ?? []);
880
+ for (const option of field.selectOptions ?? []) values.push(option.id, option.code ?? "", option.labelContentKey);
875
881
  }
876
882
  }
877
883
  return values.filter((value) => value.trim().length > 0);
@@ -881,53 +887,42 @@ const summarizeFocusItemNode = (node, outlineById) => {
881
887
  return {
882
888
  itemId: node.itemId,
883
889
  itemType: node.itemType,
884
- key: node.itemKey,
885
- fullKey: node.fullKey,
890
+ editorName: node.editorName,
891
+ breadcrumb: node.breadcrumb,
886
892
  itemLabel: node.itemLabel,
887
893
  parentId: outlineNode?.parentId,
888
894
  depth: outlineNode?.depth,
889
895
  childCount: outlineNode?.childCount ?? node.descendants.length,
890
- siblingKeys: node.siblingKeys
896
+ siblingNames: node.siblingNames
891
897
  };
892
898
  };
893
899
  const summarizeItemById = (itemId, indexById, outlineById) => {
894
900
  const outlineNode = outlineById.get(itemId);
895
901
  if (outlineNode) return summarizeOutlineNode(outlineNode);
896
902
  const indexEntry = indexById.get(itemId);
897
- if (indexEntry) return summarizeKeyIndexEntry(indexEntry, outlineById);
903
+ if (indexEntry) return summarizeItemIndexEntry(indexEntry, outlineById);
898
904
  };
899
- /**
900
- * Resolve an item by internal itemId, or fall back to matching on `key` /
901
- * `fullKey`. Model-facing text (outline rows, fullKey paths) only ever shows
902
- * key/fullKey, not the internal itemId, so models often pass a key like
903
- * "info_1" where an itemId is expected — this keeps that usage working.
904
- */
905
+ /** Resolve only persistent item IDs. Search can discover names and breadcrumbs. */
905
906
  const resolveItemSummary = (identifier, indexById, outlineById) => {
906
- const direct = summarizeItemById(identifier, indexById, outlineById);
907
- if (direct) return direct;
908
- const normalized = identifier.trim().toLowerCase();
909
- if (!normalized) return;
910
- for (const entry of indexById.values()) if (entry.key.toLowerCase() === normalized || entry.fullKey.toLowerCase() === normalized) return summarizeKeyIndexEntry(entry, outlineById);
911
- for (const node of outlineById.values()) if (node.key.toLowerCase() === normalized || node.fullKey.toLowerCase() === normalized) return summarizeOutlineNode(node);
907
+ return summarizeItemById(identifier, indexById, outlineById);
912
908
  };
913
909
  const itemMatchesQuery = (item, query) => {
914
910
  return [
915
911
  item.itemId,
916
912
  item.itemType,
917
- item.key,
918
- item.fullKey,
913
+ item.editorName,
914
+ item.breadcrumb,
919
915
  item.itemLabel,
920
916
  ...item.path ?? [],
921
- ...item.siblingKeys ?? []
917
+ ...item.siblingNames ?? []
922
918
  ].filter(Boolean).join(" ").toLowerCase().includes(query);
923
919
  };
924
920
  /**
925
- * Direct children of `parentFullKey` derived from the full-survey key index.
921
+ * Direct children of `parentId` derived from the full-survey item index.
926
922
  * This works for any item, selected or not.
927
923
  */
928
- const findDirectChildrenByFullKey = (entries, outlineById, parentFullKey) => {
929
- const prefix = `${parentFullKey}.`;
930
- return entries.filter((entry) => entry.fullKey.startsWith(prefix) && !entry.fullKey.slice(prefix.length).includes(".")).map((entry) => summarizeKeyIndexEntry(entry, outlineById));
924
+ const findDirectChildrenByParentId = (entries, outlineById, parentId) => {
925
+ return entries.filter((entry) => outlineById.get(entry.itemId)?.parentId === parentId).map((entry) => summarizeItemIndexEntry(entry, outlineById));
931
926
  };
932
927
  //#endregion
933
928
  //#region src/engine/expression-mutations.ts
@@ -965,7 +960,10 @@ const expressionSchema = z.lazy(() => z.discriminatedUnion("type", [
965
960
  }),
966
961
  z.object({
967
962
  type: z.literal(ExpressionType.ResponseVariable),
968
- variableRef: z.string().min(1),
963
+ variableRef: z.object({
964
+ slotId: z.string().min(1),
965
+ method: z.enum(["get", "isDefined"])
966
+ }).strict(),
969
967
  editorConfig: z.record(z.string(), jsonValueSchema).optional()
970
968
  }),
971
969
  z.object({
@@ -1181,10 +1179,6 @@ const toJsonValue = (value) => value;
1181
1179
  const resolveItemId = (snapshot, itemRef) => {
1182
1180
  const rawItems = getRawSurveyItems(snapshot);
1183
1181
  if (new Map(rawItems.map((item) => [item.id, item])).has(itemRef)) return { itemId: itemRef };
1184
- const keyIndexMatch = getKeyIndexEntries(snapshot).find((entry) => entry.itemId === itemRef || entry.key === itemRef || entry.fullKey === itemRef);
1185
- if (keyIndexMatch) return { itemId: keyIndexMatch.itemId };
1186
- const rawKeyMatch = rawItems.find((item) => item.key === itemRef);
1187
- if (rawKeyMatch) return { itemId: rawKeyMatch.id };
1188
1182
  return { issue: createIssue$1({
1189
1183
  code: "item-not-found",
1190
1184
  message: `Could not resolve item "${itemRef}". Inspect current context or item details first.`,
@@ -1741,7 +1735,7 @@ const RICH_TEXT_BODY_KEYS = [
1741
1735
  "blocks"
1742
1736
  ];
1743
1737
  const MAX_RICH_TEXT_NORMALIZATION_ISSUES = 32;
1744
- const INSPECTABLE_RICH_TEXT_NODE_TYPES = new Set([
1738
+ const INSPECTABLE_RICH_TEXT_NODE_TYPES = /* @__PURE__ */ new Set([
1745
1739
  "paragraph",
1746
1740
  "heading",
1747
1741
  "bulletList",
@@ -1997,8 +1991,8 @@ const normalizeSelectControllerOptions = ({ controller, fieldId, translations })
1997
1991
  options: controller.options.map((option, index) => {
1998
1992
  if (!isRecord$3(option)) return option;
1999
1993
  const optionLabel = getSelectOptionTranslationLabel(option);
2000
- const optionId = normalizeKey(getString(option.id) ?? getString(option.optionId) ?? getString(option.value) ?? (optionLabel ? normalizeKey(optionLabel) : void 0) ?? `option_${index + 1}`) || `option_${index + 1}`;
2001
- const value = getString(option.value) ?? optionId;
1994
+ const optionId = getString(option.id) ?? getString(option.optionId) ?? getString(option.value) ?? (optionLabel ? normalizeKey(optionLabel) : void 0) ?? `option_${index + 1}`;
1995
+ const code = getString(option.code) ?? optionId;
2002
1996
  if (optionLabel) translations.push({
2003
1997
  contentKey: `fields.${fieldId}.options.${optionId}.label`,
2004
1998
  plainText: optionLabel
@@ -2006,7 +2000,7 @@ const normalizeSelectControllerOptions = ({ controller, fieldId, translations })
2006
2000
  return {
2007
2001
  ...option,
2008
2002
  id: optionId,
2009
- value
2003
+ code
2010
2004
  };
2011
2005
  })
2012
2006
  };
@@ -2020,8 +2014,7 @@ const appendOptionTranslationLabels = (translations, option, optionId, path, iss
2020
2014
  };
2021
2015
  const normalizeLooseField = (field) => {
2022
2016
  if (!isRecord$3(field)) return { translations: [] };
2023
- const rawId = getString(field.id) ?? getString(field.fieldId) ?? getString(field.key);
2024
- const id = rawId ? normalizeKey(rawId) : void 0;
2017
+ const id = getString(field.id) ?? getString(field.fieldId);
2025
2018
  if (!id) return { translations: [] };
2026
2019
  const rawType = getString(field.type) ?? getString(field.controlType) ?? "input";
2027
2020
  const type = rawType === "text" || rawType === "email" ? "input" : rawType === "date" ? "datepicker" : rawType === "checkbox" ? "switch" : rawType;
@@ -2085,7 +2078,7 @@ const normalizeLooseField = (field) => {
2085
2078
  return {
2086
2079
  field: {
2087
2080
  id,
2088
- key: getString(field.key) ?? id,
2081
+ variableName: getString(field.variableName) ?? suggestResponseVariableName(getString(field.label) ?? id),
2089
2082
  type,
2090
2083
  required: field.required === true,
2091
2084
  controller: normalizeSelectControllerOptions({
@@ -2114,7 +2107,7 @@ const normalizeFormFieldGroupSelectOptions = (fieldGroups) => {
2114
2107
  ...group,
2115
2108
  fields: group.fields.map((field) => {
2116
2109
  if (!isRecord$3(field)) return field;
2117
- const fieldId = getString(field.id) ?? getString(field.fieldId) ?? getString(field.key);
2110
+ const fieldId = getString(field.id) ?? getString(field.fieldId);
2118
2111
  const controller = isRecord$3(field.controller) ? field.controller : void 0;
2119
2112
  if (!fieldId || !controller || controller.type !== "select") return field;
2120
2113
  return {
@@ -2137,7 +2130,7 @@ const normalizeLooseCreateOperation = (operation, defaultLocale, path, issues) =
2137
2130
  const item = { ...operation.item };
2138
2131
  const translations = [...flattenLooseItemTranslations(operation.translations, `${path}.translations`, issues), ...flattenLooseItemTranslations(item.translations, `${path}.item.translations`, issues)];
2139
2132
  if (!getString(item.id)) {
2140
- const itemId = getString(item.key) ?? getString(item.label);
2133
+ const itemId = getString(item.label);
2141
2134
  if (itemId) item.id = normalizeKey(itemId);
2142
2135
  }
2143
2136
  if (!isRecord$3(item.config) && isRecord$3(operation.config)) item.config = operation.config;
@@ -2150,7 +2143,7 @@ const normalizeLooseCreateOperation = (operation, defaultLocale, path, issues) =
2150
2143
  ...rawConfig,
2151
2144
  ...!Array.isArray(rawConfig.options) && looseOptions ? { options: looseOptions } : {}
2152
2145
  };
2153
- config.id = getString(config.id) ?? getString(item.id) ?? getString(item.key);
2146
+ config.id = getString(config.id) ?? getString(item.id);
2154
2147
  config.maxSelection = normalizeChoiceMaxSelection(config);
2155
2148
  config.shuffleOptions = config.shuffleOptions === true;
2156
2149
  if (Array.isArray(config.options)) config.options = config.options.map((option, optionIndex) => {
@@ -2161,7 +2154,7 @@ const normalizeLooseCreateOperation = (operation, defaultLocale, path, issues) =
2161
2154
  return {
2162
2155
  ...option,
2163
2156
  id: optionId,
2164
- key: getString(option.key) ?? getString(option.value) ?? getString(option.optionId),
2157
+ code: getString(option.code) ?? getString(option.value) ?? getString(option.optionId),
2165
2158
  label: optionLabel
2166
2159
  };
2167
2160
  });
@@ -2171,7 +2164,7 @@ const normalizeLooseCreateOperation = (operation, defaultLocale, path, issues) =
2171
2164
  }
2172
2165
  if (item.itemType === "formItem" && isRecord$3(item.config)) {
2173
2166
  const config = { ...item.config };
2174
- config.id = getString(config.id) ?? getString(item.id) ?? getString(item.key);
2167
+ config.id = getString(config.id) ?? getString(item.id);
2175
2168
  const looseFields = Array.isArray(config.fields) ? config.fields : [];
2176
2169
  const fieldTranslations = [];
2177
2170
  const normalizedLooseFields = looseFields.map((field) => {
@@ -2221,7 +2214,7 @@ const normalizeLooseCreateOperation = (operation, defaultLocale, path, issues) =
2221
2214
  const normalizeLooseFormTranslationOperation = (operation, path, issues) => {
2222
2215
  if (!isRecord$3(operation)) return operation;
2223
2216
  const operationKind = getString(operation.kind) ?? getString(operation.op);
2224
- if (operationKind !== "upsert-form-field-group" && operationKind !== "upsert-form-field" && operationKind !== "set-embedded-form") return operation;
2217
+ if (operationKind !== "upsert-form-field-group" && operationKind !== "upsert-form-field" && operationKind !== "set-embedded-form" && operationKind !== "patch-item-config") return operation;
2225
2218
  if (!Array.isArray(operation.translations)) return operation;
2226
2219
  return {
2227
2220
  ...operation,
@@ -2283,11 +2276,11 @@ const getChoiceOptionsFromRawItem = (rawItem) => {
2283
2276
  return rawOptions.filter(isRecord$2).map((option) => {
2284
2277
  const optionId = getTrimmedString(option.id);
2285
2278
  if (!optionId) return;
2286
- const optionKey = getTrimmedString(option.key);
2279
+ const optionKey = getTrimmedString(option.code);
2287
2280
  return {
2288
2281
  ...option,
2289
2282
  id: optionId,
2290
- ...optionKey ? { key: optionKey } : {}
2283
+ ...optionKey ? { code: optionKey } : {}
2291
2284
  };
2292
2285
  }).filter((option) => Boolean(option));
2293
2286
  };
@@ -2345,14 +2338,14 @@ const withFormFieldGroupsForTarget = (rawItem, fieldGroups, optionId, preserveSi
2345
2338
  const config = isRecord$2(rawItem.config) ? rawItem.config : {};
2346
2339
  return {
2347
2340
  ...rawItem,
2348
- config: transitionFormAuthoringMode({
2341
+ config: normalizeFormItemConfig(transitionFormAuthoringMode({
2349
2342
  config: {
2350
2343
  id: rawItem.id,
2351
2344
  ...config
2352
2345
  },
2353
2346
  fieldGroups,
2354
2347
  preserveSimpleMode
2355
- })
2348
+ }), rawItem.id)
2356
2349
  };
2357
2350
  }
2358
2351
  if (rawItem.itemType !== "choiceItem") return void 0;
@@ -2363,14 +2356,14 @@ const withFormFieldGroupsForTarget = (rawItem, fieldGroups, optionId, preserveSi
2363
2356
  const embeddedForm = isRecord$2(option.embeddedForm) ? option.embeddedForm : {};
2364
2357
  options[optionIndex] = {
2365
2358
  ...option,
2366
- embeddedForm: transitionFormAuthoringMode({
2359
+ embeddedForm: normalizeFormItemConfig(transitionFormAuthoringMode({
2367
2360
  config: {
2368
2361
  id: optionId,
2369
2362
  ...embeddedForm
2370
2363
  },
2371
2364
  fieldGroups,
2372
2365
  preserveSimpleMode
2373
- })
2366
+ }), optionId)
2374
2367
  };
2375
2368
  return withChoiceOptionsOnRawItem(rawItem, options);
2376
2369
  };
@@ -2396,12 +2389,12 @@ const removeDraftTranslationPrefix = ({ itemId, prefix, survey }) => {
2396
2389
  };
2397
2390
  /** Request-local mutable state for canonical, already-validated transitions. */
2398
2391
  var SurveyOperationExecutionSession = class {
2392
+ capabilities;
2399
2393
  #survey;
2400
2394
  #itemsById;
2401
2395
  #parentByChildId = /* @__PURE__ */ new Map();
2402
- #fullKeyById = /* @__PURE__ */ new Map();
2403
- #outlineItemIds;
2404
- constructor(inputSurvey) {
2396
+ constructor(inputSurvey, capabilities) {
2397
+ this.capabilities = capabilities;
2405
2398
  this.#survey = structuredCloneMethod(inputSurvey);
2406
2399
  this.#itemsById = new Map(this.#survey.surveyItems.map((item) => [item.id, item]));
2407
2400
  this.#rebuildParentIndex();
@@ -2429,14 +2422,7 @@ var SurveyOperationExecutionSession = class {
2429
2422
  return descendants;
2430
2423
  }
2431
2424
  resolveItem(identifier) {
2432
- const direct = this.getItem(identifier);
2433
- if (direct) return direct;
2434
- const normalized = identifier.trim().toLowerCase();
2435
- if (!normalized) return void 0;
2436
- for (const itemId of this.#getItemIdsInOutlineOrder()) {
2437
- const item = this.getItem(itemId);
2438
- if (item.key.toLowerCase() === normalized || this.#getFullKey(itemId).toLowerCase() === normalized) return item;
2439
- }
2425
+ return this.getItem(identifier);
2440
2426
  }
2441
2427
  resolveCreateParent(identifier) {
2442
2428
  if (identifier.trim()) return this.resolveItem(identifier);
@@ -2446,42 +2432,6 @@ var SurveyOperationExecutionSession = class {
2446
2432
  #rebuildParentIndex() {
2447
2433
  this.#parentByChildId = /* @__PURE__ */ new Map();
2448
2434
  for (const item of this.#survey.surveyItems) for (const childId of getGroupChildren(item)) if (this.#itemsById.has(childId) && !this.#parentByChildId.has(childId)) this.#parentByChildId.set(childId, item.id);
2449
- this.#fullKeyById = /* @__PURE__ */ new Map();
2450
- this.#outlineItemIds = void 0;
2451
- }
2452
- #getFullKey(itemId, stack = /* @__PURE__ */ new Set()) {
2453
- const cached = this.#fullKeyById.get(itemId);
2454
- if (cached) return cached;
2455
- const item = this.getItem(itemId);
2456
- if (!item || stack.has(itemId)) return item?.key ?? itemId;
2457
- const parentId = this.getParentId(itemId);
2458
- if (!parentId) {
2459
- this.#fullKeyById.set(itemId, item.key);
2460
- return item.key;
2461
- }
2462
- stack.add(itemId);
2463
- const fullKey = `${this.#getFullKey(parentId, stack)}.${item.key}`;
2464
- stack.delete(itemId);
2465
- this.#fullKeyById.set(itemId, fullKey);
2466
- return fullKey;
2467
- }
2468
- #getItemIdsInOutlineOrder() {
2469
- if (this.#outlineItemIds) return this.#outlineItemIds;
2470
- const ordered = [];
2471
- const visited = /* @__PURE__ */ new Set();
2472
- const visit = (itemId) => {
2473
- if (visited.has(itemId)) return;
2474
- const item = this.getItem(itemId);
2475
- if (!item) return;
2476
- visited.add(itemId);
2477
- ordered.push(itemId);
2478
- for (const childId of getGroupChildren(item)) visit(childId);
2479
- };
2480
- const root = this.#survey.surveyItems.find(isRootItem);
2481
- if (root) visit(root.id);
2482
- for (const item of [...this.#survey.surveyItems].sort((left, right) => left.id.localeCompare(right.id))) visit(item.id);
2483
- this.#outlineItemIds = ordered;
2484
- return this.#outlineItemIds;
2485
2435
  }
2486
2436
  hasLocale(locale) {
2487
2437
  return Object.hasOwn(this.#survey.translations ?? {}, locale);
@@ -2512,11 +2462,17 @@ var SurveyOperationExecutionSession = class {
2512
2462
  const previousItem = survey.surveyItems[index];
2513
2463
  survey.surveyItems[index] = item;
2514
2464
  this.#itemsById.set(item.id, item);
2515
- if (previousItem.key !== item.key) this.#fullKeyById = /* @__PURE__ */ new Map();
2516
2465
  const previousChildren = getGroupChildren(previousItem);
2517
2466
  const nextChildren = getGroupChildren(item);
2518
2467
  if (previousChildren.length !== nextChildren.length || previousChildren.some((childId, childIndex) => childId !== nextChildren[childIndex])) this.#rebuildParentIndex();
2519
2468
  };
2469
+ if (operation.kind === "update-response-settings") {
2470
+ const next = previewResponseSettings(createAssistantSurvey(survey, this.capabilities), operation.naming, operation.settings).survey;
2471
+ this.#survey = next;
2472
+ this.#itemsById = new Map(next.surveyItems.map((item) => [item.id, item]));
2473
+ this.#rebuildParentIndex();
2474
+ return;
2475
+ }
2520
2476
  if (operation.kind === "survey-json-patch") {
2521
2477
  survey = applyJsonPatch(survey, operation.patches);
2522
2478
  this.#survey = survey;
@@ -2546,12 +2502,6 @@ var SurveyOperationExecutionSession = class {
2546
2502
  });
2547
2503
  break;
2548
2504
  }
2549
- case "update-item-key":
2550
- replaceItem({
2551
- ...getItem(operation.itemId),
2552
- key: operation.key
2553
- });
2554
- break;
2555
2505
  case "update-item-label": {
2556
2506
  const item = getItem(operation.itemId);
2557
2507
  const metadata = { ...item.metadata ?? {} };
@@ -2594,16 +2544,20 @@ var SurveyOperationExecutionSession = class {
2594
2544
  const next = structuredCloneMethod(block);
2595
2545
  const changes = operation.changes;
2596
2546
  if (changes.source !== void 0) next.source = changes.source;
2597
- if (changes.alt !== void 0) if (changes.alt === null) delete next.alt;
2598
- else next.alt = changes.alt;
2599
- if (changes.caption !== void 0) if (changes.caption === null || changes.caption.length === 0) delete next.caption;
2600
- else next.caption = [{
2601
- type: "paragraph",
2602
- children: [{
2603
- type: "text",
2604
- text: changes.caption
2605
- }]
2606
- }];
2547
+ if (changes.alt !== void 0) {
2548
+ if (changes.alt === null) delete next.alt;
2549
+ else next.alt = changes.alt;
2550
+ }
2551
+ if (changes.caption !== void 0) {
2552
+ if (changes.caption === null || changes.caption.length === 0) delete next.caption;
2553
+ else next.caption = [{
2554
+ type: "paragraph",
2555
+ children: [{
2556
+ type: "text",
2557
+ text: changes.caption
2558
+ }]
2559
+ }];
2560
+ }
2607
2561
  const size = { ...next.size ?? {} };
2608
2562
  for (const key of [
2609
2563
  "width",
@@ -2634,9 +2588,26 @@ var SurveyOperationExecutionSession = class {
2634
2588
  }
2635
2589
  case "patch-item-config": {
2636
2590
  const item = getItem(operation.itemId);
2591
+ const config = applyJsonPatch(item.config ?? {}, operation.patches);
2637
2592
  replaceItem({
2638
2593
  ...item,
2639
- config: applyJsonPatch(item.config ?? {}, operation.patches)
2594
+ config
2595
+ });
2596
+ if (item.itemType === "choiceMatrix" || item.itemType === "formMatrix") {
2597
+ const translations = new SurveyTranslations(survey.translations);
2598
+ const content = translations.getItemTranslations(item.id)?.clone() ?? new SurveyItemTranslations();
2599
+ if (item.itemType === "formMatrix") cleanFormMatrixTranslations(normalizeFormMatrixConfig(item.config), normalizeFormMatrixConfig(config), content);
2600
+ else {
2601
+ const keys = new Set(getChoiceMatrixTranslationKeys(normalizeChoiceMatrixConfig(config)));
2602
+ for (const key of getChoiceMatrixTranslationKeys(normalizeChoiceMatrixConfig(item.config))) if (!keys.has(key)) content.removeContentKey(key);
2603
+ }
2604
+ translations.setItemTranslations(item.id, content);
2605
+ survey.translations = translations.serialize();
2606
+ }
2607
+ updateDraftItemTranslations({
2608
+ itemId: item.id,
2609
+ survey,
2610
+ translations: operation.translations ?? []
2640
2611
  });
2641
2612
  break;
2642
2613
  }
@@ -2645,7 +2616,7 @@ var SurveyOperationExecutionSession = class {
2645
2616
  const options = getChoiceOptionsFromRawItem(item);
2646
2617
  options.splice(operation.index ?? options.length, 0, {
2647
2618
  id: operation.option.id,
2648
- ...operation.option.key ? { key: operation.option.key } : {}
2619
+ ...operation.option.code ? { code: operation.option.code } : {}
2649
2620
  });
2650
2621
  replaceItem(withChoiceOptionsOnRawItem(item, options));
2651
2622
  updateDraftItemTranslations({
@@ -2930,16 +2901,46 @@ var SurveyOperationExecutionSession = class {
2930
2901
  * adapters use it to decide domain results before recording them through the
2931
2902
  * editor's normal mutation and history APIs.
2932
2903
  */
2933
- const executeSurveyOperationsOnRawSurvey = ({ operations, survey }) => {
2934
- const session = new SurveyOperationExecutionSession(survey);
2904
+ const executeSurveyOperationsOnRawSurvey = ({ operations, survey, capabilities }) => {
2905
+ const session = new SurveyOperationExecutionSession(survey, capabilities);
2935
2906
  for (const operation of operations) session.apply(operation);
2936
2907
  return session.serialize();
2937
2908
  };
2938
- /** @deprecated Prefer `executeSurveyOperationsOnRawSurvey` for new integrations. */
2939
- const applyValidatedSurveyOperationsToDraft = executeSurveyOperationsOnRawSurvey;
2909
+ //#endregion
2910
+ //#region src/engine/reference-diagnostics.ts
2911
+ const MAX_REFERENCE_LENGTH = 256;
2912
+ const MAX_DISTANCE = 3;
2913
+ const spellingDistance = (left, right) => {
2914
+ if (Math.abs(left.length - right.length) > MAX_DISTANCE) return Infinity;
2915
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
2916
+ for (let row = 1; row <= left.length; row++) {
2917
+ const current = [row];
2918
+ for (let column = 1; column <= right.length; column++) current[column] = Math.min(current[column - 1] + 1, previous[column] + 1, previous[column - 1] + (left[row - 1] === right[column - 1] ? 0 : 1));
2919
+ if (Math.min(...current) > MAX_DISTANCE) return Infinity;
2920
+ previous = current;
2921
+ }
2922
+ return previous[right.length];
2923
+ };
2924
+ const suggestExactReferences = (supplied, known) => {
2925
+ if (supplied.length > MAX_REFERENCE_LENGTH) return [];
2926
+ const candidates = [];
2927
+ for (const value of new Set(known)) {
2928
+ if (value.length > MAX_REFERENCE_LENGTH || value === supplied) continue;
2929
+ const distance = supplied.endsWith(`.${value}`) ? 0 : spellingDistance(supplied, value);
2930
+ if (distance <= MAX_DISTANCE) candidates.push({
2931
+ value,
2932
+ distance
2933
+ });
2934
+ }
2935
+ candidates.sort((left, right) => left.distance - right.distance || left.value.localeCompare(right.value));
2936
+ return candidates.filter((entry) => entry.distance === candidates[0]?.distance).slice(0, 3).map(({ value }) => value);
2937
+ };
2938
+ const formatExactReferenceSuggestions = (supplied, known, label) => {
2939
+ const suggestions = suggestExactReferences(supplied, known);
2940
+ return suggestions.length ? ` Similar valid ${label}: ${suggestions.map((value) => JSON.stringify(value)).join(", ")}. Confirm the intended target and copy its exact identifier.` : "";
2941
+ };
2940
2942
  //#endregion
2941
2943
  //#region src/engine/operations.ts
2942
- const ITEM_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
2943
2944
  const MAX_SURVEY_ASSISTANT_OPERATIONS_PER_PROPOSAL = 200;
2944
2945
  const MAX_SURVEY_ASSISTANT_OPERATIONS_PER_TURN_DRAFT = 1e3;
2945
2946
  const CHOICE_OPTION_LABEL_CONTENT_KEY_PATTERN = /^options\.([^.]+)\.label$/;
@@ -2981,7 +2982,8 @@ const getCreateItemNestedTranslations = (item) => {
2981
2982
  const isSurveyJsonPatchOperation = (operation) => operation.kind === "survey-json-patch";
2982
2983
  const getPatchTouchedPaths = (operation) => operation.patches.flatMap((patch) => "from" in patch ? [patch.path, patch.from] : [patch.path]);
2983
2984
  const touchesSurveySchemaPath = (path) => {
2984
- return parseJsonPointer(path)[0] === "$schema";
2985
+ const segments = parseJsonPointer(path);
2986
+ return segments[0] === "$schema" || segments[0] === "schemaVersion";
2985
2987
  };
2986
2988
  const isTranslationContainerMutation = (patch) => patch.op !== "test";
2987
2989
  const validateTranslationPatchSafety = ({ operation, survey }) => {
@@ -3134,6 +3136,8 @@ const collectRequiredLocalizedContent = (survey, snapshot) => {
3134
3136
  for (const item of survey.surveyItems) {
3135
3137
  const manifest = manifestByItemType.get(item.itemType);
3136
3138
  for (const surface of manifest?.contentSurfaces ?? []) if (surface.requiredOnCreate && !surface.pattern) addRequiredLocalizedContent(required, item.id, surface.key);
3139
+ const adapter = snapshot.trustedCapabilities?.byItemType.get(item.itemType);
3140
+ if (!adapter?.validateItem?.(item).some((issue) => issue.severity === "error")) for (const key of adapter?.requiredCreateContentKeys?.(item) ?? []) addRequiredLocalizedContent(required, item.id, key);
3137
3141
  const config = isRecord$2(item.config) ? item.config : void 0;
3138
3142
  if (item.itemType === "formItem") {
3139
3143
  collectFieldLocalizedContent({
@@ -3164,17 +3168,46 @@ const hasNonEmptySurveyTranslation = (survey, { itemId, contentKey }) => Object.
3164
3168
  const content = localeTranslations?.itemTranslations?.[itemId]?.[contentKey];
3165
3169
  return isContent(content) && !isContentEmpty(content);
3166
3170
  });
3171
+ const validateIntroducedCapabilityContent = (before, after, snapshot) => {
3172
+ const issues = [];
3173
+ for (const item of after.surveyItems) {
3174
+ const adapter = snapshot.trustedCapabilities?.byItemType.get(item.itemType);
3175
+ if (!adapter?.validContentKeys || adapter.validateItem?.(item).some((issue) => issue.severity === "error")) continue;
3176
+ const previous = before.surveyItems.find((entry) => entry.id === item.id);
3177
+ const requiredBefore = new Set(previous ? adapter.requiredCreateContentKeys?.(previous) ?? [] : []);
3178
+ for (const contentKey of adapter.requiredCreateContentKeys?.(item) ?? []) if (!hasNonEmptySurveyTranslation(after, {
3179
+ itemId: item.id,
3180
+ contentKey
3181
+ }) && (!requiredBefore.has(contentKey) || hasNonEmptySurveyTranslation(before, {
3182
+ itemId: item.id,
3183
+ contentKey
3184
+ }))) issues.push(createIssue({
3185
+ code: "missing-item-content",
3186
+ itemId: item.id,
3187
+ message: `Provide a non-empty translation for "${contentKey}" in the same proposal as the configuration edit. Use the item capability's exact content keys.`
3188
+ }));
3189
+ const validKeys = new Set(adapter.validContentKeys(item));
3190
+ for (const [locale, translations] of Object.entries(after.translations ?? {})) for (const [contentKey, content] of Object.entries(translations.itemTranslations?.[item.id] ?? {})) if (!validKeys.has(contentKey) && JSON.stringify(content) !== JSON.stringify(before.translations?.[locale]?.itemTranslations?.[item.id]?.[contentKey])) issues.push(createIssue({
3191
+ code: "invalid-item-content-key",
3192
+ itemId: item.id,
3193
+ message: `"${contentKey}" is not a rendered content key on ${item.itemType}.${formatExactReferenceSuggestions(contentKey, validKeys, "content keys")} Inspect the item's translations/configuration for exact keys; its item-types reference describes the content-key grammar.`
3194
+ }));
3195
+ }
3196
+ return issues;
3197
+ };
3167
3198
  const validateNewRawPatchLocalizedContent = ({ operation, originalSurvey, patchedSurvey, snapshot }) => {
3199
+ const capabilityIssues = validateIntroducedCapabilityContent(originalSurvey, patchedSurvey, snapshot);
3168
3200
  const existingRequired = collectRequiredLocalizedContent(originalSurvey, snapshot);
3169
3201
  const existingMissing = new Set(Array.from(existingRequired).filter(([, binding]) => !hasNonEmptySurveyTranslation(originalSurvey, binding)).map(([identity]) => identity));
3170
3202
  const missing = Array.from(collectRequiredLocalizedContent(patchedSurvey, snapshot)).filter(([identity, binding]) => {
3171
3203
  if (hasNonEmptySurveyTranslation(patchedSurvey, binding)) return false;
3172
3204
  return !existingMissing.has(identity);
3173
3205
  }).map(([, binding]) => binding);
3174
- if (missing.length === 0) return [];
3175
- return [createIssue({
3206
+ if (missing.length === 0) return capabilityIssues;
3207
+ const missingPaths = missing.map(({ itemId, contentKey }) => `/translations/<locale>/itemTranslations/${itemId}/${contentKey}`);
3208
+ return [...capabilityIssues, createIssue({
3176
3209
  code: "missing-raw-patch-localized-content",
3177
- message: `The raw patch introduces respondent-visible content without its required translation. Add non-empty Content values in the same patches array for: ${formatBoundedDiagnosticValues(missing.map(({ itemId, contentKey }) => `/translations/<locale>/itemTranslations/${itemId}/${contentKey}`))}. Content keys use config ids exactly and are case-sensitive; for embedded fields, embeddedForms.<optionId> uses the choice option's id, not its key or embeddedForm id.`,
3210
+ message: `The raw patch introduces respondent-visible content without its required translation. Add non-empty Content values in the same patches array for: ${formatBoundedDiagnosticValues(missingPaths)}. Content keys use config ids exactly and are case-sensitive; for embedded fields, embeddedForms.<optionId> uses the choice option's ID, not its export code or embeddedForm ID.`,
3178
3211
  operationIndex: 0,
3179
3212
  operationId: getOperationId(operation),
3180
3213
  itemId: missing[0]?.itemId
@@ -3198,7 +3231,6 @@ const validateNewRawPatchFormLayouts = ({ operation, originalSurvey, patchedSurv
3198
3231
  const createStructureIssue = ({ code, message, operationId, itemId }) => createIssue({
3199
3232
  code,
3200
3233
  message,
3201
- operationIndex: 0,
3202
3234
  operationId,
3203
3235
  itemId
3204
3236
  });
@@ -3212,7 +3244,7 @@ const buildResponseSlotTypesForDraft = ({ currentItem, currentItems, snapshot })
3212
3244
  return deriveResponseSlotTypes({
3213
3245
  ...snapshot.survey,
3214
3246
  surveyItems: draftItems
3215
- });
3247
+ }, snapshot.trustedCapabilities);
3216
3248
  };
3217
3249
  const isJsonExpression = (value) => isRecord$2(value) && typeof value.type === "string";
3218
3250
  const validateCreateItemExpressions = ({ issues, item, operationId, operationIndex, responseSlotTypes }) => {
@@ -3263,18 +3295,18 @@ const validateSurveyTreeStructure = (survey, operationId, availableItemTypes) =>
3263
3295
  const issues = [];
3264
3296
  if (!isRecord$2(survey)) return [createStructureIssue({
3265
3297
  code: "patched-survey-not-object",
3266
- message: "The patched survey must be a JSON object.",
3298
+ message: "The survey must be a JSON object.",
3267
3299
  operationId
3268
3300
  })];
3269
3301
  if (typeof survey.$schema !== "string" || survey.$schema.trim().length === 0) issues.push(createStructureIssue({
3270
3302
  code: "missing-survey-schema",
3271
- message: "The patched survey must keep a non-empty $schema value.",
3303
+ message: "The survey must keep a non-empty $schema value.",
3272
3304
  operationId
3273
3305
  }));
3274
3306
  if (!Array.isArray(survey.surveyItems) || survey.surveyItems.length === 0) {
3275
3307
  issues.push(createStructureIssue({
3276
3308
  code: "missing-survey-items",
3277
- message: "The patched survey must contain at least one survey item.",
3309
+ message: "The survey must contain at least one survey item.",
3278
3310
  operationId
3279
3311
  }));
3280
3312
  return issues;
@@ -3291,33 +3323,26 @@ const validateSurveyTreeStructure = (survey, operationId, availableItemTypes) =>
3291
3323
  continue;
3292
3324
  }
3293
3325
  const itemId = getTrimmedString(item.id);
3294
- const itemKey = getTrimmedString(item.key);
3295
3326
  const itemType = getTrimmedString(item.itemType);
3296
- if (!itemId || !itemKey || !itemType) {
3327
+ if (!itemId || !itemType) {
3297
3328
  issues.push(createStructureIssue({
3298
3329
  code: "invalid-survey-item-identity",
3299
- message: `Survey item at index ${itemIndex} must have non-empty id, key, and itemType fields.`,
3330
+ message: `Survey item at index ${itemIndex} must have non-empty id and itemType fields.`,
3300
3331
  operationId,
3301
3332
  itemId
3302
3333
  }));
3303
3334
  continue;
3304
3335
  }
3305
- if (!availableItemTypes.has(itemType)) issues.push(createStructureIssue({
3336
+ if (availableItemTypes && !availableItemTypes.has(itemType)) issues.push(createStructureIssue({
3306
3337
  code: "unknown-item-type",
3307
3338
  message: `Item "${itemId}" uses item type "${itemType}", which is not available in the current editor registry.`,
3308
3339
  operationId,
3309
3340
  itemId
3310
3341
  }));
3311
- if (!ITEM_KEY_PATTERN.test(itemKey)) issues.push(createStructureIssue({
3312
- code: "invalid-item-key",
3313
- message: `Item "${itemId}" has an invalid key. Keys must contain only letters, numbers, underscores, or hyphens.`,
3314
- operationId,
3315
- itemId
3316
- }));
3317
3342
  if (itemsById.has(itemId)) {
3318
3343
  issues.push(createStructureIssue({
3319
3344
  code: "duplicate-item-id",
3320
- message: `The patched survey contains duplicate item id "${itemId}".`,
3345
+ message: `The survey contains duplicate item id "${itemId}".`,
3321
3346
  operationId,
3322
3347
  itemId
3323
3348
  }));
@@ -3329,7 +3354,7 @@ const validateSurveyTreeStructure = (survey, operationId, availableItemTypes) =>
3329
3354
  }
3330
3355
  if (rootItems.length !== 1) issues.push(createStructureIssue({
3331
3356
  code: "invalid-root-count",
3332
- message: rootItems.length === 0 ? "The patched survey must contain one root group item." : "The patched survey cannot contain more than one root group item.",
3357
+ message: `The survey must contain exactly one root group item; found ${rootItems.length} (${rootItems.map((item) => item.id).sort().join(", ")}).`,
3333
3358
  operationId
3334
3359
  }));
3335
3360
  else if (rootItems[0].itemType !== ReservedSurveyItemTypes.Group) issues.push(createStructureIssue({
@@ -3405,7 +3430,15 @@ const validateSurveyTreeStructure = (survey, operationId, availableItemTypes) =>
3405
3430
  }
3406
3431
  }
3407
3432
  for (const item of itemsById.values()) {
3408
- if (isRootItem(item)) continue;
3433
+ if (isRootItem(item)) {
3434
+ if (parentByChildId.has(item.id)) issues.push(createStructureIssue({
3435
+ code: "root-has-parent",
3436
+ message: "The survey root cannot have a parent.",
3437
+ operationId,
3438
+ itemId: item.id
3439
+ }));
3440
+ continue;
3441
+ }
3409
3442
  if (!parentByChildId.has(item.id)) issues.push(createStructureIssue({
3410
3443
  code: "orphan-survey-item",
3411
3444
  message: `Item "${item.id}" is not reachable from any group parent.`,
@@ -3413,21 +3446,6 @@ const validateSurveyTreeStructure = (survey, operationId, availableItemTypes) =>
3413
3446
  itemId: item.id
3414
3447
  }));
3415
3448
  }
3416
- for (const item of itemsById.values()) {
3417
- const siblingKeys = /* @__PURE__ */ new Map();
3418
- for (const childId of getGroupChildren(item)) {
3419
- const child = itemsById.get(childId);
3420
- if (!child) continue;
3421
- const conflictingChildId = siblingKeys.get(child.key);
3422
- if (conflictingChildId) issues.push(createStructureIssue({
3423
- code: "duplicate-sibling-key",
3424
- message: `Siblings "${conflictingChildId}" and "${childId}" both use key "${child.key}".`,
3425
- operationId,
3426
- itemId: childId
3427
- }));
3428
- siblingKeys.set(child.key, childId);
3429
- }
3430
- }
3431
3449
  const rootItem = rootItems[0];
3432
3450
  if (rootItem) {
3433
3451
  const visiting = /* @__PURE__ */ new Set();
@@ -3436,7 +3454,7 @@ const validateSurveyTreeStructure = (survey, operationId, availableItemTypes) =>
3436
3454
  if (visiting.has(itemId)) {
3437
3455
  issues.push(createStructureIssue({
3438
3456
  code: "survey-item-cycle",
3439
- message: `The patched survey contains a cycle through item "${itemId}" (${path.join(" -> ")} -> ${itemId}).`,
3457
+ message: `The survey contains a cycle through item "${itemId}" (${path.join(" -> ")} -> ${itemId}).`,
3440
3458
  operationId,
3441
3459
  itemId
3442
3460
  }));
@@ -3450,9 +3468,38 @@ const validateSurveyTreeStructure = (survey, operationId, availableItemTypes) =>
3450
3468
  visited.add(itemId);
3451
3469
  };
3452
3470
  visit(rootItem.id, []);
3471
+ for (const item of itemsById.values()) if (!visited.has(item.id)) {
3472
+ issues.push(createStructureIssue({
3473
+ code: "unreachable-survey-item",
3474
+ message: `Item "${item.id}" is not reachable from the survey root.`,
3475
+ operationId,
3476
+ itemId: item.id
3477
+ }));
3478
+ visit(item.id, []);
3479
+ }
3453
3480
  }
3454
3481
  return issues;
3455
3482
  };
3483
+ /** Preserve existing defects without accepting changed or additional violations. */
3484
+ const findIntroducedSurveyStructureIssues = ({ before, after }) => {
3485
+ const fingerprint = (issue) => JSON.stringify([
3486
+ issue.code,
3487
+ issue.itemId,
3488
+ issue.message
3489
+ ]);
3490
+ const remaining = /* @__PURE__ */ new Map();
3491
+ for (const issue of before) {
3492
+ const key = fingerprint(issue);
3493
+ remaining.set(key, (remaining.get(key) ?? 0) + 1);
3494
+ }
3495
+ return after.filter((issue) => {
3496
+ const key = fingerprint(issue);
3497
+ const count = remaining.get(key) ?? 0;
3498
+ if (count === 0) return true;
3499
+ remaining.set(key, count - 1);
3500
+ return false;
3501
+ });
3502
+ };
3456
3503
  const collectIntroducedExpressionIntegrityFindings = ({ after, before, operationId, operationIndex, snapshot }) => {
3457
3504
  return findIntroducedExpressionReferenceDiagnosticsIncludingAdvisories({
3458
3505
  before: analyzeSurveyAssistantExpressionReferences(before, snapshot.trustedCapabilities),
@@ -3574,13 +3621,22 @@ const validateSurveyJsonPatchOperation = ({ allowExpressionJsonPatches, operatio
3574
3621
  operationId
3575
3622
  }));
3576
3623
  }
3577
- warnings.push(...collectIntroducedExpressionIntegrityFindings({
3578
- after: patchedSurvey,
3579
- before: snapshot.survey,
3580
- operationIndex: 0,
3581
- operationId,
3582
- snapshot
3583
- }));
3624
+ if (issues.length === 0) try {
3625
+ warnings.push(...collectIntroducedExpressionIntegrityFindings({
3626
+ after: patchedSurvey,
3627
+ before: snapshot.survey,
3628
+ operationIndex: 0,
3629
+ operationId,
3630
+ snapshot
3631
+ }));
3632
+ } catch (error) {
3633
+ issues.push(createIssue({
3634
+ code: "expression-integrity-check-failed",
3635
+ operationIndex: 0,
3636
+ operationId,
3637
+ message: error instanceof Error ? error.message : String(error)
3638
+ }));
3639
+ }
3584
3640
  if (getRawItemsFromSurvey(patchedSurvey).length !== getRawSurveyItems(snapshot).length) warnings.push(createIssue({
3585
3641
  severity: "warning",
3586
3642
  code: "raw-patch-changed-item-count",
@@ -3588,6 +3644,8 @@ const validateSurveyJsonPatchOperation = ({ allowExpressionJsonPatches, operatio
3588
3644
  operationIndex: 0,
3589
3645
  operationId
3590
3646
  }));
3647
+ const previousResponseIssues = new Set(getAssistantResponseSchemaIssues(snapshot.survey, snapshot.trustedCapabilities).map((issue) => JSON.stringify(issue)));
3648
+ issues.push(...getAssistantResponseSchemaIssues(patchedSurvey, snapshot.trustedCapabilities).filter((issue) => issue.code === "invalid-survey-visibility" || !previousResponseIssues.has(JSON.stringify(issue))));
3591
3649
  return {
3592
3650
  valid: issues.length === 0,
3593
3651
  issues,
@@ -3605,6 +3663,7 @@ const createIssue = ({ severity = "error", ...input }) => ({
3605
3663
  });
3606
3664
  const getOperationId = (operation) => operation.id;
3607
3665
  const isSurveyLevelOperation = (operation) => [
3666
+ "update-response-settings",
3608
3667
  "update-survey-metadata",
3609
3668
  "update-survey-pagination",
3610
3669
  "update-survey-translation",
@@ -3624,6 +3683,7 @@ const getOperationTarget = (operation) => {
3624
3683
  case "remove-form-field":
3625
3684
  case "move-form-field": return operation.target.itemId;
3626
3685
  case "reorder-items": return operation.parentItemId;
3686
+ case "update-response-settings":
3627
3687
  case "update-survey-metadata":
3628
3688
  case "update-survey-pagination":
3629
3689
  case "update-survey-translation":
@@ -3635,7 +3695,7 @@ const getOperationTarget = (operation) => {
3635
3695
  default: return operation.itemId;
3636
3696
  }
3637
3697
  };
3638
- const KNOWN_CHOICE_OPTION_INPUT_KEYS = new Set([
3698
+ const KNOWN_CHOICE_OPTION_INPUT_KEYS = /* @__PURE__ */ new Set([
3639
3699
  "id",
3640
3700
  "optionId",
3641
3701
  "label",
@@ -3643,7 +3703,7 @@ const KNOWN_CHOICE_OPTION_INPUT_KEYS = new Set([
3643
3703
  "title",
3644
3704
  "name",
3645
3705
  "value",
3646
- "key",
3706
+ "code",
3647
3707
  "hasDisplayCondition",
3648
3708
  "embeddedFormItemId",
3649
3709
  "embeddedForm"
@@ -3698,18 +3758,7 @@ const getItemTranslationText = ({ contentKey, getContent, itemId, locale }) => {
3698
3758
  return content ? getContentPlainText(content).trim() : void 0;
3699
3759
  };
3700
3760
  const resolveChoiceOption = ({ getContent, itemId, locale, optionRef, options }) => {
3701
- const normalizedRef = optionRef.trim();
3702
- const normalizedSearchText = normalizedRef.toLowerCase();
3703
- for (const option of options) if (option.id === normalizedRef || option.key === normalizedRef) return option;
3704
- const labelMatches = options.filter((option) => {
3705
- return getItemTranslationText({
3706
- contentKey: getChoiceOptionLabelContentKey(option.id),
3707
- getContent,
3708
- itemId,
3709
- locale
3710
- })?.toLowerCase() === normalizedSearchText;
3711
- });
3712
- return labelMatches.length === 1 ? labelMatches[0] : void 0;
3761
+ return options.find((option) => option.id === optionRef);
3713
3762
  };
3714
3763
  const createChoiceOptionsConflictIssue = ({ code, itemId, message, operationId, operationIndex }) => createIssue({
3715
3764
  code,
@@ -3740,7 +3789,7 @@ const normalizeChoiceOptionInput = ({ defaultLocale, option, usedIds }) => {
3740
3789
  },
3741
3790
  option: {
3742
3791
  id: optionId,
3743
- ...optionKey ? { key: optionKey } : {}
3792
+ ...optionKey ? { code: optionKey } : {}
3744
3793
  }
3745
3794
  };
3746
3795
  }
@@ -3751,9 +3800,11 @@ const normalizeChoiceOptionInput = ({ defaultLocale, option, usedIds }) => {
3751
3800
  "title",
3752
3801
  "name"
3753
3802
  ]);
3754
- const optionId = ensureUniqueId(getFirstStringProperty(option, ["id", "optionId"]) ?? getFirstStringProperty(option, ["value", "key"]) ?? label, usedIds);
3755
- const keyCandidate = getFirstStringProperty(option, ["key", "value"]) ?? label;
3756
- const optionKey = keyCandidate ? normalizeChoiceOptionKey(keyCandidate) : void 0;
3803
+ const idCandidate = getFirstStringProperty(option, ["id", "optionId"]) ?? getFirstStringProperty(option, ["value", "key"]) ?? label;
3804
+ const optionId = getTrimmedString(option.id) ?? ensureUniqueId(idCandidate, usedIds);
3805
+ usedIds.add(optionId);
3806
+ const keyCandidate = getFirstStringProperty(option, ["code"]) ?? label;
3807
+ const optionKey = getTrimmedString(option.code) ?? (keyCandidate ? normalizeChoiceOptionKey(keyCandidate) : void 0);
3757
3808
  const embeddedFormItemId = getTrimmedString(option.embeddedFormItemId);
3758
3809
  const unrecognizedKeys = getUnrecognizedChoiceOptionKeys(option);
3759
3810
  return {
@@ -3764,7 +3815,7 @@ const normalizeChoiceOptionInput = ({ defaultLocale, option, usedIds }) => {
3764
3815
  } : void 0,
3765
3816
  option: {
3766
3817
  id: optionId,
3767
- ...optionKey ? { key: optionKey } : {},
3818
+ ...optionKey ? { code: optionKey } : {},
3768
3819
  ...option.hasDisplayCondition === true ? { hasDisplayCondition: true } : {},
3769
3820
  ...embeddedFormItemId ? { embeddedFormItemId } : {},
3770
3821
  ...isRecord$2(option.embeddedForm) ? { embeddedForm: option.embeddedForm } : {}
@@ -3819,7 +3870,7 @@ const normalizeChoiceItemCreateOperation = (operation, defaultLocale) => {
3819
3870
  usedIds.add(optionId);
3820
3871
  normalizedOptions.push({
3821
3872
  id: optionId,
3822
- key: normalizeChoiceOptionKey(labelText)
3873
+ code: normalizeChoiceOptionKey(labelText)
3823
3874
  });
3824
3875
  }
3825
3876
  return {
@@ -3984,7 +4035,8 @@ const findNestedFieldsArrayPath = (value, path, depth) => {
3984
4035
  if (Array.isArray(value.fields) && value.fields.length > 0) return path ? `${path}.fields` : "fields";
3985
4036
  for (const [key, nested] of Object.entries(value)) {
3986
4037
  if (key === "fields" || !isRecord$2(nested)) continue;
3987
- const found = findNestedFieldsArrayPath(nested, path ? `${path}.${key}` : key, depth + 1);
4038
+ const nestedPath = path ? `${path}.${key}` : key;
4039
+ const found = findNestedFieldsArrayPath(nested, nestedPath, depth + 1);
3988
4040
  if (found) return found;
3989
4041
  }
3990
4042
  };
@@ -4155,7 +4207,6 @@ const normalizeOperation = (operation, resolvedItemId) => {
4155
4207
  item: {
4156
4208
  ...item,
4157
4209
  id: operation.item.id?.trim(),
4158
- key: operation.item.key.trim(),
4159
4210
  itemType: operation.item.itemType.trim()
4160
4211
  },
4161
4212
  translations: translations.map((translation) => ({
@@ -4165,11 +4216,6 @@ const normalizeOperation = (operation, resolvedItemId) => {
4165
4216
  }))
4166
4217
  };
4167
4218
  }
4168
- case "update-item-key": return {
4169
- ...operation,
4170
- itemId: resolvedItemId,
4171
- key: operation.key.trim()
4172
- };
4173
4219
  case "update-item-label": return {
4174
4220
  ...operation,
4175
4221
  itemId: resolvedItemId,
@@ -4204,7 +4250,12 @@ const normalizeOperation = (operation, resolvedItemId) => {
4204
4250
  };
4205
4251
  case "patch-item-config": return {
4206
4252
  ...operation,
4207
- itemId: resolvedItemId
4253
+ itemId: resolvedItemId,
4254
+ ...operation.translations ? { translations: operation.translations.map((translation) => ({
4255
+ ...translation,
4256
+ locale: translation.locale.trim(),
4257
+ contentKey: translation.contentKey.trim()
4258
+ })) } : {}
4208
4259
  };
4209
4260
  case "upsert-form-field-group":
4210
4261
  case "remove-form-field-group":
@@ -4232,7 +4283,7 @@ const normalizeOperation = (operation, resolvedItemId) => {
4232
4283
  option: {
4233
4284
  ...operation.option,
4234
4285
  id: operation.option.id?.trim(),
4235
- key: operation.option.key?.trim(),
4286
+ code: operation.option.code?.trim(),
4236
4287
  label: operation.option.label.trim()
4237
4288
  }
4238
4289
  };
@@ -4271,6 +4322,7 @@ const normalizeOperation = (operation, resolvedItemId) => {
4271
4322
  parentItemId: resolvedItemId,
4272
4323
  itemIds: operation.itemIds.map((itemId) => itemId.trim())
4273
4324
  };
4325
+ case "update-response-settings":
4274
4326
  case "update-survey-metadata":
4275
4327
  case "update-survey-pagination":
4276
4328
  case "set-image-asset":
@@ -4295,20 +4347,6 @@ const createEmptyValidationResult = (issue, operations) => ({
4295
4347
  warnings: [],
4296
4348
  normalizedOperations: operations
4297
4349
  });
4298
- const getSiblingKeyConflict = ({ deletedItemIds, getItemById, getParentId, itemId, key }) => {
4299
- const parentId = getParentId(itemId);
4300
- if (!parentId) return;
4301
- for (const siblingId of getGroupChildren(getItemById(parentId))) {
4302
- if (siblingId === itemId || deletedItemIds.has(siblingId)) continue;
4303
- if (getItemById(siblingId)?.key === key) return siblingId;
4304
- }
4305
- };
4306
- const getKeyConflictInParent = ({ deletedItemIds, getItemById, key, parentId }) => {
4307
- for (const siblingId of getGroupChildren(getItemById(parentId))) {
4308
- if (deletedItemIds.has(siblingId)) continue;
4309
- if (getItemById(siblingId)?.key === key) return siblingId;
4310
- }
4311
- };
4312
4350
  const buildSurveyDocumentVersion = (snapshot) => ({ ...snapshot.version });
4313
4351
  const resolveEngineContext = (snapshot) => "document" in snapshot ? snapshot : createSurveyAssistantEngineContext(snapshot);
4314
4352
  const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOperations = 200, operations, snapshot: snapshotInput }) => {
@@ -4341,14 +4379,14 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4341
4379
  }
4342
4380
  const turnStartItemsById = mapRawItemsById(snapshot);
4343
4381
  const outlineById = mapOutlineById(snapshot);
4344
- const indexById = mapKeyIndexById(snapshot);
4382
+ const indexById = mapItemIndexById(snapshot);
4345
4383
  const itemTypeManifestByType = new Map(snapshot.capabilities.itemTypes.map((entry) => [entry.itemType, entry]));
4346
4384
  const deterministicCreateItemTypes = new Set(snapshot.capabilities.itemTypes.filter((entry) => entry.create === "deterministic").map((entry) => entry.itemType));
4347
4385
  const issues = [];
4348
4386
  const warnings = [];
4349
4387
  const normalizedOperations = [];
4350
4388
  const deletedItemIds = /* @__PURE__ */ new Set();
4351
- const executionSession = new SurveyOperationExecutionSession(snapshot.survey);
4389
+ const executionSession = new SurveyOperationExecutionSession(snapshot.survey, snapshot.trustedCapabilities);
4352
4390
  const getCurrentItemTranslationContent = (itemId, locale, contentKey) => executionSession.getItemTranslationContent(itemId, locale, contentKey);
4353
4391
  const hasCurrentNonEmptyItemTranslation = (itemId, contentKey) => executionSession.hasNonEmptyItemTranslation(itemId, contentKey);
4354
4392
  const introducedLocaleWarnings = /* @__PURE__ */ new Set();
@@ -4377,7 +4415,7 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4377
4415
  } catch (error) {
4378
4416
  transitionFailed = true;
4379
4417
  issues.push(createIssue({
4380
- code: "expression-integrity-check-failed",
4418
+ code: "operation-apply-failed",
4381
4419
  message: `The accepted operation could not be applied to prospective survey state: ${error instanceof Error ? error.message : String(error)}`,
4382
4420
  operationIndex,
4383
4421
  operationId
@@ -4404,7 +4442,7 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4404
4442
  if (!parent) {
4405
4443
  issues.push(createIssue({
4406
4444
  code: "target-parent-not-found",
4407
- message: normalized.target.parentItemId.length > 0 ? `No parent group matching "${normalized.target.parentItemId}" was found.` : "No root group parent could be inferred for this create-item operation.",
4445
+ message: normalized.target.parentItemId.length > 0 ? `No parent group matching "${normalized.target.parentItemId}" was found. Available parent group IDs: ${formatBoundedDiagnosticValues(executionSession.getItems().filter((entry) => entry.itemType === ReservedSurveyItemTypes.Group).map((entry) => JSON.stringify(entry.id)))}. Use the exact ID, not its editor name or the survey key.` : "No root group parent could be inferred for this create-item operation.",
4408
4446
  operationIndex,
4409
4447
  operationId
4410
4448
  }));
@@ -4441,16 +4479,6 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4441
4479
  }));
4442
4480
  return;
4443
4481
  }
4444
- if (!ITEM_KEY_PATTERN.test(normalized.item.key)) {
4445
- issues.push(createIssue({
4446
- code: "invalid-item-key",
4447
- message: "Item keys must contain only letters, numbers, underscores, or hyphens.",
4448
- operationIndex,
4449
- operationId,
4450
- itemId
4451
- }));
4452
- return;
4453
- }
4454
4482
  if (normalized.item.itemType === "root" || isRecord$2(normalized.item.config) && normalized.item.config.isRoot === true) {
4455
4483
  issues.push(createIssue({
4456
4484
  code: "cannot-create-root",
@@ -4471,22 +4499,6 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4471
4499
  }));
4472
4500
  return;
4473
4501
  }
4474
- const conflictId = getKeyConflictInParent({
4475
- deletedItemIds,
4476
- getItemById: (candidateId) => executionSession.getItem(candidateId),
4477
- key: normalized.item.key,
4478
- parentId: parent.itemId
4479
- });
4480
- if (conflictId) {
4481
- issues.push(createIssue({
4482
- code: "duplicate-sibling-key",
4483
- message: `The key "${normalized.item.key}" is already used by sibling item "${conflictId}".`,
4484
- operationIndex,
4485
- operationId,
4486
- itemId
4487
- }));
4488
- return;
4489
- }
4490
4502
  if (normalized.target.index !== void 0) {
4491
4503
  const maximumUsefulIndex = getGroupChildren(parent.rawItem).length;
4492
4504
  if (normalized.target.index > maximumUsefulIndex) warnings.push(createIssue({
@@ -4560,8 +4572,32 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4560
4572
  id: itemId
4561
4573
  };
4562
4574
  const trustedCapability = snapshot.trustedCapabilities?.byItemType.get(normalizedItem.itemType);
4563
- if (trustedCapability?.normalizeCreate) normalizedItem = trustedCapability.normalizeCreate(normalizedItem);
4564
- if (normalizedItem.id !== itemId || normalizedItem.itemType !== normalized.item.itemType || normalizedItem.key !== normalized.item.key) issues.push(createIssue({
4575
+ try {
4576
+ if ("key" in normalizedItem) throw new Error("Item keys were removed. Use metadata.itemLabel for editor names and response-variable naming for coding.");
4577
+ if (normalized.presetId) {
4578
+ if (!trustedCapability?.materializePreset) throw new Error(`Item type "${normalizedItem.itemType}" has no server-supported creation presets.`);
4579
+ const preset = trustedCapability.materializePreset(normalized.presetId, normalized.translations?.[0]?.locale ?? snapshot.ui.selectedLocale ?? "en");
4580
+ normalizedItem.config = {
4581
+ ...isRecord$2(preset.config) ? preset.config : {},
4582
+ ...isRecord$2(normalizedItem.config) ? normalizedItem.config : {}
4583
+ };
4584
+ const presetContentKeys = trustedCapability.validContentKeys ? new Set(trustedCapability.validContentKeys(normalizedItem)) : void 0;
4585
+ const explicitKeys = new Set((normalized.translations ?? []).map((translation) => `${translation.locale}\0${translation.contentKey}`));
4586
+ normalized.translations = [...preset.translations.filter((translation) => (!presetContentKeys || presetContentKeys.has(translation.contentKey)) && !explicitKeys.has(`${translation.locale}\0${translation.contentKey}`)), ...normalized.translations ?? []];
4587
+ delete normalized.presetId;
4588
+ }
4589
+ if (trustedCapability?.normalizeCreate) normalizedItem = trustedCapability.normalizeCreate(normalizedItem);
4590
+ } catch (error) {
4591
+ issues.push(createIssue({
4592
+ code: "invalid-item-config",
4593
+ message: error instanceof Error ? error.message : String(error),
4594
+ operationIndex,
4595
+ operationId,
4596
+ itemId
4597
+ }));
4598
+ return;
4599
+ }
4600
+ if (normalizedItem.id !== itemId || normalizedItem.itemType !== normalized.item.itemType) issues.push(createIssue({
4565
4601
  code: "invalid-capability-normalization",
4566
4602
  message: "The trusted item capability changed the new item's identity during normalization.",
4567
4603
  operationIndex,
@@ -4577,17 +4613,69 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4577
4613
  };
4578
4614
  (normalizedIssue.severity === "warning" ? warnings : issues).push(normalizedIssue);
4579
4615
  }
4580
- validateCreateItemExpressions({
4581
- issues,
4582
- item: normalizedItem,
4583
- operationId,
4616
+ if (issues.some((issue) => issue.operationIndex === operationIndex)) return;
4617
+ const requiredContentKeys = trustedCapability?.requiredCreateContentKeys?.(normalizedItem) ?? [];
4618
+ for (const contentKey of requiredContentKeys) if (!(normalized.translations ?? []).some((translation) => translation.contentKey === contentKey && (translation.plainText?.trim() || translation.content && !isContentEmpty(translation.content)))) issues.push(createIssue({
4619
+ code: "missing-create-content",
4620
+ message: `Provide respondent-visible content for "${contentKey}" in the create operation.`,
4584
4621
  operationIndex,
4585
- responseSlotTypes: buildResponseSlotTypesForDraft({
4586
- currentItem: normalizedItem,
4587
- currentItems: executionSession.getItems(),
4588
- snapshot
4589
- })
4590
- });
4622
+ operationId,
4623
+ itemId
4624
+ }));
4625
+ if (issues.some((issue) => issue.operationIndex === operationIndex)) return;
4626
+ try {
4627
+ const before = createAssistantSurvey(executionSession.serialize(), snapshot.trustedCapabilities);
4628
+ const core = createAssistantSurvey({
4629
+ ...executionSession.serialize(),
4630
+ surveyItems: [...executionSession.getItems(), normalizedItem]
4631
+ }, snapshot.trustedCapabilities).surveyItems.get(itemId);
4632
+ const readPath = (value, path) => path.reduce((value, key) => value && typeof value === "object" ? value[key] : void 0, value);
4633
+ const explicit = core.getResponseSlotDefinitions().flatMap((slot) => {
4634
+ const path = slot.naming?.kind === "matrix" ? slot.naming.prefixPath : slot.naming?.path;
4635
+ const value = path && readPath(normalized.item.config, path);
4636
+ return path && typeof value === "string" ? [{
4637
+ path,
4638
+ value
4639
+ }] : [];
4640
+ });
4641
+ allocateCopiedResponseNames(before, [core]);
4642
+ const allocated = structuredClone(core.rawItem);
4643
+ for (const { path, value } of explicit) {
4644
+ const parent = readPath(allocated.config, path.slice(0, -1));
4645
+ parent[path.at(-1)] = value;
4646
+ }
4647
+ normalizedItem = allocated;
4648
+ } catch (error) {
4649
+ issues.push(createIssue({
4650
+ code: "invalid-response-schema",
4651
+ message: error instanceof Error ? error.message : String(error),
4652
+ operationIndex,
4653
+ operationId,
4654
+ itemId
4655
+ }));
4656
+ return;
4657
+ }
4658
+ try {
4659
+ validateCreateItemExpressions({
4660
+ issues,
4661
+ item: normalizedItem,
4662
+ operationId,
4663
+ operationIndex,
4664
+ responseSlotTypes: buildResponseSlotTypesForDraft({
4665
+ currentItem: normalizedItem,
4666
+ currentItems: executionSession.getItems(),
4667
+ snapshot
4668
+ })
4669
+ });
4670
+ } catch (error) {
4671
+ issues.push(createIssue({
4672
+ code: "invalid-response-schema",
4673
+ message: error instanceof Error ? error.message : String(error),
4674
+ operationIndex,
4675
+ operationId,
4676
+ itemId
4677
+ }));
4678
+ }
4591
4679
  if (issues.some((issue) => issue.operationIndex === operationIndex)) return;
4592
4680
  acceptOperation({
4593
4681
  ...normalized,
@@ -4603,6 +4691,40 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4603
4691
  if (isSurveyLevelOperation(operation)) {
4604
4692
  const normalized = normalizeOperation(operation, "");
4605
4693
  switch (normalized.kind) {
4694
+ case "update-response-settings":
4695
+ if (!normalized.naming.length && !normalized.settings.length) {
4696
+ issues.push(createIssue({
4697
+ code: "empty-response-settings",
4698
+ message: "Supply at least one response naming or settings edit.",
4699
+ operationIndex,
4700
+ operationId
4701
+ }));
4702
+ return;
4703
+ }
4704
+ try {
4705
+ const slots = createAssistantSurvey(executionSession.serialize(), snapshot.trustedCapabilities).getResponseSlotRegistry().definitions;
4706
+ const owners = new Set(slots.filter((slot) => normalized.naming.some((edit) => "slotId" in edit ? edit.slotId === slot.slotId : edit.primitiveId === slot.primitiveId) || normalized.settings.some((edit) => edit.slotId === slot.slotId)).map((slot) => slot.itemId));
4707
+ for (const owner of owners) {
4708
+ const item = executionSession.getItem(owner);
4709
+ const adapter = snapshot.trustedCapabilities?.byItemType.get(item.itemType);
4710
+ if (!itemTypeManifestByType.get(item.itemType)?.supportedOperations.includes(normalized.kind) || adapter?.supportsOperation?.(normalized) === false) issues.push(createIssue({
4711
+ code: "operation-not-supported-for-item-type",
4712
+ message: `Item type "${item.itemType}" does not support response-settings edits.`,
4713
+ operationIndex,
4714
+ operationId,
4715
+ itemId: owner
4716
+ }));
4717
+ }
4718
+ } catch (error) {
4719
+ issues.push(createIssue({
4720
+ code: "invalid-response-schema",
4721
+ message: error instanceof Error ? error.message : String(error),
4722
+ operationIndex,
4723
+ operationId
4724
+ }));
4725
+ }
4726
+ if (issues.some((issue) => issue.operationIndex === operationIndex)) return;
4727
+ break;
4606
4728
  case "update-survey-metadata":
4607
4729
  if (Object.keys(normalized.values).length === 0) {
4608
4730
  issues.push(createIssue({
@@ -4669,19 +4791,15 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4669
4791
  }
4670
4792
  break;
4671
4793
  case "update-image-asset-metadata":
4672
- case "remove-image-asset":
4673
- if (!executionSession.hasAsset(normalized.assetId)) {
4674
- issues.push(createIssue({
4675
- code: "image-asset-not-found",
4676
- message: `Image asset "${normalized.assetId}" was not found.`,
4677
- operationIndex,
4678
- operationId
4679
- }));
4680
- return;
4681
- }
4682
- break;
4683
- case "set-image-asset":
4684
- case "update-survey-pagination": break;
4794
+ case "remove-image-asset": if (!executionSession.hasAsset(normalized.assetId)) {
4795
+ issues.push(createIssue({
4796
+ code: "image-asset-not-found",
4797
+ message: `Image asset "${normalized.assetId}" was not found.`,
4798
+ operationIndex,
4799
+ operationId
4800
+ }));
4801
+ return;
4802
+ }
4685
4803
  }
4686
4804
  acceptOperation(normalized);
4687
4805
  return;
@@ -4696,7 +4814,7 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4696
4814
  if (!item) {
4697
4815
  issues.push(createIssue({
4698
4816
  code: "item-not-found",
4699
- message: `No survey item matching "${getOperationTarget(operation)}" was found.`,
4817
+ message: `No survey item matching "${getOperationTarget(operation)}" was found.${formatExactReferenceSuggestions(getOperationTarget(operation), executionSession.getItems().map((entry) => entry.id), "item IDs")} Inspect the current outline if the intended item is not listed.`,
4700
4818
  operationIndex,
4701
4819
  operationId
4702
4820
  }));
@@ -4729,7 +4847,8 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4729
4847
  ...rawNormalized,
4730
4848
  contentKey: normalizeContentKeyForItemType(rawNormalized.contentKey, itemTypeManifestByType.get(rawItem.itemType))
4731
4849
  } : rawNormalized.kind === "upsert-form-field-group" || rawNormalized.kind === "upsert-form-field" ? normalizeFormOperationTranslations(rawNormalized, rawNormalized.target.optionId) : rawNormalized.kind === "set-embedded-form" ? normalizeEmbeddedFormAuthoringMode(normalizeFormOperationTranslations(rawNormalized, rawNormalized.optionId)) : rawNormalized;
4732
- if (!itemTypeManifestByType.get(rawItem.itemType)?.supportedOperations.includes(normalized.kind)) {
4850
+ const itemCapability = itemTypeManifestByType.get(rawItem.itemType);
4851
+ if (!itemCapability?.supportedOperations.includes(normalized.kind)) {
4733
4852
  issues.push(createIssue({
4734
4853
  code: "operation-not-supported-for-item-type",
4735
4854
  message: `Item type "${rawItem.itemType}" does not support operation "${normalized.kind}".`,
@@ -4751,45 +4870,6 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4751
4870
  return;
4752
4871
  }
4753
4872
  switch (normalized.kind) {
4754
- case "update-item-key": {
4755
- if (!ITEM_KEY_PATTERN.test(normalized.key)) {
4756
- issues.push(createIssue({
4757
- code: "invalid-item-key",
4758
- message: "Item keys must contain only letters, numbers, underscores, or hyphens.",
4759
- operationIndex,
4760
- operationId,
4761
- itemId: item.itemId
4762
- }));
4763
- break;
4764
- }
4765
- const conflictId = getSiblingKeyConflict({
4766
- deletedItemIds,
4767
- getItemById: (candidateId) => executionSession.getItem(candidateId),
4768
- getParentId: (candidateId) => executionSession.getParentId(candidateId),
4769
- itemId: item.itemId,
4770
- key: normalized.key
4771
- });
4772
- if (conflictId) {
4773
- issues.push(createIssue({
4774
- code: "duplicate-sibling-key",
4775
- message: `The key "${normalized.key}" is already used by sibling item "${conflictId}".`,
4776
- operationIndex,
4777
- operationId,
4778
- itemId: item.itemId
4779
- }));
4780
- break;
4781
- }
4782
- if (rawItem.key === normalized.key) warnings.push(createIssue({
4783
- severity: "warning",
4784
- code: "no-key-change",
4785
- message: `The item already has the key "${normalized.key}".`,
4786
- operationIndex,
4787
- operationId,
4788
- itemId: item.itemId
4789
- }));
4790
- acceptOperation(normalized);
4791
- break;
4792
- }
4793
4873
  case "update-item-label":
4794
4874
  if ((rawItem.metadata?.itemLabel ?? "") === normalized.itemLabel) warnings.push(createIssue({
4795
4875
  severity: "warning",
@@ -4869,14 +4949,37 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
4869
4949
  break;
4870
4950
  }
4871
4951
  case "patch-item-config": {
4952
+ if (normalized.translations?.length && !itemCapability.supportedOperations.includes("update-item-translation")) {
4953
+ issues.push(createIssue({
4954
+ code: "operation-not-supported-for-item-type",
4955
+ operationIndex,
4956
+ operationId,
4957
+ itemId: item.itemId,
4958
+ message: `Item type "${rawItem.itemType}" does not permit translation edits.`
4959
+ }));
4960
+ break;
4961
+ }
4872
4962
  let nextConfig;
4873
4963
  try {
4874
4964
  nextConfig = applyJsonPatch(rawItem.config ?? {}, normalized.patches);
4875
4965
  } catch (error) {
4876
4966
  const patchError = error instanceof JsonPatchError ? error : void 0;
4967
+ const failedPatch = patchError?.operationIndex === void 0 ? void 0 : normalized.patches[patchError.operationIndex];
4877
4968
  issues.push(createIssue({
4878
4969
  code: patchError?.code ?? "item-config-patch-failed",
4879
- message: error instanceof Error ? error.message : "The item config JSON Patch could not be applied.",
4970
+ message: error instanceof Error ? `${failedPatch ? `Config patch ${patchError.operationIndex + 1} (${failedPatch.op} ${failedPatch.path}): ` : ""}${error.message}${patchError?.code === "path-not-found" && failedPatch?.op === "replace" ? " Replace requires an existing target. Use add for a new property whose parent exists, and account for earlier array edits in this patch." : ""}` : "The item config JSON Patch could not be applied.",
4971
+ operationIndex,
4972
+ operationId,
4973
+ itemId: item.itemId
4974
+ }));
4975
+ break;
4976
+ }
4977
+ const previousStructure = isRecord$2(rawItem.config) ? rawItem.config : {};
4978
+ const nextStructure = isRecord$2(nextConfig) ? nextConfig : {};
4979
+ if (previousStructure.isRoot !== nextStructure.isRoot || JSON.stringify(previousStructure.items) !== JSON.stringify(nextStructure.items)) {
4980
+ issues.push(createIssue({
4981
+ code: "structural-item-config-patch",
4982
+ message: "Item config patches cannot change root identity or group children. Use create-item, move-item, remove-item, or reorder-items; use one validated survey-json-patch for an atomic whole-survey restructuring.",
4880
4983
  operationIndex,
4881
4984
  operationId,
4882
4985
  itemId: item.itemId
@@ -5000,10 +5103,10 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
5000
5103
  issues.push(contentIssue);
5001
5104
  break;
5002
5105
  }
5003
- if (groups.flatMap((group) => group.fields).find((field) => field.id !== normalized.field.id && field.key === normalized.field.key)) {
5106
+ if (groups.flatMap((group) => group.fields).find((field) => field.id !== normalized.field.id && field.variableName === normalized.field.variableName)) {
5004
5107
  issues.push(createIssue({
5005
5108
  code: "duplicate-form-field-key",
5006
- message: `Form field key "${normalized.field.key}" is already in use.`,
5109
+ message: `Form variable name "${normalized.field.variableName}" is already in use.`,
5007
5110
  operationIndex,
5008
5111
  operationId,
5009
5112
  itemId: item.itemId
@@ -5140,9 +5243,19 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
5140
5243
  }
5141
5244
  const options = getChoiceOptionsFromRawItem(rawItem);
5142
5245
  const usedIds = new Set(options.map((option) => option.id));
5143
- const usedKeys = new Set(options.map((option) => option.key).filter((key) => Boolean(key)));
5144
- const optionId = ensureUniqueId(normalized.option.id ?? normalized.option.label, usedIds);
5145
- const optionKey = ensureUniqueChoiceOptionKey(normalized.option.key ?? normalized.option.label, usedKeys);
5246
+ const usedKeys = new Set(options.map((option) => option.code).filter((key) => Boolean(key)));
5247
+ if (normalized.option.id && usedIds.has(normalized.option.id)) {
5248
+ issues.push(createIssue({
5249
+ code: "duplicate-option-id",
5250
+ message: `Option ID "${normalized.option.id}" already exists.`,
5251
+ operationIndex,
5252
+ operationId,
5253
+ itemId: item.itemId
5254
+ }));
5255
+ break;
5256
+ }
5257
+ const optionId = normalized.option.id ?? ensureUniqueId(normalized.option.label, usedIds);
5258
+ const optionKey = normalized.option.code ?? ensureUniqueChoiceOptionKey(normalized.option.label, usedKeys);
5146
5259
  const insertIndex = normalized.index === void 0 ? options.length : Math.min(options.length, Math.max(0, normalized.index));
5147
5260
  if (normalized.index !== void 0 && normalized.index > options.length) warnings.push(createIssue({
5148
5261
  severity: "warning",
@@ -5158,7 +5271,7 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
5158
5271
  index: insertIndex,
5159
5272
  option: {
5160
5273
  id: optionId,
5161
- key: optionKey,
5274
+ code: optionKey,
5162
5275
  label: normalized.option.label
5163
5276
  }
5164
5277
  });
@@ -5435,6 +5548,13 @@ const validateSurveyOperations = ({ allowExpressionJsonPatches = false, maxOpera
5435
5548
  });
5436
5549
  if (issues.length === 0) try {
5437
5550
  const prospectiveSurvey = executionSession.serialize();
5551
+ issues.push(...validateIntroducedCapabilityContent(snapshot.survey, prospectiveSurvey, snapshot));
5552
+ issues.push(...findIntroducedSurveyStructureIssues({
5553
+ before: validateSurveyTreeStructure(snapshot.survey),
5554
+ after: validateSurveyTreeStructure(prospectiveSurvey)
5555
+ }));
5556
+ const previousResponseIssues = new Set(getAssistantResponseSchemaIssues(snapshot.survey, snapshot.trustedCapabilities).map((issue) => JSON.stringify(issue)));
5557
+ issues.push(...getAssistantResponseSchemaIssues(prospectiveSurvey, snapshot.trustedCapabilities).filter((issue) => issue.code === "invalid-survey-visibility" || !previousResponseIssues.has(JSON.stringify(issue))));
5438
5558
  warnings.push(...collectIntroducedExpressionIntegrityFindings({
5439
5559
  after: prospectiveSurvey,
5440
5560
  before: snapshot.survey,
@@ -5505,8 +5625,8 @@ const QUALITY_PRINCIPLES = [
5505
5625
  },
5506
5626
  {
5507
5627
  id: "key-semantic-consistency",
5508
- title: "Coding-key consistency",
5509
- prompt: "Do item, option, and field keys follow the survey's established convention and avoid contradicting the content they currently identify? Generic, numeric, or opaque keys are acceptable when that is the survey's consistent pattern; flag descriptive keys when they have become stale or misleading, and mixed conventions when the inconsistency appears accidental."
5628
+ title: "Variable-name and export-code consistency",
5629
+ prompt: "Do response variable names and option export codes follow the survey's established convention and avoid contradicting the content they currently identify? Generic, numeric, or opaque keys are acceptable when that is the survey's consistent pattern; flag descriptive keys when they have become stale or misleading, and mixed conventions when the inconsistency appears accidental."
5510
5630
  },
5511
5631
  {
5512
5632
  id: "flow",
@@ -5567,50 +5687,44 @@ const summarizeItemContent = (context, locale, item) => {
5567
5687
  return {};
5568
5688
  };
5569
5689
  const collectKeyReviewEntries = (context, sourceLocale) => {
5690
+ const model = createAssistantSurvey(context.survey, context.trustedCapabilities);
5570
5691
  const entries = [];
5571
- const addFormFields = (item, groups, contentPrefix) => {
5572
- for (const group of groups) for (const field of records(group.fields)) {
5573
- if (typeof field.id !== "string" || typeof field.key !== "string") continue;
5574
- const contentKey = `${contentPrefix}fields.${field.id}.label`;
5575
- const content = sourceLocale ? translationText(context, sourceLocale, item.id, contentKey) : "";
5576
- entries.push({
5577
- kind: "form-field",
5578
- itemId: item.id,
5579
- itemKey: item.key,
5580
- key: field.key,
5581
- keySource: "explicit",
5582
- contentKey,
5583
- ...content ? { content } : {}
5584
- });
5585
- }
5586
- };
5587
- for (const item of context.survey.surveyItems) {
5692
+ for (const slot of model.getResponseSlotRegistry().definitions) {
5693
+ const item = context.survey.surveyItems.find((item) => item.id === slot.itemId);
5588
5694
  entries.push({
5589
- kind: "item",
5590
- itemId: item.id,
5591
- itemKey: item.key,
5592
- key: item.key,
5695
+ kind: "response-variable",
5696
+ slotId: slot.slotId,
5697
+ valueType: slot.valueType,
5698
+ computed: !!slot.computed,
5699
+ itemId: slot.itemId,
5700
+ itemKey: model.resolveItemEditorName(slot.itemId, sourceLocale ?? context.ui.selectedLocale),
5701
+ key: slot.variableName,
5593
5702
  keySource: "explicit",
5594
- ...summarizeItemContent(context, sourceLocale, item)
5703
+ ...(() => {
5704
+ const target = slot.prefillTarget;
5705
+ const keys = target?.type === "embeddedField" ? [`embeddedForms.${target.optionId}.fields.${target.fieldId}.label`] : target?.type === "field" ? [`fields.${target.fieldId}.label`] : [slot.rowId && `${slot.rowId}.label`, slot.columnId && `${slot.columnId}.label`].filter((key) => !!key);
5706
+ const texts = sourceLocale ? keys.map((key) => translationText(context, sourceLocale, slot.itemId, key)).filter(Boolean) : [];
5707
+ return keys.length ? {
5708
+ contentKey: keys.join(" + "),
5709
+ content: texts.join(" / ")
5710
+ } : summarizeItemContent(context, sourceLocale, item);
5711
+ })()
5595
5712
  });
5596
- const config = isRecord$1(item.config) ? item.config : {};
5597
- if (item.itemType === "choiceItem") for (const option of records(config.options)) {
5598
- if (typeof option.id !== "string") continue;
5599
- const explicitKey = typeof option.key === "string" && option.key.trim() ? option.key : null;
5600
- const contentKey = `options.${option.id}.label`;
5601
- const content = sourceLocale ? translationText(context, sourceLocale, item.id, contentKey) : "";
5713
+ for (const [id, code] of Object.entries(slot.referenceValueKeys ?? {})) {
5714
+ const contentKey = slot.referenceValueLabelKeys?.[id];
5602
5715
  entries.push({
5603
5716
  kind: "choice-option",
5604
- itemId: item.id,
5605
- itemKey: item.key,
5606
- key: explicitKey ?? option.id,
5607
- keySource: explicitKey ? "explicit" : "fallback-id",
5608
- contentKey,
5609
- ...content ? { content } : {}
5717
+ slotId: slot.slotId,
5718
+ itemId: slot.itemId,
5719
+ itemKey: model.resolveItemEditorName(slot.itemId, sourceLocale ?? context.ui.selectedLocale),
5720
+ key: code || id,
5721
+ keySource: code ? "explicit" : "fallback-id",
5722
+ ...contentKey ? {
5723
+ contentKey,
5724
+ content: sourceLocale ? translationText(context, sourceLocale, slot.itemId, contentKey) : ""
5725
+ } : {}
5610
5726
  });
5611
- if (option.embeddedForm) addFormFields(item, getGroups(item, option.id), `embeddedForms.${option.id}.`);
5612
5727
  }
5613
- else if (item.itemType === "formItem") addFormFields(item, getGroups(item), "");
5614
5728
  }
5615
5729
  return entries;
5616
5730
  };
@@ -5673,22 +5787,6 @@ const auditSurvey = (context, options = {}) => {
5673
5787
  category: "structure",
5674
5788
  message: `Expected one survey root but found ${roots.length}.`
5675
5789
  });
5676
- for (const parent of items) {
5677
- const siblingKeys = /* @__PURE__ */ new Map();
5678
- for (const childId of strings(isRecord$1(parent.config) ? parent.config.items : void 0)) {
5679
- const child = itemsById.get(childId);
5680
- if (!child) continue;
5681
- const existing = siblingKeys.get(child.key);
5682
- if (existing) add({
5683
- code: "duplicate-sibling-key",
5684
- severity: "error",
5685
- category: "structure",
5686
- itemId: child.id,
5687
- message: `Key "${child.key}" duplicates sibling "${existing}".`
5688
- });
5689
- siblingKeys.set(child.key, child.id);
5690
- }
5691
- }
5692
5790
  const locales = context.inspection.locales;
5693
5791
  const sourceLocale = options.sourceLocale ?? context.ui.selectedLocale ?? locales[0];
5694
5792
  const targetLocales = options.targetLocales ?? locales.filter((locale) => locale !== sourceLocale);
@@ -5784,6 +5882,10 @@ const auditSurvey = (context, options = {}) => {
5784
5882
  });
5785
5883
  }
5786
5884
  }
5885
+ for (const issue of getAssistantResponseSchemaIssues(context.survey, context.trustedCapabilities)) add({
5886
+ ...issue,
5887
+ category: "responses"
5888
+ });
5787
5889
  for (const diagnostic of analyzeSurveyAssistantExpressionReferences(context.survey, context.trustedCapabilities)) {
5788
5890
  const blocking = isBlockingSurveyExpressionReferenceDiagnostic(diagnostic);
5789
5891
  add({
@@ -5846,12 +5948,12 @@ const auditSurvey = (context, options = {}) => {
5846
5948
  //#region src/engine/mutation-recovery.ts
5847
5949
  const UNIDENTIFIED_MUTATION_TARGET = "unidentified-mutation";
5848
5950
  const SATISFIED_MUTATION_TARGET = "satisfied-mutation";
5849
- const SATISFIED_ISSUES = new Set([
5951
+ const SATISFIED_ISSUES = /* @__PURE__ */ new Set([
5850
5952
  "survey-locale-already-exists",
5851
5953
  "item-already-in-target-parent",
5852
5954
  "no-expression-patches"
5853
5955
  ]);
5854
- const VALUE_ONLY_ISSUES = new Set([
5956
+ const VALUE_ONLY_ISSUES = /* @__PURE__ */ new Set([
5855
5957
  "invalid-mutation-value",
5856
5958
  "target-index-after-end",
5857
5959
  "option-index-after-end",
@@ -5877,13 +5979,13 @@ const exceptionalRecoveryTarget = (kind, operationIndex, issues) => {
5877
5979
  const errors = issues.filter((issue) => issue.severity === "error" && (issue.operationIndex === operationIndex || issue.operationIndex === void 0));
5878
5980
  if (errors.length === 0) return void 0;
5879
5981
  if (errors.every((issue) => SATISFIED_ISSUES.has(issue.code))) return SATISFIED_MUTATION_TARGET;
5880
- if (!errors.every((issue) => VALUE_ONLY_ISSUES.has(issue.code) || kind === "update-item-key" && ["invalid-item-key", "duplicate-sibling-key"].includes(issue.code))) return UNIDENTIFIED_MUTATION_TARGET;
5982
+ if (!errors.every((issue) => VALUE_ONLY_ISSUES.has(issue.code))) return UNIDENTIFIED_MUTATION_TARGET;
5881
5983
  };
5882
5984
  const record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
5883
5985
  const at = (value, path) => path.split(".").reduce((entry, key) => record(entry)[key], value);
5884
5986
  const identityFields = {
5885
- "create-item": ["target.parentItemId", "item.key"],
5886
- "update-item-key": ["itemId"],
5987
+ "update-response-settings": ["naming", "settings"],
5988
+ "create-item": ["target.parentItemId", "item.id"],
5887
5989
  "update-item-label": ["itemId"],
5888
5990
  "update-item-color": ["itemId"],
5889
5991
  "update-item-translation": [
@@ -5958,11 +6060,9 @@ const identities = (kind, value, fields, context) => {
5958
6060
  const path = optional ? field.slice(0, -1) : field;
5959
6061
  let part = at(value, path);
5960
6062
  if (typeof part === "string" && context && /(?:^|\.)(?:itemId|parentItemId)$/.test(path)) {
5961
- const identifier = part.trim().toLowerCase();
5962
6063
  const direct = context.survey.surveyItems.find((item) => item.id === part);
5963
- const indexed = "inspection" in context ? context.inspection.keyIndex.find((entry) => entry.key.toLowerCase() === identifier || entry.fullKey.toLowerCase() === identifier) : void 0;
5964
- if (!direct && !indexed) return void 0;
5965
- part = direct?.id ?? indexed?.itemId;
6064
+ if (!direct) return void 0;
6065
+ part = direct.id;
5966
6066
  }
5967
6067
  if (part === void 0 && optional) parts.push(null);
5968
6068
  else if (typeof part === "string" && part.length > 0 || path === "imageIndex" && typeof part === "number" && Number.isInteger(part) && part >= 0) parts.push(part);
@@ -5978,6 +6078,18 @@ const collectSurveyOperationRecoveryTargets = (operations, context, issues = [])
5978
6078
  if (kind === "survey-json-patch" || kind === "patch-item-config") return [UNIDENTIFIED_MUTATION_TARGET];
5979
6079
  const exceptional = exceptionalRecoveryTarget(kind, operationIndex, issues);
5980
6080
  if (exceptional) return [exceptional];
6081
+ if (kind === "update-response-settings") {
6082
+ const targets = ["naming", "settings"].flatMap((surface) => Array.isArray(value[surface]) ? value[surface].map((edit) => {
6083
+ const entry = record(edit);
6084
+ const identity = entry.slotId ?? entry.primitiveId;
6085
+ return typeof identity === "string" && identity.length ? JSON.stringify([
6086
+ kind,
6087
+ surface,
6088
+ identity
6089
+ ]) : UNIDENTIFIED_MUTATION_TARGET;
6090
+ }) : []);
6091
+ return targets.length ? targets : [UNIDENTIFIED_MUTATION_TARGET];
6092
+ }
5981
6093
  const parts = identities(kind, value, identityFields[kind], context);
5982
6094
  if (!parts) return [UNIDENTIFIED_MUTATION_TARGET];
5983
6095
  const fields = kind === "update-survey-metadata" ? value.values : kind === "update-image-asset-metadata" ? value.metadata : kind === "update-placed-image" ? value.changes : void 0;
@@ -6036,7 +6148,7 @@ const collectMalformedSurveyOperationRecoveryTargets = (operations, context, nor
6036
6148
  }
6037
6149
  const value = record(operation);
6038
6150
  const kind = value.kind;
6039
- const valueField = kind === "update-item-label" ? "itemLabel" : kind === "update-item-color" ? "color" : kind === "update-item-key" ? "key" : void 0;
6151
+ const valueField = kind === "update-item-label" ? "itemLabel" : kind === "update-item-color" ? "color" : void 0;
6040
6152
  const parts = typeof kind === "string" && Object.hasOwn(identityFields, kind) ? identities(kind, value, identityFields[kind], context) : void 0;
6041
6153
  const item = context?.survey.surveyItems.find((entry) => entry.id === parts?.[1]);
6042
6154
  const supported = item && context?.capabilities.itemTypes.find((entry) => entry.itemType === item.itemType)?.supportedOperations.includes(kind);
@@ -6125,10 +6237,9 @@ const collectRemovedRecoveryTargetRefinements = (targets, before, after) => {
6125
6237
  //#endregion
6126
6238
  //#region src/engine/turn-survey-draft.ts
6127
6239
  const createId = () => globalThis.crypto.randomUUID();
6128
- const operationTargets = (operation) => {
6240
+ const operationTargets = (operation, context) => {
6129
6241
  switch (operation.kind) {
6130
6242
  case "create-item": return [`item:${operation.item.id}`];
6131
- case "update-item-key":
6132
6243
  case "update-item-label":
6133
6244
  case "update-item-color":
6134
6245
  case "update-item-translation":
@@ -6152,6 +6263,10 @@ const operationTargets = (operation) => {
6152
6263
  case "set-image-asset":
6153
6264
  case "update-image-asset-metadata":
6154
6265
  case "remove-image-asset": return [`asset:${operation.assetId}`];
6266
+ case "update-response-settings": {
6267
+ const targets = [...new Set(context?.responseVariables.filter((slot) => operation.naming.some((edit) => "slotId" in edit ? edit.slotId === slot.slotId : edit.primitiveId === slot.primitiveId) || operation.settings.some((edit) => edit.slotId === slot.slotId)).map((slot) => `item:${slot.itemId}`) ?? [])];
6268
+ return targets.length ? targets : ["survey"];
6269
+ }
6155
6270
  case "update-survey-metadata":
6156
6271
  case "update-survey-pagination":
6157
6272
  case "update-survey-translation":
@@ -6160,7 +6275,7 @@ const operationTargets = (operation) => {
6160
6275
  case "survey-json-patch": return operation.patches.map((patch) => `path:${patch.path}`);
6161
6276
  }
6162
6277
  };
6163
- const collectOperationTargets = (operations) => new Set(operations.flatMap(operationTargets));
6278
+ const collectOperationTargets = (operations, context) => new Set(operations.flatMap((operation) => operationTargets(operation, context)));
6164
6279
  const mutationRecoveryRouteByIssueCode = { "expression-patch-requires-specialized-tool": "expression" };
6165
6280
  const mutationRouteTarget = (route) => `mutation-route:${route}`;
6166
6281
  const collectExclusiveMutationRecoveryTargets = (changeSet) => {
@@ -6173,6 +6288,7 @@ const collectExclusiveMutationRecoveryTargets = (changeSet) => {
6173
6288
  };
6174
6289
  const MAX_POST_CHANGE_TARGETS = 16;
6175
6290
  const MAX_POST_CHANGE_ITEMS = 6;
6291
+ const MAX_POST_CHANGE_CHILDREN = 16;
6176
6292
  const MAX_POST_CHANGE_TRANSLATIONS = 12;
6177
6293
  const MAX_POST_CHANGE_RESPONSE_SLOTS = 12;
6178
6294
  const MAX_POST_CHANGE_TEXT_CHARS = 100;
@@ -6180,10 +6296,21 @@ const MAX_VERIFICATION_AUDIT_FINDINGS = 8;
6180
6296
  const MAX_VERIFICATION_MESSAGE_CHARS = 240;
6181
6297
  const SUBSTANTIAL_CHANGE_ITEM_COUNT = 6;
6182
6298
  const SUBSTANTIAL_CHANGE_OPERATION_COUNT = 12;
6183
- const createPostChangeProjection = (context, targets) => {
6184
- const allTargets = [...targets];
6299
+ const createPostChangeProjection = (context, targets, before, reorderedParentIds) => {
6300
+ const beforeItems = new Map(before?.survey.surveyItems.map((item) => [item.id, item]));
6301
+ const previewedChildOrders = new Set(reorderedParentIds);
6302
+ for (const item of context.survey.surveyItems) {
6303
+ if (item.itemType !== "group") continue;
6304
+ const previous = beforeItems.get(item.id);
6305
+ const children = getGroupChildren(item);
6306
+ const previousChildren = getGroupChildren(previous);
6307
+ if (!previous || children.length !== previousChildren.length || children.some((id, index) => id !== previousChildren[index])) previewedChildOrders.add(item.id);
6308
+ }
6309
+ const allTargets = [.../* @__PURE__ */ new Set([...targets, ...[...previewedChildOrders].map((id) => `item:${id}`)])];
6185
6310
  const requestedItemIds = allTargets.filter((target) => target.startsWith("item:")).map((target) => target.slice(5));
6186
- const itemEntries = requestedItemIds.map((itemId) => context.inspection.keyIndex.find((entry) => entry.itemId === itemId)).filter((entry) => entry !== void 0);
6311
+ const itemEntries = requestedItemIds.map((itemId) => context.inspection.itemIndex.find((entry) => entry.itemId === itemId)).filter((entry) => entry !== void 0);
6312
+ const itemsById = new Map(context.survey.surveyItems.map((item) => [item.id, item]));
6313
+ const outlineById = new Map(context.inspection.outline.map((item) => [item.itemId, item]));
6187
6314
  const surveyTranslations = new SurveyTranslations(context.survey.translations);
6188
6315
  let truncated = allTargets.length > MAX_POST_CHANGE_TARGETS || itemEntries.length > MAX_POST_CHANGE_ITEMS;
6189
6316
  const touchedItems = itemEntries.slice(0, MAX_POST_CHANGE_ITEMS).map((entry) => {
@@ -6199,19 +6326,43 @@ const createPostChangeProjection = (context, targets) => {
6199
6326
  };
6200
6327
  }));
6201
6328
  const translations = allTranslations.slice(0, MAX_POST_CHANGE_TRANSLATIONS);
6202
- const allResponseSlots = Object.keys(context.responseSlotTypes).filter((slot) => slot.startsWith(`${entry.itemId}...`));
6329
+ const allResponseSlots = context.responseVariables.filter((slot) => slot.itemId === entry.itemId).map((slot) => slot.slotId);
6203
6330
  const responseSlots = allResponseSlots.slice(0, MAX_POST_CHANGE_RESPONSE_SLOTS);
6204
6331
  if (allTranslations.length > translations.length || allResponseSlots.length > responseSlots.length || translations.some((translation) => translation.truncated)) truncated = true;
6332
+ const parentId = outlineById.get(entry.itemId)?.parentId;
6333
+ const parent = parentId ? itemsById.get(parentId) : void 0;
6334
+ const siblings = parent?.itemType === "group" ? getGroupChildren(parent) : void 0;
6335
+ const index = siblings?.indexOf(entry.itemId) ?? -1;
6336
+ const placement = parent && siblings && index >= 0 ? {
6337
+ parentItemId: parent.id,
6338
+ index,
6339
+ siblingCount: siblings.length,
6340
+ ...index > 0 ? { previousItemId: siblings[index - 1] } : {},
6341
+ ...index + 1 < siblings.length ? { nextItemId: siblings[index + 1] } : {},
6342
+ shuffled: isRecord$2(parent.config) && parent.config.shuffleItems === true
6343
+ } : void 0;
6344
+ const item = itemsById.get(entry.itemId);
6345
+ const children = previewedChildOrders.has(entry.itemId) ? getGroupChildren(item) : void 0;
6346
+ const childOrder = children ? {
6347
+ itemIds: children.slice(0, MAX_POST_CHANGE_CHILDREN),
6348
+ total: children.length,
6349
+ truncated: children.length > MAX_POST_CHANGE_CHILDREN,
6350
+ shuffled: isRecord$2(item?.config) && item.config.shuffleItems === true
6351
+ } : void 0;
6352
+ if (childOrder?.truncated) truncated = true;
6205
6353
  return {
6206
6354
  itemId: entry.itemId,
6355
+ ...placement ? { placement } : {},
6356
+ ...childOrder ? { childOrder } : {},
6207
6357
  itemType: entry.itemType,
6208
- key: entry.key,
6209
- fullKey: entry.fullKey,
6358
+ editorName: entry.editorName,
6359
+ breadcrumb: entry.breadcrumb,
6210
6360
  ...entry.itemLabel ? { itemLabel: entry.itemLabel } : {},
6211
6361
  translationCount: allTranslations.length,
6212
6362
  translations,
6213
6363
  responseSlotCount: allResponseSlots.length,
6214
- responseSlots
6364
+ responseSlots,
6365
+ responseVariables: context.responseVariables.filter((slot) => responseSlots.includes(slot.slotId))
6215
6366
  };
6216
6367
  });
6217
6368
  return {
@@ -6302,11 +6453,6 @@ var TurnSurveyDraft = class {
6302
6453
  this.#assertOpen();
6303
6454
  if (!this.#context && snapshot && "document" in snapshot && "trustedCapabilities" in snapshot) this.#context = snapshot;
6304
6455
  const currentContext = this.#context ?? snapshot;
6305
- const targets = new Set([
6306
- ...collectOperationTargets(operations),
6307
- ...addressedTargets,
6308
- ...handledMutationRoutes.map(mutationRouteTarget)
6309
- ]);
6310
6456
  const recoveryTargets = addressedTargets.length > 0 ? new Set(addressedTargets) : collectSurveyOperationRecoveryTargets(operations, currentContext);
6311
6457
  const delta = createSurveyChangeSet({
6312
6458
  allowExpressionJsonPatches,
@@ -6352,7 +6498,8 @@ var TurnSurveyDraft = class {
6352
6498
  if (this.#context && delta.operations.length > 0) {
6353
6499
  const survey = executeSurveyOperationsOnRawSurvey({
6354
6500
  operations: delta.operations,
6355
- survey: this.#context.survey
6501
+ survey: this.#context.survey,
6502
+ capabilities: this.#context.trustedCapabilities
6356
6503
  });
6357
6504
  nextContext = createSurveyAssistantEngineContext({
6358
6505
  ...this.#context.document,
@@ -6367,7 +6514,11 @@ var TurnSurveyDraft = class {
6367
6514
  this.#refineRecoveryTargets(removedRefinements);
6368
6515
  const priorCandidate = this.#latestChangeSetId ? this.#candidates.get(this.#latestChangeSetId) : void 0;
6369
6516
  const acknowledgment = delta.operations.length === 0;
6370
- const postChange = acknowledgment ? priorCandidate?.postChange : this.#context ? createPostChangeProjection(this.#context, targets) : void 0;
6517
+ const postChange = acknowledgment ? priorCandidate?.postChange : this.#context ? createPostChangeProjection(this.#context, /* @__PURE__ */ new Set([
6518
+ ...collectOperationTargets(delta.operations, nextContext),
6519
+ ...addressedTargets,
6520
+ ...handledMutationRoutes.map(mutationRouteTarget)
6521
+ ]), beforeContext, delta.operations.flatMap((operation) => operation.kind === "reorder-items" ? [operation.parentItemId] : [])) : void 0;
6371
6522
  const verification = acknowledgment ? priorCandidate?.verification : beforeContext && this.#context && postChange ? createChangeVerification({
6372
6523
  after: this.#context,
6373
6524
  before: beforeContext,
@@ -6390,7 +6541,7 @@ var TurnSurveyDraft = class {
6390
6541
  });
6391
6542
  this.#draftOperations = registered.operations;
6392
6543
  this.#latestChangeSetId = registered.operations.length > 0 ? registered.id : void 0;
6393
- const resolvedTargets = new Set([...recoveryTargets, ...handledMutationRoutes.map(mutationRouteTarget)]);
6544
+ const resolvedTargets = /* @__PURE__ */ new Set([...recoveryTargets, ...handledMutationRoutes.map(mutationRouteTarget)]);
6394
6545
  for (const [changeSetId, unresolvedTargets] of this.#unresolvedCandidates) {
6395
6546
  for (const target of resolvedTargets) if (target !== "unidentified-mutation" && target !== "satisfied-mutation") unresolvedTargets.delete(target);
6396
6547
  if (retryOfChangeIds.includes(changeSetId)) {
@@ -6491,7 +6642,9 @@ const getCenteredNeighbors = (values, focusIndex, limit) => {
6491
6642
  return values.slice(start, start + windowSize).filter((_, index) => start + index !== focusIndex);
6492
6643
  };
6493
6644
  const buildSurveyAssistantTaskContext = (snapshot, options) => {
6494
- const { inspection } = createSurveyAssistantEngineContext(snapshot, void 0, { focusItemId: options.focusItemId });
6645
+ const { inspection } = createSurveyAssistantEngineContext(snapshot, options.capabilities, { focusItemId: options.focusItemId });
6646
+ const model = createAssistantSurvey(snapshot.survey, options.capabilities);
6647
+ const itemName = (id) => model.resolveItemEditorName(id, snapshot.ui.selectedLocale);
6495
6648
  const itemsById = new Map(snapshot.survey.surveyItems.map((item) => [item.id, item]));
6496
6649
  const parentById = new Map(inspection.outline.flatMap((item) => item.parentId ? [[item.itemId, item.parentId]] : []));
6497
6650
  const childIdsByParent = /* @__PURE__ */ new Map();
@@ -6501,7 +6654,7 @@ const buildSurveyAssistantTaskContext = (snapshot, options) => {
6501
6654
  childIds.push(item.itemId);
6502
6655
  childIdsByParent.set(item.parentId, childIds);
6503
6656
  }
6504
- const fullKeyById = new Map(inspection.keyIndex.map((item) => [item.itemId, item.fullKey]));
6657
+ const breadcrumbById = new Map(inspection.itemIndex.map((item) => [item.itemId, item.breadcrumb]));
6505
6658
  const summarizeItem = (item) => {
6506
6659
  const allVisibleText = [];
6507
6660
  let visibleTextValueTruncated = false;
@@ -6522,8 +6675,8 @@ const buildSurveyAssistantTaskContext = (snapshot, options) => {
6522
6675
  }
6523
6676
  return {
6524
6677
  id: item.id,
6525
- key: item.key,
6526
- fullKey: fullKeyById.get(item.id) ?? item.key,
6678
+ editorName: itemName(item.id),
6679
+ breadcrumb: breadcrumbById.get(item.id) ?? itemName(item.id),
6527
6680
  itemType: item.itemType,
6528
6681
  itemLabel: getItemLabel(item),
6529
6682
  visibleText: allVisibleText.slice(0, MAX_VISIBLE_TEXT_ENTRIES_PER_ITEM),
@@ -6531,15 +6684,26 @@ const buildSurveyAssistantTaskContext = (snapshot, options) => {
6531
6684
  };
6532
6685
  };
6533
6686
  const rootItemId = inspection.outline.find((item) => item.depth === 0)?.itemId;
6534
- const rootItem = rootItemId ? itemsById.get(rootItemId) : void 0;
6687
+ rootItemId && itemsById.get(rootItemId);
6688
+ const slots = model.getResponseSlotRegistry().definitions;
6689
+ const requested = options.slotIds ?? (options.focusSlotId ? [options.focusSlotId] : []);
6690
+ const selectedSlots = slots.filter((slot) => requested.includes(slot.slotId));
6691
+ const matrixIds = new Set(selectedSlots.filter((slot) => slot.naming?.kind === "matrix").map((slot) => slot.primitiveId));
6535
6692
  const result = {
6693
+ existingVariableNames: slots.map((slot) => slot.variableName),
6694
+ responseVariables: slots.filter((slot) => requested.includes(slot.slotId) || matrixIds.has(slot.primitiveId)).map((slot) => ({ ...slot })),
6695
+ responseOwners: [...new Set(slots.filter((slot) => requested.includes(slot.slotId) || matrixIds.has(slot.primitiveId)).map((slot) => slot.itemId))].map((id) => ({
6696
+ item: summarizeItem(itemsById.get(id)),
6697
+ configuration: itemsById.get(id).config,
6698
+ content: snapshot.survey.translations?.[snapshot.ui.selectedLocale]?.itemTranslations?.[id] ?? {}
6699
+ })),
6536
6700
  document: {
6537
6701
  revision: snapshot.version.revision,
6538
6702
  contentHash: snapshot.version.contentHash,
6539
6703
  capabilityDigest: snapshot.version.capabilityDigest
6540
6704
  },
6541
6705
  survey: {
6542
- key: rootItem?.key,
6706
+ key: snapshot.survey.metadata?.surveyKey,
6543
6707
  locales: inspection.locales,
6544
6708
  itemCount: inspection.itemCount,
6545
6709
  selectedLocale: snapshot.ui.selectedLocale
@@ -6578,7 +6742,7 @@ const buildSurveyAssistantTaskContext = (snapshot, options) => {
6578
6742
  item: summarizeItem(focusItem),
6579
6743
  parent: parentItem ? summarizeItem(parentItem) : void 0,
6580
6744
  ancestorPath: ancestorItems.map(summarizeItem),
6581
- siblingKeys: siblingItems.map((item) => item.key),
6745
+ siblingNames: siblingItems.map((item) => itemName(item.id)),
6582
6746
  siblingItems: siblingItems.map(summarizeItem),
6583
6747
  siblingItemCount,
6584
6748
  siblingItemsTruncated: siblingItemCount > siblingItems.length,
@@ -6587,7 +6751,7 @@ const buildSurveyAssistantTaskContext = (snapshot, options) => {
6587
6751
  relatedItemsTruncated: allRelatedItems.length > relatedItems.length
6588
6752
  };
6589
6753
  }
6590
- if ((options.purpose === "suggest-item-key" || options.purpose === "suggest-item-label") && focusItem) {
6754
+ if ((options.purpose === "suggest-response-name" || options.purpose === "suggest-response-settings" || options.purpose === "suggest-item-label") && focusItem) {
6591
6755
  const parentId = parentById.get(focusItem.id);
6592
6756
  const siblingIds = parentId ? childIdsByParent.get(parentId) ?? [] : [];
6593
6757
  const siblingPositionById = new Map(siblingIds.map((itemId, index) => [itemId, index]));
@@ -6605,18 +6769,19 @@ const buildSurveyAssistantTaskContext = (snapshot, options) => {
6605
6769
  };
6606
6770
  return rank[left.relationship] - rank[right.relationship] || left.siblingDistance - right.siblingDistance || left.index - right.index;
6607
6771
  });
6608
- const allKeyExamples = rankedItems.map(({ item, relationship }) => ({
6609
- itemId: item.id,
6610
- itemType: item.itemType,
6611
- key: item.key,
6612
- relationship
6772
+ const allKeyExamples = model.getResponseSlotRegistry().definitions.filter((slot) => !requested.includes(slot.slotId) && !matrixIds.has(slot.primitiveId)).map((slot) => ({
6773
+ itemId: slot.itemId,
6774
+ itemType: itemsById.get(slot.itemId).itemType,
6775
+ key: slot.variableName,
6776
+ slotId: slot.slotId,
6777
+ relationship: "survey"
6613
6778
  }));
6614
6779
  const allLabelExamples = rankedItems.flatMap(({ item, relationship }) => {
6615
6780
  const label = getItemLabel(item);
6616
6781
  return label ? [{
6617
6782
  itemId: item.id,
6618
6783
  itemType: item.itemType,
6619
- key: item.key,
6784
+ key: itemName(item.id),
6620
6785
  label,
6621
6786
  relationship
6622
6787
  }] : [];
@@ -6677,7 +6842,7 @@ const buildSurveyAssistantTaskContext = (snapshot, options) => {
6677
6842
  };
6678
6843
  //#endregion
6679
6844
  //#region src/engine/task-result-cache.ts
6680
- const DEFAULT_TTL_MS = 900 * 1e3;
6845
+ const DEFAULT_TTL_MS = 9e5;
6681
6846
  const DEFAULT_MAX_ENTRIES = 64;
6682
6847
  /** Small in-memory LRU cache for deterministic, snapshot-scoped one-shot results. */
6683
6848
  var SurveyAssistantTaskResultCache = class {
@@ -6718,6 +6883,6 @@ var SurveyAssistantTaskResultCache = class {
6718
6883
  }
6719
6884
  };
6720
6885
  //#endregion
6721
- export { sliceWithCursor as $, buildEmbeddedFormDetails as A, formSelectOptionDetailsSchema as B, buildExpressionMutationBatch as C, DEFAULT_LIMIT as D, expressionSchema as E, clampLimit as F, itemMatchesQuery as G, getRawSurvey$1 as H, findDirectChildrenByFullKey as I, mapOutlineById as J, itemSummarySchema as K, formFieldDetailsSchema as L, buildItemTranslationEntries as M, buildSurveyTranslations as N, MAX_LIMIT as O, choiceOptionDetailsSchema as P, resolveItemSummary as Q, formFieldGroupDetailsSchema as R, normalizeSurveyChangeOperations as S, expressionMutationSchema as T, getRawSurveyItems as U, getKeyIndexEntries as V, itemConfigDetailsSchema as W, normalizeText as X, mapRawItemsById as Y, parseCursor as Z, buildSurveyDocumentVersion as _, normalizeFormAuthoringConfig$1 as a, summarizeRawItem as at, applyValidatedSurveyOperationsToDraft as b, buildSurveyAssistantTaskContext as c, analyzeSurveyAssistantExpressionReferences as ct, collectExpressionMutationRecoveryTargets as d, inferExpressionReturnType as dt, summarizeFocusItemNode as et, collectMalformedSurveyOperationRecoveryTargets as f, validateExpression as ft, MAX_SURVEY_ASSISTANT_OPERATIONS_PER_TURN_DRAFT as g, MAX_SURVEY_ASSISTANT_OPERATIONS_PER_PROPOSAL as h, isValidSimpleFormConfig$1 as i, summarizeOutlineNode as it, buildItemConfigDetails as j, buildConfigSearchText as k, TurnSurveyDraft as l, deriveSurveyExpressionResponseSlots as lt, auditSurvey as m, deriveResponseSlotTypes as mt, hasSingleFormField as n, summarizeKeyIndexEntries as nt, transitionFormAuthoringMode$1 as o, translationEntrySchema as ot, collectSurveyOperationRecoveryTargets as p, createSurveyAssistantEngineContext as pt, mapKeyIndexById as q, isCompatibleSimpleFieldUpsert$1 as r, summarizeKeyIndexEntry as rt, SurveyAssistantTaskResultCache as s, unique as st, getFormAuthoringMode$1 as t, summarizeItemById as tt, collectExpressionMutationRecoveryRefinements as u, getSurveyAssistantExpressionDiagnosticGuidance as ut, createSurveyChangeSet as v, buildExpressionMutationPatches as w, executeSurveyOperationsOnRawSurvey as x, validateSurveyOperations as y, formFieldGroupLayoutSchema as z };
6886
+ export { parseCursor as $, MAX_LIMIT as A, formFieldGroupDetailsSchema as B, isRecord$2 as C, expressionMutationSchema as D, buildExpressionMutationPatches as E, buildSurveyTranslations as F, getRawSurveyItems as G, formSelectOptionDetailsSchema as H, choiceOptionDetailsSchema as I, itemSummarySchema as J, itemConfigDetailsSchema as K, clampLimit as L, buildEmbeddedFormDetails as M, buildItemConfigDetails as N, expressionSchema as O, buildItemTranslationEntries as P, normalizeText as Q, findDirectChildrenByParentId as R, executeSurveyOperationsOnRawSurvey as S, buildExpressionMutationBatch as T, getItemIndexEntries as U, formFieldGroupLayoutSchema as V, getRawSurvey$1 as W, mapOutlineById as X, mapItemIndexById as Y, mapRawItemsById as Z, buildSurveyDocumentVersion as _, createAssistantSurvey as _t, normalizeFormAuthoringConfig$1 as a, summarizeItemIndexEntry as at, validateSurveyOperations as b, buildSurveyAssistantTaskContext as c, translationEntrySchema as ct, collectExpressionMutationRecoveryTargets as d, deriveSurveyExpressionResponseSlots as dt, resolveItemSummary as et, collectMalformedSurveyOperationRecoveryTargets as f, getSurveyAssistantExpressionDiagnosticGuidance as ft, MAX_SURVEY_ASSISTANT_OPERATIONS_PER_TURN_DRAFT as g, deriveResponseSlotTypes as gt, MAX_SURVEY_ASSISTANT_OPERATIONS_PER_PROPOSAL as h, createSurveyAssistantEngineContext as ht, isValidSimpleFormConfig$1 as i, summarizeItemIndexEntries as it, buildConfigSearchText as j, DEFAULT_LIMIT as k, TurnSurveyDraft as l, unique as lt, auditSurvey as m, validateExpression as mt, hasSingleFormField as n, summarizeFocusItemNode as nt, transitionFormAuthoringMode$1 as o, summarizeOutlineNode as ot, collectSurveyOperationRecoveryTargets as p, inferExpressionReturnType as pt, itemMatchesQuery as q, isCompatibleSimpleFieldUpsert$1 as r, summarizeItemById as rt, SurveyAssistantTaskResultCache as s, summarizeRawItem as st, getFormAuthoringMode$1 as t, sliceWithCursor as tt, collectExpressionMutationRecoveryRefinements as u, analyzeSurveyAssistantExpressionReferences as ut, createSurveyChangeSet as v, getAssistantVisibilityIssues as vt, normalizeSurveyChangeOperations as w, validateSurveyTreeStructure as x, findIntroducedSurveyStructureIssues as y, formFieldDetailsSchema as z };
6722
6887
 
6723
- //# sourceMappingURL=engine-Bo85Oyj0.mjs.map
6888
+ //# sourceMappingURL=engine-nXoCWYpQ.mjs.map