@qoretechnologies/reqraft 0.10.54 → 0.10.55-pr.115.g37abdfc

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 (370) 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_COMPACT.md +34 -0
  6. package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
  7. package/design/FORM_ENGINE_FIELD_MIGRATION.md +155 -1
  8. package/design/IDE_INTEGRATION.md +13 -0
  9. package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
  10. package/design/SELECT_DIALOG_FLOOR.md +86 -0
  11. package/design/SMART_EDITOR_UX.md +21 -0
  12. package/design/UNAVAILABLE_ALLOWED_VALUES.md +226 -0
  13. package/dist/components/composer/Composer.d.ts +8 -3
  14. package/dist/components/composer/Composer.d.ts.map +1 -1
  15. package/dist/components/composer/Composer.js.map +1 -1
  16. package/dist/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
  17. package/dist/components/dpqlEditor/DpqlEditor.js +3 -3
  18. package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
  19. package/dist/components/dpqlEditor/dpqlHelpers.d.ts +6 -0
  20. package/dist/components/dpqlEditor/dpqlHelpers.d.ts.map +1 -1
  21. package/dist/components/dpqlEditor/dpqlHelpers.js +21 -2
  22. package/dist/components/dpqlEditor/dpqlHelpers.js.map +1 -1
  23. package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
  24. package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
  25. package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
  26. package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
  27. package/dist/components/dpqlEditor/dpqlTags.d.ts +8 -0
  28. package/dist/components/dpqlEditor/dpqlTags.d.ts.map +1 -1
  29. package/dist/components/dpqlEditor/dpqlTags.js +5 -1
  30. package/dist/components/dpqlEditor/dpqlTags.js.map +1 -1
  31. package/dist/components/dpqlEditor/index.d.ts +4 -0
  32. package/dist/components/dpqlEditor/index.d.ts.map +1 -1
  33. package/dist/components/dpqlEditor/index.js +5 -1
  34. package/dist/components/dpqlEditor/index.js.map +1 -1
  35. package/dist/components/dpqlEditor/types.d.ts +42 -2
  36. package/dist/components/dpqlEditor/types.d.ts.map +1 -1
  37. package/dist/components/dpqlEditor/types.js +0 -4
  38. package/dist/components/dpqlEditor/types.js.map +1 -1
  39. package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
  40. package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
  41. package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
  42. package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
  43. package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
  44. package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
  45. package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
  46. package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
  47. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  48. package/dist/components/form/engine/CompactRow.js +414 -128
  49. package/dist/components/form/engine/CompactRow.js.map +1 -1
  50. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  51. package/dist/components/form/engine/CompactToolbar.js +5 -2
  52. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  53. package/dist/components/form/engine/FormEngine.d.ts +272 -19
  54. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  55. package/dist/components/form/engine/FormEngine.js +666 -161
  56. package/dist/components/form/engine/FormEngine.js.map +1 -1
  57. package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
  58. package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
  59. package/dist/components/form/engine/FormFieldsSkeleton.js +41 -0
  60. package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
  61. package/dist/components/form/engine/OptionFieldMessages.d.ts +108 -2
  62. package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
  63. package/dist/components/form/engine/OptionFieldMessages.js +203 -13
  64. package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
  65. package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
  66. package/dist/components/form/engine/_structuredData/SchemaDataView.js +68 -5
  67. package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
  68. package/dist/components/form/engine/compactRowContext.d.ts +15 -1
  69. package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
  70. package/dist/components/form/engine/compactRowContext.js.map +1 -1
  71. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  72. package/dist/components/form/engine/compactRowStyles.js +55 -29
  73. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  74. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  75. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  76. package/dist/components/form/engine/optionActions.d.ts +37 -1
  77. package/dist/components/form/engine/optionActions.d.ts.map +1 -1
  78. package/dist/components/form/engine/optionActions.js +71 -1
  79. package/dist/components/form/engine/optionActions.js.map +1 -1
  80. package/dist/components/form/engine/optionsContext.d.ts +22 -0
  81. package/dist/components/form/engine/optionsContext.d.ts.map +1 -0
  82. package/dist/components/form/engine/optionsContext.js +20 -0
  83. package/dist/components/form/engine/optionsContext.js.map +1 -0
  84. package/dist/components/form/engine/readFirst.d.ts +42 -1
  85. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  86. package/dist/components/form/engine/readFirst.js +76 -8
  87. package/dist/components/form/engine/readFirst.js.map +1 -1
  88. package/dist/components/form/engine/rendererTypes.d.ts +42 -3
  89. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
  90. package/dist/components/form/engine/rendererTypes.js +3 -1
  91. package/dist/components/form/engine/rendererTypes.js.map +1 -1
  92. package/dist/components/form/engine/rowFocus.d.ts +30 -0
  93. package/dist/components/form/engine/rowFocus.d.ts.map +1 -0
  94. package/dist/components/form/engine/rowFocus.js +93 -0
  95. package/dist/components/form/engine/rowFocus.js.map +1 -0
  96. package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
  97. package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
  98. package/dist/components/form/engine/rowMenuContext.js +103 -0
  99. package/dist/components/form/engine/rowMenuContext.js.map +1 -0
  100. package/dist/components/form/engine/rowOpenPicker.d.ts +27 -0
  101. package/dist/components/form/engine/rowOpenPicker.d.ts.map +1 -0
  102. package/dist/components/form/engine/rowOpenPicker.js +30 -0
  103. package/dist/components/form/engine/rowOpenPicker.js.map +1 -0
  104. package/dist/components/form/engine/typeChoices.d.ts +42 -0
  105. package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
  106. package/dist/components/form/engine/typeChoices.js +33 -0
  107. package/dist/components/form/engine/typeChoices.js.map +1 -0
  108. package/dist/components/form/expressions/DpqlRendering.d.ts +26 -0
  109. package/dist/components/form/expressions/DpqlRendering.d.ts.map +1 -0
  110. package/dist/components/form/expressions/DpqlRendering.js +53 -0
  111. package/dist/components/form/expressions/DpqlRendering.js.map +1 -0
  112. package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
  113. package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
  114. package/dist/components/form/expressions/ExpressionField.js +203 -45
  115. package/dist/components/form/expressions/ExpressionField.js.map +1 -1
  116. package/dist/components/form/expressions/argumentPresence.d.ts +84 -0
  117. package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
  118. package/dist/components/form/expressions/argumentPresence.js +217 -0
  119. package/dist/components/form/expressions/argumentPresence.js.map +1 -0
  120. package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
  121. package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
  122. package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
  123. package/dist/components/form/expressions/builder/index.d.ts +15 -2
  124. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  125. package/dist/components/form/expressions/builder/index.js +21 -65
  126. package/dist/components/form/expressions/builder/index.js.map +1 -1
  127. package/dist/components/form/expressions/builder/renderTemplate.d.ts +4 -1
  128. package/dist/components/form/expressions/builder/renderTemplate.d.ts.map +1 -1
  129. package/dist/components/form/expressions/builder/renderTemplate.js +10 -67
  130. package/dist/components/form/expressions/builder/renderTemplate.js.map +1 -1
  131. package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
  132. package/dist/components/form/expressions/mockExpressions.js +8 -4
  133. package/dist/components/form/expressions/mockExpressions.js.map +1 -1
  134. package/dist/components/form/expressions/renderExpressionToText.d.ts +1 -1
  135. package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
  136. package/dist/components/form/expressions/renderExpressionToText.js +95 -19
  137. package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
  138. package/dist/components/form/expressions/types.d.ts +7 -0
  139. package/dist/components/form/expressions/types.d.ts.map +1 -1
  140. package/dist/components/form/expressions/useExpressions.d.ts.map +1 -1
  141. package/dist/components/form/expressions/useExpressions.js +5 -2
  142. package/dist/components/form/expressions/useExpressions.js.map +1 -1
  143. package/dist/components/form/expressions/useRenderExpression.d.ts +7 -16
  144. package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
  145. package/dist/components/form/expressions/useRenderExpression.js +50 -126
  146. package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
  147. package/dist/components/form/fields/Field.d.ts +18 -0
  148. package/dist/components/form/fields/Field.d.ts.map +1 -1
  149. package/dist/components/form/fields/Field.js +54 -8
  150. package/dist/components/form/fields/Field.js.map +1 -1
  151. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
  152. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  153. package/dist/components/form/fields/allowed-values/AllowedValues.js +52 -8
  154. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  155. package/dist/components/form/fields/auto/AutoFormField.d.ts +7 -1
  156. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  157. package/dist/components/form/fields/auto/AutoFormField.js +91 -28
  158. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  159. package/dist/components/form/fields/date/Date.d.ts +1 -1
  160. package/dist/components/form/fields/date/Date.d.ts.map +1 -1
  161. package/dist/components/form/fields/date/Date.js +16 -2
  162. package/dist/components/form/fields/date/Date.js.map +1 -1
  163. package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
  164. package/dist/components/form/fields/long-string/LongString.js +14 -2
  165. package/dist/components/form/fields/long-string/LongString.js.map +1 -1
  166. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
  167. package/dist/components/form/fields/multi-select/MultiSelectFormField.js +50 -4
  168. package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
  169. package/dist/components/form/fields/rich-text/RichText.d.ts +47 -2
  170. package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
  171. package/dist/components/form/fields/rich-text/RichText.js +153 -12
  172. package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
  173. package/dist/components/form/fields/select/Select.d.ts.map +1 -1
  174. package/dist/components/form/fields/select/Select.js +120 -20
  175. package/dist/components/form/fields/select/Select.js.map +1 -1
  176. package/dist/components/form/fields/select/SelectCollection.d.ts +166 -9
  177. package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
  178. package/dist/components/form/fields/select/SelectCollection.js +200 -7
  179. package/dist/components/form/fields/select/SelectCollection.js.map +1 -1
  180. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
  181. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
  182. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
  183. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
  184. package/dist/components/form/fields/template/TemplateField.d.ts +34 -2
  185. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  186. package/dist/components/form/fields/template/TemplateField.js +606 -58
  187. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  188. package/dist/components/form/fields/template/TemplateText.d.ts +36 -0
  189. package/dist/components/form/fields/template/TemplateText.d.ts.map +1 -0
  190. package/dist/components/form/fields/template/TemplateText.js +80 -0
  191. package/dist/components/form/fields/template/TemplateText.js.map +1 -0
  192. package/dist/components/form/index.d.ts +2 -0
  193. package/dist/components/form/index.d.ts.map +1 -1
  194. package/dist/components/form/index.js +4 -0
  195. package/dist/components/form/index.js.map +1 -1
  196. package/dist/components/smartEditor/SmartEditor.d.ts.map +1 -1
  197. package/dist/components/smartEditor/SmartEditor.js +29 -8
  198. package/dist/components/smartEditor/SmartEditor.js.map +1 -1
  199. package/dist/components/smartEditor/useLspSession.d.ts +5 -1
  200. package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
  201. package/dist/components/smartEditor/useLspSession.js +16 -2
  202. package/dist/components/smartEditor/useLspSession.js.map +1 -1
  203. package/dist/components/supportTicket/TicketReplyBox.d.ts +8 -4
  204. package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -1
  205. package/dist/components/supportTicket/TicketReplyBox.js.map +1 -1
  206. package/dist/helpers/common.d.ts.map +1 -1
  207. package/dist/helpers/common.js +7 -3
  208. package/dist/helpers/common.js.map +1 -1
  209. package/dist/helpers/dpqlDetection.d.ts +58 -0
  210. package/dist/helpers/dpqlDetection.d.ts.map +1 -0
  211. package/dist/helpers/dpqlDetection.js +164 -0
  212. package/dist/helpers/dpqlDetection.js.map +1 -0
  213. package/dist/helpers/optionUiTypes.d.ts +31 -3
  214. package/dist/helpers/optionUiTypes.d.ts.map +1 -1
  215. package/dist/helpers/optionUiTypes.js +36 -1
  216. package/dist/helpers/optionUiTypes.js.map +1 -1
  217. package/dist/helpers/options.d.ts +57 -0
  218. package/dist/helpers/options.d.ts.map +1 -1
  219. package/dist/helpers/options.js +97 -1
  220. package/dist/helpers/options.js.map +1 -1
  221. package/dist/helpers/templateItems.d.ts +47 -0
  222. package/dist/helpers/templateItems.d.ts.map +1 -0
  223. package/dist/helpers/templateItems.js +88 -0
  224. package/dist/helpers/templateItems.js.map +1 -0
  225. package/dist/helpers/templateText.d.ts +80 -0
  226. package/dist/helpers/templateText.d.ts.map +1 -0
  227. package/dist/helpers/templateText.js +291 -0
  228. package/dist/helpers/templateText.js.map +1 -0
  229. package/dist/helpers/templates.d.ts +30 -12
  230. package/dist/helpers/templates.d.ts.map +1 -1
  231. package/dist/helpers/templates.js +91 -33
  232. package/dist/helpers/templates.js.map +1 -1
  233. package/dist/helpers/validations.d.ts +17 -8
  234. package/dist/helpers/validations.d.ts.map +1 -1
  235. package/dist/helpers/validations.js +117 -22
  236. package/dist/helpers/validations.js.map +1 -1
  237. package/dist/hooks/useArgSchema.d.ts +2 -1
  238. package/dist/hooks/useArgSchema.d.ts.map +1 -1
  239. package/dist/hooks/useFetch/useFetch.d.ts +3 -1
  240. package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
  241. package/dist/hooks/useFetch/useFetch.js +47 -9
  242. package/dist/hooks/useFetch/useFetch.js.map +1 -1
  243. package/dist/hooks/useTemplates.d.ts +20 -0
  244. package/dist/hooks/useTemplates.d.ts.map +1 -1
  245. package/dist/hooks/useTemplates.js +34 -2
  246. package/dist/hooks/useTemplates.js.map +1 -1
  247. package/dist/index.d.ts +2 -1
  248. package/dist/index.d.ts.map +1 -1
  249. package/dist/index.js +5 -1
  250. package/dist/index.js.map +1 -1
  251. package/dist/utils/fetch.d.ts +151 -1
  252. package/dist/utils/fetch.d.ts.map +1 -1
  253. package/dist/utils/fetch.js +256 -44
  254. package/dist/utils/fetch.js.map +1 -1
  255. package/dist/utils/lspClient.d.ts +6 -0
  256. package/dist/utils/lspClient.d.ts.map +1 -1
  257. package/dist/utils/lspClient.js +62 -11
  258. package/dist/utils/lspClient.js.map +1 -1
  259. package/dist/utils/websocket.d.ts +6 -0
  260. package/dist/utils/websocket.d.ts.map +1 -1
  261. package/dist/utils/websocket.js +15 -7
  262. package/dist/utils/websocket.js.map +1 -1
  263. package/package.json +8 -4
  264. package/src/components/composer/Composer.tsx +7 -2
  265. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +3 -119
  266. package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
  267. package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
  268. package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
  269. package/src/components/dpqlEditor/dpqlTags.ts +13 -0
  270. package/src/components/dpqlEditor/index.ts +4 -0
  271. package/src/components/dpqlEditor/types.ts +38 -2
  272. package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
  273. package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
  274. package/src/components/form/engine/CompactRow.tsx +487 -160
  275. package/src/components/form/engine/CompactToolbar.tsx +10 -3
  276. package/src/components/form/engine/FormEngine.stories.tsx +1294 -161
  277. package/src/components/form/engine/FormEngine.tsx +916 -213
  278. package/src/components/form/engine/FormEngineStickyHeaders.stories.tsx +331 -0
  279. package/src/components/form/engine/FormFieldsSkeleton.tsx +77 -0
  280. package/src/components/form/engine/OptionFieldMessages.tsx +300 -14
  281. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +94 -3
  282. package/src/components/form/engine/compactRowContext.ts +15 -6
  283. package/src/components/form/engine/compactRowStyles.ts +56 -2
  284. package/src/components/form/engine/compactToolbarContext.ts +1 -1
  285. package/src/components/form/engine/optionActions.ts +89 -1
  286. package/src/components/form/engine/optionsContext.ts +24 -0
  287. package/src/components/form/engine/readFirst.ts +88 -8
  288. package/src/components/form/engine/rendererTypes.ts +48 -4
  289. package/src/components/form/engine/rowFocus.ts +101 -0
  290. package/src/components/form/engine/rowMenuContext.ts +159 -0
  291. package/src/components/form/engine/rowOpenPicker.ts +27 -0
  292. package/src/components/form/engine/typeChoices.ts +63 -0
  293. package/src/components/form/expressions/DpqlRendering.tsx +85 -0
  294. package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
  295. package/src/components/form/expressions/ExpressionField.tsx +269 -55
  296. package/src/components/form/expressions/argumentPresence.ts +213 -0
  297. package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +38 -21
  298. package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
  299. package/src/components/form/expressions/builder/index.tsx +39 -81
  300. package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
  301. package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
  302. package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
  303. package/src/components/form/expressions/mockExpressions.ts +8 -4
  304. package/src/components/form/expressions/renderExpressionToText.ts +112 -22
  305. package/src/components/form/expressions/types.ts +7 -0
  306. package/src/components/form/expressions/useExpressions.ts +5 -1
  307. package/src/components/form/expressions/useRenderExpression.ts +43 -127
  308. package/src/components/form/fields/Field.tsx +99 -20
  309. package/src/components/form/fields/allowed-values/AllowedValues.tsx +106 -44
  310. package/src/components/form/fields/auto/AutoFormField.stories.tsx +85 -18
  311. package/src/components/form/fields/auto/AutoFormField.tsx +97 -21
  312. package/src/components/form/fields/date/Date.tsx +17 -1
  313. package/src/components/form/fields/long-string/LongString.tsx +15 -3
  314. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +65 -6
  315. package/src/components/form/fields/rich-text/RichText.tsx +248 -30
  316. package/src/components/form/fields/select/Select.stories.tsx +392 -0
  317. package/src/components/form/fields/select/Select.tsx +164 -27
  318. package/src/components/form/fields/select/SelectCollection.tsx +350 -6
  319. package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
  320. package/src/components/form/fields/template/TemplateField.stories.tsx +93 -14
  321. package/src/components/form/fields/template/TemplateField.tsx +825 -131
  322. package/src/components/form/fields/template/TemplateText.tsx +115 -0
  323. package/src/components/form/index.tsx +4 -0
  324. package/src/components/log/Log.stories.tsx +2 -1
  325. package/src/components/searchFilterBar/SearchFilterBar.stories.tsx +3 -3
  326. package/src/components/smartEditor/SmartEditor.tsx +41 -9
  327. package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
  328. package/src/components/smartEditor/useLspSession.ts +21 -3
  329. package/src/components/supportTicket/TicketReplyBox.tsx +8 -4
  330. package/src/components/ticketThread/TicketThread.stories.tsx +1 -0
  331. package/src/helpers/common.ts +7 -3
  332. package/src/helpers/dpqlDetection.ts +209 -0
  333. package/src/helpers/optionUiTypes.ts +38 -0
  334. package/src/helpers/options.ts +96 -0
  335. package/src/helpers/templateItems.ts +81 -0
  336. package/src/helpers/templateText.ts +350 -0
  337. package/src/helpers/templates.ts +94 -41
  338. package/src/helpers/validations.ts +138 -39
  339. package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
  340. package/src/hooks/useFetch/useFetch.tsx +53 -7
  341. package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
  342. package/src/hooks/useTemplates.ts +41 -0
  343. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
  344. package/src/index.tsx +14 -1
  345. package/src/stores/currentUser/currentUser.stories.tsx +2 -1
  346. package/src/stories/storyNetwork.ts +92 -0
  347. package/src/utils/fetch.ts +311 -21
  348. package/src/utils/lspClient.ts +68 -11
  349. package/src/utils/websocket.ts +20 -9
  350. package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts +0 -73
  351. package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts.map +0 -1
  352. package/dist/components/form/engine/__fixtures__/basicFormOptions.js +0 -317
  353. package/dist/components/form/engine/__fixtures__/basicFormOptions.js.map +0 -1
  354. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts +0 -41
  355. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts.map +0 -1
  356. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js +0 -45
  357. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js.map +0 -1
  358. package/dist/components/form/expressions/dpqlMockLsp.d.ts +0 -3
  359. package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +0 -1
  360. package/dist/components/form/expressions/dpqlMockLsp.js +0 -91
  361. package/dist/components/form/expressions/dpqlMockLsp.js.map +0 -1
  362. package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts +0 -2
  363. package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts.map +0 -1
  364. package/dist/components/form/fields/template/__fixtures__/longExampleValue.js +0 -9
  365. package/dist/components/form/fields/template/__fixtures__/longExampleValue.js.map +0 -1
  366. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +0 -55
  367. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +0 -1
  368. package/dist/components/smartEditor/__fixtures__/mockLspServer.js +0 -126
  369. package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +0 -1
  370. package/dist/stories/Data/fileFieldArgSchema.json +0 -86
