@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.
- package/README.md +58 -3
- package/dist/{attachments-B0hg3vpT.mjs → attachments-BuNni5vB.mjs} +1 -1
- package/dist/{attachments-B0hg3vpT.mjs.map → attachments-BuNni5vB.mjs.map} +1 -1
- package/dist/{authoring-references-BJQ_1nMX.mjs → authoring-references-DbuOKerg.mjs} +12 -3
- package/dist/authoring-references-DbuOKerg.mjs.map +1 -0
- package/dist/{capabilities-DVTfogjv.d.mts → capabilities-CmeeAjEc.d.mts} +3 -4
- package/dist/capabilities-CmeeAjEc.d.mts.map +1 -0
- package/dist/{capabilities-1KNnNO1_.mjs → capabilities-DCMA71PP.mjs} +4 -19
- package/dist/capabilities-DCMA71PP.mjs.map +1 -0
- package/dist/capabilities-default.d.mts +13 -12
- package/dist/capabilities-default.d.mts.map +1 -1
- package/dist/capabilities-default.mjs +2 -411
- package/dist/{constants-Be91Gay9.d.mts → constants-B6HzpEsx.d.mts} +7 -28
- package/dist/constants-B6HzpEsx.d.mts.map +1 -0
- package/dist/{constants-0UHGFcRJ.mjs → constants-Ct-vv9cu.mjs} +1 -1
- package/dist/{constants-0UHGFcRJ.mjs.map → constants-Ct-vv9cu.mjs.map} +1 -1
- package/dist/controller-proxy-D7uQfm5f.d.mts +65 -0
- package/dist/controller-proxy-D7uQfm5f.d.mts.map +1 -0
- package/dist/{controller-proxy-BH05e-C2.mjs → controller-proxy-DEeFl9IA.mjs} +10 -20
- package/dist/controller-proxy-DEeFl9IA.mjs.map +1 -0
- package/dist/default-fhSon2RM.mjs +760 -0
- package/dist/default-fhSon2RM.mjs.map +1 -0
- package/dist/{digest-SJmoYVaK.d.mts → digest-C39WyAWG.d.mts} +2 -3
- package/dist/digest-C39WyAWG.d.mts.map +1 -0
- package/dist/digest.d.mts +1 -1
- package/dist/digest.mjs.map +1 -1
- package/dist/{engine-Bo85Oyj0.mjs → engine-nXoCWYpQ.mjs} +755 -590
- package/dist/engine-nXoCWYpQ.mjs.map +1 -0
- package/dist/engine.d.mts +3 -3
- package/dist/engine.mjs +3 -3
- package/dist/{fair-guidance-CRYCRxQm.mjs → fair-guidance-BmO4PswW.mjs} +1 -1
- package/dist/{fair-guidance-CRYCRxQm.mjs.map → fair-guidance-BmO4PswW.mjs.map} +1 -1
- package/dist/{index-Tnqv-yKW.d.mts → index-Ciq7vHIs.d.mts} +80 -141
- package/dist/index-Ciq7vHIs.d.mts.map +1 -0
- package/dist/{index-nhrgbgO5.d.mts → index-D7wT6P3t.d.mts} +277 -60
- package/dist/index-D7wT6P3t.d.mts.map +1 -0
- package/dist/{index-l_EK4yK7.d.mts → index-WB5kfnz0.d.mts} +15 -71
- package/dist/index-WB5kfnz0.d.mts.map +1 -0
- package/dist/{index-x0qprHgK.d.mts → index-r5riv4md.d.mts} +33 -24
- package/dist/index-r5riv4md.d.mts.map +1 -0
- package/dist/{lifecycle-Bx0aq_4S.d.mts → lifecycle-Dm8u7QFh.d.mts} +34 -18
- package/dist/lifecycle-Dm8u7QFh.d.mts.map +1 -0
- package/dist/{memory-thread-repository-KWHQWFEW.mjs → memory-thread-repository-DAnOGIB3.mjs} +51 -27
- package/dist/memory-thread-repository-DAnOGIB3.mjs.map +1 -0
- package/dist/{protocol-BhzQbx81.mjs → protocol-CF4Bh_Ch.mjs} +118 -36
- package/dist/protocol-CF4Bh_Ch.mjs.map +1 -0
- package/dist/protocol.d.mts +4 -4
- package/dist/protocol.mjs +3 -3
- package/dist/{react-B3ybjJ-X.mjs → react-C_GY-Fsc.mjs} +146 -39
- package/dist/react-C_GY-Fsc.mjs.map +1 -0
- package/dist/react-integration.d.mts +2 -2
- package/dist/react-integration.mjs +2 -2
- package/dist/react.d.mts +3 -3
- package/dist/react.mjs +3 -3
- package/dist/references/assistant-operations.md +100 -31
- package/dist/references/core-rules.md +6 -3
- package/dist/references/embedded-forms.md +34 -14
- package/dist/references/expressions.md +220 -58
- package/dist/references/fair-by-design.md +9 -9
- package/dist/references/item-types.md +305 -71
- package/dist/references/response-variables.md +38 -0
- package/dist/references/source-material-surveys.md +4 -3
- package/dist/references/survey-data-model.md +23 -8
- package/dist/{request-context-CG4SWijt.mjs → request-context-Dg4jSwN1.mjs} +4 -10
- package/dist/request-context-Dg4jSwN1.mjs.map +1 -0
- package/dist/{request-guardrails-TmgQtqp4.mjs → request-guardrails-C0ugBOp0.mjs} +6 -5
- package/dist/request-guardrails-C0ugBOp0.mjs.map +1 -0
- package/dist/server-agent.d.mts +37 -39
- package/dist/server-agent.d.mts.map +1 -1
- package/dist/server-agent.mjs +45 -20
- package/dist/server-agent.mjs.map +1 -1
- package/dist/server-runtime.d.mts +23 -42
- package/dist/server-runtime.d.mts.map +1 -1
- package/dist/server-runtime.mjs +12 -12
- package/dist/server-runtime.mjs.map +1 -1
- package/dist/server-tasks.d.mts +2 -2
- package/dist/server-tasks.mjs +2 -313
- package/dist/server-tools.d.mts +1 -1
- package/dist/server-tools.mjs +1 -1
- package/dist/server.d.mts +13 -36
- package/dist/server.d.mts.map +1 -1
- package/dist/server.mjs +5 -5
- package/dist/server.mjs.map +1 -1
- package/dist/storage-postgres.d.mts +9 -25
- package/dist/storage-postgres.d.mts.map +1 -1
- package/dist/storage-postgres.mjs +5 -4
- package/dist/storage-postgres.mjs.map +1 -1
- package/dist/{tasks-CYqrcaRO.d.mts → tasks-B9h3LPam.d.mts} +18 -34
- package/dist/tasks-B9h3LPam.d.mts.map +1 -0
- package/dist/tasks-BZEiwgxA.mjs +396 -0
- package/dist/tasks-BZEiwgxA.mjs.map +1 -0
- package/dist/thread-documents-DrHbaXqP.d.mts +119 -0
- package/dist/thread-documents-DrHbaXqP.d.mts.map +1 -0
- package/dist/{thread-handlers-DBAaU7BA.d.mts → thread-handlers-Bef0td9N.d.mts} +8 -40
- package/dist/thread-handlers-Bef0td9N.d.mts.map +1 -0
- package/dist/{tools-BvSovehT.mjs → tools-CEsv-WoO.mjs} +296 -105
- package/dist/tools-CEsv-WoO.mjs.map +1 -0
- package/dist/ui.css +1 -1
- package/dist/ui.d.mts +15 -54
- package/dist/ui.d.mts.map +1 -1
- package/dist/ui.mjs +172 -137
- package/dist/ui.mjs.map +1 -1
- package/docs/integration.md +11 -1
- package/package.json +40 -35
- package/dist/authoring-references-BJQ_1nMX.mjs.map +0 -1
- package/dist/capabilities-1KNnNO1_.mjs.map +0 -1
- package/dist/capabilities-DVTfogjv.d.mts.map +0 -1
- package/dist/capabilities-default.mjs.map +0 -1
- package/dist/constants-Be91Gay9.d.mts.map +0 -1
- package/dist/controller-proxy-BH05e-C2.mjs.map +0 -1
- package/dist/controller-proxy-Bd0ZHBPF.d.mts +0 -85
- package/dist/controller-proxy-Bd0ZHBPF.d.mts.map +0 -1
- package/dist/digest-SJmoYVaK.d.mts.map +0 -1
- package/dist/engine-Bo85Oyj0.mjs.map +0 -1
- package/dist/index-Tnqv-yKW.d.mts.map +0 -1
- package/dist/index-l_EK4yK7.d.mts.map +0 -1
- package/dist/index-nhrgbgO5.d.mts.map +0 -1
- package/dist/index-x0qprHgK.d.mts.map +0 -1
- package/dist/lifecycle-Bx0aq_4S.d.mts.map +0 -1
- package/dist/memory-thread-repository-KWHQWFEW.mjs.map +0 -1
- package/dist/protocol-BhzQbx81.mjs.map +0 -1
- package/dist/react-B3ybjJ-X.mjs.map +0 -1
- package/dist/request-context-CG4SWijt.mjs.map +0 -1
- package/dist/request-guardrails-TmgQtqp4.mjs.map +0 -1
- package/dist/server-tasks.mjs.map +0 -1
- package/dist/tasks-CYqrcaRO.d.mts.map +0 -1
- package/dist/thread-documents-rmd6nyX9.d.mts +0 -107
- package/dist/thread-documents-rmd6nyX9.d.mts.map +0 -1
- package/dist/thread-handlers-DBAaU7BA.d.mts.map +0 -1
- package/dist/tools-BvSovehT.mjs.map +0 -1
|
@@ -1,86 +1,116 @@
|
|
|
1
|
-
import { At as surveyContentSchema,
|
|
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-
|
|
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,
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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$
|
|
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
|
|
137
|
+
const itemIndex = outline.map((node) => ({
|
|
108
138
|
itemId: node.itemId,
|
|
109
139
|
itemType: node.itemType,
|
|
110
|
-
|
|
111
|
-
|
|
140
|
+
editorName: node.editorName,
|
|
141
|
+
breadcrumb: node.breadcrumb,
|
|
112
142
|
itemLabel: node.itemLabel,
|
|
113
|
-
path: node.
|
|
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
|
-
|
|
129
|
-
|
|
158
|
+
editorName: name(item.id),
|
|
159
|
+
breadcrumb: getNamePath(itemId).join(" / "),
|
|
130
160
|
itemLabel: item.metadata?.itemLabel,
|
|
131
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
172
|
-
responseSlots:
|
|
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:
|
|
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
|
|
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
|
-
|
|
503
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
621
|
-
/** The full-survey
|
|
622
|
-
const
|
|
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
|
-
|
|
634
|
-
|
|
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
|
-
|
|
663
|
-
|
|
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
|
|
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
|
-
|
|
675
|
-
|
|
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
|
|
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.
|
|
730
|
-
if (key) details.
|
|
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
|
|
763
|
+
const buildFormFieldDetails = (field, position, translationNamespace) => {
|
|
759
764
|
const id = asString(field.id);
|
|
760
|
-
const key = asString(field.
|
|
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.
|
|
777
|
-
if (value) details.
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
863
|
+
const syntheticRawItem = {
|
|
859
864
|
id: choiceOption.embeddedFormItemId ?? choiceOption.id,
|
|
860
|
-
|
|
865
|
+
metadata: { itemLabel: choiceOption.code ?? choiceOption.id },
|
|
861
866
|
itemType: "formItem",
|
|
862
867
|
config: embeddedForm
|
|
863
|
-
}
|
|
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.
|
|
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.
|
|
874
|
-
for (const option of field.selectOptions ?? []) values.push(option.id, option.
|
|
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
|
-
|
|
885
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
918
|
-
item.
|
|
913
|
+
item.editorName,
|
|
914
|
+
item.breadcrumb,
|
|
919
915
|
item.itemLabel,
|
|
920
916
|
...item.path ?? [],
|
|
921
|
-
...item.
|
|
917
|
+
...item.siblingNames ?? []
|
|
922
918
|
].filter(Boolean).join(" ").toLowerCase().includes(query);
|
|
923
919
|
};
|
|
924
920
|
/**
|
|
925
|
-
* Direct children of `
|
|
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
|
|
929
|
-
|
|
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.
|
|
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 =
|
|
2001
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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.
|
|
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)
|
|
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
|
-
|
|
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)
|
|
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.
|
|
2279
|
+
const optionKey = getTrimmedString(option.code);
|
|
2287
2280
|
return {
|
|
2288
2281
|
...option,
|
|
2289
2282
|
id: optionId,
|
|
2290
|
-
...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
|
-
|
|
2403
|
-
|
|
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
|
-
|
|
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)
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
2939
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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 || !
|
|
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
|
|
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
|
|
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:
|
|
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))
|
|
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
|
|
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
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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 ? {
|
|
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
|
|
3755
|
-
const
|
|
3756
|
-
|
|
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 ? {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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: "
|
|
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
|
-
|
|
4564
|
-
|
|
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
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
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
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
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
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
5144
|
-
|
|
5145
|
-
|
|
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
|
-
|
|
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: "
|
|
5509
|
-
prompt: "Do
|
|
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
|
|
5572
|
-
|
|
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: "
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
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
|
-
...
|
|
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
|
|
5597
|
-
|
|
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
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
...
|
|
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)
|
|
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
|
-
"
|
|
5886
|
-
"
|
|
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
|
-
|
|
5964
|
-
|
|
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" :
|
|
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
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
6209
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
6526
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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-
|
|
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 =
|
|
6609
|
-
itemId:
|
|
6610
|
-
itemType:
|
|
6611
|
-
key:
|
|
6612
|
-
|
|
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.
|
|
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 =
|
|
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 {
|
|
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-
|
|
6888
|
+
//# sourceMappingURL=engine-nXoCWYpQ.mjs.map
|