@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.ge2d364c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/.claude/CLAUDE.md +25 -0
  2. package/.tasks/INDEX.md +1 -0
  3. package/.tasks/STORY_NETWORK_ISOLATION.md +98 -0
  4. package/README.md +1 -1
  5. package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
  6. package/design/RENDER_EXPRESSION_TRANSPORT.md +18 -0
  7. package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
  8. package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
  9. package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
  10. package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
  11. package/dist/components/dpqlEditor/index.d.ts +4 -0
  12. package/dist/components/dpqlEditor/index.d.ts.map +1 -1
  13. package/dist/components/dpqlEditor/index.js +5 -1
  14. package/dist/components/dpqlEditor/index.js.map +1 -1
  15. package/dist/components/dpqlEditor/types.d.ts +29 -2
  16. package/dist/components/dpqlEditor/types.d.ts.map +1 -1
  17. package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
  18. package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
  19. package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
  20. package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
  21. package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
  22. package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
  23. package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
  24. package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
  25. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  26. package/dist/components/form/engine/CompactRow.js +186 -21
  27. package/dist/components/form/engine/CompactRow.js.map +1 -1
  28. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  29. package/dist/components/form/engine/CompactToolbar.js +5 -2
  30. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  31. package/dist/components/form/engine/FormEngine.d.ts +159 -11
  32. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  33. package/dist/components/form/engine/FormEngine.js +376 -75
  34. package/dist/components/form/engine/FormEngine.js.map +1 -1
  35. package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
  36. package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
  37. package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
  38. package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
  39. package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
  40. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  41. package/dist/components/form/engine/OptionFieldMessages.js +61 -11
  42. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  43. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
  44. package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
  45. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
  46. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  47. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  48. package/dist/components/form/engine/readFirst.d.ts +42 -1
  49. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  50. package/dist/components/form/engine/readFirst.js +74 -7
  51. package/dist/components/form/engine/readFirst.js.map +1 -1
  52. package/dist/components/form/engine/rendererTypes.d.ts +13 -0
  53. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
  54. package/dist/components/form/engine/rendererTypes.js.map +1 -1
  55. package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
  56. package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
  57. package/dist/components/form/engine/rowMenuContext.js +103 -0
  58. package/dist/components/form/engine/rowMenuContext.js.map +1 -0
  59. package/dist/components/form/engine/typeChoices.d.ts +37 -0
  60. package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
  61. package/dist/components/form/engine/typeChoices.js +25 -0
  62. package/dist/components/form/engine/typeChoices.js.map +1 -0
  63. package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
  64. package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
  65. package/dist/components/form/expressions/ExpressionField.js +181 -33
  66. package/dist/components/form/expressions/ExpressionField.js.map +1 -1
  67. package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
  68. package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
  69. package/dist/components/form/expressions/argumentPresence.js +203 -0
  70. package/dist/components/form/expressions/argumentPresence.js.map +1 -0
  71. package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
  72. package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
  73. package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
  74. package/dist/components/form/expressions/builder/index.d.ts +15 -2
  75. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  76. package/dist/components/form/expressions/builder/index.js +19 -57
  77. package/dist/components/form/expressions/builder/index.js.map +1 -1
  78. package/dist/components/form/expressions/dpqlMockLsp.d.ts +17 -0
  79. package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
  80. package/dist/components/form/expressions/dpqlMockLsp.js +75 -9
  81. package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
  82. package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
  83. package/dist/components/form/expressions/renderExpressionToText.js +94 -19
  84. package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
  85. package/dist/components/form/fields/Field.d.ts.map +1 -1
  86. package/dist/components/form/fields/Field.js +3 -1
  87. package/dist/components/form/fields/Field.js.map +1 -1
  88. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
  89. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  90. package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
  91. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  92. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  93. package/dist/components/form/fields/auto/AutoFormField.js +13 -2
  94. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  95. package/dist/components/form/fields/rich-text/RichText.d.ts +11 -0
  96. package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
  97. package/dist/components/form/fields/rich-text/RichText.js +84 -5
  98. package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
  99. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
  100. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
  101. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
  102. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
  103. package/dist/components/form/fields/template/TemplateField.d.ts +31 -1
  104. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  105. package/dist/components/form/fields/template/TemplateField.js +507 -40
  106. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  107. package/dist/components/form/index.d.ts +1 -0
  108. package/dist/components/form/index.d.ts.map +1 -1
  109. package/dist/components/form/index.js +1 -0
  110. package/dist/components/form/index.js.map +1 -1
  111. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +1 -0
  112. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
  113. package/dist/components/smartEditor/__fixtures__/mockLspServer.js +6 -1
  114. package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
  115. package/dist/helpers/dpqlDetection.d.ts +58 -0
  116. package/dist/helpers/dpqlDetection.d.ts.map +1 -0
  117. package/dist/helpers/dpqlDetection.js +164 -0
  118. package/dist/helpers/dpqlDetection.js.map +1 -0
  119. package/dist/helpers/optionUiTypes.d.ts +1 -1
  120. package/dist/helpers/options.d.ts +37 -0
  121. package/dist/helpers/options.d.ts.map +1 -1
  122. package/dist/helpers/options.js +78 -1
  123. package/dist/helpers/options.js.map +1 -1
  124. package/dist/helpers/templateItems.d.ts +47 -0
  125. package/dist/helpers/templateItems.d.ts.map +1 -0
  126. package/dist/helpers/templateItems.js +88 -0
  127. package/dist/helpers/templateItems.js.map +1 -0
  128. package/dist/helpers/templates.d.ts +21 -0
  129. package/dist/helpers/templates.d.ts.map +1 -1
  130. package/dist/helpers/templates.js +49 -3
  131. package/dist/helpers/templates.js.map +1 -1
  132. package/dist/helpers/validations.d.ts.map +1 -1
  133. package/dist/helpers/validations.js +56 -9
  134. package/dist/helpers/validations.js.map +1 -1
  135. package/dist/hooks/useArgSchema.d.ts +2 -1
  136. package/dist/hooks/useArgSchema.d.ts.map +1 -1
  137. package/dist/hooks/useFetch/useFetch.d.ts +3 -1
  138. package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
  139. package/dist/hooks/useFetch/useFetch.js +47 -9
  140. package/dist/hooks/useFetch/useFetch.js.map +1 -1
  141. package/dist/hooks/useTemplates.d.ts +20 -0
  142. package/dist/hooks/useTemplates.d.ts.map +1 -1
  143. package/dist/hooks/useTemplates.js +34 -2
  144. package/dist/hooks/useTemplates.js.map +1 -1
  145. package/dist/index.d.ts +2 -1
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js +5 -1
  148. package/dist/index.js.map +1 -1
  149. package/dist/stories/storyNetwork.d.ts +36 -0
  150. package/dist/stories/storyNetwork.d.ts.map +1 -0
  151. package/dist/stories/storyNetwork.js +82 -0
  152. package/dist/stories/storyNetwork.js.map +1 -0
  153. package/dist/utils/fetch.d.ts +151 -1
  154. package/dist/utils/fetch.d.ts.map +1 -1
  155. package/dist/utils/fetch.js +256 -44
  156. package/dist/utils/fetch.js.map +1 -1
  157. package/dist/utils/lspClient.d.ts +6 -0
  158. package/dist/utils/lspClient.d.ts.map +1 -1
  159. package/dist/utils/lspClient.js +62 -11
  160. package/dist/utils/lspClient.js.map +1 -1
  161. package/dist/utils/websocket.d.ts +6 -0
  162. package/dist/utils/websocket.d.ts.map +1 -1
  163. package/dist/utils/websocket.js +15 -7
  164. package/dist/utils/websocket.js.map +1 -1
  165. package/package.json +2 -2
  166. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +0 -2
  167. package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
  168. package/src/components/dpqlEditor/index.ts +4 -0
  169. package/src/components/dpqlEditor/types.ts +26 -2
  170. package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
  171. package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
  172. package/src/components/form/engine/CompactRow.tsx +231 -11
  173. package/src/components/form/engine/CompactToolbar.tsx +10 -3
  174. package/src/components/form/engine/FormEngine.stories.tsx +331 -30
  175. package/src/components/form/engine/FormEngine.tsx +504 -87
  176. package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
  177. package/src/components/form/engine/OptionFieldMessages.tsx +89 -11
  178. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
  179. package/src/components/form/engine/compactToolbarContext.ts +1 -1
  180. package/src/components/form/engine/readFirst.ts +86 -7
  181. package/src/components/form/engine/rendererTypes.ts +12 -0
  182. package/src/components/form/engine/rowMenuContext.ts +159 -0
  183. package/src/components/form/engine/typeChoices.ts +55 -0
  184. package/src/components/form/expressions/ExpressionField.stories.tsx +828 -31
  185. package/src/components/form/expressions/ExpressionField.tsx +234 -29
  186. package/src/components/form/expressions/argumentPresence.ts +200 -0
  187. package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
  188. package/src/components/form/expressions/builder/index.tsx +31 -75
  189. package/src/components/form/expressions/dpqlMockLsp.ts +81 -1
  190. package/src/components/form/expressions/renderExpressionToText.ts +106 -17
  191. package/src/components/form/fields/Field.tsx +3 -2
  192. package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
  193. package/src/components/form/fields/auto/AutoFormField.tsx +12 -2
  194. package/src/components/form/fields/rich-text/RichText.tsx +112 -19
  195. package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
  196. package/src/components/form/fields/template/TemplateField.tsx +667 -95
  197. package/src/components/form/index.tsx +1 -0
  198. package/src/components/log/Log.stories.tsx +2 -1
  199. package/src/components/smartEditor/__fixtures__/mockLspServer.ts +6 -1
  200. package/src/helpers/dpqlDetection.ts +209 -0
  201. package/src/helpers/options.ts +70 -0
  202. package/src/helpers/templateItems.ts +81 -0
  203. package/src/helpers/templates.ts +51 -2
  204. package/src/helpers/validations.ts +59 -9
  205. package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
  206. package/src/hooks/useFetch/useFetch.tsx +53 -7
  207. package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
  208. package/src/hooks/useTemplates.ts +41 -0
  209. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
  210. package/src/index.tsx +14 -1
  211. package/src/stores/currentUser/currentUser.stories.tsx +2 -1
  212. package/src/stories/storyNetwork.ts +92 -0
  213. package/src/utils/fetch.ts +311 -21
  214. package/src/utils/lspClient.ts +68 -11
  215. package/src/utils/websocket.ts +20 -9
@@ -97,11 +97,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
97
97
  return (mod && mod.__esModule) ? mod : { "default": mod };
98
98
  };
99
99
  Object.defineProperty(exports, "__esModule", { value: true });
100
- exports.FormEngine = exports.getTypeAndCanBeNull = exports.flattenOptions = exports.fixOptions = exports.OptionsContext = exports.hasRequiredOptions = exports.fixOperatorValue = exports.getType = void 0;
100
+ exports.FormEngine = exports.getTypeAndCanBeNull = exports.flattenOptions = exports.shouldEmitLocalValue = exports.agreementFromRebuild = exports.isMirrorOfValue = exports.fixOptions = exports.OptionsContext = exports.hasRequiredOptions = exports.getOptionFieldStorageType = exports.fixOperatorValue = exports.getType = void 0;
101
101
  var react_1 = require("react");