@@ -1,4 +1,5 @@
1
1
  import { modalStore } from '@qoretechnologies/reqore';
2
+ import { TReqoreTooltipProp } from '@qoretechnologies/reqore/dist/types/global';
2
3
  import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
3
4
  import {
4
5
  TReqoreDropdownItem,
@@ -20,6 +21,24 @@ import { areQorusTypesCompatible } from './expressions';
20
21
  */
21
22
  export const TEMPLATE_EXAMPLE_PREVIEW_LENGTH = 150;
22
23
 
24
+ /**
25
+ * How template picker entries are ordered: by the label a reader actually sees.
26
+ *
27
+ * The server sends groups and their items in whatever order it assembled them —
28
+ * for the test vocabulary that is step order, for an app's actions it is the
29
+ * connector's own. Neither is an order a reader can predict, and a picker
30
+ * holding 29 items is navigated by looking for a NAME.
31
+ *
32
+ * `localeCompare` with `numeric` so `item2` sorts before `item10`, and
33
+ * `sensitivity: 'base'` so case and accents do not split otherwise-adjacent
34
+ * neighbours.
35
+ */
36
+ const byLabel = (a: { label?: unknown }, b: { label?: unknown }): number =>
37
+ String(a?.label ?? '').localeCompare(String(b?.label ?? ''), undefined, {
38
+ numeric: true,
39
+ sensitivity: 'base',
40
+ });
41
+
23
42
  /**
24
43
  * Shared template string utilities.
25
44
  * Used by both validations and TemplateField.
@@ -106,9 +125,42 @@ const matchesTemplateValue = (item: TReqoreDropdownItem, value: string): boolean
106
125
  /** Splits `$data:{a.b.c}` into its key (`$data`) and inner path (`a.b.c`). */
107
126
  const TEMPLATE_TOKEN_PATH = /^(\$[A-Za-z_][\w-]*):\{(.*)\}$/;
108
127
 
128
+ /**
129
+ * The KEY-LESS spelling — `$.create.status`, `$._case.mode`, `$.order[0].sku`.
130
+ *
131
+ * A host may name its own references without a key and without braces: a Qorus
132
+ * test reads what a step captured as `$.create`, and that is the whole token.
133
+ * The path is captured WITH its leading `.`, so a prefix comparison is between
134
+ * two strings of the same shape and `$.create` still ends at a path boundary
135
+ * inside `$.create.status`.
136
+ *
137
+ * Deliberately strict — a root segment, then any number of further `.field`
138
+ * steps and `[0]` indices, and nothing else — so it cannot claim a keyed token
139
+ * (which carries a `:`) or a bare `$word`.
140
+ */
141
+ const KEYLESS_TOKEN_PATH = /^\$((?:\.[A-Za-z_][\w-]*|\[\d+\])+)$/;
142
+
143
+ /**
144
+ * "No key" is a key of its own.
145
+ *
146
+ * `findTemplateByPath` refuses to name a value from an item under a different
147
+ * key, and the key-less grammar has to take part in that rule rather than sit
148
+ * outside it: a `$data:{create.status}` item must not name `$.create.status`,
149
+ * and a `$.create` item must not name `$data:{create.status}`. Every keyed
150
+ * token's key begins with `$`, so the empty string can never collide with one.
151
+ */
152
+ const NO_TOKEN_KEY = '';
153
+
109
154
  const getTokenPath = (value?: unknown): { key: string; path: string } | undefined => {
110
- const match = typeof value === 'string' ? TEMPLATE_TOKEN_PATH.exec(value) : null;
111
- return match ? { key: match[1], path: match[2] } : undefined;
155
+ if (typeof value !== 'string') {
156
+ return undefined;
157
+ }
158
+ const keyed = TEMPLATE_TOKEN_PATH.exec(value);
159
+ if (keyed) {
160
+ return { key: keyed[1], path: keyed[2] };
161
+ }
162
+ const keyless = KEYLESS_TOKEN_PATH.exec(value);
163
+ return keyless ? { key: NO_TOKEN_KEY, path: keyless[1] } : undefined;
112
164
  };
113
165
 
114
166
  /**
@@ -124,7 +176,12 @@ const getTokenPath = (value?: unknown): { key: string; path: string } | undefine
124
176
  *
125
177
  * The match must break at a path boundary (`.` or `[`) so `choicesOther` is
126
178
  * never named after `choices`, and the token keys must agree so a `$config:`
127
- * item never names a `$data:` value.
179
+ * item never names a `$data:` value — "no key" included, so the key-less
180
+ * grammar and the keyed one never name each other's references.
181
+ *
182
+ * Both grammars arrive here: `$data:{create.choices[0]}` and the key-less
183
+ * `$.create.status` are the same question asked in two spellings, and a
184
+ * catalogue written in either one names the walks its author took past it.
128
185
  */
129
186
  export const findTemplateByPath = (
130
187
  templates: IReqoreFormTemplates,
@@ -239,9 +296,14 @@ export const resolveTemplateLabel = (
239
296
  * never fetches one), and there the raw token is the only thing left to show.
240
297
  * Showing it as `$data:{…}` renders a value as code; showing the path renders
241
298
  * it as a name, which is the honest floor.
299
+ *
300
+ * Only a BRACED wrapper is stripped, because only a braced wrapper is one.
301
+ * A key-less `$._case.mode` stripped of its `$` reads as `._case.mode`, which
302
+ * is neither the reference nor a name for it; with nothing to call it, the
303
+ * reference as written is the honest answer.
242
304
  */
243
305
  export const getTemplateReferencePath = (value: string): string =>
244
- getTokenPath(value)?.path ?? value;
306
+ (typeof value === 'string' ? TEMPLATE_TOKEN_PATH.exec(value)?.[2] : undefined) ?? value;
245
307
 
246
308
  /**
247
309
  * The best available name for a reference, for a surface that must show
@@ -258,43 +320,34 @@ export const describeTemplateReference = (
258
320
  return resolved.item ? resolved : { label: getTemplateReferencePath(value) };
259
321
  };
260
322
 
261
- const EMBEDDED_TEMPLATE_TOKEN = new RegExp(TEMPLATE_TOKEN_SOURCE, 'g');
262
-
263
- export type TTemplateTextSegment =
264
- | { kind: 'text'; text: string }
265
- | { kind: 'token'; text: string };
266
-
267
323
  /**
268
- * Splits prose that embeds template tokens into its literal and token parts —
269
- * `trim("$data:{…}")` becomes `trim("`, the token, `")`. Used to chip the
270
- * references inside a rendered expression instead of printing them raw.
324
+ * What a chosen template's hover should say.
325
+ *
326
+ * The reference itself — `$._case.title`, `$data:{dc_ai_reply.choices[0]…}` —
327
+ * is an implementation detail of the grammar, and it was what every template
328
+ * chip put in its tooltip. An author hovering a chip wants to know WHAT VALUE
329
+ * this is, and the catalogue already carries that: the entry's `description`
330
+ * is the prose the picker lists it under, written from the schema that declares
331
+ * it, with its type appended.
332
+ *
333
+ * So the hover reads as a small card — the value's name as the title, its
334
+ * description as the body — and the path stays where it is genuinely needed:
335
+ * the field's own help, which teaches the grammar with a worked example, and
336
+ * the picker, for the walks no list can hold.
337
+ *
338
+ * Falls back to the resolved label (itself the stripped path when the catalogue
339
+ * explains nothing), because a surface with no catalogue must still say what
340
+ * the value is rather than nothing at all.
271
341
  */
272
- export const splitTemplateTokens = (text?: string): TTemplateTextSegment[] => {
273
- if (!text) {
274
- return [];
275
- }
276
-
277
- const segments: TTemplateTextSegment[] = [];
278
- let lastIndex = 0;
279
-
280
- // A fresh regex per call: a shared /g instance carries `lastIndex` between
281
- // calls and would skip tokens on the second string it is given.
282
- const pattern = new RegExp(EMBEDDED_TEMPLATE_TOKEN.source, 'g');
283
- let match: RegExpExecArray | null;
284
-
285
- while ((match = pattern.exec(text)) !== null) {
286
- if (match.index > lastIndex) {
287
- segments.push({ kind: 'text', text: text.slice(lastIndex, match.index) });
288
- }
289
- segments.push({ kind: 'token', text: match[0] });
290
- lastIndex = match.index + match[0].length;
291
- }
292
-
293
- if (lastIndex < text.length) {
294
- segments.push({ kind: 'text', text: text.slice(lastIndex) });
295
- }
296
-
297
- return segments;
342
+ export const templateTooltip = (
343
+ templates: IReqoreFormTemplates | undefined,
344
+ value: string
345
+ ): TReqoreTooltipProp => {
346
+ const { label, item } = describeTemplateReference(templates, value);
347
+ const description = (item as { description?: unknown } | undefined)?.description;
348
+ return typeof description === 'string' && description.trim() ?
349
+ { title: label, content: description, maxWidth: '360px' }
350
+ : label || value;
298
351
  };
299
352
 
300
353
  // Ported verbatim from qorus-ide `helpers/functions.tsx` (FIELD_STACK_REPORT
@@ -506,7 +559,7 @@ export const buildTemplates = (
506
559
 
507
560
  return item;
508
561
  }
509
- );
562
+ ).sort(byLabel);
510
563
 
511
564
  return {
512
565
  items: Object.values(templates).map(
@@ -544,6 +597,6 @@ export const buildTemplates = (
544
597
  data_role
545
598
  ),
546
599
  })
547
- ),
600
+ ).sort(byLabel),
548
601
  } as IReqoreFormTemplates;
