@qoretechnologies/reqraft 0.10.55-pr.124.g43fa138 → 0.10.55

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 (375) 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 +273 -22
  54. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  55. package/dist/components/form/engine/FormEngine.js +669 -192
  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 +17 -2
  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 +21 -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/number/Number.d.ts +12 -1
  170. package/dist/components/form/fields/number/Number.d.ts.map +1 -1
  171. package/dist/components/form/fields/number/Number.js +6 -3
  172. package/dist/components/form/fields/number/Number.js.map +1 -1
  173. package/dist/components/form/fields/rich-text/RichText.d.ts +47 -2
  174. package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
  175. package/dist/components/form/fields/rich-text/RichText.js +153 -12
  176. package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
  177. package/dist/components/form/fields/select/Select.d.ts.map +1 -1
  178. package/dist/components/form/fields/select/Select.js +120 -20
  179. package/dist/components/form/fields/select/Select.js.map +1 -1
  180. package/dist/components/form/fields/select/SelectCollection.d.ts +166 -9
  181. package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
  182. package/dist/components/form/fields/select/SelectCollection.js +200 -7
  183. package/dist/components/form/fields/select/SelectCollection.js.map +1 -1
  184. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
  185. package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
  186. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
  187. package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
  188. package/dist/components/form/fields/template/TemplateField.d.ts +34 -2
  189. package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
  190. package/dist/components/form/fields/template/TemplateField.js +606 -58
  191. package/dist/components/form/fields/template/TemplateField.js.map +1 -1
  192. package/dist/components/form/fields/template/TemplateText.d.ts +36 -0
  193. package/dist/components/form/fields/template/TemplateText.d.ts.map +1 -0
  194. package/dist/components/form/fields/template/TemplateText.js +80 -0
  195. package/dist/components/form/fields/template/TemplateText.js.map +1 -0
  196. package/dist/components/form/index.d.ts +2 -0
  197. package/dist/components/form/index.d.ts.map +1 -1
  198. package/dist/components/form/index.js +4 -0
  199. package/dist/components/form/index.js.map +1 -1
  200. package/dist/components/smartEditor/SmartEditor.d.ts.map +1 -1
  201. package/dist/components/smartEditor/SmartEditor.js +29 -8
  202. package/dist/components/smartEditor/SmartEditor.js.map +1 -1
  203. package/dist/components/smartEditor/useLspSession.d.ts +5 -1
  204. package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
  205. package/dist/components/smartEditor/useLspSession.js +16 -2
  206. package/dist/components/smartEditor/useLspSession.js.map +1 -1
  207. package/dist/components/supportTicket/TicketReplyBox.d.ts +8 -4
  208. package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -1
  209. package/dist/components/supportTicket/TicketReplyBox.js.map +1 -1
  210. package/dist/helpers/common.d.ts.map +1 -1
  211. package/dist/helpers/common.js +7 -3
  212. package/dist/helpers/common.js.map +1 -1
  213. package/dist/helpers/dpqlDetection.d.ts +58 -0
  214. package/dist/helpers/dpqlDetection.d.ts.map +1 -0
  215. package/dist/helpers/dpqlDetection.js +164 -0
  216. package/dist/helpers/dpqlDetection.js.map +1 -0
  217. package/dist/helpers/optionUiTypes.d.ts +31 -3
  218. package/dist/helpers/optionUiTypes.d.ts.map +1 -1
  219. package/dist/helpers/optionUiTypes.js +36 -1
  220. package/dist/helpers/optionUiTypes.js.map +1 -1
  221. package/dist/helpers/options.d.ts +57 -0
  222. package/dist/helpers/options.d.ts.map +1 -1
  223. package/dist/helpers/options.js +97 -1
  224. package/dist/helpers/options.js.map +1 -1
  225. package/dist/helpers/templateItems.d.ts +47 -0
  226. package/dist/helpers/templateItems.d.ts.map +1 -0
  227. package/dist/helpers/templateItems.js +88 -0
  228. package/dist/helpers/templateItems.js.map +1 -0
  229. package/dist/helpers/templateText.d.ts +80 -0
  230. package/dist/helpers/templateText.d.ts.map +1 -0
  231. package/dist/helpers/templateText.js +291 -0
  232. package/dist/helpers/templateText.js.map +1 -0
  233. package/dist/helpers/templates.d.ts +30 -12
  234. package/dist/helpers/templates.d.ts.map +1 -1
  235. package/dist/helpers/templates.js +91 -33
  236. package/dist/helpers/templates.js.map +1 -1
  237. package/dist/helpers/validations.d.ts +35 -8
  238. package/dist/helpers/validations.d.ts.map +1 -1
  239. package/dist/helpers/validations.js +162 -25
  240. package/dist/helpers/validations.js.map +1 -1
  241. package/dist/hooks/useArgSchema.d.ts +2 -1
  242. package/dist/hooks/useArgSchema.d.ts.map +1 -1
  243. package/dist/hooks/useFetch/useFetch.d.ts +3 -1
  244. package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
  245. package/dist/hooks/useFetch/useFetch.js +47 -9
  246. package/dist/hooks/useFetch/useFetch.js.map +1 -1
  247. package/dist/hooks/useTemplates.d.ts +20 -0
  248. package/dist/hooks/useTemplates.d.ts.map +1 -1
  249. package/dist/hooks/useTemplates.js +34 -2
  250. package/dist/hooks/useTemplates.js.map +1 -1
  251. package/dist/index.d.ts +2 -1
  252. package/dist/index.d.ts.map +1 -1
  253. package/dist/index.js +5 -1
  254. package/dist/index.js.map +1 -1
  255. package/dist/utils/fetch.d.ts +151 -1
  256. package/dist/utils/fetch.d.ts.map +1 -1
  257. package/dist/utils/fetch.js +256 -44
  258. package/dist/utils/fetch.js.map +1 -1
  259. package/dist/utils/lspClient.d.ts +6 -0
  260. package/dist/utils/lspClient.d.ts.map +1 -1
  261. package/dist/utils/lspClient.js +62 -11
  262. package/dist/utils/lspClient.js.map +1 -1
  263. package/dist/utils/websocket.d.ts +6 -0
  264. package/dist/utils/websocket.d.ts.map +1 -1
  265. package/dist/utils/websocket.js +15 -7
  266. package/dist/utils/websocket.js.map +1 -1
  267. package/package.json +10 -6
  268. package/src/components/composer/Composer.tsx +7 -2
  269. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +3 -119
  270. package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
  271. package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
  272. package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
  273. package/src/components/dpqlEditor/dpqlTags.ts +13 -0
  274. package/src/components/dpqlEditor/index.ts +4 -0
  275. package/src/components/dpqlEditor/types.ts +38 -2
  276. package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
  277. package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
  278. package/src/components/form/engine/CompactRow.tsx +487 -160
  279. package/src/components/form/engine/CompactToolbar.tsx +10 -3
  280. package/src/components/form/engine/FormEngine.stories.tsx +1269 -201
  281. package/src/components/form/engine/FormEngine.tsx +922 -270
  282. package/src/components/form/engine/FormEngineStickyHeaders.stories.tsx +331 -0
  283. package/src/components/form/engine/FormFieldsSkeleton.tsx +77 -0
  284. package/src/components/form/engine/OptionFieldMessages.tsx +300 -14
  285. package/src/components/form/engine/_structuredData/SchemaDataView.tsx +94 -3
  286. package/src/components/form/engine/compactRowContext.ts +15 -6
  287. package/src/components/form/engine/compactRowStyles.ts +56 -2
  288. package/src/components/form/engine/compactToolbarContext.ts +1 -1
  289. package/src/components/form/engine/optionActions.ts +89 -1
  290. package/src/components/form/engine/optionsContext.ts +24 -0
  291. package/src/components/form/engine/readFirst.ts +88 -8
  292. package/src/components/form/engine/rendererTypes.ts +48 -4
  293. package/src/components/form/engine/rowFocus.ts +101 -0
  294. package/src/components/form/engine/rowMenuContext.ts +159 -0
  295. package/src/components/form/engine/rowOpenPicker.ts +27 -0
  296. package/src/components/form/engine/typeChoices.ts +63 -0
  297. package/src/components/form/expressions/DpqlRendering.tsx +85 -0
  298. package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
  299. package/src/components/form/expressions/ExpressionField.tsx +269 -55
  300. package/src/components/form/expressions/argumentPresence.ts +213 -0
  301. package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +38 -21
  302. package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
  303. package/src/components/form/expressions/builder/index.tsx +39 -81
  304. package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
  305. package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
  306. package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
  307. package/src/components/form/expressions/mockExpressions.ts +8 -4
  308. package/src/components/form/expressions/renderExpressionToText.ts +112 -22
  309. package/src/components/form/expressions/types.ts +17 -2
  310. package/src/components/form/expressions/useExpressions.ts +5 -1
  311. package/src/components/form/expressions/useRenderExpression.ts +43 -127
  312. package/src/components/form/fields/Field.tsx +102 -20
  313. package/src/components/form/fields/allowed-values/AllowedValues.tsx +106 -44
  314. package/src/components/form/fields/auto/AutoFormField.stories.tsx +85 -18
  315. package/src/components/form/fields/auto/AutoFormField.tsx +97 -21
  316. package/src/components/form/fields/date/Date.tsx +17 -1
  317. package/src/components/form/fields/long-string/LongString.tsx +15 -3
  318. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +65 -6
  319. package/src/components/form/fields/number/Number.tsx +25 -0
  320. package/src/components/form/fields/rich-text/RichText.tsx +248 -30
  321. package/src/components/form/fields/select/Select.stories.tsx +392 -0
  322. package/src/components/form/fields/select/Select.tsx +164 -27
  323. package/src/components/form/fields/select/SelectCollection.tsx +350 -6
  324. package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
  325. package/src/components/form/fields/template/TemplateField.stories.tsx +93 -14
  326. package/src/components/form/fields/template/TemplateField.tsx +825 -131
  327. package/src/components/form/fields/template/TemplateText.tsx +115 -0
  328. package/src/components/form/index.tsx +4 -0
  329. package/src/components/log/Log.stories.tsx +2 -1
  330. package/src/components/searchFilterBar/SearchFilterBar.stories.tsx +3 -3
  331. package/src/components/smartEditor/SmartEditor.tsx +41 -9
  332. package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
  333. package/src/components/smartEditor/useLspSession.ts +21 -3
  334. package/src/components/supportTicket/TicketReplyBox.tsx +8 -4
  335. package/src/components/ticketThread/TicketThread.stories.tsx +1 -0
  336. package/src/helpers/common.ts +7 -3
  337. package/src/helpers/dpqlDetection.ts +209 -0
  338. package/src/helpers/optionUiTypes.ts +38 -0
  339. package/src/helpers/options.ts +96 -0
  340. package/src/helpers/templateItems.ts +81 -0
  341. package/src/helpers/templateText.ts +350 -0
  342. package/src/helpers/templates.ts +94 -41
  343. package/src/helpers/validations.ts +203 -51
  344. package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
  345. package/src/hooks/useFetch/useFetch.tsx +53 -7
  346. package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
  347. package/src/hooks/useTemplates.ts +41 -0
  348. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
  349. package/src/index.tsx +14 -1
  350. package/src/stores/currentUser/currentUser.stories.tsx +2 -1
  351. package/src/stories/storyNetwork.ts +92 -0
  352. package/src/utils/fetch.ts +311 -21
  353. package/src/utils/lspClient.ts +68 -11
  354. package/src/utils/websocket.ts +20 -9
  355. package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts +0 -73
  356. package/dist/components/form/engine/__fixtures__/basicFormOptions.d.ts.map +0 -1
  357. package/dist/components/form/engine/__fixtures__/basicFormOptions.js +0 -317
  358. package/dist/components/form/engine/__fixtures__/basicFormOptions.js.map +0 -1
  359. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts +0 -41
  360. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.d.ts.map +0 -1
  361. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js +0 -45
  362. package/dist/components/form/engine/__fixtures__/fieldChromeOptions.js.map +0 -1
  363. package/dist/components/form/expressions/dpqlMockLsp.d.ts +0 -3
  364. package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +0 -1
  365. package/dist/components/form/expressions/dpqlMockLsp.js +0 -91
  366. package/dist/components/form/expressions/dpqlMockLsp.js.map +0 -1
  367. package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts +0 -2
  368. package/dist/components/form/fields/template/__fixtures__/longExampleValue.d.ts.map +0 -1
  369. package/dist/components/form/fields/template/__fixtures__/longExampleValue.js +0 -9
  370. package/dist/components/form/fields/template/__fixtures__/longExampleValue.js.map +0 -1
  371. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +0 -55
  372. package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +0 -1
  373. package/dist/components/smartEditor/__fixtures__/mockLspServer.js +0 -126
  374. package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +0 -1
  375. package/dist/stories/Data/fileFieldArgSchema.json +0 -86