102
102
  var jsx_runtime_1 = require("react/jsx-runtime");
103
103
  var reqore_1 = require("@qoretechnologies/reqore");
104
104
  var colors_1 = require("@qoretechnologies/reqore/dist/helpers/colors");
105
+ var argumentPresence_1 = require("../expressions/argumentPresence");
106
+ var typeChoices_1 = require("./typeChoices");
105
107
  var optionActions_1 = require("./optionActions");
106
108
  var rendererTypes_1 = require("./rendererTypes");
107
109
  var lodash_1 = require("lodash");
@@ -124,6 +126,7 @@ var FocusedEditing_1 = require("../../FocusedEditing");
124
126
  var Select_1 = require("../fields/select/Select");
125
127
  var TemplateField_1 = require("../fields/template/TemplateField");
126
128
  var CompactRow_1 = require("./CompactRow");
129
+ var FormFieldsSkeleton_1 = require("./FormFieldsSkeleton");
127
130
  var compactRowContext_1 = require("./compactRowContext");
128
131
  var compactRowStyles_1 = require("./compactRowStyles");
129
132
  var CompactToolbar_1 = require("./CompactToolbar");
@@ -188,8 +191,7 @@ var PositiveColorEffect = {
188
191
  // group panels; colours come in as props so the layout follows the Reqore theme.
189
192
  var StyledCompactWrap = styled_components_1.default.div(templateObject_3 || (templateObject_3 = __makeTemplateObject(["\n display: flex;\n flex-flow: column;\n gap: 10px;\n width: 100%;\n /* Allow the wrap to shrink inside flex/grid parents so its rows' ellipsis can\n engage instead of overflowing the container horizontally. */\n min-width: 0;\n max-width: 100%;\n\n /* Scroll ownership \u2014 see `compactScroll`.\n\n 'host' (the default): the page or panel around us already scrolls, so we\n must not cap our height and scroll our own body, or the reader gets two\n scrollbars in two styles and cannot tell which moves what.\n\n `overflow-x: clip`, NOT `hidden`: CSS coerces an `overflow-y: visible`\n to `auto` whenever the other axis is neither visible nor clip, which would\n leave this box a scroll container \u2014 the element stops scrolling only\n because `max-height: none` means nothing overflows, while `position:\n sticky` inside it still resolves against US instead of the host, so the\n toolbar silently stops pinning. `clip` keeps the horizontal guard without\n that coercion. */\n ", "\n\n /* Option logos (e.g. language images) render as <img> inside ReqoreIcon's\n square box; constrain them so portrait PNGs don't overflow the row. */\n .reqore-icon img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n }\n"], ["\n display: flex;\n flex-flow: column;\n gap: 10px;\n width: 100%;\n /* Allow the wrap to shrink inside flex/grid parents so its rows' ellipsis can\n engage instead of overflowing the container horizontally. */\n min-width: 0;\n max-width: 100%;\n\n /* Scroll ownership \u2014 see \\`compactScroll\\`.\n\n 'host' (the default): the page or panel around us already scrolls, so we\n must not cap our height and scroll our own body, or the reader gets two\n scrollbars in two styles and cannot tell which moves what.\n\n \\`overflow-x: clip\\`, NOT \\`hidden\\`: CSS coerces an \\`overflow-y: visible\\`\n to \\`auto\\` whenever the other axis is neither visible nor clip, which would\n leave this box a scroll container \u2014 the element stops scrolling only\n because \\`max-height: none\\` means nothing overflows, while \\`position:\n sticky\\` inside it still resolves against US instead of the host, so the\n toolbar silently stops pinning. \\`clip\\` keeps the horizontal guard without\n that coercion. */\n ", "\n\n /* Option logos (e.g. language images) render as <img> inside ReqoreIcon's\n square box; constrain them so portrait PNGs don't overflow the row. */\n .reqore-icon img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n }\n"])), function (_a) {
190
193
  var $ownScroll = _a.$ownScroll;
191
- return $ownScroll
192
- ? (0, styled_components_1.css)(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n /* 'own': we are the scroller. The sticky toolbar pins to whatever\n scrolls, so a host whose scrollport carries top padding would\n resolve sticky `top: 0` against its padding box and leave an\n unblurred strip above the toolbar. Scrolling in this unpadded box\n pins it flush. Cap at the host's available height, but do not grow\n to fill it \u2014 short forms still hug their content.\n\n `min-height: 0` is what lets a flex item shrink below its content\n so there is something to scroll; it belongs to this branch only. */\n min-height: 0;\n max-height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n "], ["\n /* 'own': we are the scroller. The sticky toolbar pins to whatever\n scrolls, so a host whose scrollport carries top padding would\n resolve sticky \\`top: 0\\` against its padding box and leave an\n unblurred strip above the toolbar. Scrolling in this unpadded box\n pins it flush. Cap at the host's available height, but do not grow\n to fill it \u2014 short forms still hug their content.\n\n \\`min-height: 0\\` is what lets a flex item shrink below its content\n so there is something to scroll; it belongs to this branch only. */\n min-height: 0;\n max-height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n "]))) : (0, styled_components_1.css)(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n /* `min-height: auto` is load-bearing, not a default: inside a column\n flex parent the shrink allowance above collapses this box to zero\n height while its rows paint outside it, so the form looks right but\n contributes nothing to layout \u2014 the host cannot size or scroll to\n it and anything below it stacks against nothing. */\n min-height: auto;\n max-height: none;\n overflow-y: visible;\n overflow-x: clip;\n "], ["\n /* \\`min-height: auto\\` is load-bearing, not a default: inside a column\n flex parent the shrink allowance above collapses this box to zero\n height while its rows paint outside it, so the form looks right but\n contributes nothing to layout \u2014 the host cannot size or scroll to\n it and anything below it stacks against nothing. */\n min-height: auto;\n max-height: none;\n overflow-y: visible;\n overflow-x: clip;\n "])));
194
+ return $ownScroll ? (0, styled_components_1.css)(templateObject_1 || (templateObject_1 = __makeTemplateObject(["\n /* 'own': we are the scroller. The sticky toolbar pins to whatever\n scrolls, so a host whose scrollport carries top padding would\n resolve sticky `top: 0` against its padding box and leave an\n unblurred strip above the toolbar. Scrolling in this unpadded box\n pins it flush. Cap at the host's available height, but do not grow\n to fill it \u2014 short forms still hug their content.\n\n `min-height: 0` is what lets a flex item shrink below its content\n so there is something to scroll; it belongs to this branch only. */\n min-height: 0;\n max-height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n "], ["\n /* 'own': we are the scroller. The sticky toolbar pins to whatever\n scrolls, so a host whose scrollport carries top padding would\n resolve sticky \\`top: 0\\` against its padding box and leave an\n unblurred strip above the toolbar. Scrolling in this unpadded box\n pins it flush. Cap at the host's available height, but do not grow\n to fill it \u2014 short forms still hug their content.\n\n \\`min-height: 0\\` is what lets a flex item shrink below its content\n so there is something to scroll; it belongs to this branch only. */\n min-height: 0;\n max-height: 100%;\n overflow-y: auto;\n overflow-x: hidden;\n "]))) : (0, styled_components_1.css)(templateObject_2 || (templateObject_2 = __makeTemplateObject(["\n /* `min-height: auto` is load-bearing, not a default: inside a column\n flex parent the shrink allowance above collapses this box to zero\n height while its rows paint outside it, so the form looks right but\n contributes nothing to layout \u2014 the host cannot size or scroll to\n it and anything below it stacks against nothing. */\n min-height: auto;\n max-height: none;\n overflow-y: visible;\n overflow-x: clip;\n "], ["\n /* \\`min-height: auto\\` is load-bearing, not a default: inside a column\n flex parent the shrink allowance above collapses this box to zero\n height while its rows paint outside it, so the form looks right but\n contributes nothing to layout \u2014 the host cannot size or scroll to\n it and anything below it stacks against nothing. */\n min-height: auto;\n max-height: none;\n overflow-y: visible;\n overflow-x: clip;\n "])));
193
195
  });
194
196
  // Batched-commit dock: a floating Save/Discard card pinned bottom-right. Sticky
195
197
  // (not fixed) so it stays within the form's own scroll bounds when the engine
@@ -231,14 +233,55 @@ var getOptionSchemaStorageType = function (option, isRendererOnly) {
231
233
  : isRendererOnly(option === null || option === void 0 ? void 0 : option.ui_type) ? (option === null || option === void 0 ? void 0 : option.type) || (option === null || option === void 0 ? void 0 : option.ui_type)
232
234
  : (option === null || option === void 0 ? void 0 : option.ui_type) || (option === null || option === void 0 ? void 0 : option.type)) || 'any');
233
235
  };
234
- var getOptionFieldStorageType = function (optionName, fieldType, schema, operators, operatorData, isRendererOnly) {
236
+ /**
237
+ * Whether an option currently holds an expression, in EITHER shape it comes in.
238
+ *
239
+ * At runtime the editor writes the flag onto the option itself
240
+ * (`{ type, is_expression: true, value }`), but a value loaded from a saved
241
+ * draft carries the envelope NESTED (`{ type, value: { is_expression: true,
242
+ * value } }`). Same thing, two shapes, and only the flat one was ever checked —
243
+ * so a freshly typed expression behaved while a reloaded one did not, which is
244
+ * exactly how this survived several fixes: every test used the runtime shape.
245
+ */
246
+ var optionHoldsExpression = function (option) {
247
+ var _a;
248
+ var o = option;
249
+ return !!((o === null || o === void 0 ? void 0 : o.is_expression) || ((_a = o === null || o === void 0 ? void 0 : o.value) === null || _a === void 0 ? void 0 : _a.is_expression));
250
+ };
251
+ /**
252
+ * Which type an option resolves to — the schema's, or the one stored on the
253
+ * value.
254
+ *
255
+ * Exported because this single decision is what every option field is rendered
256
+ * with, and what an expression's return type is checked against. It deserves
257
+ * to be assertable without standing up a form.
258
+ */
259
+ var getOptionFieldStorageType = function (optionName, fieldType, schema, operators, operatorData, isRendererOnly,
260
+ /** Whether the stored value is an expression — see below. */
261
+ isExpression) {
235
262
  if (isRendererOnly === void 0) { isRendererOnly = rendererTypes_1.isRendererOnlyUiType; }
236
263
  var schemaOption = schema === null || schema === void 0 ? void 0 : schema[optionName];
237
- var storedType = fieldType && !(fieldType === (schemaOption === null || schemaOption === void 0 ? void 0 : schemaOption.ui_type) && isRendererOnly(fieldType)) ?
264
+ /* A STORED type does not describe an expression.
265
+
266
+ An expression value is `{ is_expression: true, value: {...} }`, so the type
267
+ recorded beside it describes that envelope — `hash` — and not what the
268
+ field holds. Letting it win means the SCHEMA's declared type is ignored and
269
+ the wrong answer is handed to the expression editor as the return type to
270
+ check against: `1 + 2` on a field declared `"type": "auto"` came back
271
+ "The expression returns int, and this field holds hash". A literal `1` was
272
+ fine, because a literal is stored as a plain value whose type matches.
273
+
274
+ `auto`/`any` accept anything, which is exactly why nothing should be
275
+ checked against them; a concrete declared type still is. The schema is the
276
+ only party that knows what the field accepts. */
277
+ var storedType = (!isExpression &&
278
+ fieldType &&
279
+ !(fieldType === (schemaOption === null || schemaOption === void 0 ? void 0 : schemaOption.ui_type) && isRendererOnly(fieldType))) ?
238
280
  fieldType
239
281
  : getOptionSchemaStorageType(schemaOption, isRendererOnly);
240
282
  return (0, exports.getType)(storedType, operators, operatorData);
241
283
  };
284
+ exports.getOptionFieldStorageType = getOptionFieldStorageType;
242
285
  var hasRequiredOptions = function (options) {
243
286
  if (options === void 0) { options = {}; }
244
287
  return !!(0, lodash_1.findKey)(options, function (option) { return option.required; });
@@ -370,6 +413,64 @@ var normalizeEmptyFieldValues = function (fields) {
370
413
  return acc;
371
414
  }, {});
372
415
  };