549
602
  };
@@ -1064,14 +1064,39 @@ export const _validateField = (
1064
1064
  }
1065
1065
  }
1066
1066
 
1067
- if (parsedData) {
1068
- return withContext(
1069
- validateFieldWithResult(getTypeFromValue(parsedData), value),
1070
- 'Auto-detected type is invalid'
1071
- );
1067
+ /* A FALSY value is not an ABSENT one. `null`, `0`, `false` and `""` are
1068
+ all values an author may hold in an `auto`/`any` field — a test
1069
+ assertion expecting a count of zero, a flag that is off, or a blank
1070
+ string — and the truthiness gate that used to stand here refused every
1071
+ one of them as *"Value is empty"*. It surfaced on an assertion's
1072
+ Expected Value holding `null`: opening it reported *"Value for argument
1073
+ 1 ("any") is invalid: Value is empty"* on the value the author had
1074
+ deliberately chosen. Only `undefined` is actually empty.
1075
+
1076
+ `null` and `""` are answered here rather than delegated, for two
1077
+ different reasons: `getTypeFromValue(null)` answers `auto`, which would
1078
+ re-enter this very branch, and the `string` validator rejects `""`
1079
+ because that is the rule for a REQUIRED text field — a different
1080
+ question from whether an untyped field may hold a blank string. */
1081
+ if (parsedData === undefined) {
1082
+ return invalidResult('Value is empty');
1083
+ }
1084
+
1085
+ if (parsedData === null || parsedData === '') {
1086
+ return validResult();
1072
1087
  }
1073
1088
 
1074
- return invalidResult('Value is empty');
1089
+ /* Validate what was PARSED, against the type detected from the same
1090
+ parse. Validating the raw `value` instead made the two disagree
1091
+ whenever parsing changed the shape: typing `1` into an `auto` field
1092
+ parsed to the number 1, detected `int`, and then validated the STRING
1093
+ "1" against it — "Value must be an integer", on a value that is one.
1094
+ The author saw their field go invalid on the first character they
1095
+ typed. */
1096
+ return withContext(
1097
+ validateFieldWithResult(getTypeFromValue(parsedData), parsedData),
1098
+ 'Auto-detected type is invalid'
1099
+ );
1075
1100
  }