@@ -10,8 +10,9 @@ import {
10
10
  ReqoreTagGroup,
11
11
  } from '@qoretechnologies/reqore';
12
12
  import { IReqoreDropdownItem } from '@qoretechnologies/reqore/dist/components/Dropdown/list';
13
+ import { RowMenuContext, useRowMenuRegistry } from './rowMenuContext';
13
14
  import { IReqorePanelAction } from '@qoretechnologies/reqore/dist/components/Panel';
14
- import { resolveOptionActions } from './optionActions';
15
+ import { optionActionAsMenuItem, resolveOptionActions, splitOptionActions } from './optionActions';
15
16
  import {
16
17
  IQorusFormField,
17
18
  IQorusFormSchema,
@@ -24,16 +25,11 @@ import size from 'lodash/size';
24
25
  import React, { memo } from 'react';
25
26
  import { useContextSelector } from 'use-context-selector';
26
27
  import { hasAllDependenciesFullfilled, parseDependency } from '../../../helpers/validations';
27
- import {
28
- findTemplate,
29
- getTemplateTagStyle,
30
- isValueTemplate,
31
- describeTemplateReference,
32
- splitTemplateTokens,
33
- TTemplateMeta,
34
- } from '../../../helpers/templates';
28
+ import { findTemplate, isValueTemplate } from '../../../helpers/templates';
35
29
  import { getDefaultValue, richtextToSegments, richtextToString } from '../../../helpers/common';
30
+ import { hasTemplateText, templateTextSegments } from '../../../helpers/templateText';
36
31
  import { ReadOnlyTemplateTag } from '../fields/template/ReadOnlyTemplateTag';
32
+ import { TemplateText } from '../fields/template/TemplateText';
37
33
  import { describeCodeSize, formatCodeChars, formatCodeLines } from '../../codeSize';
38
34
  import { Description } from '../../Description';
39
35
  import { useMarkdownRenderer } from '../../Description/markdownRendererContext';
@@ -57,6 +53,8 @@ import {
57
53
  StyledStatusDot,
58
54
  } from './compactRowStyles';
59
55
  import { getShownSchemaMessages, getOptionFieldMessages } from './OptionFieldMessages';
56
+ import { findRowFocusTarget, isClosedPickerTrigger } from './rowFocus';
57
+ import { RowOpenPickerContext } from './rowOpenPicker';
60
58
  import { SchemaDataView, canRenderWithSchema } from './_structuredData/SchemaDataView';
61
59
  import { query } from '../../../utils/fetch';
62
60
  import {
@@ -64,6 +62,7 @@ import {
64
62
  findAllowedValueOption,
65
63
  formatBytes,
66
64
  formatOptionValue,
65
+ getAllowedValueIcon,
67
66
  getAllowedValueImage,
68
67
  getFileSize,
69
68
  getHashEntries,
@@ -72,8 +71,11 @@ import {
72
71
  isFixedCompactAllowedValueOption,
73
72
  isOptionValueEmpty,
74
73
  optionHasImages,
74
+ getExpressionAst,
75
75
  } from './readFirst';
76
76
  import { StructuredDataView } from './_structuredData/StructuredDataView';
77
+ import { DpqlRendering } from '../expressions/DpqlRendering';
78
+ import { TFieldWithOwnTemplates } from './rendererTypes';
77
79
 
78
80
  /**
79
81
  * Does this markdown value have more document in it than one row can show?
@@ -131,12 +133,6 @@ const COMPACT_SINGLE_VALUE_TYPES = new Set([
131
133
  'method-name',
132
134
  ]);
133
135
 
134
- // How many consumer-injected actions may sit inline in a row before the rest
135
- // overflow into the row's menu. A row's action slot shares space with the
136
- // value; an unbounded button strip would squeeze the value out, and the
137
- // consumer controls how many actions it injects.
138
- const MAX_INLINE_OPTION_ACTIONS = 2;
139
-
140
136
  // One read-first row: label | value | action collapsed; the real editor (the
141
137
  // classic renderOption) expanded. `hidden` = search-surfaced optional —
142
138
  // activating the row adds the field first.
@@ -202,7 +198,6 @@ export const CompactRow = memo(
202
198
  const isExpanded = useContextSelector(CompactRowContext, (v) =>
203
199
  v.expandedOptions.includes(optionName)
204
200
  );
205
- const autoFocusNameRef = useContextSelector(CompactRowContext, (v) => v.autoFocusNameRef);
206
201
  const isHighlighted = useContextSelector(CompactRowContext, (v) =>
207
202
  v.highlightedOptions.includes(optionName)
208
203
  );
@@ -215,6 +210,44 @@ export const CompactRow = memo(
215
210
  );
216
211
  const setFocusedEditing = useContextSelector(CompactRowContext, (v) => v.setFocusedEditing);
217
212
  const readRowHeights = useContextSelector(CompactRowContext, (v) => v.readRowHeights);
213
+ const onReadOnlyActivate = useContextSelector(
214
+ CompactRowContext,
215
+ (v) => v.onReadOnlyActivate
216
+ );
217
+
218
+ /**
219
+ * The floor recorded when a row is opened lasts until the author changes
220
+ * something.
221
+ *
222
+ * `activate()` measures the row as it was READ and pins that as the
223
+ * editing row's `min-height`, so opening a row cannot yank the content
224
+ * below it up under the pointer. That is right for the transition and
225
+ * wrong for the rest of the session: a row that was tall because it
226
+ * carried a warning keeps that height after the author fixes the very
227
+ * thing the warning was about, leaving a large empty box where the
228
+ * explanation used to be — reported as "the chip is removed, but the
229
+ * vertical size of the option remains the same".
230
+ *
231
+ * Released on the first change to the value, which is exactly the moment
232
+ * the author has acted and the row should settle to what it now holds.
233
+ * Nothing is yanked by that: the author caused it and is looking at it.
234
+ *
235
+ * Keyed on a STRING rather than the value itself. A form value is often a
236
+ * fresh object on every render (`{source, value}` for a reference), so a
237
+ * dependency on its identity would drop the floor on the first re-render
238
+ * and the transition it exists for would never be smoothed at all.
239
+ */
240
+ const valueKey =
241
+ optionField?.value === null || typeof optionField?.value !== 'object' ?
242
+ String(optionField?.value)
243
+ : JSON.stringify(optionField?.value);
244
+
245
+ /* `valueKey` alone, deliberately - see above. No eslint-disable: this repo
246
+ does not load `react-hooks`, so a suppression for a rule that is not
247
+ there is itself an error, and the pre-push lint refuses the push. */
248
+ React.useEffect(() => {
249
+ delete readRowHeights.current[optionName];
250
+ }, [valueKey]);
218
251
  const originalValue = useContextSelector(CompactRowContext, (v) => v.originalValue);
219
252
  const availableOptions = useContextSelector(CompactRowContext, (v) => v.availableOptions);
220
253
  const requiredGroupsInfo = useContextSelector(CompactRowContext, (v) => v.requiredGroupsInfo);
@@ -255,27 +288,6 @@ export const CompactRow = memo(
255
288
 
256
289
  // Value-cell content: colour adds a swatch, file an icon + size; hash keeps
257
290
  // its "N fields" summary (sub-fields reveal beneath the row).
258
- /**
259
- * Flattens the line breaks in one prose segment of a richtext SUMMARY.
260
- *
261
- * The read-first row is a single line: every other value type reaches it
262
- * through `whiteSpace: 'nowrap'`, which collapses newlines for free. The
263
- * richtext branch is the only one that opts into `'pre'` — it has to, or the
264
- * spaces that separate a word from the chip beside it are dropped — and `pre`
265
- * also honours the newlines, which `nowrap` would have eaten.
266
- *
267
- * So a genuinely multi-line value (an alert rule's Gmail message body is five
268
- * `\n`-separated lines) gave each segment after the first a blank first line.
269
- * The wrapper centres its items, so a two-line-tall box centred against
270
- * one-line chips put every word 12px below the chip beside it, and the row
271
- * read as a staircase. Measured on supah: prose boxes 30px against 14px chips.
272
- *
273
- * A space, not nothing: consecutive prose segments are merged before they get
274
- * here, so a value with a break and no chip between its lines would otherwise
275
- * lose the word boundary entirely.
276
- */
277
- const collapseSummaryBreaks = (text: string): string => text.replace(/\s*\r?\n\s*/g, ' ');
278
-
279
291
  const renderReadFirstValue = (
280
292
  field: IQorusFormField,
281
293
  schema: TQorusFormFieldSchema | undefined,
@@ -299,6 +311,43 @@ export const CompactRow = memo(
299
311
  { whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }
300
312
  : { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
301
313
 
314
+ /* A row has to SAY when its value is an expression.
315
+
316
+ Reported from the live IDE: an author accepted the *Use as expression*
317
+ offer on one field and not on the other, and the two rows read exactly
318
+ the same - `1 + 2` and `2 + 1`, both plain text - so there was no way
319
+ to tell which had been converted. The assertion then compared the
320
+ string `"1 + 2"` against the number `3` and the author had no way to
321
+ see why.
322
+
323
+ The same Σ the expression editor uses, so the mark means the same thing
324
+ wherever it appears. */
325
+ const withExpressionMarker = (rendered: React.ReactNode): React.ReactNode => {
326
+ if (!(field as { is_expression?: boolean })?.is_expression) {
327
+ return rendered;
328
+ }
329
+
330
+ return (
331
+ <span style={{ ...wrapStyle, gap: 4 }}>
332
+ <ReqoreIcon
333
+ icon='Functions'
334
+ size='tiny'
335
+ /* A stable hook: reqore renders every icon as a bare
336
+ `.reqore-icon`, so without this there is nothing to select the
337
+ mark by — in a test or in a consumer's stylesheet. */
338
+ className='reqraft-expression-marker'
339
+ /* Opacity through `effect`, which is how this repo dims a reqore
340
+ component (see the card's `short_desc` below); only the flex
341
+ rule, which reqore has no prop for, stays inline. */
342
+ effect={{ opacity: 0.7 }}
343
+ style={{ flexShrink: 0 }}
344
+ tooltip='This value is an expression'
345
+ />
346
+ {rendered}
347
+ </span>
348
+ );
349
+ };
350
+
302
351
  if (valueType === 'rgbcolor') {
303
352
  const swatch = colorToCss(field?.value);
304
353
  return (
@@ -351,6 +400,52 @@ export const CompactRow = memo(
351
400
  );
352
401
  }
353
402
 
403
+ // A test's cases read as CHIPS here, the same as the names in the table
404
+ // this row opens (`CasesTable`, qorus-ide). `test-cases` is a
405
+ // renderer-only ui_type, so it reached none of the branches above and
406
+ // fell through to the generic summary, which flattens the array of case
407
+ // hashes to a comma-joined string — "case_1, resolved_expected_values".
408
+ // A case name is an identifier the author types into `$.` references and
409
+ // skip conditions, and a chip is how the rest of the product writes an
410
+ // identifier down; the table already agreed, so the collapsed row was the
411
+ // only surface still printing them as prose.
412
+ //
413
+ // `intent` is a FILL, which is what a tag is — the hazard the other
414
+ // branches warn about is only ever using an intent as a TEXT colour.
415
+ //
416
+ // Falls through to the plain summary unless EVERY entry yields a name: a
417
+ // partly-chipped row would read as though the unnamed cases were missing.
418
+ if (valueType === 'test-cases' && Array.isArray(field?.value)) {
419
+ const caseNames = (field.value as unknown[]).map((item) => {
420
+ const name = (item as { name?: unknown } | null | undefined)?.name;
421
+ return typeof name === 'string' ? name.trim() : '';
422
+ });
423
+ if (caseNames.length > 0 && caseNames.every((name) => name !== '')) {
424
+ return (
425
+ <ReqoreControlGroup
426
+ gapSize='tiny'
427
+ verticalAlign='center'
428
+ // An editable row is ONE line: further chips clip with the row
429
+ // instead of growing it. A read-only row is the only rendering
430
+ // the value gets, so it wraps and shows every case.
431
+ wrap={full}
432
+ style={full ? undefined : { minWidth: 0, overflow: 'hidden' }}
433
+ >
434
+ {caseNames.map((name, index) => (
435
+ <ReqoreTag
436
+ key={`${name}-${index}`}
437
+ className='options-readfirst-case-tag'
438
+ size='small'
439
+ minimal
440
+ intent='info'
441
+ label={name}
442
+ />
443
+ ))}
444
+ </ReqoreControlGroup>
445
+ );
446
+ }
447
+ }
448
+
354
449
  // Code-editor read summary: replace the truncated string with a chip
355
450
  // showing line count + character count. The actual code renders below
356
451
  // the row as a monospace `<pre>` inside a `ReqoreCollapsibleContent` —
@@ -418,15 +513,43 @@ export const CompactRow = memo(
418
513
  );
419
514
  }
420
515
 
516
+ // The same, for an option whose mark is an ICON rather than a logo. Only
517
+ // images were drawn here, so a list whose entries are told apart by their
518
+ // icon — every interface kind is a bare word, `Service`, `Job`, `Qog` —
519
+ // showed the mark while choosing and lost it the moment the row went
520
+ // read-only, which is where a reader spends most of their time.
521
+ const allowedIcon = getAllowedValueIcon(field?.value, schema);
522
+ if (allowedIcon) {
523
+ return (
524
+ <span style={wrapStyle}>
525
+ <ReqoreIcon icon={allowedIcon as any} size='16px' style={{ flexShrink: 0 }} />
526
+ <span style={textStyle}>{formatted}</span>
527
+ </span>
528
+ );
529
+ }
530
+
421
531
  // Template value ($local:…): the read-only template picker — the SAME chip
422
532
  // TemplateField renders when disabled, so a template reads identically here
423
533
  // and in the editor (qorus-ide intent scheme: info / qorus purple).
534
+ //
535
+ // A field may also declare templates OF ITS OWN, and a value drawn from
536
+ // that list is just as much a template even when it does not look like
537
+ // `$name:key` — `isValueTemplate` is a guess at the shape of the built-in
538
+ // grammar, and a host's own grammar (a Qorus test's `$.order.id`) fails it.
539
+ // Asking the field's list whether it CONTAINS the value settles it without
540
+ // guessing at syntax, and hands back the entry whose label the chip prints,
541
+ // so a reference reads as the name it was chosen by here as well as in the
542
+ // editor rather than as a raw path.
543
+ const ownTemplates = (schema as TFieldWithOwnTemplates | undefined)?.templates ?? templates;
544
+ // The grammar the field declares its references in, when it speaks one of
545
+ // its own — the same schema key its rich-text editor is given.
546
+ const templateGrammar = (schema as TFieldWithOwnTemplates | undefined)?.templateToken;
424
547
  if (
425
548
  typeof field?.value === 'string' &&
426
549
  !(field as { is_expression?: boolean }).is_expression &&
427
- isValueTemplate(field.value)
550
+ (isValueTemplate(field.value) || !!findTemplate(ownTemplates ?? {}, field.value))
428
551
  ) {
429
- return <ReadOnlyTemplateTag value={field.value} templates={templates} size='small' />;
552
+ return <ReadOnlyTemplateTag value={field.value} templates={ownTemplates} size='small' />;
430
553
  }
431
554
 
432
555
  // A LIST whose elements are rich-text envelopes is a list of strings, so it
@@ -449,81 +572,48 @@ export const CompactRow = memo(
449
572
  if (valueType === 'richtext' && Array.isArray(field?.value)) {
450
573
  const segments = richtextToSegments(field.value as never);
451
574
  if (segments.some((segment) => segment.kind === 'tag')) {
452
- return (
453
- <span style={{ ...wrapStyle, gap: 4, overflow: 'hidden' }}>
454
- {segments.map((segment, index) =>
455
- segment.kind === 'tag' ?
456
- <ReqoreTag
457
- key={index}
458
- size='tiny'
459
- // The chip's label has to be the same size as the prose it is
460
- // embedded in. The wrapper centres boxes, so two different text
461
- // sizes centred against each other cannot share a baseline, and
462
- // the chips visibly float above the words. Inheriting the row's
463
- // size makes centring align the baselines too, at whatever font
464
- // size the consuming app uses.
465
- style={{ fontSize: 'inherit' }}
466
- icon='ExchangeDollarLine'
467
- label={segment.text || segment.value}
468
- tooltip={segment.value}
469
- {...getTemplateTagStyle(
470
- (templates ? findTemplate(templates, segment.value || '') : undefined)
471
- ?.metadata as TTemplateMeta | undefined
472
- )}
473
- />
474
- : <span key={index} style={{ whiteSpace: full ? 'pre-wrap' : 'pre' }}>
475
- {full ? segment.text : collapseSummaryBreaks(segment.text)}
476
- </span>
477
- )}
478
- </span>
479
- );
575
+ return <TemplateText segments={segments} templates={ownTemplates} full={full} />;
480
576
  }
481
577
  }
482
578
 
483
- // An expression summarises to one line of text, and a template reference
484
- // inside it printed as its own raw token — `$data:{…}` renders a value as
485
- // code where a name belongs. Chip every reference here, on the richtext
486
- // branch's pattern above, so the wrapper (`trim(`…`)`) still reads as the
487
- // text it is while the reference reads as a name.
488
- //
489
- // Unconditionally: a surface with no catalogue (the Automation Hub
490
- // template preview never fetches one) still shows the reference's own
491
- // path, which is the point — a conditional chip is no chip at all
492
- // exactly where the raw token looks worst.
579
+ // An expression reads as DPQL wherever it is shown without being edited:
580
+ // the same rendering its Explain panel and Preview use — monospace,
581
+ // coloured by the language server, template references as chips — so a
582
+ // collapsed row and the editor it opens never disagree about what the
583
+ // value looks like. See `DpqlRendering`.
493
584
  if ((field as { is_expression?: boolean })?.is_expression && formatted) {
494
- const segments = splitTemplateTokens(formatted);
495
- const named = segments.map((segment) =>
496
- segment.kind === 'token' ?
497
- { ...segment, ...describeTemplateReference(templates, segment.text) }
498
- : segment
585
+ return withExpressionMarker(
586
+ <span style={{ minWidth: 0, flex: '1 1 auto', ...(full ? {} : { overflow: 'hidden' }) }}>
587
+ <DpqlRendering text={formatted} templates={ownTemplates} />
588
+ </span>
499
589
  );
590
+ }
500
591
 
501
- if (named.some((segment) => segment.kind === 'token')) {
502
- return (
503
- <span style={{ ...wrapStyle, gap: 4, overflow: 'hidden' }}>
504
- {named.map((segment, index) =>
505
- segment.kind === 'token' ?
506
- <ReqoreTag
507
- key={index}
508
- size='tiny'
509
- // Inherit the row's font size so the chip shares a baseline
510
- // with the text around it — see the richtext branch above.
511
- style={{ fontSize: 'inherit' }}
512
- icon='ExchangeDollarLine'
513
- label={segment.label}
514
- tooltip={segment.text}
515
- {...getTemplateTagStyle(segment.item?.metadata as TTemplateMeta | undefined)}
516
- />
517
- : <span key={index} style={{ whiteSpace: full ? 'pre-wrap' : 'pre' }}>
518
- {segment.text}
519
- </span>
520
- )}
521
- </span>
522
- );
523
- }
592
+ /* A value that MENTIONS a reference rather than being one.
593
+
594
+ The branch above answers "is this value a template?", and a predicate is
595
+ not: `$._case.mode != 'simulate'` is a comparison the author wrote with a
596
+ reference inside it. So it fell all the way through to the plain string
597
+ and the row printed the engine's own spelling of a value the author had
598
+ picked by name — the one thing the template mechanism exists to stop.
599
+ Reported against the Skip-when field of a test case, where the list
600
+ above the drawer already named it and the drawer contradicted it.
601
+
602
+ Drawn from `formatted` rather than from the raw value, so everything the
603
+ formatter already did — a joined list, a sensitive value's dots — still
604
+ holds, and only the references inside the resulting line become chips.
605
+ The author's own text around them survives exactly as typed. */
606
+ const templateSegments =
607
+ typeof formatted === 'string' && formatted ?
608
+ templateTextSegments(formatted, ownTemplates, templateGrammar)
609
+ : [];
610
+ if (templateSegments.some((segment) => segment.kind === 'tag')) {
611
+ return withExpressionMarker(
612
+ <TemplateText segments={templateSegments} templates={ownTemplates} full={full} />
613
+ );
524
614
  }
525
615
 
526
- return full ? <span style={textStyle}>{formatted}</span> : formatted;
616
+ return withExpressionMarker(full ? <span style={textStyle}>{formatted}</span> : formatted);
527
617
  };
528
618
 
529
619
  const schema = options?.[optionName];
@@ -540,37 +630,15 @@ export const CompactRow = memo(
540
630
  [availableOptions?.[optionName], optionActions, optionName, schema]
541
631
  );
542
632
  // Which injected actions stay as inline buttons, and which move into the
543
- // overflow menu. Everything collapses on touch / narrow viewports — a
544
- // hover-gated button is unreachable without a hover — and anything past the
545
- // inline cap overflows regardless, so a consumer injecting ten actions can
546
- // never push the row's value out of view.
633
+ // overflow menu — see `splitOptionActions`.
547
634
  const [inlineOptionActions, menuOptionActions] = React.useMemo<
548
635
  [IReqorePanelAction[], IReqorePanelAction[]]
549
636
  >(
550
- () =>
551
- collapseOptionActions ?
552
- [[], injectedOptionActions]
553
- : [
554
- injectedOptionActions.slice(0, MAX_INLINE_OPTION_ACTIONS),
555
- injectedOptionActions.slice(MAX_INLINE_OPTION_ACTIONS),
556
- ],
637
+ () => splitOptionActions(injectedOptionActions, !!collapseOptionActions),
557
638
  [collapseOptionActions, injectedOptionActions]
558
639
  );
559
640
  const injectedOptionActionMenuItems = React.useMemo<IReqoreDropdownItem[]>(
560
- () =>
561
- menuOptionActions.map(
562
- (action, index) =>
563
- ({
564
- // A panel action labels itself with `label`, but an icon-only one
565
- // (the IDE's AI-assist button) carries its name in the tooltip —
566
- // a menu row has no hover affordance to fall back on.
567
- label: action.label ?? action.tooltip ?? `Action ${index + 1}`,
568
- icon: action.icon,
569
- intent: action.intent,
570
- disabled: action.disabled,
571
- onClick: () => action.onClick?.(),
572
- }) as IReqoreDropdownItem
573
- ),
641
+ () => menuOptionActions.map(optionActionAsMenuItem),
574
642
  [menuOptionActions]
575
643
  );
576
644
  const renderInjectedOptionAction = (
@@ -627,8 +695,23 @@ export const CompactRow = memo(
627
695
  const changed = !hidden && !readOnly && hasOptionChanged(optionField?.value, optionName);
628
696
  // "Has a value" = set to anything non-empty. Drives the edit-row Clear
629
697
  // button and the cluster node's filled state (see `memberSet`).
698
+ /* "Is there anything to clear?" — this predicate has exactly one job, and
699
+ both of its uses are the Clear-value button.
700
+
701
+ An empty list is not a value. `[]` is neither `undefined`, `null` nor `''`,
702
+ so a field whose last item had just been removed went on offering to clear
703
+ it, and the confirmation asked whether to clear nothing. A test's cases
704
+ table is where this shows: delete the last case and a red clear-all sat
705
+ above an empty table, guarding a no-op.
706
+
707
+ Deliberately arrays only. An empty hash is arguable — "present but empty"
708
+ can be a real state for a hash — and nothing has demonstrated the same
709
+ defect there, so widening this predicate would be a guess. */
630
710
  const hasValue =
631
- optionField?.value !== undefined && optionField?.value !== null && optionField?.value !== '';
711
+ optionField?.value !== undefined &&
712
+ optionField?.value !== null &&
713
+ optionField?.value !== '' &&
714
+ !(Array.isArray(optionField.value) && optionField.value.length === 0);
632
715
  // Required-group membership shows a PERSISTENT chip on every member: amber
633
716
  // "One of" while the group is unmet (tap → flash siblings), then a muted-green
634
717
  // resolution once satisfied — "Covers" on the field that satisfied it,
@@ -705,7 +788,7 @@ export const CompactRow = memo(
705
788
  : (schema?.ui_type as string) || (schema?.type as string)) ?? '';
706
789
  // Scalars edit in place inside the row; complex fields (tall or nested
707
790
  // editors) still open the expanded card below.
708
- const inlineEditable =
791
+ const inlineEditableNow =
709
792
  !readOnly &&
710
793
  !schema?.arg_schema &&
711
794
  !(operators && size(operators)) &&
@@ -715,22 +798,160 @@ export const CompactRow = memo(
715
798
  !optionHasImages(schema) &&
716
799
  !COMPACT_COMPLEX_TYPES.has(editType);
717
800
 
718
- // Auto-focus the editor's first input when a field is opened, so you can type
719
- // straight away (matches the prototype's tap-to-edit feel).
801
+ /* Which of the two editors a row gets is settled when the row OPENS.
802
+ *
803
+ * Every term above reads the VALUE or a schema derived from it, so the
804
+ * answer can change while the author is inside the control — and the two
805
+ * branches are different subtrees, so a changed answer does not restyle
806
+ * the editor, it unmounts one and mounts the other. Everything the old one
807
+ * held goes with it: what was typed, the caret, and any state the editor
808
+ * keeps about how it was entered.
809
+ *
810
+ * `is_expression` is the term that actually fires, and it fires on the one
811
+ * interaction that exists to keep the author where they are: accepting
812
+ * "Use as expression" is the moment the value becomes an expression, and
813
+ * the expression shell opens on its Text view precisely because the author
814
+ * has just written the expression as text. The remount discarded that and
815
+ * dropped them into the visual builder, so the sentence they had typed was
816
+ * nowhere on screen. Clicking Text then appeared to work and bounced back,
817
+ * because the next value change remounted the row again.
818
+ *
819
+ * So the row keeps the editor it opened with for as long as it is open,
820
+ * and asks the question again when it is next opened — the same trade
821
+ * `editEntryValue` above makes for a different reason, and the one
822
+ * qorus-ide's `referenceEditorLatch` makes for this exact one. A field that
823
+ * already holds an expression when it is opened still gets the card; it is
824
+ * only a field that BECOMES one under the author that stays put.
825
+ *
826
+ * Held in a ref and settled during render rather than in an effect: an
827
+ * effect lands one render late, and one render late is a visible flash of
828
+ * the wrong editor — which is the thing being fixed.
829
+ */
830
+ const inlineEditableLatch = React.useRef<boolean | undefined>(undefined);
831
+ /* Whether THIS opening was the author asking to change the value.
832
+ *
833
+ * A click (or Enter/Space) on a closed row means "let me edit this"; a row
834
+ * opened by the form itself — `initialExpandedOptions` from an address, the
835
+ * first-attention row, a diagram opening the step it is showing — means
836
+ * "here it is". Only the first answers the question the picker answers, so
837
+ * only the first opens one (see the caret effect below). A ref, because it
838
+ * is read by the effect that runs after the open and must not itself cause
839
+ * a render. */
840
+ const openedByAuthor = React.useRef(false);
841
+ if (!isExpanded) {
842
+ inlineEditableLatch.current = undefined;
843
+ } else if (inlineEditableLatch.current === undefined) {
844
+ inlineEditableLatch.current = inlineEditableNow;
845
+ }
846
+ const inlineEditable = isExpanded ? !!inlineEditableLatch.current : inlineEditableNow;
847
+
848
+ /* The author's intent survives until the row is closed again, and no longer.
849
+ *
850
+ * An EFFECT, keyed on the row actually closing, rather than a line in the
851
+ * render body: between the click and the row opening there is a committed
852
+ * render that still reads `expandedOptions` without this row in it (the
853
+ * context propagates a beat after the handler runs), and clearing the flag
854
+ * on every collapsed render cleared it in that gap — the picker then never
855
+ * opened, which is the defect this whole mechanism exists to fix. Keyed on
856
+ * `isExpanded`, the transient render is not a change and the effect does
857
+ * not run; a genuine collapse is, and it does. */
858
+ React.useEffect(() => {
859
+ if (!isExpanded) {
860
+ openedByAuthor.current = false;
861
+ }
862
+ }, [isExpanded]);
863
+
864
+ /* The instruction the row gives its editor when it opens into a picker.
865
+ *
866
+ * State, not a call on the control: reqore's popover binds its own click
867
+ * listener in an effect keyed on the trigger element arriving, so a
868
+ * synthetic click dispatched from the row's own effect lands before
869
+ * anything is listening and does nothing at all. The editor opening itself
870
+ * from a prop has no such ordering to lose. */
871
+ const [openThePicker, setOpenThePicker] = React.useState(false);
872
+ React.useEffect(() => {
873
+ // One shot: true for the commit the editor acts on, false again after.
874
+ if (openThePicker) {
875
+ setOpenThePicker(false);
876
+ }
877
+ }, [openThePicker]);
878
+
879
+ // Put the caret in the row's editor when the row opens, so the reader can
880
+ // answer straight away (the prototype's tap-to-edit feel) — and so
881
+ // `autoFocusFirstRequired` means what it says.
882
+ //
883
+ // Whatever control the row OFFERS: `findRowFocusTarget` knows a pick-one is
884
+ // a set of `[tabindex]` checkboxes and a selector is a `<button>`, which a
885
+ // text-only scan skipped — so the engine opened the right row and the caret
886
+ // never moved. That is most of the first questions a form asks.
887
+ //
888
+ // The row is revealed before the caret lands in it, `block: 'nearest'` on
889
+ // the ROW. A caret nobody can see is not a caret: a form opened by a click
890
+ // usually mounts below the fold, and focusing something 700px down changes
891
+ // nothing on screen while the next keystroke goes somewhere invisible.
892
+ // `nearest` is by spec a no-op for a row already in view, so a form on
893
+ // screen never jumps, and it is the row that carries the label saying what
894
+ // the question is. A caller that wants the row opened WITHOUT the caret
895
+ // has `expandFirstRequired` for exactly that.
896
+ //
897
+ // A MutationObserver, not a timer: a host editor that resolves a catalogue
898
+ // before it can draw a control mounts it several commits after this effect
899
+ // runs, and there is no fixed time to wait for it — the control appearing
900
+ // IS the event. The 60ms timer this replaces missed every such editor, and
901
+ // fired too early or too late for every other one. One shot, and it gives
902
+ // up the moment the caret belongs to someone outside this row, so it can
903
+ // never pull a reader out of a control they moved into themselves.
720
904
  const editorRef = React.useRef<HTMLDivElement>(null);
905
+ const rowRef = React.useRef<HTMLDivElement>(null);
721
906
  React.useEffect(() => {
722
907
  if (!isExpanded) return undefined;
723
- const id = window.setTimeout(() => {
724
- const el = editorRef.current?.querySelector<HTMLElement>(
725
- 'input:not([type="hidden"]):not([disabled]), textarea, [contenteditable="true"]'
726
- );
727
- // A user-driven expand scrolls the just-opened editor into view; the
728
- // programmatic `autoFocusFirstRequired` expand must NOT — otherwise an
729
- // off-screen / below-the-fold form scrolls itself into view on mount.
730
- el?.focus(autoFocusNameRef?.current === optionName ? { preventScroll: true } : undefined);
731
- }, 60);
732
- return () => window.clearTimeout(id);
733
- }, [isExpanded, optionName, autoFocusNameRef]);
908
+ const editor = editorRef.current;
909
+ if (!editor) return undefined;
910
+ let settled = false;
911
+ const takeCaret = (): boolean => {
912
+ if (settled) return true;
913
+ const active = document.activeElement as HTMLElement | null;
914
+ if (active && active !== document.body && !rowRef.current?.contains(active)) {
915
+ // Someone else holds the caret — a field the reader moved into while
916
+ // this row's editor was still arriving. Leave it where it is.
917
+ settled = true;
918
+ return true;
919
+ }
920
+ const control = findRowFocusTarget(editor);
921
+ if (!control) return false;
922
+ // Optional call: jsdom does not implement `scrollIntoView`.
923
+ rowRef.current?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' });
924
+ // `preventScroll`, because the reveal above has already put the row
925
+ // where it belongs; letting focus scroll as well moves it twice.
926
+ control.focus({ preventScroll: true });
927
+ /* …and if the control the row offers is a PICKER, open it.
928
+ *
929
+ * A closed picker prints the value it holds, which is exactly what the
930
+ * read row printed — so the row opened, nothing on screen changed, and
931
+ * changing the value still cost a second click. Read-first is the
932
+ * COLLAPSED row's job; an open row is the editor.
933
+ *
934
+ * Narrow on purpose, and each term is a case where read-first is still
935
+ * right: only a row the author opened themselves (a form that opens a
936
+ * row to SHOW it must not throw a list over it), only an inline scalar
937
+ * row (a complex field opens a card that is a form in its own right —
938
+ * its first control is one of several and nobody asked for it), and
939
+ * only a control that declares itself a picker, so nothing else is
940
+ * ever clicked on the author's behalf. */
941
+ if (openedByAuthor.current && inlineEditable && isClosedPickerTrigger(control)) {
942
+ openedByAuthor.current = false;
943
+ setOpenThePicker(true);
944
+ }
945
+ settled = true;
946
+ return true;
947
+ };
948
+ if (takeCaret() || typeof MutationObserver === 'undefined') return undefined;
949
+ const observer = new MutationObserver(() => {
950
+ if (takeCaret()) observer.disconnect();
951
+ });
952
+ observer.observe(editor, { childList: true, subtree: true });
953
+ return () => observer.disconnect();
954
+ }, [isExpanded, optionName, inlineEditable]);
734
955
 
735
956
  // What the field held when it was opened — the baseline Cancel restores.
736
957
  //
@@ -1061,6 +1282,19 @@ export const CompactRow = memo(
1061
1282
  // stacks children flush, and the class rule that used to carry a 4px gap
1062
1283
  // never reached this element at all — which is why two messages read as one
1063
1284
  // two-tone block with no space between them.
1285
+ /* A message is PROSE, so it is drawn by the host's prose renderer.
1286
+
1287
+ `markdownRendererContext` says this package renders markdown "in exactly
1288
+ one place — a field's description". That was one place short: a schema
1289
+ message is written by the same author, in the same dialect, and it landed
1290
+ here as a raw string. A Qorus warning reading "Give that step a **Fixture
1291
+ Output**" showed the asterisks, and every reference inside it stayed a raw
1292
+ `$.` path where the editor two lines above renders it as a named chip.
1293
+
1294
+ `compact` for the same reason the row inset asks for it: this is a strip
1295
+ inside a row, never a page, and markdown authored as a document opens with
1296
+ a heading that would outgrow the field label above it. Without a host
1297
+ renderer the string is drawn as it always was. */
1064
1298
  const renderInfoStrip = (m: TInfoMsg, index: number) => (
1065
1299
  <ReqoreMessage
1066
1300
  key={`${m.content}-${index}`}
@@ -1071,7 +1305,9 @@ export const CompactRow = memo(
1071
1305
  title={m.title}
1072
1306
  style={{ marginBottom: 8 }}
1073
1307
  >
1074
- {m.content}
1308
+ {markdownRenderer && typeof m.content === 'string' ?
1309
+ markdownRenderer({ value: m.content, compact: true })
1310
+ : m.content}
1075
1311
  </ReqoreMessage>
1076
1312
  );
1077
1313
  const reasonColor = (intent?: string) =>
@@ -1108,6 +1344,9 @@ export const CompactRow = memo(
1108
1344
  }
1109
1345
  : {};
1110
1346
 
1347
+ // Items the EDITOR inside this row published — see `rowMenuContext`.
1348
+ const { rowMenu, items: editorMenuItems } = useRowMenuRegistry();
1349
+
1111
1350
  // Secondary edit actions tuck into a "More" (⋮) menu so the card header stays
1112
1351
  // calm: Fullscreen always, plus Remove field for a removable option. Rendered
1113
1352
  // just before the Done ✓ in both expanded layouts.
@@ -1144,6 +1383,10 @@ export const CompactRow = memo(
1144
1383
  // wholesale on touch) land here — this row already has a menu, so they
1145
1384
  // reuse it rather than adding a second one beside it.
1146
1385
  ...injectedOptionActionMenuItems,
1386
+ // ...and for the same reason, so do the editor's own actions. Without
1387
+ // this an editor with affordances of its own had to draw a second ⋮
1388
+ // inside the value cell, beside this one.
1389
+ ...editorMenuItems,
1147
1390
  ]}
1148
1391
  />
1149
1392
  );
@@ -1218,6 +1461,7 @@ export const CompactRow = memo(
1218
1461
  const editingRow = (
1219
1462
  <div
1220
1463
  key={optionName}
1464
+ ref={rowRef}
1221
1465
  data-field={optionName}
1222
1466
  className='readfirst-row readfirst-row-editing options-readfirst-inline options-readfirst-value'
1223
1467
  style={
@@ -1316,7 +1560,17 @@ export const CompactRow = memo(
1316
1560
  editor they belong to — the language you are writing in is
1317
1561
  read before the code, not after it. */}
1318
1562
  {absorbedNodes}
1319
- {renderOption(optionName, optionField, 'small', true)}
1563
+ {/* The editor may publish into THIS row's ⋮ — see `rowMenuContext`.
1564
+ Only the editors rendered in the row are given the channel: the
1565
+ fullscreen modal below has no row menu on screen, so an editor
1566
+ there keeps drawing its own control. */}
1567
+ {/* Scoped to the row's OWN editor: the absorbed siblings above
1568
+ are other fields, which this row did not open. */}
1569
+ <RowOpenPickerContext.Provider value={openThePicker}>
1570
+ <RowMenuContext.Provider value={rowMenu}>
1571
+ {renderOption(optionName, optionField, 'small', true)}
1572
+ </RowMenuContext.Provider>
1573
+ </RowOpenPickerContext.Provider>
1320
1574
  </div>
1321
1575
  <StyledRowActions>
1322
1576
  {draftChip}
@@ -1556,7 +1810,9 @@ export const CompactRow = memo(
1556
1810
  longDescriptionShownByDefault
1557
1811
  />
1558
1812
  : null}
1559
- {renderOption(optionName, optionField)}
1813
+ <RowMenuContext.Provider value={rowMenu}>
1814
+ {renderOption(optionName, optionField)}
1815
+ </RowMenuContext.Provider>
1560
1816
  </FocusedEditing>
1561
1817
  </div>
1562
1818
  </StyledEditCard>
@@ -1596,11 +1852,21 @@ export const CompactRow = memo(
1596
1852
  ];
1597
1853
  // A hash row reveals its sub-fields as read-only sub-rows under a "view
1598
1854
  // more" disclosure; the row itself still expands the real editor on click.
1855
+ /* An EXPRESSION is not a hash, whatever shape it is stored in.
1856
+
1857
+ `{is_expression: true, value: {exp, args}}` is hash-shaped, so a field
1858
+ holding one earned the structured inset and the row printed the syntax
1859
+ tree — `is_expression true / value / exp + / args 1 2` — directly under a
1860
+ summary line that already read `1 + 2`. The AST is how an expression is
1861
+ STORED, not what it is; the row's line is the whole value, and the
1862
+ expression editor is where its parts are looked at. Same exclusion, and
1863
+ for the same reason, as `schema-definition` beside it. */
1599
1864
  const hashEntries =
1600
1865
  (
1601
1866
  !hidden &&
1602
1867
  (valueType === 'hash' || valueType === 'free-hash') &&
1603
- (schema as { ui_type?: string } | undefined)?.ui_type !== 'schema-definition'
1868
+ (schema as { ui_type?: string } | undefined)?.ui_type !== 'schema-definition' &&
1869
+ getExpressionAst(optionField) === undefined
1604
1870
  ) ?
1605
1871
  getHashEntries(optionField, schema)
1606
1872
  : [];
@@ -1647,6 +1913,35 @@ export const CompactRow = memo(
1647
1913
  valueType === 'richtext' &&
1648
1914
  Array.isArray(optionField?.value) &&
1649
1915
  richtextToSegments(optionField.value as never).some((segment) => segment.kind === 'tag');
1916
+ /* The same pair, for a value that IS one template rather than prose with
1917
+ templates embedded in it. `showsTemplateChips` above closed the richtext
1918
+ case and left this one open: a chosen reference renders as
1919
+ `ReadOnlyTemplateTag`, which carries its own popover, while the row went
1920
+ on setting a native `title` holding the raw reference. Hovering fired
1921
+ both — two tooltips in two places, and the one that says something useful
1922
+ is not the one a screenshot captures. */
1923
+ const showsTemplateTag =
1924
+ !hidden &&
1925
+ typeof optionField?.value === 'string' &&
1926
+ !(optionField as { is_expression?: boolean } | undefined)?.is_expression &&
1927
+ (isValueTemplate(optionField.value) ||
1928
+ !!findTemplate(
1929
+ (schema as TFieldWithOwnTemplates | undefined)?.templates ?? templates ?? {},
1930
+ optionField.value
1931
+ ));
1932
+ /* And once more for a value that only MENTIONS references — a predicate, a
1933
+ greeting with a name in it. The row draws those as prose plus chips too,
1934
+ so a native `title` would hover the raw spelling of exactly what the chips
1935
+ replaced, and each chip's own popover would fire alongside it. */
1936
+ const showsTemplateText =
1937
+ !hidden &&
1938
+ !showsTemplateTag &&
1939
+ !!formatted &&
1940
+ hasTemplateText(
1941
+ formatted,
1942
+ (schema as TFieldWithOwnTemplates | undefined)?.templates ?? templates,
1943
+ (schema as TFieldWithOwnTemplates | undefined)?.templateToken
1944
+ );
1650
1945
  // Markdown reads the same way for the same reason: the row itself can only
1651
1946
  // show a line of text, and for markdown that line is the SOURCE — the reader
1652
1947
  // gets `## ` and `**` where the point of the value is what it looks like
@@ -1749,9 +2044,16 @@ export const CompactRow = memo(
1749
2044
  return {
1750
2045
  name: depName,
1751
2046
  exists: !!options?.[depName],
1752
- label: !op ? depLabel : `${depLabel} ${op === '=' ? '=' : '≠'} ${expected}`,
2047
+ // `!name` compares against nothing — it is satisfied by the sibling
2048
+ // being unanswered — so it says so, rather than going through the
2049
+ // comparison template and reading `Name ≠ undefined`.
2050
+ label:
2051
+ op === '!' ? `${depLabel} unset`
2052
+ : !op ? depLabel
2053
+ : `${depLabel} ${op === '=' ? '=' : '≠'} ${expected}`,
1753
2054
  fulfilled:
1754
- !op ? !isOptionValueEmpty(depValue)
2055
+ op === '!' ? isOptionValueEmpty(depValue)
2056
+ : !op ? !isOptionValueEmpty(depValue)
1755
2057
  : depValue == null ? false
1756
2058
  : op === '=' ? String(depValue) === expected
1757
2059
  : String(depValue) !== expected,
@@ -1812,14 +2114,26 @@ export const CompactRow = memo(
1812
2114
  // with `readOnly` handed down (an id in a greyed-out input, a choice
1813
2115
  // that could still be changed); then into a read view of the same
1814
2116
  // value — which showed nothing the row could not show itself once it
1815
- // stopped truncating. So it shows everything, and opens nothing.
2117
+ // stopped truncating. So it shows everything, and opens nothing HERE.
2118
+ //
2119
+ // Where the consumer says the field is editable somewhere else, the row
2120
+ // is the way in to that place: a reader clicking a value they want to
2121
+ // change has said exactly what they want, and answering with silence
2122
+ // teaches them the surface is broken.
1816
2123
  if (fieldDisabled || readOnly) {
2124
+ if (readOnly && !fieldDisabled) {
2125
+ onReadOnlyActivate?.(optionName);
2126
+ }
1817
2127
  return;
1818
2128
  }
1819
2129
  const target = event?.currentTarget as HTMLElement | undefined;
1820
2130
  if (target?.classList?.contains('readfirst-row')) {
1821
2131
  readRowHeights.current[optionName] = Math.round(target.getBoundingClientRect().height);
1822
2132
  }
2133
+ // The author asked for this one — so the row opens all the way (a picker
2134
+ // opens its list rather than reprinting the value as a closed trigger).
2135
+ // Only when it is OPENING: the same handler collapses an open row.
2136
+ openedByAuthor.current = !isExpanded;
1823
2137
  // Optional fields aren't "added" — every one is editable in place. Opening a
1824
2138
  // not-yet-set field just expands its editor (with an empty value); it joins
1825
2139
  // the form's output the moment a value is set (and moves to Set / Needs
@@ -1876,19 +2190,30 @@ export const CompactRow = memo(
1876
2190
  : dotStatus === 'todo' ? cWarning
1877
2191
  : dotStatus === 'set' ? cSuccess || cInfo
1878
2192
  : undefined;
2193
+ /* A read-only row is a control only when the consumer gave it somewhere to
2194
+ go. Then it takes a button's role, focus and keyboard handling, because
2195
+ a thing that acts on a click has to be reachable without a mouse and has
2196
+ to announce what it does — and "Edit X", not "X", is what it does. */
2197
+ const readOnlyIsAWayIn = !!readOnly && !fieldDisabled && !!onReadOnlyActivate;
2198
+ const actsOnClick = !readOnly || readOnlyIsAWayIn;
2199
+
1879
2200
  const row = (
1880
2201
  <div
1881
2202
  key={optionName}
1882
2203
  data-field={optionName}
1883
- role={readOnly ? undefined : 'button'}
1884
- tabIndex={readOnly ? undefined : 0}
1885
- aria-label={readOnly ? undefined : label}
1886
- className={`readfirst-row options-readfirst-value${readOnly ? ' readfirst-row-read' : ''}${hidden ? ' readfirst-row-hidden' : ''}${fieldDisabled ? ' readfirst-row-disabled' : ''}${isHighlighted ? ' readfirst-row-group-highlight' : ''}${isFlashed ? ' readfirst-row-flash' : ''}${showLabelDesc ? ' readfirst-row-info-open' : ''}${panelMessages.length || showStructuredPreview || showCodePreview || showMarkdownPreview || showLongTextInset ? ' readfirst-row-tall' : ''}${clusterBlockClass ? ' ' + clusterBlockClass : ''}`}
2204
+ role={actsOnClick ? 'button' : undefined}
2205
+ tabIndex={actsOnClick ? 0 : undefined}
2206
+ aria-label={
2207
+ readOnlyIsAWayIn ? `Edit ${label}`
2208
+ : readOnly ? undefined
2209
+ : label
2210
+ }
2211
+ className={`readfirst-row options-readfirst-value${readOnly ? ' readfirst-row-read' : ''}${readOnlyIsAWayIn ? ' readfirst-row-way-in' : ''}${hidden ? ' readfirst-row-hidden' : ''}${fieldDisabled ? ' readfirst-row-disabled' : ''}${isHighlighted ? ' readfirst-row-group-highlight' : ''}${isFlashed ? ' readfirst-row-flash' : ''}${showLabelDesc ? ' readfirst-row-info-open' : ''}${panelMessages.length || showStructuredPreview || showCodePreview || showMarkdownPreview || showLongTextInset ? ' readfirst-row-tall' : ''}${clusterBlockClass ? ' ' + clusterBlockClass : ''}`}
1887
2212
  aria-disabled={fieldDisabled || undefined}
1888
- style={stripeStyle}
1889
- onClick={readOnly ? undefined : activate}
2213
+ style={readOnlyIsAWayIn ? { ...stripeStyle, cursor: 'pointer' } : stripeStyle}
2214
+ onClick={actsOnClick ? activate : undefined}
1890
2215
  onKeyDown={
1891
- readOnly ? undefined : (
2216
+ !actsOnClick ? undefined : (
1892
2217
  (event) => {
1893
2218
  if (event.key === 'Enter' || event.key === ' ') {
1894
2219
  event.preventDefault();
@@ -1960,6 +2285,8 @@ export const CompactRow = memo(
1960
2285
  !showCodePreview &&
1961
2286
  !showMarkdownPreview &&
1962
2287
  !showsTemplateChips &&
2288
+ !showsTemplateTag &&
2289
+ !showsTemplateText &&
1963
2290
  typeof formatted === 'string'
1964
2291
  ) ?
1965
2292
  formatted