416
+ /**
417
+ * Does this rebuild say anything the parent does not already hold?
418
+ *
419
+ * `false` means the rebuild is a MIRROR: `fixOptions` found no default to
420
+ * materialise, so the result repeats the `value` the parent handed down. The
421
+ * compare is empty-normalized because `fixOptions` round-trips a required-empty
422
+ * field between `{ value: '' }` and no `value` key, and that cosmetic
423
+ * difference would otherwise read as new information forever.
424
+ */
425
+ var isMirrorOfValue = function (fixedValue, value) { return (0, lodash_1.isEqual)(normalizeEmptyFieldValues(fixedValue), normalizeEmptyFieldValues(value)); };
426
+ exports.isMirrorOfValue = isMirrorOfValue;
427
+ /**
428
+ * What this rebuild lets the form agree with the parent on, if anything.
429
+ *
430
+ * Returns the rebuild when it mirrors `value`, and `undefined` when it
431
+ * contributed something the parent has yet to learn.
432
+ *
433
+ * Adopting the parent's value IS agreement, and saying so matters as much as
434
+ * staying quiet about it. The sync-down skips only when the parent echoed what
435
+ * this form last sent; if adopting a value never updates that record, it points
436
+ * at an older state forever, the skip can never fire again, and the form
437
+ * rebuilds on every single change. Holding the mirror back WITHOUT recording
438
+ * the agreement was measured making the starvation worse, not better — 8 s
439
+ * became a 45 s timeout — which is why the two belong together.
440
+ */
441
+ var agreementFromRebuild = function (fixedValue, value) {
442
+ return (0, exports.isMirrorOfValue)(fixedValue, value) ? fixedValue : undefined;
443
+ };
444
+ exports.agreementFromRebuild = agreementFromRebuild;
445
+ /**
446
+ * Should the form report `localValue` to its parent as an answer?
447
+ *
448
+ * Extracted and pure because the situation it exists for cannot be reproduced
449
+ * through a rendered form: it needs the emit to be decided against a `value`
450
+ * NEWER than the `localValue` built from it, which only happens when two
451
+ * components' effects land in one React commit — and a test renderer flushes
452
+ * effects between updates, so the two never fall out of step. The states below
453
+ * were recorded from a live instrumented run instead, and this is what makes
454
+ * them assertable.
455
+ *
456
+ * Two ways the answer is no:
457
+ *
458
+ * 1. `localValue` is a mirror — a rebuild of the parent's own value that added
459
+ * nothing. Repeating it back is not an answer. **Checked first**, because
460
+ * the comparison in (2) reads whatever `value` has become by now, which,
461
+ * when a nested engine shares the parent value, is already that engine's
462
+ * newer write; comparing against it would find a difference this form never
463
+ * made and emit the older mirror over the newer write. That is the loop.
464
+ * 2. `localValue` already equals `value` — there is nothing to report.
465
+ */
466
+ var shouldEmitLocalValue = function (_a) {
467
+ var localValue = _a.localValue, value = _a.value, mirroredValue = _a.mirroredValue;
468
+ if (mirroredValue !== undefined && (0, lodash_1.isEqual)(localValue, mirroredValue)) {
469
+ return false;
470
+ }
471
+ return !(0, lodash_1.isEqual)(localValue, value);
472
+ };
473
+ exports.shouldEmitLocalValue = shouldEmitLocalValue;
373
474
  var flattenOptions = function (options) {
374
475
  return (0, reduce_1.default)(options, function (newOptions, option, optionName) {
375
476
  var _a;
@@ -415,12 +516,12 @@ var FormEngineImpl = function (_a) {
415
516
  // been a no-op. Removing it from the API is a separate, breaking change —
416
517
  // every consumer passing it today already gets the flush layout by default.
417
518
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
418
- compactFlush = _l === void 0 ? false : _l, _m = _a.compactNested, compactNested = _m === void 0 ? false : _m, _o = _a.compactScroll, compactScroll = _o === void 0 ? 'host' : _o, compactPanelProps = _a.compactPanelProps, _p = _a.compactCollapsedGroups, compactCollapsedGroups = _p === void 0 ? ['optional'] : _p, _q = _a.compactToolbar, compactToolbar = _q === void 0 ? true : _q, _r = _a.commitMode, commitMode = _r === void 0 ? 'immediate' : _r, _s = _a.expandMode, expandMode = _s === void 0 ? 'single' : _s, onCommit = _a.onCommit, operatorsProp = _a.operators, groups = _a.groups, optionsLoader = _a.optionsLoader, onValidityChange = _a.onValidityChange, optionActions = _a.optionActions, _t = _a.optionActionsCollapse, optionActionsCollapse = _t === void 0 ? 'auto' : _t, componentOverrides = _a.componentOverrides, codePreviewRenderer = _a.codePreviewRenderer,
519
+ compactFlush = _l === void 0 ? false : _l, _m = _a.compactNested, compactNested = _m === void 0 ? false : _m, _o = _a.compactScroll, compactScroll = _o === void 0 ? 'host' : _o, compactPanelProps = _a.compactPanelProps, compactCollapsedGroupsProp = _a.compactCollapsedGroups, compactBoxLabels = _a.compactBoxLabels, _p = _a.compactToolbar, compactToolbar = _p === void 0 ? true : _p, _q = _a.commitMode, commitMode = _q === void 0 ? 'immediate' : _q, _r = _a.expandMode, expandMode = _r === void 0 ? 'single' : _r, onCommit = _a.onCommit, operatorsProp = _a.operators, groups = _a.groups, optionsLoader = _a.optionsLoader, onValidityChange = _a.onValidityChange, optionActions = _a.optionActions, _s = _a.optionActionsCollapse, optionActionsCollapse = _s === void 0 ? 'auto' : _s, componentOverrides = _a.componentOverrides, codePreviewRenderer = _a.codePreviewRenderer,
419
520
  // consumed by the wrapper below, which publishes it to every description this
420
521
  // form draws; destructured here only so it cannot reach `rest` and be spread
421
522
  // onto a DOM node
422
523
  _markdownRenderer = _a.markdownRenderer, // eslint-disable-line @typescript-eslint/no-unused-vars
423
- rendererOnlyUiTypes = _a.rendererOnlyUiTypes, inheritedFromParent = _a.inheritedFromParent, autoFocusFirstRequired = _a.autoFocusFirstRequired, expandFirstRequired = _a.expandFirstRequired, initialExpandedOptions = _a.initialExpandedOptions, maxFieldsShown = _a.maxFieldsShown, rest = __rest(_a, ["name", "uniqueName", "value", "onChange", "onSingleOptionsChange", "onDependableOptionChange", "placeholder", "noValueString", "isValid", "url", "customUrl", "operatorsUrl", "onOptionsLoaded", "recordRequiresSearchOptions", "readOnly", "allowTemplates", "interfaceContext", "templateFieldProps", "forceDropdown", "showTypeToggle", "compact", "compactFlush", "compactNested", "compactScroll", "compactPanelProps", "compactCollapsedGroups", "compactToolbar", "commitMode", "expandMode", "onCommit", "operators", "groups", "optionsLoader", "onValidityChange", "optionActions", "optionActionsCollapse", "componentOverrides", "codePreviewRenderer", "markdownRenderer", "rendererOnlyUiTypes", "inheritedFromParent", "autoFocusFirstRequired", "expandFirstRequired", "initialExpandedOptions", "maxFieldsShown"]);
524
+ rendererOnlyUiTypes = _a.rendererOnlyUiTypes, templateAwareUiTypes = _a.templateAwareUiTypes, anyLikeUiTypes = _a.anyLikeUiTypes, inheritedFromParent = _a.inheritedFromParent, autoFocusFirstRequired = _a.autoFocusFirstRequired, expandFirstRequired = _a.expandFirstRequired, initialExpandedOptions = _a.initialExpandedOptions, maxFieldsShown = _a.maxFieldsShown, rest = __rest(_a, ["name", "uniqueName", "value", "onChange", "onSingleOptionsChange", "onDependableOptionChange", "placeholder", "noValueString", "isValid", "url", "customUrl", "operatorsUrl", "onOptionsLoaded", "recordRequiresSearchOptions", "readOnly", "allowTemplates", "interfaceContext", "templateFieldProps", "forceDropdown", "showTypeToggle", "compact", "compactFlush", "compactNested", "compactScroll", "compactPanelProps", "compactCollapsedGroups", "compactBoxLabels", "compactToolbar", "commitMode", "expandMode", "onCommit", "operators", "groups", "optionsLoader", "onValidityChange", "optionActions", "optionActionsCollapse", "componentOverrides", "codePreviewRenderer", "markdownRenderer", "rendererOnlyUiTypes", "templateAwareUiTypes", "anyLikeUiTypes", "inheritedFromParent", "autoFocusFirstRequired", "expandFirstRequired", "initialExpandedOptions", "maxFieldsShown"]);
424
525
  // Built-ins + whatever the consumer declared for its own injected editors.
425
526
  var isRendererOnly = (0, react_2.useMemo)(function () { return (0, rendererTypes_1.createRendererOnlyUiTypeCheck)(rendererOnlyUiTypes); }, [JSON.stringify(rendererOnlyUiTypes)]);
426
527
  // Pointer CAPABILITY and viewport WIDTH are different questions and both
@@ -438,33 +539,41 @@ var FormEngineImpl = function (_a) {
438
539
  var collapseOptionActions = optionActionsCollapse === 'always' ? true
439
540
  : optionActionsCollapse === 'never' ? false
440
541
  : !isHoverCapable || !!isMobile;
441
- var _u = (0, react_2.useState)((rest === null || rest === void 0 ? void 0 : rest.options) || undefined), options = _u[0], setOptions = _u[1];
542
+ /* The schema, as the form should ASK it — not always as it was served.
543
+ `resolveDegenerateRequiredGroups` turns a one-of group that has a single
544
+ member in this schema back into a plain required field, because a choice
545
+ with one option is not a choice. Applied here, at the one place the schema
546
+ enters the component, so the header, the chips, the messages, the meter and
547
+ the read-first summary cannot disagree about it: every one of them reads
548
+ `options`, and none of them has to know the rule. */
549
+ var _t = (0, react_2.useState)((rest === null || rest === void 0 ? void 0 : rest.options) || undefined), servedOptions = _t[0], setOptions = _t[1];
550
+ var options = (0, react_2.useMemo)(function () { return (0, options_1.resolveDegenerateRequiredGroups)(servedOptions); }, [servedOptions]);
442
551
  // optionsLoader lifecycle: loading feeds the skeleton gate, error the banner.
443
- var _v = (0, react_2.useState)(!!optionsLoader && !(rest === null || rest === void 0 ? void 0 : rest.options)), optionsLoading = _v[0], setOptionsLoading = _v[1];
444
- var _w = (0, react_2.useState)(), optionsError = _w[0], setOptionsError = _w[1];
552
+ var _u = (0, react_2.useState)(!!optionsLoader && !(rest === null || rest === void 0 ? void 0 : rest.options)), optionsLoading = _u[0], setOptionsLoading = _u[1];
553
+ var _v = (0, react_2.useState)(), optionsError = _v[0], setOptionsError = _v[1];
445
554
  // Operators: prop-provided (compact) or fetched via operatorsUrl (dpql,
446
555
  // ported from IDE Options) — the fetch overrides the seeded prop value.
447
- var _x = (0, react_2.useState)(operatorsProp), operators = _x[0], setOperators = _x[1];
556
+ var _w = (0, react_2.useState)(operatorsProp), operators = _w[0], setOperators = _w[1];
448
557
  // Remote-fetch loading (ported from IDE Options); only relevant when one of
449
558
  // the fetch urls is set — schema-as-props consumers never see the skeleton.
450
- var _y = (0, react_2.useState)(!!(url || customUrl || operatorsUrl)), loading = _y[0], setLoading = _y[1];
559
+ var _x = (0, react_2.useState)(!!(url || customUrl || operatorsUrl)), loading = _x[0], setLoading = _x[1];
451
560
  var confirmAction = (0, reqore_1.useReqoreProperty)('confirmAction');
452
561
  var theme = (0, reqore_1.useReqoreTheme)();
453
- var _z = (0, react_2.useState)(), focusedEditing = _z[0], setFocusedEditing = _z[1];
454
- var _0 = (0, react_2.useState)(false), showFieldTypes = _0[0], setShowFieldTypes = _0[1];
562
+ var _y = (0, react_2.useState)(), focusedEditing = _y[0], setFocusedEditing = _y[1];
563
+ var _z = (0, react_2.useState)(false), showFieldTypes = _z[0], setShowFieldTypes = _z[1];
455
564
  // Global toggle (toolbar ⓘ): reveal the short-description info panel on every
456
565
  // field that has a short_desc. Per-row ⓘ overrides still win over it.
457
566
  // Global field-info visibility, tri-state: `undefined` = default (critical
458
567
  // messages auto-open, the rest closed); `true` = show all; `false` = hide all
459
568
  // (even message fields). The toolbar ⓘ drives it.
460
- var _1 = (0, react_2.useState)(undefined), showAllDescriptions = _1[0], setShowAllDescriptions = _1[1];
461
- var _2 = (0, react_2.useState)(), showHelpForOption = _2[0], setShowHelpForOption = _2[1];
462
- var _3 = (0, react_2.useState)(false), showInvalidOptionsOnly = _3[0], setShowInvalidOptionsOnly = _3[1];
569
+ var _0 = (0, react_2.useState)(undefined), showAllDescriptions = _0[0], setShowAllDescriptions = _0[1];
570
+ var _1 = (0, react_2.useState)(), showHelpForOption = _1[0], setShowHelpForOption = _1[1];
571
+ var _2 = (0, react_2.useState)(false), showInvalidOptionsOnly = _2[0], setShowInvalidOptionsOnly = _2[1];
463
572
  // Which options are expanded into their editor (several can be open at once).
464
- var _4 = (0, react_2.useState)([]), expandedOptions = _4[0], setExpandedOptions = _4[1];
573
+ var _3 = (0, react_2.useState)([]), expandedOptions = _3[0], setExpandedOptions = _3[1];
465
574
  /** Status boxes whose "N more fields" control the reader has opened. Keyed by
466
575
  box so opening Optional does not also unfold Set. */
467
- var _5 = (0, react_2.useState)([]), expandedMoreBoxes = _5[0], setExpandedMoreBoxes = _5[1];
576
+ var _4 = (0, react_2.useState)([]), expandedMoreBoxes = _4[0], setExpandedMoreBoxes = _4[1];
468
577
  // --- Reveal a status box's content when it is opened ----------------------
469
578
  //
470
579
  // The status boxes stack, so the last one ("Optional", which holds every
@@ -480,7 +589,7 @@ var FormEngineImpl = function (_a) {
480
589
  // box has not grown and there is nothing to scroll to. A layout effect runs
481
590
  // after the DOM is updated and before paint, which makes the reveal
482
591
  // deterministic instead of a guessed delay.
483
- var _6 = (0, react_2.useState)(undefined), openedStatusBox = _6[0], setOpenedStatusBox = _6[1];
592
+ var _5 = (0, react_2.useState)(undefined), openedStatusBox = _5[0], setOpenedStatusBox = _5[1];
484
593
  var statusBoxRefs = (0, react_2.useRef)({});
485
594
  (0, react_2.useLayoutEffect)(function () {
486
595
  var _a, _b;
@@ -511,13 +620,13 @@ var FormEngineImpl = function (_a) {
511
620
  var settledBucket = (0, react_2.useRef)({});
512
621
  // Measured form width (not viewport — the form lives in drawers/panels of
513
622
  // arbitrary width) drives the stacked narrow layout.
514
- var _7 = (0, react_use_1.useMeasure)(), compactWrapRef = _7[0], compactWrapWidth = _7[1].width;
623
+ var _6 = (0, react_use_1.useMeasure)(), compactWrapRef = _6[0], compactWrapWidth = _6[1].width;
515
624
  // Own handle on the scroll wrap (useMeasure's ref is a callback, no `.current`)
516
625
  // so the label-column measurement can publish its CSS var on the element. It's
517
626
  // STATE, not a ref: the wrap mounts only after the loading-skeleton gate
518
627
  // resolves, so the measurement effect must re-run when the node appears — a
519
628
  // ref wouldn't retrigger it.
520
- var _8 = (0, react_2.useState)(null), compactWrapNode = _8[0], setCompactWrapNode = _8[1];
629
+ var _7 = (0, react_2.useState)(null), compactWrapNode = _7[0], setCompactWrapNode = _7[1];
521
630
  // Ref mirror of the wrap node for callbacks that must not re-create when it
522
631
  // mounts (flashOptions reads it inside a rAF).
523
632
  var compactWrapNodeRef = (0, react_2.useRef)(null);
@@ -571,8 +680,8 @@ var FormEngineImpl = function (_a) {
571
680
  var readRowHeights = (0, react_2.useRef)({});
572
681
  // Required-groups linkage: hovering a group chip highlights every member row;
573
682
  // clicking a sibling in the chip's popover scrolls to it and flashes it.
574
- var _9 = (0, react_2.useState)([]), highlightedOptions = _9[0], setHighlightedOptions = _9[1];
575
- var _10 = (0, react_2.useState)([]), flashedOptions = _10[0], setFlashedOptions = _10[1];
683
+ var _8 = (0, react_2.useState)([]), highlightedOptions = _8[0], setHighlightedOptions = _8[1];
684
+ var _9 = (0, react_2.useState)([]), flashedOptions = _9[0], setFlashedOptions = _9[1];
576
685
  var flashTimeout = (0, react_2.useRef)();
577
686
  var flashOptions = (0, react_2.useCallback)(function (optionNames, scrollToFirst) {
578
687
  if (scrollToFirst === void 0) { scrollToFirst = false; }
@@ -621,28 +730,58 @@ var FormEngineImpl = function (_a) {
621
730
  });
622
731
  var compactNarrow = !!compactWrapWidth && compactWrapWidth < 480;
623
732
  // Info panels auto-open on Tier-1 content; the per-row user override sticks.
624
- var _11 = (0, react_2.useState)({}), infoPanelOverrides = _11[0], setInfoPanelOverrides = _11[1];
733
+ var _10 = (0, react_2.useState)({}), infoPanelOverrides = _10[0], setInfoPanelOverrides = _10[1];
625
734
  // Toolbar filters affect the listed rows only — the meter reflects the full set.
626
- var _12 = (0, react_2.useState)(false), requiredOnly = _12[0], setRequiredOnly = _12[1];
627
- var _13 = (0, react_2.useState)(''), compactQuery = _13[0], setCompactQuery = _13[1];
735
+ var _11 = (0, react_2.useState)(false), requiredOnly = _11[0], setRequiredOnly = _11[1];
736
+ var _12 = (0, react_2.useState)(''), compactQuery = _12[0], setCompactQuery = _12[1];
628
737
  // Compact field sort (Fields menu → "Sort by"); 'schema' = declared order.
629
- var _14 = (0, react_2.useState)('schema'), compactSort = _14[0], setCompactSort = _14[1];
630
- var _15 = (0, react_2.useState)(function () { return ({
738
+ var _13 = (0, react_2.useState)('schema'), compactSort = _13[0], setCompactSort = _13[1];
739
+ var _14 = (0, react_2.useState)(function () { return ({
631
740
  fields: (0, exports.fixOptions)(value, options || {}, undefined, isRendererOnly),
632
741
  meta: undefined,
633
- }); }), localValue = _15[0], setLocalValue = _15[1];
742
+ }); }), localValue = _14[0], setLocalValue = _14[1];
634
743
  var originalValue = (0, react_2.useRef)();
635
744
  // Track the last value we emitted via onChange so we can skip re-applying fixOptions
636
745
  // when the parent echoes it back as the new value prop (controlled component loop prevention)
637
746
  var lastEmittedValue = (0, react_2.useRef)(value);
747
+ /**
748
+ * A `localValue` that is only a MIRROR of the incoming `value`, and so must
749
+ * never be emitted back as though this form had answered something.
750
+ *
751
+ * `localValue` is derived state: the sync-down effect below rebuilds it from
752
+ * the `value` prop. When that rebuild adds nothing (`fixOptions` had no
753
+ * default to materialise), the result says exactly what the parent already
754
+ * holds, and emitting it is not a change — it is this form repeating the
755
+ * parent's own words back at it.
756
+ *
757
+ * That repetition is harmless in a form that owns its state outright and
758
+ * fatal in one that shares a parent with a NESTED engine. The two write to
759
+ * the same parent value, so each sees the other's write arrive as an external
760
+ * change; `lastEmittedValue` is then always one step behind and its guard
761
+ * never fires. The engines alternate between two states forever, several
762
+ * times a second, and the newer write is repeatedly overwritten by the older
763
+ * one. Measured in the test editor: the row's summary flipped between two
764
+ * readings indefinitely and `setTimeout` was starved past a 45-second
765
+ * deadline.
766
+ *
767
+ * Only a mirror that CHANGES nothing is held back. A rebuild that restores a
768
+ * required or preselected field's default still emits, because that default
769
+ * is genuinely new information the parent has to be told about — which is the
770
+ * whole reason the sync-down runs `fixOptions` at all.
771
+ */
772
+ var mirroredValue = (0, react_2.useRef)(undefined);
638
773
  if (originalValue.current === undefined && (0, size_1.default)(value)) {
639
774
  originalValue.current = localValue.fields;
640
775
  }
641
776
  var unavailableOptionsCount = (0, react_2.useRef)(0);
642
- var _16 = (0, useQorusTypes_1.useQorusTypes)(), compactValue = _16.compactValue, typesLoading = _16.loading;
777
+ var _15 = (0, useQorusTypes_1.useQorusTypes)(), compactValue = _15.compactValue, typesLoading = _15.loading;
643
778
  var templates = (0, useTemplates_1.useTemplates)(allowTemplates, rest.stringTemplates, interfaceContext);
644
779
  (0, react_2.useEffect)(function () {
645
- if ((0, lodash_1.isEqual)(localValue.fields, value)) {
780
+ if (!(0, exports.shouldEmitLocalValue)({
781
+ localValue: localValue.fields,
782
+ value: value,
783
+ mirroredValue: mirroredValue.current,
784
+ })) {
646
785
  return;
647
786
  }
648
787
  var toEmit = (0, size_1.default)(localValue.fields) ? localValue.fields : undefined;
@@ -822,20 +961,49 @@ var FormEngineImpl = function (_a) {
822
961
  // Note: compare fixedValue against value, not localValue.fields — localValue may have been
823
962
  // updated by nested FormEngine emissions, so comparing against it would never skip.
824
963
  var normalizedValue = normalizeEmptyFieldValues(value);
964
+ /* What this rebuild lets us agree with the parent on — `undefined` when it
965
+ contributed something the parent has yet to learn. */
966
+ var agreed = (0, exports.agreementFromRebuild)(fixedValue, value);
825
967
  if ((0, lodash_1.isEqual)(normalizedValue, normalizeEmptyFieldValues(lastEmittedValue.current)) &&
826
- (0, lodash_1.isEqual)(normalizeEmptyFieldValues(fixedValue), normalizedValue)) {
968
+ agreed !== undefined) {
827
969
  return;
828
970
  }
829
971
  if (!originalValue.current && (0, size_1.default)(fixedValue)) {
830
972
  originalValue.current = fixedValue;
831
973
  }
974
+ /* Remember a mirror so the emit effect can tell one from an answer, and
975
+ record the agreement it represents. A rebuild that DID add something
976
+ leaves both alone — the parent has to learn about it. */
977
+ mirroredValue.current = agreed;
978
+ if (agreed !== undefined) {
979
+ lastEmittedValue.current = agreed;
980
+ }
832
981
  setLocalValue === null || setLocalValue === void 0 ? void 0 : setLocalValue({ fields: fixedValue, meta: undefined });
833
982
  }, [JSON.stringify(options), JSON.stringify(value), isRendererOnly]);
983
+ /**
984
+ * Fields the reader has actually edited in this instance.
985
+ *
986
+ * "This field is required" is an ERROR message, and an error is a report that
987
+ * something went wrong. Under a field nobody has been in yet it reports
988
+ * nothing — the form is empty because it is new — while the requirement is
989
+ * already stated by the asterisk, by the Needs-attention box the row sits in,
990
+ * and by the completion meter. A form whose first act is to accuse the reader
991
+ * of a mistake they have not made teaches them to discount its warnings.
992
+ *
993
+ * So the message waits for a touch. Being IN the field and leaving it empty
994
+ * is a real gap, and that one shows. A ref, not state: every edit re-renders
995
+ * anyway, so the following render reads the current set with no second pass.
996
+ */
997
+ var touchedOptionsRef = (0, react_2.useRef)(new Set());
834
998
  var handleValueChange = (0, react_2.useCallback)(function (optionName, val, _type, isFunction) {
999
+ // Every route into this handler is a person acting: typing, picking from a
1000
+ // menu, clearing the value, adding an optional field. Marked here rather
1001
+ // than inside the updater below, which React may invoke twice.
1002
+ touchedOptionsRef.current.add(optionName);
835
1003
  setLocalValue(function (_a) {
836
1004
  var _b, _c;
837
- var _d, _f, _g, _h, _j, _k, _l, _m, _o, _p;
838
- var _q = _a.fields, fields = _q === void 0 ? {} : _q;
1005
+ var _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1006
+ var _r = _a.fields, fields = _r === void 0 ? {} : _r;
839
1007
  var schemaType = getOptionSchemaStorageType(options === null || options === void 0 ? void 0 : options[optionName], isRendererOnly);
840
1008
  var isAnyLike = schemaType === 'any' || schemaType === 'auto';
841
1009
  // For any/auto schema types, preserve the user's chosen type stored in the field
@@ -868,27 +1036,46 @@ var FormEngineImpl = function (_a) {
868
1036
  // by TemplateField/auto) drives the option's `is_expression` flag;
869
1037
  // the value itself is the raw AST `{ exp, args }`.
870
1038
  var updatedValue = __assign(__assign({}, fields), (_c = {}, _c[optionName] = __assign(__assign({}, fields[optionName]), { type: type, value: val }), _c));
871
- if (isFunction) {
1039
+ /* The flag belongs to the VALUE, so a change that said nothing about it
1040
+ does not throw it away — `undefined` is no opinion, not a denial.
1041
+ Deleting it unconditionally left the AST on the row with nothing
1042
+ marking it as one: an assertion's Expected Value came back from a
1043
+ saved draft drawn as a two-field hash of `exp` and `args`, because
1044
+ merely opening the row and reverting had stripped the flag. */
1045
+ if ((0, argumentPresence_1.shouldMarkAsExpression)(isFunction, val, (_l = fields[optionName]) === null || _l === void 0 ? void 0 : _l.is_expression)) {
872
1046
  updatedValue[optionName].is_expression = true;
873
1047
  }
874
1048
  else {
875
1049
  delete updatedValue[optionName].is_expression;
876
1050
  }
877
1051
  var meta = {};
878
- if (((_l = options === null || options === void 0 ? void 0 : options[optionName]) === null || _l === void 0 ? void 0 : _l.has_dependents) &&
1052
+ if (((_m = options === null || options === void 0 ? void 0 : options[optionName]) === null || _m === void 0 ? void 0 : _m.has_dependents) &&
879
1053
  val !== undefined &&
880
- val !== ((_m = fields[optionName]) === null || _m === void 0 ? void 0 : _m.value)) {
1054
+ val !== ((_o = fields[optionName]) === null || _o === void 0 ? void 0 : _o.value)) {
881
1055
  (0, lodash_1.forEach)(options, function (option, depName) {
1056
+ /* A dependency is `name`, `name=value` or `name!=value`, so the
1057
+ entry has to be PARSED to find which option it names. Compared as
1058
+ a whole string, only the bare spelling ever matched — so a field
1059
+ declared `depends_on: ['subject_iface_kind=workflow']` was never
1060
+ cleared when that option changed.
1061
+
1062
+ What that cost: a test whose subject was switched from a workflow
1063
+ to a service kept the workflow's `Subject Interface Version`.
1064
+ Services and jobs are versioned but only the latest is testable,
1065
+ so the field no longer applied, yet it stayed on the form as a
1066
+ locked control in front of a value the author could not clear —
1067
+ the value being there is exactly what stops the field being
1068
+ withheld outright. */
882
1069
  if (option.depends_on &&
883
- (0, lodash_1.flatten)(option.depends_on).includes(optionName) &&
1070
+ (0, lodash_1.flatten)(option.depends_on).some(function (dependency) { return (0, validations_1.parseDependency)(dependency).name === optionName; }) &&
884
1071
  updatedValue[depName]) {
885
1072
  updatedValue[depName].value = undefined;
886
1073
  }
887
1074
  });
888
1075
  onDependableOptionChange === null || onDependableOptionChange === void 0 ? void 0 : onDependableOptionChange(optionName, val, updatedValue, options);
889
1076
  }
890
- if ((0, size_1.default)((_o = options === null || options === void 0 ? void 0 : options[optionName]) === null || _o === void 0 ? void 0 : _o.on_change)) {
891
- meta.events = (_p = options === null || options === void 0 ? void 0 : options[optionName]) === null || _p === void 0 ? void 0 : _p.on_change;
1077
+ if ((0, size_1.default)((_p = options === null || options === void 0 ? void 0 : options[optionName]) === null || _p === void 0 ? void 0 : _p.on_change)) {
1078
+ meta.events = (_q = options === null || options === void 0 ? void 0 : options[optionName]) === null || _q === void 0 ? void 0 : _q.on_change;
892
1079
  }
893
1080
  onSingleOptionsChange === null || onSingleOptionsChange === void 0 ? void 0 : onSingleOptionsChange(optionName, updatedValue[optionName]);
894
1081
  if (compact && !readOnly && (0, readFirst_1.shouldAutoCollapseCompactOption)(options === null || options === void 0 ? void 0 : options[optionName], val)) {
@@ -1006,7 +1193,7 @@ var FormEngineImpl = function (_a) {
1006
1193
  return newValue;
1007
1194
  }
1008
1195
  var rendererType = (0, exports.getType)((options[optionName].ui_type || options[optionName].type), operators, option === null || option === void 0 ? void 0 : option.op);
1009
- var storageType = getOptionFieldStorageType(optionName, option === null || option === void 0 ? void 0 : option.type, options, operators, option === null || option === void 0 ? void 0 : option.op, isRendererOnly);
1196
+ var storageType = (0, exports.getOptionFieldStorageType)(optionName, option === null || option === void 0 ? void 0 : option.type, options, operators, option === null || option === void 0 ? void 0 : option.op, isRendererOnly, optionHoldsExpression(option));
1010
1197
  if (!(0, lodash_1.isPlainObject)(option)) {
1011
1198
  return __assign(__assign({}, newValue), (_a = {}, _a[optionName] = {
1012
1199
  type: storageType || rendererType,
@@ -1107,7 +1294,7 @@ var FormEngineImpl = function (_a) {
1107
1294
  var getValidityData = (0, react_2.useCallback)(function () {
1108
1295
  var fields = (0, reduce_1.default)(availableOptions, function (result, option, optionName) {
1109
1296
  var _a, _b;
1110
- var type = getOptionFieldStorageType(optionName, option.type, options, operators, option.op, isRendererOnly);
1297
+ var type = (0, exports.getOptionFieldStorageType)(optionName, option.type, options, operators, option.op, isRendererOnly, optionHoldsExpression(option));
1111
1298
  var optionValue = option.value;
1112
1299
  var isRequired = (_a = options === null || options === void 0 ? void 0 : options[optionName]) === null || _a === void 0 ? void 0 : _a.required;
1113
1300
  var hasRequiredGroups = !!((_b = options === null || options === void 0 ? void 0 : options[optionName]) === null || _b === void 0 ? void 0 : _b.required_groups);
@@ -1151,7 +1338,7 @@ var FormEngineImpl = function (_a) {
1151
1338
  return (0, reduce_1.default)(availableOptions, function (newValue, option, optionName) {
1152
1339
  var _a;
1153
1340
  if (showInvalidOptionsOnly &&
1154
- isOptionValid(optionName, getOptionFieldStorageType(optionName, option.type, options, operators, option.op, isRendererOnly), option.value)) {
1341
+ isOptionValid(optionName, (0, exports.getOptionFieldStorageType)(optionName, option.type, options, operators, option.op, isRendererOnly, optionHoldsExpression(option)), option.value)) {
1155
1342
  return newValue;
1156
1343
  }
1157
1344
  return __assign(__assign({}, newValue), (_a = {}, _a[optionName] = option, _a));
@@ -1185,7 +1372,7 @@ var FormEngineImpl = function (_a) {
1185
1372
  if (hidden)
1186
1373
  return 'optional';
1187
1374
  var schema = options === null || options === void 0 ? void 0 : options[name];
1188
- var type = getOptionFieldStorageType(name, (_a = availableOptions === null || availableOptions === void 0 ? void 0 : availableOptions[name]) === null || _a === void 0 ? void 0 : _a.type, options, operators, (_b = availableOptions === null || availableOptions === void 0 ? void 0 : availableOptions[name]) === null || _b === void 0 ? void 0 : _b.op, isRendererOnly);
1375
+ var type = (0, exports.getOptionFieldStorageType)(name, (_a = availableOptions === null || availableOptions === void 0 ? void 0 : availableOptions[name]) === null || _a === void 0 ? void 0 : _a.type, options, operators, (_b = availableOptions === null || availableOptions === void 0 ? void 0 : availableOptions[name]) === null || _b === void 0 ? void 0 : _b.op, isRendererOnly, optionHoldsExpression(availableOptions === null || availableOptions === void 0 ? void 0 : availableOptions[name]));
1189
1376
  var value = (_c = availableOptions === null || availableOptions === void 0 ? void 0 : availableOptions[name]) === null || _c === void 0 ? void 0 : _c.value;
1190
1377
  // A schema-declared hash whose every entry is a materialised-but-unset
1191
1378
  // field holds nothing, so it is not "set" — without this the row showed a
@@ -1307,11 +1494,16 @@ var FormEngineImpl = function (_a) {
1307
1494
  if (!names.length) {
1308
1495
  return;
1309
1496
  }
1310
- hasAppliedInitialExpansionRef.current = true;
1311
1497
  var toExpand = initialExpandedOptions.filter(function (name) { return names.includes(name); });
1312
1498
  if (!toExpand.length) {
1499
+ // Not "there is no such row" — "not YET". A server-driven schema arrives
1500
+ // in pieces, and the row the caller named can be in a later one (a test's
1501
+ // `cases` field materialises once its step-kind catalogue lands). Latching
1502
+ // here would spend the one shot on a form that did not yet contain the
1503
+ // field, and the address would never be applied at all.
1313
1504
  return;
1314
1505
  }
1506
+ hasAppliedInitialExpansionRef.current = true;
1315
1507
  setExpandedOptions(function (prev) { return __spreadArray(__spreadArray([], prev, true), toExpand.filter(function (name) { return !prev.includes(name); }), true); });
1316
1508
  }, [initialExpandedOptions, compact, availableOptions]);
1317
1509
  // --- First-attention-field autofocus (opt-in) -----------------------------
@@ -1394,7 +1586,14 @@ var FormEngineImpl = function (_a) {
1394
1586
  }
1395
1587
  setExpandedOptions(function (prev) {
1396
1588
  return prev.includes(target) ? prev
1397
- : expandMode === 'multi' ? __spreadArray(__spreadArray([], prev, true), [target], false) : [target];
1589
+ : expandMode === 'multi' ? __spreadArray(__spreadArray([], prev, true), [target], false)
1590
+ // `expandMode: 'single'` is a rule about what a CLICK does: opening a
1591
+ // row closes the one the reader last opened. It is not a licence to
1592
+ // close a row the HOST named through `initialExpandedOptions`. That
1593
+ // is an address — a statement about what the page is FOR, made before
1594
+ // the reader did anything — and this scan is a convenience layered on
1595
+ // top of it. Keep the named rows; replace only what a click could.
1596
+ : __spreadArray(__spreadArray([], prev.filter(function (name) { return initialExpandedOptions === null || initialExpandedOptions === void 0 ? void 0 : initialExpandedOptions.includes(name); }), true), [target], false);
1398
1597
  });
1399
1598
  }
1400
1599
  }, [
@@ -1407,10 +1606,13 @@ var FormEngineImpl = function (_a) {
1407
1606
  dependencyLockedNames,
1408
1607
  getOptionBucket,
1409
1608
  expandMode,
1609
+ initialExpandedOptions,
1410
1610
  ]);
1411
1611
  // Read-first completion summary (how many shown options have a value set),
1412
1612
  // surfaced as a progress meter at the top of the compact form.
1413
- var readFirstCompletion = (0, react_2.useMemo)(function () { return (0, readFirst_1.getReadFirstCompletion)(availableOptions); }, [JSON.stringify(availableOptions)]);
1613
+ var readFirstCompletion = (0, react_2.useMemo)(function () {
1614
+ return (0, readFirst_1.getReadFirstCompletion)(availableOptions, function (name) { return getOptionBucket(name) === 'attention'; });
1615
+ }, [JSON.stringify(availableOptions), getOptionBucket]);
1414
1616
  var optionalFields = (0, react_2.useMemo)(function () {
1415
1617
  return Object.keys(filteredOptions).map(function (option) {
1416
1618
  var _a, _b, _c, _d, _f, _g;
@@ -1477,7 +1679,7 @@ var FormEngineImpl = function (_a) {
1477
1679
  // The info panel below the row keeps showing schema messages while editing —
1478
1680
  // rendering them in the editor too would balloon a one-line edit.
1479
1681
  suppressSchemaMessages) {
1480
- var _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
1682
+ var _b, _c, _d, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
1481
1683
  var type = _a.type, other = __rest(_a, ["type"]);
1482
1684
  var operatorParts = (0, common_1.fixOperatorValue)(other.op);
1483
1685
  // The RENDERER type for this row — which editor mounts. Distinct from the
@@ -1494,11 +1696,54 @@ var FormEngineImpl = function (_a) {
1494
1696
  var optionSchema = options === null || options === void 0 ? void 0 : options[optionName];
1495
1697
  var schemaUiType = optionSchema === null || optionSchema === void 0 ? void 0 : optionSchema.ui_type;
1496
1698
  var uiTypeIsAnyLike = schemaUiType === 'any' || schemaUiType === 'auto';
1699
+ /* An EXPRESSION's stored type is skipped in this chain.
1700
+
1701
+ The value is `{ is_expression: true, value: {...} }`, so the type saved
1702
+ beside it describes that envelope — `hash` — not what the field holds.
1703
+ With no `ui_type` to win first, that `hash` beat the schema's `auto`
1704
+ and was handed to the expression editor as the return type to check
1705
+ against: `1 + 2` on a field declared `auto` came back "This does not
1706
+ fit. The expression returns int, and this field holds hash". A literal
1707
+ `1` was fine, because a literal is stored as a plain value whose type
1708
+ matches.
1709
+
1710
+ Dropping the stored type here lets the schema decide, which is the
1711
+ only party that knows what the field accepts. This is the row
1712
+ renderer's OWN resolution and does not go through
1713
+ `getOptionFieldStorageType`, so guarding that one alone left this
1714
+ untouched. */
1715
+ var storedRowType = optionHoldsExpression(other) ? undefined : type;
1716
+ /* A RELOADED expression is handed to the editor in the shape the editor
1717
+ was written against.
1718
+
1719
+ An expression is written as `{ type, is_expression: true, value: ast }`
1720
+ and saved as `{ type, value: { is_expression: true, value: ast } }` —
1721
+ the flag moves ONTO the value. The editor was only ever given
1722
+ `other.is_expression`, which is absent in the saved shape, so a
1723
+ reloaded expression was not recognised as one: an `auto` field then
1724
+ resolved its type from the value it could see, decided the envelope was
1725
+ a `hash`, and rendered `1 + 2` as a raw tree of `is_expression` /
1726
+ `exp` / `args` for the author to edit by hand.
1727
+
1728
+ Both halves have to move together. Flipping the flag alone would put
1729
+ the editor into expression mode and then hand it the ENVELOPE where it
1730
+ expects the expression, so the value is unwrapped here too — after
1731
+ which a saved expression is indistinguishable from one just typed,
1732
+ which is what every component downstream already assumes.
1733
+
1734
+ Reported as "after Run assertion the value shows as raw data", but the
1735
+ run is incidental: the field beside it, which no run touched, was
1736
+ equally raw. The trigger is a RELOAD, which is the only thing that puts
1737
+ an expression into its saved shape. */
1738
+ var nestedExpression = !!((_b = other === null || other === void 0 ? void 0 : other.value) === null || _b === void 0 ? void 0 : _b.is_expression);
1739
+ var expressionAwareValue = nestedExpression ?
1740
+ other.value.value
1741
+ : other.value;
1497
1742
  var resolvedType = ((0, readFirst_1.isFixedCompactAllowedValueOption)(optionSchema) ?
1498
1743
  getOptionSchemaStorageType(optionSchema, isRendererOnly)
1499
1744
  : schemaUiType && !uiTypeIsAnyLike ? schemaUiType
1500
1745
  : undefined) ||
1501
- type ||
1746
+ storedRowType ||
1502
1747
  schemaUiType ||
1503
1748
  (optionSchema === null || optionSchema === void 0 ? void 0 : optionSchema.type) ||
1504
1749
  'any';
@@ -1537,7 +1782,7 @@ var FormEngineImpl = function (_a) {
1537
1782
  (0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, { disabled: readOnly, icon: 'DeleteBinLine', effect: NegativeColorEffect, fixed: true, onClick: function () { return handleRemoveOperator(optionName, fixedValue, index); } })
1538
1783
  : null] }, index));
1539
1784
  }) }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreVerticalSpacer, { height: 5 })] })
1540
- : null, (0, react_1.createElement)(TemplateField_1.TemplateField, __assign({ fluid: true }, options === null || options === void 0 ? void 0 : options[optionName], resolveInheritProps((_b = options === null || options === void 0 ? void 0 : options[optionName]) === null || _b === void 0 ? void 0 : _b.inherit_props, availableOptions, inheritedFromParent), {
1785
+ : null, (0, react_1.createElement)(TemplateField_1.TemplateField, __assign({ fluid: true }, options === null || options === void 0 ? void 0 : options[optionName], resolveInheritProps((_c = options === null || options === void 0 ? void 0 : options[optionName]) === null || _c === void 0 ? void 0 : _c.inherit_props, availableOptions, inheritedFromParent), {
1541
1786
  // qorus#347-followup (scope forwarding): merge accumulated
1542
1787
  // inheritance (`inheritedFromParent`) with THIS field's freshly
1543
1788
  // resolved inherit_props, and pass down as a single bag so any
@@ -1549,13 +1794,13 @@ var FormEngineImpl = function (_a) {
1549
1794
  // `inherit_props: { language: 'language' }`, the list renderer
1550
1795
  // forwards it into each row, and the row's body resolves against
1551
1796
  // the accumulated bag.
1552
- inheritedFromParent: __assign(__assign({}, inheritedFromParent), resolveInheritProps((_c = options === null || options === void 0 ? void 0 : options[optionName]) === null || _c === void 0 ? void 0 : _c.inherit_props, availableOptions, inheritedFromParent)),
1797
+ inheritedFromParent: __assign(__assign({}, inheritedFromParent), resolveInheritProps((_d = options === null || options === void 0 ? void 0 : options[optionName]) === null || _d === void 0 ? void 0 : _d.inherit_props, availableOptions, inheritedFromParent)),
1553
1798
  // Propagate compact so an arg_schema field renders a COMPACT sub-form
1554
1799
  // (consistent with the parent) rather than the classic FormEngine.
1555
1800
  compact: compact,
1556
1801
  // SEAM: forwarded through TemplateField's rest-spread to AutoFormField,
1557
1802
  // which renders consumer-injected editors by field type/ui_type.
1558
- componentOverrides: componentOverrides, allowTemplates: !!(allowTemplates && ((_d = options === null || options === void 0 ? void 0 : options[optionName]) === null || _d === void 0 ? void 0 : _d.supports_templates)), allowFunctions: !!((_f = options === null || options === void 0 ? void 0 : options[optionName]) === null || _f === void 0 ? void 0 : _f.supports_expressions),
1803
+ componentOverrides: componentOverrides, templateAwareUiTypes: templateAwareUiTypes, allowTemplates: !!(allowTemplates && ((_f = options === null || options === void 0 ? void 0 : options[optionName]) === null || _f === void 0 ? void 0 : _f.supports_templates)), allowFunctions: !!((_g = options === null || options === void 0 ? void 0 : options[optionName]) === null || _g === void 0 ? void 0 : _g.supports_expressions),
1559
1804
  // reqraft: form-level expression fields get the Visual/Text shell
1560
1805
  // (DPQL text mode); opt out per-form via `templateFieldProps`.
1561
1806
  allowTextExpressions: true,
@@ -1567,25 +1812,42 @@ var FormEngineImpl = function (_a) {
1567
1812
  // control groups. The allowed-values renderer itself enforces the
1568
1813
  // fixed catalogue and never exposes free-form entry.
1569
1814
  allowCustomValues: (0, readFirst_1.isFixedCompactAllowedValueOption)(optionSchema) ||
1570
- (((_g = options === null || options === void 0 ? void 0 : options[optionName]) === null || _g === void 0 ? void 0 : _g.supports_custom_values) !== false && resolvedType !== 'any'), templates: templates.value }, (0, exports.getTypeAndCanBeNull)(
1815
+ (((_h = options === null || options === void 0 ? void 0 : options[optionName]) === null || _h === void 0 ? void 0 : _h.supports_custom_values) !== false && resolvedType !== 'any'),
1816
+ // A field may declare a template list OF ITS OWN, and when it does
1817
+ // that list is the one to offer. The engine-wide `stringTemplates`
1818
+ // are the form's shared vocabulary - config items, system properties
1819
+ // - and every field draws on the same set. A per-field list answers a
1820
+ // question only that field asks: a test assertion's `$.` references
1821
+ // are the values ITS case captured, and no other field in the form
1822
+ // should be offering them.
1823
+ //
1824
+ // Passing the engine's list unconditionally overwrote the schema's,
1825
+ // so a field that declared templates had none by the time it
1826
+ // rendered. On a typed field that only cost the picker; on an
1827
+ // ANY-LIKE one it changed the question, because `TemplateField`
1828
+ // opens on the template selector only when there is something to
1829
+ // pick and otherwise falls back to the type picker. An untyped field
1830
+ // therefore asked the author to choose `string`/`int`/`hash` before
1831
+ // it would let them name a value they had already captured.
1832
+ templates: (_j = optionSchema === null || optionSchema === void 0 ? void 0 : optionSchema.templates) !== null && _j !== void 0 ? _j : templates.value }, (0, exports.getTypeAndCanBeNull)(
1571
1833
  // The RENDERER type picks the editor — the storage type lives on
1572
1834
  // the value envelope. Passing storage here rendered a `richtext`
1573
1835
  // field as a plain string input.
1574
- resolvedType, (_h = options === null || options === void 0 ? void 0 : options[optionName]) === null || _h === void 0 ? void 0 : _h.allowed_values, other.op), { ui_type: resolvedType, name: optionName, uniqueName: "".concat(uniqueName ? "".concat(uniqueName, ".") : "".concat(name ? "".concat(name, ".") : '')).concat(optionName), onChange:
1836
+ resolvedType, (_k = options === null || options === void 0 ? void 0 : options[optionName]) === null || _k === void 0 ? void 0 : _k.allowed_values, other.op), { ui_type: resolvedType, name: optionName, uniqueName: "".concat(uniqueName ? "".concat(uniqueName, ".") : "".concat(name ? "".concat(name, ".") : '')).concat(optionName), onChange:
1575
1837
  // Identity-stable on purpose: the typed fields debounce on
1576
1838
  // `[localValue, onChange]` — an inline lambda resets the pending emit
1577
1839
  // every render and the typed value can starve.
1578
- handleValueChange, key: optionName, arg_schema: (_j = options === null || options === void 0 ? void 0 : options[optionName]) === null || _j === void 0 ? void 0 : _j.arg_schema, noSoft: !!(rest === null || rest === void 0 ? void 0 : rest.options), value: other.value, isFunction: other.is_expression, isDefaultFunction: ((_k = options === null || options === void 0 ? void 0 : options[optionName]) === null || _k === void 0 ? void 0 : _k.default_view) === 'expression', sensitive: (_l = options === null || options === void 0 ? void 0 : options[optionName]) === null || _l === void 0 ? void 0 : _l.sensitive, default_value: (0, common_1.getDefaultValue)(options === null || options === void 0 ? void 0 : options[optionName]), isDefaultTemplate: ((_m = options === null || options === void 0 ? void 0 : options[optionName]) === null || _m === void 0 ? void 0 : _m.default_view) === 'template', allowed_values: (_o = options === null || options === void 0 ? void 0 : options[optionName]) === null || _o === void 0 ? void 0 : _o.allowed_values, allowed_values_creatable: (_p = options === null || options === void 0 ? void 0 : options[optionName]) === null || _p === void 0 ? void 0 : _p.allowed_values_creatable, disabled: ((_q = options === null || options === void 0 ? void 0 : options[optionName]) === null || _q === void 0 ? void 0 : _q.disabled) ||
1840
+ handleValueChange, key: optionName, arg_schema: (_l = options === null || options === void 0 ? void 0 : options[optionName]) === null || _l === void 0 ? void 0 : _l.arg_schema, noSoft: !!(rest === null || rest === void 0 ? void 0 : rest.options), value: expressionAwareValue, isFunction: optionHoldsExpression(other), isDefaultFunction: ((_m = options === null || options === void 0 ? void 0 : options[optionName]) === null || _m === void 0 ? void 0 : _m.default_view) === 'expression', sensitive: (_o = options === null || options === void 0 ? void 0 : options[optionName]) === null || _o === void 0 ? void 0 : _o.sensitive, default_value: (0, common_1.getDefaultValue)(options === null || options === void 0 ? void 0 : options[optionName]), isDefaultTemplate: ((_p = options === null || options === void 0 ? void 0 : options[optionName]) === null || _p === void 0 ? void 0 : _p.default_view) === 'template', allowed_values: (_q = options === null || options === void 0 ? void 0 : options[optionName]) === null || _q === void 0 ? void 0 : _q.allowed_values, allowed_values_creatable: (_r = options === null || options === void 0 ? void 0 : options[optionName]) === null || _r === void 0 ? void 0 : _r.allowed_values_creatable, disabled: ((_s = options === null || options === void 0 ? void 0 : options[optionName]) === null || _s === void 0 ? void 0 : _s.disabled) ||
1579
1841
  (
1580
1842
  // a system-owned field states that as `readonly`; only the older
1581
1843
  // compat projection also sent `disabled`, so honouring only
1582
1844
  // `disabled` rendered an editable control for a value the user
1583
1845
  // cannot change (see CompactRow's `fieldReadonly`)
1584
- (_r = options === null || options === void 0 ? void 0 : options[optionName]) === null || _r === void 0 ? void 0 : _r.readonly) ||
1846
+ (_t = options === null || options === void 0 ? void 0 : options[optionName]) === null || _t === void 0 ? void 0 : _t.readonly) ||
1585
1847
  readOnly ||
1586
- !(0, validations_1.hasAllDependenciesFullfilled)((_s = options === null || options === void 0 ? void 0 : options[optionName]) === null || _s === void 0 ? void 0 : _s.depends_on, availableOptions, options || {}), readOnly: readOnly, size: editorSize || rest.size, menuItems: ((_t = options === null || options === void 0 ? void 0 : options[optionName]) === null || _t === void 0 ? void 0 : _t.ui_type) === 'any' ?
1848
+ !(0, validations_1.hasAllDependenciesFullfilled)((_u = options === null || options === void 0 ? void 0 : options[optionName]) === null || _u === void 0 ? void 0 : _u.depends_on, availableOptions, options || {}), readOnly: readOnly, size: editorSize || rest.size, menuItems: (0, typeChoices_1.offersTypeChoices)(options === null || options === void 0 ? void 0 : options[optionName], anyLikeUiTypes) ?
1587
1849
  getCustomMenuTemplateItems(optionName)
1588
- : undefined }, templateFieldProps, { forceDropdown: effectiveForceDropdown })), (0, jsx_runtime_1.jsx)(OptionFieldMessages_1.OptionFieldMessages, { schema: options || {}, allOptions: availableOptions, name: optionName, option: __assign({ type: resolvedType }, other), getType: getTypeForOption }), operators && (0, size_1.default)(operators) && (0, size_1.default)(other.op) ?
1850
+ : undefined }, templateFieldProps, { forceDropdown: effectiveForceDropdown })), (0, jsx_runtime_1.jsx)(OptionFieldMessages_1.OptionFieldMessages, { schema: options || {}, allOptions: availableOptions, name: optionName, option: __assign({ type: resolvedType }, other), getType: getTypeForOption, untouched: !touchedOptionsRef.current.has(optionName) }), operators && (0, size_1.default)(operators) && (0, size_1.default)(other.op) ?
1589
1851
  (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreVerticalSpacer, { height: 5 }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreMessage, { size: 'small', children: (0, jsx_runtime_1.jsxs)(reqore_1.ReqoreTagGroup, { children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'small', labelKey: 'WHERE', label: optionName }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'small', labelKey: 'IS', label: operatorParts.join(' ') }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreTag, { size: 'small', intent: 'info', label: other.value ?
1590
1852
  resolvedType === 'richtext' ?
1591
1853
  (0, common_1.richtextToString)(other.value)
@@ -1756,7 +2018,21 @@ var FormEngineImpl = function (_a) {
1756
2018
  fields: false,
1757
2019
  help: false,
1758
2020
  },
1759
- hasMultipleOptions: (0, size_1.default)(availableOptions) > 1,
2021
+ /* From the SCHEMA, not from the value.
2022
+ *
2023
+ * `availableOptions` is keyed off the form's VALUE, so it grows as data
2024
+ * arrives — and the search row is rendered only when this is true
2025
+ * (`CompactToolbar`). A form whose value lands after its first render
2026
+ * therefore ADDED a 38px row to its header a beat later, and everything
2027
+ * below it moved. Measured on the Qorus IDE's test editor, on the nested
2028
+ * sub-form of a case: 33 elements jumped down 51px in one frame, on every
2029
+ * cold load.
2030
+ *
2031
+ * How many fields a form has is a property of its schema. The two agree
2032
+ * once the value has arrived — `fixOptions` materialises an entry per
2033
+ * declared field — so this only changes the answer during the load, which
2034
+ * is the whole point. */
2035
+ hasMultipleOptions: (0, size_1.default)(options) > 1,
1760
2036
  compactQuery: compactQuery,
1761
2037
  setCompactQuery: setCompactQuery,
1762
2038
  requiredOnly: requiredOnly,
@@ -1807,6 +2083,7 @@ var FormEngineImpl = function (_a) {
1807
2083
  // removes it, and then the header carries just the consumer's own actions.
1808
2084
  var compactHeaderActions = (0, react_2.useMemo)(function () { return __spreadArray(__spreadArray([], (compactToolbarParts ? [{ as: CompactToolbar_1.CompactToolbar, responsive: false }] : []), true), ((compactPanelProps === null || compactPanelProps === void 0 ? void 0 : compactPanelProps.actions) || []), true); }, [compactPanelProps === null || compactPanelProps === void 0 ? void 0 : compactPanelProps.actions, compactToolbarParts]);
1809
2085
  var renderCompact = function () {
2086
+ var _a, _b, _c;
1810
2087
  var headerBg = "".concat((0, colors_1.changeDarkness)((0, colors_1.getMainBackgroundColor)(theme), 0.02)).concat((0, colors_1.percentToHexAlpha)(88));
1811
2088
  // Toolbar filters narrow the listed rows; the meter reflects the full set.
1812
2089
  var query = compactQuery.trim().toLowerCase();
@@ -1899,7 +2176,7 @@ var FormEngineImpl = function (_a) {
1899
2176
  var isUnset_1 = function (name) { var _a; return (0, readFirst_1.isOptionValueEmpty)((_a = shownOptions[name]) === null || _a === void 0 ? void 0 : _a.value); };
1900
2177
  var isFieldInvalid_1 = function (name) {
1901
2178
  var _a, _b, _c;
1902
- return !isOptionValid(name, getOptionFieldStorageType(name, (_a = shownOptions[name]) === null || _a === void 0 ? void 0 : _a.type, options, operators, (_b = shownOptions[name]) === null || _b === void 0 ? void 0 : _b.op, isRendererOnly), (_c = shownOptions[name]) === null || _c === void 0 ? void 0 : _c.value);
2179
+ return !isOptionValid(name, (0, exports.getOptionFieldStorageType)(name, (_a = shownOptions[name]) === null || _a === void 0 ? void 0 : _a.type, options, operators, (_b = shownOptions[name]) === null || _b === void 0 ? void 0 : _b.op, isRendererOnly), (_c = shownOptions[name]) === null || _c === void 0 ? void 0 : _c.value);
1903
2180
  };
1904
2181
  var comparator_1 = function (a, b) {
1905
2182
  switch (compactSort) {
@@ -1985,6 +2262,13 @@ var FormEngineImpl = function (_a) {
1985
2262
  var bucketCount = function (b) {
1986
2263
  return bucketGroups[b].reduce(function (n, g) { return n + buckets[b][g].length; }, 0);
1987
2264
  };
2265
+ /* The default is to collapse the Optional box, and an explicit pass is
2266
+ what waives the "never collapse the sole box" escape below. Resolving it
2267
+ here rather than in the parameter list is what makes the two
2268
+ distinguishable — `['optional']` as a default and `['optional']` from a
2269
+ caller mean different things. */
2270
+ var compactCollapsedGroups = compactCollapsedGroupsProp !== null && compactCollapsedGroupsProp !== void 0 ? compactCollapsedGroupsProp : ['optional'];
2271
+ var collapsedGroupsRequested = compactCollapsedGroupsProp !== undefined;
1988
2272
  // "Is the Optional box the whole form?" — when nothing needs attention and
1989
2273
  // nothing is set, collapsing it leaves a card with no visible content at all.
1990
2274
  var onlyOptionalRows = bucketCount('optional') > 0 && bucketCount('attention') === 0 && bucketCount('set') === 0;
@@ -2025,9 +2309,18 @@ var FormEngineImpl = function (_a) {
2025
2309
  bucketGroups[boxKey].some(function (groupName) { return groupHasPreselectedRow(boxKey, groupName); });
2026
2310
  };
2027
2311
  var STATUS_BOXES = [
2028
- { key: 'attention', label: 'Needs attention', intent: 'warning', icon: 'ErrorWarningLine' },
2029
- { key: 'set', label: 'Set', intent: 'success', icon: 'CheckLine' },
2030
- { key: 'optional', label: 'Optional', icon: 'CheckboxBlankCircleLine' },
2312
+ {
2313
+ key: 'attention',
2314
+ label: (_a = compactBoxLabels === null || compactBoxLabels === void 0 ? void 0 : compactBoxLabels.attention) !== null && _a !== void 0 ? _a : 'Needs attention',
2315
+ intent: 'warning',
2316
+ icon: 'ErrorWarningLine',
2317
+ },
2318
+ { key: 'set', label: (_b = compactBoxLabels === null || compactBoxLabels === void 0 ? void 0 : compactBoxLabels.set) !== null && _b !== void 0 ? _b : 'Set', intent: 'success', icon: 'CheckLine' },
2319
+ {
2320
+ key: 'optional',
2321
+ label: (_c = compactBoxLabels === null || compactBoxLabels === void 0 ? void 0 : compactBoxLabels.optional) !== null && _c !== void 0 ? _c : 'Optional',
2322
+ icon: 'CheckboxBlankCircleLine',
2323
+ },
2031
2324
  ];
2032
2325
  // Build the rows for one group: contiguous required-group members are pulled
2033
2326
  // together at the first member's slot and rendered as a connected rail (flat
@@ -2108,7 +2401,7 @@ var FormEngineImpl = function (_a) {
2108
2401
  // Mirrors the toolbar's own search-row gate: when no search
2109
2402
  // row renders, the header is a thin strip and sits tight to
2110
2403
  // the first status box (see StyledCompactPanel).
2111
- , __assign({ "$tightHeader": !((compactToolbarParts === null || compactToolbarParts === void 0 ? void 0 : compactToolbarParts.search) && (0, size_1.default)(availableOptions) > 1), "$headerBg": compactNested ? 'transparent' : headerBg, "$nested": compactNested, flat: true, raised: true, minimal: true,
2404
+ , __assign({ "$tightHeader": !((compactToolbarParts === null || compactToolbarParts === void 0 ? void 0 : compactToolbarParts.search) && (0, size_1.default)(options) > 1), "$headerBg": compactNested ? 'transparent' : headerBg, "$nested": compactNested, flat: true, raised: true, minimal: true,
2112
2405
  // No panel background: the form sits transparently on whatever
2113
2406
  // hosts it (page, drawer, or — for an arg_schema field — the
2114
2407
  // parent's edit card) instead of stacking its own dark surface.
@@ -2130,7 +2423,7 @@ var FormEngineImpl = function (_a) {
2130
2423
  var hasPreselectedRows = boxHasPreselectedRows(box.key);
2131
2424
  var wouldCollapse = compactCollapsedGroups.includes(box.key) &&
2132
2425
  !query &&
2133
- !(box.key === 'optional' && onlyOptionalRows);
2426
+ !(box.key === 'optional' && onlyOptionalRows && !collapsedGroupsRequested);
2134
2427
  // Only a box that was opened SOLELY to reveal a preselected
2135
2428
  // row splits its body; a box that renders open anyway shows
2136
2429
  // all of its groups as before.
@@ -2231,15 +2524,23 @@ var FormEngineImpl = function (_a) {
2231
2524
  : 'Resolve the invalid fields before saving', onClick: handleCommitClick, children: "Save" }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreButton, { size: 'small', minimal: true, flat: true, fixed: true, icon: 'HistoryLine', className: 'options-readfirst-discard', onClick: handleRevertChangesClick, children: "Discard" })] }) })
2232
2525
  : null] }) })] }) }) }));
2233
2526
  };
2234
- if (rest.skeleton ||
2235
- templates.loading ||
2236
- typesLoading ||
2237
- optionsLoading ||
2238
- // Remote-fetch gates, mirroring IDE Options (systemOptions.tsx:1097-1102).
2239
- loading ||
2240
- (operatorsUrl && !operators) ||
2241
- ((url || customUrl) && !options)) {
2242
- return ((0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { className: 'options-loading-skeleton', vertical: true, fill: true, fluid: true, style: { flexGrow: 1 }, gapSize: 'big', children: [(0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { fixed: true, fill: false, children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreSkeleton, {}), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreSkeleton, {}), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreSkeleton, { width: '100%' })] }), (0, jsx_runtime_1.jsxs)(reqore_1.ReqoreControlGroup, { vertical: true, fill: false, children: [(0, jsx_runtime_1.jsx)(reqore_1.ReqoreSkeleton, { width: '100%', height: '150px' }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreSkeleton, { width: '100%', height: '150px' }), (0, jsx_runtime_1.jsx)(reqore_1.ReqoreSkeleton, { width: '100%', height: '150px' })] })] }));
2527
+ /* Which of the seven waits is holding the form, in the order they are
2528
+ tested. Rendered as `data-wait` on the placeholder so a page can be asked
2529
+ what it is waiting FOR instead of only that it is waiting. */
2530
+ var waitReason = rest.skeleton ? (rest.skeletonReason ? "host:".concat(rest.skeletonReason) : 'host')
2531
+ : templates.loading ? 'templates'
2532
+ : typesLoading ? 'types'
2533
+ : optionsLoading ? 'options'
2534
+ : loading ? 'fetch'
2535
+ : operatorsUrl && !operators ? 'operators'
2536
+ : (url || customUrl) && !options ? 'schema'
2537
+ : undefined;
2538
+ if (waitReason) {
2539
+ /* The shape of the FORM, not a generic block arrangement. This used to be
2540
+ three bars over three big panels, which resembles nothing this gate is
2541
+ waiting for and shared no vocabulary with the other waits on the same
2542
+ page — see `FormFieldsSkeleton`. */
2543
+ return (0, jsx_runtime_1.jsx)(FormFieldsSkeleton_1.FormFieldsSkeleton, { fill: true, className: 'options-loading-skeleton', reason: waitReason });
2243
2544
  }
2244
2545
  // A loader that rejected (and produced no usable schema) surfaces its error
2245
2546
  // instead of the generic "No options available" empty state.