1076
1101
  case 'processor': {
1077
1102
  if (!value || !value['processor-input-type'] || !value['processor-output-type']) {
@@ -1295,6 +1320,22 @@ export const _validateField = (
1295
1320
  continue;
1296
1321
  }
1297
1322
 
1323
+ /* An option whose dependencies are not fulfilled is not part of this
1324
+ object, so it cannot make the object invalid.
1325
+
1326
+ The form already takes that view — it HIDES such an option — and
1327
+ the two disagreeing is a trap with no way out of it: a test whose
1328
+ subject moved from a versioned kind to a service keeps the
1329
+ `subject_iface_version` it no longer has a field for, and Submit
1330
+ goes dead naming an option that is nowhere on screen. Nulling the
1331
+ value does not help either, because a null is still a value the
1332
+ loop reaches.
1333
+
1334
+ A REQUIRED option with unfulfilled dependencies is unaffected:
1335
+ `getUnresolvedRequiredOptions` above reports it as `dependency`
1336
+ before this loop runs, and it is the only owner of that question.
1337
+ This branch was therefore only ever able to fail an option the
1338
+ author could not see and did not need. */
1298
1339
  if (
1299
1340
  (optionSchema?.[option] as TQorusFormFieldSchema)?.depends_on &&
1300
1341
  !hasAllDependenciesFullfilled(
@@ -1303,7 +1344,7 @@ export const _validateField = (
1303
1344
  optionSchema
1304
1345
  )
1305
1346
  ) {
1306
- return invalidResult(`Option ${option} dependencies are not fulfilled`);
1347
+ continue;
1307
1348
  }
1308
1349
 
1309
1350
  const optionResult =
@@ -1512,6 +1553,31 @@ export const _validateField = (
1512
1553
  const argDefinition = expressionDefinition.varargs
1513
1554
  ? expressionDefinition.args[0]
1514
1555
  : expressionDefinition.args[index];
1556
+ /* The argument's name for the message.
1557
+
1558
+ `display_name` is what a catalogue MAY carry; the served one carries
1559
+ `name`, so every message about an argument read "argument 1
1560
+ ("undefined") is invalid" and named a field that does not exist. */
1561
+ const argLabel =
1562
+ argDefinition?.display_name ?? argDefinition?.name ?? `argument ${index + 1}`;
1563
+
1564
+ /* An explicit null is a VALUE, and DPQL says so: `null` is a literal
1565
+ that parses, serializes and round-trips like any other.
1566
+
1567
+ Treated as a missing one, it made "this returns no value"
1568
+ unsayable. Typing `null` into the text editor produces the literal
1569
+ expression `{exp: "value", args: [null]}`; the visual view then
1570
+ called it invalid and refused to render a summary, and a raw null
1571
+ argument crashed the validator outright on `argValue.type`. Reported
1572
+ from the live IDE by an author with no other way to assert that a
1573
+ service method returns nothing.
1574
+
1575
+ Only an EXPLICIT null counts. An argument that is simply absent, or
1576
+ an envelope holding `undefined`, is still missing — which is the
1577
+ distinction the author is making when they write it. */
1578
+ if (argValue === null || (isObject(argValue) && (argValue as any).value === null)) {
1579
+ continue;
1580
+ }
1515
1581
 
1516
1582
  if (!argValue?.value && !argDefinition?.required) {
1517
1583
  continue;
@@ -1526,30 +1592,34 @@ export const _validateField = (
1526
1592
  if (!result.isValid) {
1527
1593
  return withContext(
1528
1594
  result,
1529
- `Sub-expression for argument ${index + 1} ("${argDefinition?.display_name}") is invalid`
1595
+ `Sub-expression for argument ${index + 1} ("${argLabel}") is invalid`
1530
1596
  );
1531
1597
  }
1532
1598
 
1533
1599
  continue;
1534
1600
  }
1535
1601
 
1536
- // A slot with no operand at all is a missing value, not a crash:
1537
- // judge it as an empty value of the type the catalogue expects.
1538
- const result = validateFieldWithResult(
1539
- argValue?.type ?? argDefinition?.ui_type,
1540
- argValue?.value,
1541
- {
1542
- expressions,
1543
- allowed_values: argDefinition?.allowed_values,
1544
- element_allowed_values: argDefinition?.element_allowed_values,
1545
- has_to_have_value: argDefinition?.required,
1546
- }
1547
- );
1602
+ /* A slot with no operand at all is judged as what it is — a missing
1603
+ value — rather than read for a `type` it does not have.
1604
+
1605
+ An operand that HOLDS something is judged by the envelope's own type,
1606
+ or — when the envelope carries none (a raw parse operand widened
1607
+ without a `ui_type`, a slot reset by a type change) — by the type the
1608
+ catalogue declares. Without that fallback `_validateField` refuses it
1609
+ for "Missing type" however right the value is. */
1610
+ const operandType =
1611
+ argValue?.type ?? (argValue?.value === undefined ? undefined : argDefinition?.ui_type);
1612
+ const result = validateFieldWithResult(operandType, argValue?.value, {
1613
+ expressions,
1614
+ allowed_values: argDefinition?.allowed_values,
1615
+ element_allowed_values: argDefinition?.element_allowed_values,
1616
+ has_to_have_value: argDefinition?.required,
1617
+ });
1548
1618
 
1549
1619
  if (!result.isValid) {
1550
1620
  return withContext(
1551
1621
  result,
1552
- `Value for argument ${index + 1} ("${argDefinition?.display_name}") is invalid`
1622
+ `Value for argument ${index + 1} ("${argLabel}") is invalid`
1553
1623
  );
1554
1624
  }
1555
1625
  }
@@ -1818,19 +1888,28 @@ export const getUnresolvedRequiredOptions = (
1818
1888
  /**
1819
1889
  * Split a dependency entry into the sibling it names and how its value is compared.
1820
1890
  *
1821
- * Three forms: `name` (the sibling must simply have a value), `name=value` (it must
1822
- * have exactly that value) and `name!=value` (it must have a value, and not that
1823
- * one). The negative form is what lets a field say it applies to every variant of a
1824
- * record but one — spelling that as an `any-of` list of every other variant is both
1825
- * unreadable in the "Depends on" lock and silently wrong the moment a variant is
1826
- * added.
1891
+ * Four forms: `name` (the sibling must simply have a value), `!name` (it must have
1892
+ * none), `name=value` (it must have exactly that value) and `name!=value` (it must
1893
+ * have a value, and not that one). The negative comparison is what lets a field say
1894
+ * it applies to every variant of a record but one — spelling that as an `any-of`
1895
+ * list of every other variant is both unreadable in the "Depends on" lock and
1896
+ * silently wrong the moment a variant is added.
1827
1897
  *
1828
1898
  * `!=` deliberately requires the sibling to be answered: "not X" is a statement about
1829
1899
  * an answer, so an unanswered sibling does not satisfy it and the field stays locked.
1900
+ *
1901
+ * `!name` is the form that statement cannot make. Two options that exclude one
1902
+ * another (a converter that reads NodeSet2 and one that writes OpenAPI 3) each
1903
+ * apply only while the OTHER has not been answered at all, and `name!=value` cannot
1904
+ * say that: it requires an answer. Being a predicate like any other, it composes
1905
+ * with the AND of the top-level entries and the OR of a nested list for free.
1906
+ *
1907
+ * `!` binds to the whole entry, so `!name` never carries a value; `!=` is checked
1908
+ * first, which is what keeps `name!=value` from being read as a leading `!`.
1830
1909
  */
1831
1910
  export const parseDependency = (
1832
1911
  dependency: string
1833
- ): { name: string; op?: '=' | '!='; value?: string } => {
1912
+ ): { name: string; op?: '=' | '!=' | '!'; value?: string } => {
1834
1913
  const neqIdx = dependency.indexOf('!=');
1835
1914
  if (neqIdx !== -1) {
1836
1915
  return {
@@ -1849,9 +1928,25 @@ export const parseDependency = (
1849
1928
  };
1850
1929
  }
1851
1930
 
1931
+ if (dependency.startsWith('!')) {
1932
+ return { name: dependency.substring(1), op: '!' };
1933
+ }
1934
+
1852
1935
  return { name: dependency };
1853
1936
  };
1854
1937
 
1938
+ /** Whether the named sibling currently holds a value this form accepts. */
1939
+ const siblingHasValue = (
1940
+ name: string,
1941
+ options: TQorusForm,
1942
+ optionsSchema?: IQorusFormSchema
1943
+ ): boolean =>
1944
+ validateField(options?.[name]?.type, options?.[name]?.value, {
1945
+ ...(optionsSchema?.[name] as unknown as IFieldValidationProps),
1946
+ options,
1947
+ optionSchema: optionsSchema,
1948
+ });
1949
+
1855
1950
  /** Whether one dependency entry holds against the current form values. */
1856
1951
  const isDependencyFulfilled = (
1857
1952
  dependency: string,
@@ -1860,6 +1955,15 @@ const isDependencyFulfilled = (
1860
1955
  ): boolean => {
1861
1956
  const { name, op, value } = parseDependency(dependency);
1862
1957
 
1958
+ if (op === '!') {
1959
+ // "Has no value" is true of a field the form has not materialized at all —
1960
+ // there is nothing there to be the value. So an absent sibling satisfies
1961
+ // this, exactly as it satisfies the bare form below: neither has an answer
1962
+ // to judge, and a mutual exclusion that locked both halves until one of
1963
+ // them was added would offer the author no way in.
1964
+ return !options?.[name] || !siblingHasValue(name, options, optionsSchema);
1965
+ }
1966
+
1863
1967
  if (op) {
1864
1968
  const optValue = options?.[name]?.value;
1865
1969
  if (optValue == null) {
@@ -1868,17 +1972,12 @@ const isDependencyFulfilled = (
1868
1972
  return op === '=' ? String(optValue) === value : String(optValue) !== value;
1869
1973
  }
1870
1974
 
1871
- return options?.[name]
1872
- ? validateField(options[name].type, options[name].value, {
1873
- ...(optionsSchema?.[name] as unknown as IFieldValidationProps),
1874
- options,
1875
- optionSchema: optionsSchema,
1876
- })
1877
- : true;
1975
+ return options?.[name] ? siblingHasValue(name, options, optionsSchema) : true;
1878
1976
  };
1879
1977
 
1880
1978
  export const hasAllDependenciesFullfilled = (
1881
- dependencies: string[] | string[][],
1979
+ /** Every entry must hold; a nested list is an ANY of its entries. Mixed freely. */
1980
+ dependencies: ReadonlyArray<string | readonly string[]>,
1882
1981
  options: TQorusForm,
1883
1982
  optionsSchema?: IQorusFormSchema
1884
1983
  ): boolean => {
@@ -1886,20 +1985,20 @@ export const hasAllDependenciesFullfilled = (
1886
1985
  return true;
1887
1986
  }
1888
1987
 
1889
- return dependencies.every((dependency: string | string[]) => {
1988
+ return dependencies.every((dependency) => {
1890
1989
  // A nested list is an ANY. It used to look each entry up as a whole form-field
1891
1990
  // name, so a `name=value` entry inside one found no field and returned `true`
1892
1991
  // unconditionally — the any-of group was satisfied by anything at all, while
1893
1992
  // CompactRow's lock rendered those same entries as real comparisons. Both halves
1894
1993
  // now go through one parser, so the grammar cannot mean two things.
1895
1994
  if (isArray(dependency)) {
1896
- return (dependency as string[]).some((dep) =>
1995
+ return (dependency as readonly string[]).some((dep) =>
1897
1996
  isDependencyFulfilled(dep, options, optionsSchema)
1898
1997
  );
1899
1998
  }
1900
1999
 
1901
2000
  return isString(dependency)
1902
- ? isDependencyFulfilled(dependency as string, options, optionsSchema)
2001
+ ? isDependencyFulfilled(dependency, options, optionsSchema)
1903
2002
  : true;
1904
2003
  });
1905
2004
  };
@@ -1,6 +1,7 @@
1
1
  import { ReqoreSpinner, ReqoreTree } from '@qoretechnologies/reqore';
2
2
  import { StoryObj } from '@storybook/react-vite';
3
3
  import { testsWaitForText } from '../../../__tests__/utils';
4
+ import { storyApiUrl } from '../../stories/storyNetwork';
4
5
  import { StoryMeta } from '../../types';
5
6
  import { useFetch } from './useFetch';
6
7
 
@@ -44,7 +45,7 @@ export const Get: Story = {
44
45
  mockData: [
45
46
  {
46
47
  // An array of mock objects which will add in every story
47
- url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
48
+ url: storyApiUrl('public/info'),
48
49
  method: 'GET',
49
50
  status: 200,
50
51
  delay: 200,
@@ -81,7 +82,7 @@ export const Put: Story = {
81
82
  mockData: [
82
83
  {
83
84
  // An array of mock objects which will add in every story
84
- url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
85
+ url: storyApiUrl('public/info'),
85
86
  method: 'PUT',
86
87
  status: 200,
87
88
  response: {
@@ -109,7 +110,7 @@ export const Post: Story = {
109
110
  mockData: [
110
111
  {
111
112
  // An array of mock objects which will add in every story
112
- url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
113
+ url: storyApiUrl('public/info'),
113
114
  method: 'POST',
114
115
  status: 200,
115
116
  response: {
@@ -137,7 +138,7 @@ export const Del: Story = {
137
138
  mockData: [
138
139
  {
139
140
  // An array of mock objects which will add in every story
140
- url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
141
+ url: storyApiUrl('public/info'),
141
142
  method: 'DELETE',
142
143
  status: 200,
143
144
  response: {
@@ -165,7 +166,7 @@ export const Error409: Story = {
165
166
  mockData: [
166
167
  {
167
168
  // An array of mock objects which will add in every story
168
- url: 'https://hq.qoretechnologies.com:8092/api/latest/public/info',
169
+ url: storyApiUrl('public/info'),
169
170
  method: 'POST',
170
171
  status: 409,
171
172
  response: {
@@ -1,8 +1,10 @@
1
+ import { QueryClient, useQueryClient } from '@tanstack/react-query';
1
2
  import { useCallback, useMemo, useState } from 'react';
2
3
  import { useEffectOnce } from 'react-use';
3
4
  import { useContextSelector } from 'use-context-selector';
4
5
  import { FetchContext } from '../../contexts/FetchContext';
5
- import { IReqraftQueryConfig } from '../../utils/fetch';
6
+ import { ReqraftQueryClient } from '../../providers/ReqraftProvider';
7
+ import { IReqraftQueryConfig, reqraftCacheKey } from '../../utils/fetch';
6
8
 
7
9
  export interface IReqraftUseFetch<T> {
8
10
  data: T | undefined;
@@ -41,8 +43,40 @@ export function useFetch<T>({
41
43
  }
42
44
  });
43
45
 
44
- const [loading, setLoading] = useState(loadOnMount);
45
- const [data, setData] = useState<T | undefined>(defaultData);
46
+ /* An answer we ALREADY HAVE is not a loading state.
47
+ `loading` started at `loadOnMount` unconditionally, so every consumer that
48
+ mounted reported "loading" for at least one async turn even when the
49
+ response was sitting in the cache — and `FormEngine` renders its
50
+ `options-loading-skeleton` whenever any of its sources says that. So each
51
+ form appearing on a page flashed a skeleton whether or not anything was
52
+ actually fetched, which is what the repeated skeletons on an IDE page are:
53
+ measured on a fully warm SECOND visit to one page, three skeleton waves
54
+ against two requests, the last wave with no request anywhere near it.
55
+ Seeded from the query cache instead, so a resource already in hand renders
56
+ immediately. */
57
+ /* The provider is OPTIONAL here. `useQueryClient` THROWS when no
58
+ `QueryClientProvider` is above it, and plenty of consumers mount a form
59
+ with only a `FetchContext` — requiring one turned a rendering optimisation
60
+ into a hard dependency and took 131 tests down with it. `useContext` runs
61
+ before that throw, so hook order is unaffected and catching is safe. The
62
+ fallback is the same client `query` itself defaults to, so a cache hit is
63
+ found either way. */
64
+ let queryClient: QueryClient;
65
+ try {
66
+ queryClient = useQueryClient();
67
+ } catch {
68
+ queryClient = ReqraftQueryClient;
69
+ }
70
+
71
+ const cached = queryClient.getQueryData([
72
+ reqraftCacheKey({ url, method, body, noApiPrefix }),
73
+ ]) as { data?: T; ok?: boolean } | undefined;
74
+ const hasCachedAnswer = !!cached && cached.ok !== false && cached.data !== undefined;
75
+
76
+ const [loading, setLoading] = useState(!!loadOnMount && !hasCachedAnswer);
77
+ const [data, setData] = useState<T | undefined>(
78
+ hasCachedAnswer ? (cached?.data as T) : defaultData
79
+ );
46
80
  const [response, setResponse] = useState<Response | undefined>();
47
81
  const [error, setError] = useState<Error | undefined>();
48
82
  const [errorData, setErrorData] = useState<any>();
@@ -51,13 +85,23 @@ export function useFetch<T>({
51
85
  async ({
52
86
  body: customBody,
53
87
  mergeBodies,
54
- }: { body?: Record<string | number, any>; mergeBodies?: boolean } = {}) => {
55
- setLoading(true);
88
+ silent,
89
+ }: {
90
+ body?: Record<string | number, any>;
91
+ mergeBodies?: boolean;
92
+ /** Revalidate without announcing a loading state — see the note above. */
93
+ silent?: boolean;
94
+ } = {}) => {
95
+ if (!silent) {
96
+ setLoading(true);
97
+ }
56
98
 
57
99
  const _body = mergeBodies ? { ...body, ...customBody } : customBody || body;
58
100
  const response = await query<T>({ url, body: _body, cache, noApiPrefix });
59
101
 
60
- setLoading(false);
102
+ if (!silent) {
103
+ setLoading(false);
104
+ }
61
105
 
62
106
  if (response.ok) {
63
107
  setError(undefined);
@@ -75,7 +119,9 @@ export function useFetch<T>({
75
119
 
76
120
  useEffectOnce(() => {
77
121
  if (loadOnMount) {
78
- load();
122
+ // Still revalidates when the entry is cached — `query` honours its own
123
+ // staleness — but silently, so a cached answer never flashes a skeleton.
124
+ load({ silent: hasCachedAnswer });
79
125
  }
80
126
  });
81
127
 
@@ -4,6 +4,7 @@ import { useRef } from 'react';
4
4
  import { fireEvent, within } from 'storybook/test';
5
5
  import { storiesStorageMock } from '../../../__tests__/ mock';
6
6
  import { testsWaitForText } from '../../../__tests__/utils';
7
+ import { storyApiUrl } from '../../stories/storyNetwork';
7
8
  import { StoryMeta } from '../../types';
8
9
  import { useReqraftStorage } from './useStorage';
9
10
 
@@ -44,7 +45,7 @@ export const DefaultValue: Story = {
44
45
  },
45
46
  mockData: [
46
47
  {
47
- url: 'https://hq.qoretechnologies.com:8092/api/latest/users?action=current',
48
+ url: storyApiUrl('users?action=current'),
48
49
  method: 'GET',
49
50
  status: 200,
50
51
  response: {},