@qoretechnologies/reqraft 0.10.25 → 0.10.26

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 (310) hide show
  1. package/.claude/CLAUDE.md +52 -23
  2. package/design/COMPACT_ENGINE_REDESIGN.md +156 -0
  3. package/design/FORM_ENGINE_COMPACT_UX_PLAN.md +353 -0
  4. package/dist/components/FocusedEditing/index.d.ts +9 -1
  5. package/dist/components/FocusedEditing/index.d.ts.map +1 -1
  6. package/dist/components/FocusedEditing/index.js +2 -2
  7. package/dist/components/FocusedEditing/index.js.map +1 -1
  8. package/dist/components/composer/Composer.d.ts +108 -0
  9. package/dist/components/composer/Composer.d.ts.map +1 -0
  10. package/dist/components/composer/Composer.js +238 -0
  11. package/dist/components/composer/Composer.js.map +1 -0
  12. package/dist/components/composer/helpers.d.ts +21 -0
  13. package/dist/components/composer/helpers.d.ts.map +1 -0
  14. package/dist/components/composer/helpers.js +75 -0
  15. package/dist/components/composer/helpers.js.map +1 -0
  16. package/dist/components/composer/index.d.ts +3 -0
  17. package/dist/components/composer/index.d.ts.map +1 -0
  18. package/dist/components/composer/index.js +19 -0
  19. package/dist/components/composer/index.js.map +1 -0
  20. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  21. package/dist/components/form/engine/CompactRow.js +307 -110
  22. package/dist/components/form/engine/CompactRow.js.map +1 -1
  23. package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
  24. package/dist/components/form/engine/CompactToolbar.js +123 -106
  25. package/dist/components/form/engine/CompactToolbar.js.map +1 -1
  26. package/dist/components/form/engine/FormEngine.d.ts +107 -11
  27. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  28. package/dist/components/form/engine/FormEngine.js +618 -179
  29. package/dist/components/form/engine/FormEngine.js.map +1 -1
  30. package/dist/components/form/engine/compactRowContext.d.ts +9 -0
  31. package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
  32. package/dist/components/form/engine/compactRowContext.js.map +1 -1
  33. package/dist/components/form/engine/compactRowStyles.d.ts +7 -3
  34. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  35. package/dist/components/form/engine/compactRowStyles.js +88 -51
  36. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  37. package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
  38. package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
  39. package/dist/components/form/engine/compactToolbarContext.js.map +1 -1
  40. package/dist/components/form/engine/optionActions.d.ts +28 -0
  41. package/dist/components/form/engine/optionActions.d.ts.map +1 -0
  42. package/dist/components/form/engine/optionActions.js +18 -0
  43. package/dist/components/form/engine/optionActions.js.map +1 -0
  44. package/dist/components/form/engine/readFirst.d.ts +63 -2
  45. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  46. package/dist/components/form/engine/readFirst.js +121 -6
  47. package/dist/components/form/engine/readFirst.js.map +1 -1
  48. package/dist/components/form/engine/rendererTypes.d.ts +35 -0
  49. package/dist/components/form/engine/rendererTypes.d.ts.map +1 -0
  50. package/dist/components/form/engine/rendererTypes.js +63 -0
  51. package/dist/components/form/engine/rendererTypes.js.map +1 -0
  52. package/dist/components/form/engine/variants/VariantCalmTable.d.ts +6 -0
  53. package/dist/components/form/engine/variants/VariantCalmTable.d.ts.map +1 -0
  54. package/dist/components/form/engine/variants/VariantCalmTable.js +94 -0
  55. package/dist/components/form/engine/variants/VariantCalmTable.js.map +1 -0
  56. package/dist/components/form/engine/variants/VariantCards.d.ts +6 -0
  57. package/dist/components/form/engine/variants/VariantCards.d.ts.map +1 -0
  58. package/dist/components/form/engine/variants/VariantCards.js +80 -0
  59. package/dist/components/form/engine/variants/VariantCards.js.map +1 -0
  60. package/dist/components/form/engine/variants/VariantFocus.d.ts +7 -0
  61. package/dist/components/form/engine/variants/VariantFocus.d.ts.map +1 -0
  62. package/dist/components/form/engine/variants/VariantFocus.js +138 -0
  63. package/dist/components/form/engine/variants/VariantFocus.js.map +1 -0
  64. package/dist/components/form/engine/variants/VariantMinimal.d.ts +6 -0
  65. package/dist/components/form/engine/variants/VariantMinimal.d.ts.map +1 -0
  66. package/dist/components/form/engine/variants/VariantMinimal.js +73 -0
  67. package/dist/components/form/engine/variants/VariantMinimal.js.map +1 -0
  68. package/dist/components/form/engine/variants/focusDemo.d.ts +13 -0
  69. package/dist/components/form/engine/variants/focusDemo.d.ts.map +1 -0
  70. package/dist/components/form/engine/variants/focusDemo.js +139 -0
  71. package/dist/components/form/engine/variants/focusDemo.js.map +1 -0
  72. package/dist/components/form/engine/variants/variantModel.d.ts +70 -0
  73. package/dist/components/form/engine/variants/variantModel.d.ts.map +1 -0
  74. package/dist/components/form/engine/variants/variantModel.js +133 -0
  75. package/dist/components/form/engine/variants/variantModel.js.map +1 -0
  76. package/dist/components/form/engine/variants/variantParts.d.ts +79 -0
  77. package/dist/components/form/engine/variants/variantParts.d.ts.map +1 -0
  78. package/dist/components/form/engine/variants/variantParts.js +191 -0
  79. package/dist/components/form/engine/variants/variantParts.js.map +1 -0
  80. package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
  81. package/dist/components/form/expressions/builder/index.js +13 -2
  82. package/dist/components/form/expressions/builder/index.js.map +1 -1
  83. package/dist/components/form/expressions/useExpressions.d.ts.map +1 -1
  84. package/dist/components/form/expressions/useExpressions.js +4 -1
  85. package/dist/components/form/expressions/useExpressions.js.map +1 -1
  86. package/dist/components/form/fields/Field.d.ts.map +1 -1
  87. package/dist/components/form/fields/Field.js +5 -3
  88. package/dist/components/form/fields/Field.js.map +1 -1
  89. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +2 -1
  90. package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
  91. package/dist/components/form/fields/allowed-values/AllowedValues.js +8 -26
  92. package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
  93. package/dist/components/form/fields/auto/AutoFormField.d.ts +3 -0
  94. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  95. package/dist/components/form/fields/auto/AutoFormField.js +42 -8
  96. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  97. package/dist/components/form/fields/binary/Binary.d.ts +15 -0
  98. package/dist/components/form/fields/binary/Binary.d.ts.map +1 -0
  99. package/dist/components/form/fields/binary/Binary.js +74 -0
  100. package/dist/components/form/fields/binary/Binary.js.map +1 -0
  101. package/dist/components/form/fields/boolean/Boolean.js +1 -1
  102. package/dist/components/form/fields/boolean/Boolean.js.map +1 -1
  103. package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
  104. package/dist/components/form/fields/multi-select/MultiSelectFormField.js +5 -4
  105. package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
  106. package/dist/components/form/fields/select/Select.d.ts.map +1 -1
  107. package/dist/components/form/fields/select/Select.js +29 -12
  108. package/dist/components/form/fields/select/Select.js.map +1 -1
  109. package/dist/components/form/fields/select/SelectCollection.d.ts +8 -2
  110. package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
  111. package/dist/components/form/fields/select/SelectCollection.js +52 -16
  112. package/dist/components/form/fields/select/SelectCollection.js.map +1 -1
  113. package/dist/components/form/index.d.ts +2 -0
  114. package/dist/components/form/index.d.ts.map +1 -1
  115. package/dist/components/form/index.js +2 -0
  116. package/dist/components/form/index.js.map +1 -1
  117. package/dist/components/imageLightbox/ImageLightbox.d.ts +21 -0
  118. package/dist/components/imageLightbox/ImageLightbox.d.ts.map +1 -0
  119. package/dist/components/imageLightbox/ImageLightbox.js +169 -0
  120. package/dist/components/imageLightbox/ImageLightbox.js.map +1 -0
  121. package/dist/components/imageLightbox/index.d.ts +2 -0
  122. package/dist/components/imageLightbox/index.d.ts.map +1 -0
  123. package/dist/components/imageLightbox/index.js +18 -0
  124. package/dist/components/imageLightbox/index.js.map +1 -0
  125. package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
  126. package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
  127. package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
  128. package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
  129. package/dist/components/searchFilterBar/index.d.ts +2 -0
  130. package/dist/components/searchFilterBar/index.d.ts.map +1 -0
  131. package/dist/components/searchFilterBar/index.js +18 -0
  132. package/dist/components/searchFilterBar/index.js.map +1 -0
  133. package/dist/components/supportTicket/AttachmentChips.d.ts +10 -0
  134. package/dist/components/supportTicket/AttachmentChips.d.ts.map +1 -0
  135. package/dist/components/supportTicket/AttachmentChips.js +122 -0
  136. package/dist/components/supportTicket/AttachmentChips.js.map +1 -0
  137. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +43 -0
  138. package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -0
  139. package/dist/components/supportTicket/InterfaceReferenceTags.js +31 -0
  140. package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -0
  141. package/dist/components/supportTicket/ReferencePicker.d.ts +87 -0
  142. package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -0
  143. package/dist/components/supportTicket/ReferencePicker.js +295 -0
  144. package/dist/components/supportTicket/ReferencePicker.js.map +1 -0
  145. package/dist/components/supportTicket/TicketHeader.d.ts +86 -0
  146. package/dist/components/supportTicket/TicketHeader.d.ts.map +1 -0
  147. package/dist/components/supportTicket/TicketHeader.js +149 -0
  148. package/dist/components/supportTicket/TicketHeader.js.map +1 -0
  149. package/dist/components/supportTicket/TicketMetaTags.d.ts +32 -0
  150. package/dist/components/supportTicket/TicketMetaTags.d.ts.map +1 -0
  151. package/dist/components/supportTicket/TicketMetaTags.js +21 -0
  152. package/dist/components/supportTicket/TicketMetaTags.js.map +1 -0
  153. package/dist/components/supportTicket/TicketReplyBox.d.ts +63 -0
  154. package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -0
  155. package/dist/components/supportTicket/TicketReplyBox.js +115 -0
  156. package/dist/components/supportTicket/TicketReplyBox.js.map +1 -0
  157. package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
  158. package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
  159. package/dist/components/supportTicket/attachmentAccess.js +132 -0
  160. package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
  161. package/dist/components/supportTicket/fileToAttachment.d.ts +9 -0
  162. package/dist/components/supportTicket/fileToAttachment.d.ts.map +1 -0
  163. package/dist/components/supportTicket/fileToAttachment.js +28 -0
  164. package/dist/components/supportTicket/fileToAttachment.js.map +1 -0
  165. package/dist/components/supportTicket/index.d.ts +11 -0
  166. package/dist/components/supportTicket/index.d.ts.map +1 -0
  167. package/dist/components/supportTicket/index.js +27 -0
  168. package/dist/components/supportTicket/index.js.map +1 -0
  169. package/dist/components/supportTicket/meta.d.ts +71 -0
  170. package/dist/components/supportTicket/meta.d.ts.map +1 -0
  171. package/dist/components/supportTicket/meta.js +115 -0
  172. package/dist/components/supportTicket/meta.js.map +1 -0
  173. package/dist/components/supportTicket/ticketModel.d.ts +102 -0
  174. package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
  175. package/dist/components/supportTicket/ticketModel.js +3 -0
  176. package/dist/components/supportTicket/ticketModel.js.map +1 -0
  177. package/dist/components/ticketReferences/TicketReferences.d.ts +52 -0
  178. package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -0
  179. package/dist/components/ticketReferences/TicketReferences.js +492 -0
  180. package/dist/components/ticketReferences/TicketReferences.js.map +1 -0
  181. package/dist/components/ticketReferences/index.d.ts +3 -0
  182. package/dist/components/ticketReferences/index.d.ts.map +1 -0
  183. package/dist/components/ticketReferences/index.js +6 -0
  184. package/dist/components/ticketReferences/index.js.map +1 -0
  185. package/dist/components/ticketThread/TicketThread.d.ts +57 -0
  186. package/dist/components/ticketThread/TicketThread.d.ts.map +1 -0
  187. package/dist/components/ticketThread/TicketThread.js +299 -0
  188. package/dist/components/ticketThread/TicketThread.js.map +1 -0
  189. package/dist/components/ticketThread/index.d.ts +3 -0
  190. package/dist/components/ticketThread/index.d.ts.map +1 -0
  191. package/dist/components/ticketThread/index.js +7 -0
  192. package/dist/components/ticketThread/index.js.map +1 -0
  193. package/dist/helpers/common.d.ts +5 -0
  194. package/dist/helpers/common.d.ts.map +1 -1
  195. package/dist/helpers/common.js +20 -1
  196. package/dist/helpers/common.js.map +1 -1
  197. package/dist/helpers/scrollbar.d.ts +2 -0
  198. package/dist/helpers/scrollbar.d.ts.map +1 -0
  199. package/dist/helpers/scrollbar.js +25 -0
  200. package/dist/helpers/scrollbar.js.map +1 -0
  201. package/dist/index.d.ts +8 -1
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +10 -1
  204. package/dist/index.js.map +1 -1
  205. package/dist/providers/StorageProvider.d.ts +13 -0
  206. package/dist/providers/StorageProvider.d.ts.map +1 -1
  207. package/dist/providers/StorageProvider.js +29 -5
  208. package/dist/providers/StorageProvider.js.map +1 -1
  209. package/package.json +6 -5
  210. package/src/components/FocusedEditing/index.tsx +17 -2
  211. package/src/components/composer/Composer.stories.tsx +238 -0
  212. package/src/components/composer/Composer.tsx +545 -0
  213. package/src/components/composer/helpers.ts +74 -0
  214. package/src/components/composer/index.ts +2 -0
  215. package/src/components/dpqlEditor/DpqlEditor.stories.tsx +74 -2
  216. package/src/components/form/engine/CompactRow.tsx +561 -283
  217. package/src/components/form/engine/CompactToolbar.tsx +216 -182
  218. package/src/components/form/engine/FormEngine.stories.tsx +1736 -162
  219. package/src/components/form/engine/FormEngine.tsx +886 -170
  220. package/src/components/form/engine/FormEngineRemote.stories.tsx +377 -0
  221. package/src/components/form/engine/_structuredData/StructuredDataView.stories.tsx +8 -0
  222. package/src/components/form/engine/compactRowContext.ts +14 -0
  223. package/src/components/form/engine/compactRowStyles.ts +273 -146
  224. package/src/components/form/engine/compactToolbarContext.ts +1 -0
  225. package/src/components/form/engine/optionActions.ts +40 -0
  226. package/src/components/form/engine/readFirst.ts +169 -10
  227. package/src/components/form/engine/rendererTypes.ts +55 -0
  228. package/src/components/form/engine/variants/FormEngineVariants.stories.tsx +159 -0
  229. package/src/components/form/engine/variants/VariantCalmTable.tsx +242 -0
  230. package/src/components/form/engine/variants/VariantCards.tsx +212 -0
  231. package/src/components/form/engine/variants/VariantFocus.tsx +382 -0
  232. package/src/components/form/engine/variants/VariantMinimal.tsx +170 -0
  233. package/src/components/form/engine/variants/focusDemo.ts +145 -0
  234. package/src/components/form/engine/variants/variantModel.ts +216 -0
  235. package/src/components/form/engine/variants/variantParts.tsx +313 -0
  236. package/src/components/form/expressions/ExpressionField.stories.tsx +84 -2
  237. package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +213 -19
  238. package/src/components/form/expressions/builder/index.tsx +12 -1
  239. package/src/components/form/expressions/useExpressions.ts +4 -1
  240. package/src/components/form/fields/Field.stories.tsx +40 -0
  241. package/src/components/form/fields/Field.tsx +10 -1
  242. package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -36
  243. package/src/components/form/fields/array/ArrayAutoField.stories.tsx +64 -0
  244. package/src/components/form/fields/auto/AutoFormField.stories.tsx +146 -2
  245. package/src/components/form/fields/auto/AutoFormField.tsx +42 -1
  246. package/src/components/form/fields/binary/Binary.stories.tsx +122 -0
  247. package/src/components/form/fields/binary/Binary.tsx +81 -0
  248. package/src/components/form/fields/boolean/Boolean.stories.tsx +58 -0
  249. package/src/components/form/fields/boolean/Boolean.tsx +1 -1
  250. package/src/components/form/fields/byte-size/ByteSize.stories.tsx +24 -0
  251. package/src/components/form/fields/color/Color.stories.tsx +30 -0
  252. package/src/components/form/fields/cron/Cron.stories.tsx +8 -0
  253. package/src/components/form/fields/date/Date.stories.tsx +22 -0
  254. package/src/components/form/fields/file/File.stories.tsx +48 -0
  255. package/src/components/form/fields/long-string/LongString.stories.tsx +24 -0
  256. package/src/components/form/fields/markdown/Markdown.stories.tsx +8 -0
  257. package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +14 -10
  258. package/src/components/form/fields/number/Number.stories.tsx +31 -0
  259. package/src/components/form/fields/object/Object.stories.tsx +88 -0
  260. package/src/components/form/fields/radio-group/RadioGroup.stories.tsx +24 -0
  261. package/src/components/form/fields/rich-text/RichText.stories.tsx +23 -0
  262. package/src/components/form/fields/schema-definition/SchemaDefinitionField.stories.tsx +72 -0
  263. package/src/components/form/fields/select/Select.stories.tsx +108 -0
  264. package/src/components/form/fields/select/Select.tsx +38 -12
  265. package/src/components/form/fields/select/SelectCollection.tsx +78 -17
  266. package/src/components/form/fields/string/String.stories.tsx +16 -0
  267. package/src/components/form/fields/template/TemplateField.stories.tsx +160 -0
  268. package/src/components/form/fields/url/Url.stories.tsx +24 -0
  269. package/src/components/form/index.tsx +2 -0
  270. package/src/components/imageLightbox/ImageLightbox.stories.tsx +238 -0
  271. package/src/components/imageLightbox/ImageLightbox.tsx +236 -0
  272. package/src/components/imageLightbox/index.ts +1 -0
  273. package/src/components/log/Log.stories.tsx +96 -0
  274. package/src/components/menu/Menu.stories.tsx +54 -0
  275. package/src/components/qonsoleSmartInput/QonsoleSmartInput.stories.tsx +83 -37
  276. package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
  277. package/src/components/searchFilterBar/index.ts +1 -0
  278. package/src/components/smartEditor/SmartEditor.stories.tsx +32 -0
  279. package/src/components/supportTicket/AttachmentChips.stories.tsx +171 -0
  280. package/src/components/supportTicket/AttachmentChips.tsx +147 -0
  281. package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +171 -0
  282. package/src/components/supportTicket/InterfaceReferenceTags.tsx +96 -0
  283. package/src/components/supportTicket/ReferencePicker.stories.tsx +519 -0
  284. package/src/components/supportTicket/ReferencePicker.tsx +563 -0
  285. package/src/components/supportTicket/TicketConversation.stories.tsx +121 -0
  286. package/src/components/supportTicket/TicketHeader.stories.tsx +183 -0
  287. package/src/components/supportTicket/TicketHeader.tsx +247 -0
  288. package/src/components/supportTicket/TicketMetaTags.stories.tsx +103 -0
  289. package/src/components/supportTicket/TicketMetaTags.tsx +85 -0
  290. package/src/components/supportTicket/TicketReplyBox.stories.tsx +351 -0
  291. package/src/components/supportTicket/TicketReplyBox.tsx +146 -0
  292. package/src/components/supportTicket/attachmentAccess.ts +97 -0
  293. package/src/components/supportTicket/fileToAttachment.ts +24 -0
  294. package/src/components/supportTicket/index.ts +10 -0
  295. package/src/components/supportTicket/meta.ts +185 -0
  296. package/src/components/supportTicket/ticketModel.ts +131 -0
  297. package/src/components/ticketReferences/TicketReferences.stories.tsx +315 -0
  298. package/src/components/ticketReferences/TicketReferences.tsx +886 -0
  299. package/src/components/ticketReferences/index.ts +2 -0
  300. package/src/components/ticketThread/TicketThread.stories.tsx +828 -0
  301. package/src/components/ticketThread/TicketThread.tsx +704 -0
  302. package/src/components/ticketThread/index.ts +6 -0
  303. package/src/helpers/common.ts +19 -0
  304. package/src/helpers/scrollbar.ts +32 -0
  305. package/src/hooks/useFetch/useFetch.stories.tsx +30 -0
  306. package/src/hooks/useStorage/useStorage.stories.tsx +82 -0
  307. package/src/hooks/useWebSocket/useWebsocket.stories.tsx +127 -1
  308. package/src/index.tsx +8 -1
  309. package/src/providers/StorageProvider.tsx +30 -4
  310. package/src/stores/currentUser/currentUser.stories.tsx +49 -0
@@ -10,6 +10,8 @@ import {
10
10
  ReqoreTagGroup,
11
11
  } from '@qoretechnologies/reqore';
12
12
  import { IReqoreDropdownItem } from '@qoretechnologies/reqore/dist/components/Dropdown/list';
13
+ import { IReqorePanelAction } from '@qoretechnologies/reqore/dist/components/Panel';
14
+ import { resolveOptionActions } from './optionActions';
13
15
  import {
14
16
  IQorusFormField,
15
17
  TQorusForm,
@@ -35,17 +37,17 @@ import {
35
37
  StyledActionSlot,
36
38
  StyledCardHeading,
37
39
  StyledCardLabel,
38
- StyledClusterNode,
40
+ StyledCodePreview,
39
41
  StyledColorSwatch,
40
42
  StyledColumn,
41
43
  StyledEditCard,
42
- StyledInfoPanel,
43
44
  StyledLabelBlock,
44
45
  StyledLabelDesc,
45
46
  StyledRowActions,
46
47
  StyledRowInset,
47
48
  StyledRowLabel,
48
49
  StyledRowValue,
50
+ StyledStatusDot,
49
51
  } from './compactRowStyles';
50
52
  import { getOptionFieldMessages } from './OptionFieldMessages';
51
53
  import {
@@ -55,7 +57,9 @@ import {
55
57
  getAllowedValueImage,
56
58
  getFileSize,
57
59
  getHashEntries,
60
+ getReadFirstStatus,
58
61
  getValueType,
62
+ isFixedCompactAllowedValueOption,
59
63
  isOptionValueEmpty,
60
64
  optionHasImages,
61
65
  } from './readFirst';
@@ -92,7 +96,7 @@ const COMPACT_COMPLEX_TYPES = new Set([
92
96
 
93
97
  // Card editors that hold a SINGLE value through one text-bearing input — their
94
98
  // built-in ReqoreInput clear duplicates the card's own "Clear value" action, so
95
- // we suppress it (the card is the single source). Structural/multi-input
99
+ // we suppress it (the card trash action is the single source). Structural/multi-input
96
100
  // editors (hash, list, schema…) and polymorphic ones (auto/any) are NOT listed:
97
101
  // their per-sub-field clears are distinct affordances and must stay, while the
98
102
  // card ✕ clears the whole value. (Operators force a card on a scalar value, so
@@ -106,6 +110,12 @@ const COMPACT_SINGLE_VALUE_TYPES = new Set([
106
110
  'method-name',
107
111
  ]);
108
112
 
113
+ // How many consumer-injected actions may sit inline in a row before the rest
114
+ // overflow into the row's menu. A row's action slot shares space with the
115
+ // value; an unbounded button strip would squeeze the value out, and the
116
+ // consumer controls how many actions it injects.
117
+ const MAX_INLINE_OPTION_ACTIONS = 2;
118
+
109
119
 
110
120
  // One read-first row: label | value | action collapsed; the real editor (the
111
121
  // classic renderOption) expanded. `hidden` = search-surfaced optional —
@@ -142,34 +152,23 @@ export const CompactRow = memo(
142
152
  const isExpanded = useContextSelector(CompactRowContext, (v) =>
143
153
  v.expandedOptions.includes(optionName)
144
154
  );
155
+ const autoFocusNameRef = useContextSelector(CompactRowContext, (v) => v.autoFocusNameRef);
145
156
  const isHighlighted = useContextSelector(CompactRowContext, (v) =>
146
157
  v.highlightedOptions.includes(optionName)
147
158
  );
148
159
  const isFlashed = useContextSelector(CompactRowContext, (v) =>
149
160
  v.flashedOptions.includes(optionName)
150
161
  );
151
- const infoPanelOverride = useContextSelector(
152
- CompactRowContext,
153
- (v) => v.infoPanelOverrides[optionName]
154
- );
155
162
  const setHighlightedOptions = useContextSelector(
156
163
  CompactRowContext,
157
164
  (v) => v.setHighlightedOptions
158
165
  );
159
- const setInfoPanelOverrides = useContextSelector(
160
- CompactRowContext,
161
- (v) => v.setInfoPanelOverrides
162
- );
163
166
  const setFocusedEditing = useContextSelector(CompactRowContext, (v) => v.setFocusedEditing);
164
167
  const readRowHeights = useContextSelector(CompactRowContext, (v) => v.readRowHeights);
165
168
  const originalValue = useContextSelector(CompactRowContext, (v) => v.originalValue);
166
169
  const availableOptions = useContextSelector(CompactRowContext, (v) => v.availableOptions);
167
170
  const requiredGroupsInfo = useContextSelector(CompactRowContext, (v) => v.requiredGroupsInfo);
168
171
  const handleValueChange = useContextSelector(CompactRowContext, (v) => v.handleValueChange);
169
- const handleAddOptionalFieldChange = useContextSelector(
170
- CompactRowContext,
171
- (v) => v.handleAddOptionalFieldChange
172
- );
173
172
  const toggleExpandedOption = useContextSelector(
174
173
  CompactRowContext,
175
174
  (v) => v.toggleExpandedOption
@@ -186,18 +185,21 @@ export const CompactRow = memo(
186
185
  );
187
186
  const getTypeForOption = useContextSelector(CompactRowContext, (v) => v.getTypeForOption);
188
187
  const confirmAction = useContextSelector(CompactRowContext, (v) => v.confirmAction);
188
+ const optionActions = useContextSelector(CompactRowContext, (v) => v.optionActions);
189
+ const collapseOptionActions = useContextSelector(
190
+ CompactRowContext,
191
+ (v) => v.collapseOptionActions
192
+ );
189
193
  const renderOption = useContextSelector(CompactRowContext, (v) => v.renderOption);
190
194
  const theme = useContextSelector(CompactRowContext, (v) => v.theme);
191
195
  const cMuted = useContextSelector(CompactRowContext, (v) => v.cMuted);
192
196
  const templates = useContextSelector(CompactRowContext, (v) => v.templates);
193
- const cFaint = useContextSelector(CompactRowContext, (v) => v.cFaint);
194
197
  const cKey = useContextSelector(CompactRowContext, (v) => v.cKey);
195
198
  const cDivider = useContextSelector(CompactRowContext, (v) => v.cDivider);
196
199
  const cHover = useContextSelector(CompactRowContext, (v) => v.cHover);
197
200
  const cDanger = useContextSelector(CompactRowContext, (v) => v.cDanger);
198
201
  const cWarning = useContextSelector(CompactRowContext, (v) => v.cWarning);
199
202
  const cInfo = useContextSelector(CompactRowContext, (v) => v.cInfo);
200
- const cBg = useContextSelector(CompactRowContext, (v) => v.cBg);
201
203
 
202
204
  // Value-cell content: colour adds a swatch, file an icon + size; hash keeps
203
205
  // its "N fields" summary (sub-fields reveal beneath the row).
@@ -270,6 +272,26 @@ export const CompactRow = memo(
270
272
  );
271
273
  }
272
274
 
275
+ // Code-editor read summary: replace the truncated string with a chip
276
+ // showing line count + character count. The actual code renders below
277
+ // the row as a monospace `<pre>` inside a `ReqoreCollapsibleContent` —
278
+ // see `showCodePreview` below. Keeps the row height fixed while making
279
+ // multi-line source readable without opening the full editor.
280
+ if (valueType === 'code-editor' && typeof field?.value === 'string') {
281
+ const lines = field.value.split('\n').length;
282
+ const chars = field.value.length;
283
+ return (
284
+ <ReqoreTag
285
+ size='small'
286
+ minimal
287
+ intent='info'
288
+ icon='CodeLine'
289
+ label={`${lines} ${lines === 1 ? 'line' : 'lines'}`}
290
+ labelKey={`${chars} ${chars === 1 ? 'char' : 'chars'}`}
291
+ />
292
+ );
293
+ }
294
+
273
295
  // Selected allowed-value / enum item with a logo (e.g. language images):
274
296
  // render it beside the value, where it belongs — not as the field's icon.
275
297
  const allowedImage = getAllowedValueImage(field?.value, schema);
@@ -330,6 +352,97 @@ export const CompactRow = memo(
330
352
 
331
353
  const schema = options?.[optionName];
332
354
  const label = schema?.display_name || optionName;
355
+ const injectedOptionActions = React.useMemo<IReqorePanelAction[]>(
356
+ () =>
357
+ schema ?
358
+ resolveOptionActions(optionActions, {
359
+ name: optionName,
360
+ schema,
361
+ value: availableOptions?.[optionName],
362
+ }).filter((action) => action.show !== false)
363
+ : [],
364
+ [availableOptions?.[optionName], optionActions, optionName, schema]
365
+ );
366
+ // Which injected actions stay as inline buttons, and which move into the
367
+ // overflow menu. Everything collapses on touch / narrow viewports — a
368
+ // hover-gated button is unreachable without a hover — and anything past the
369
+ // inline cap overflows regardless, so a consumer injecting ten actions can
370
+ // never push the row's value out of view.
371
+ const [inlineOptionActions, menuOptionActions] = React.useMemo<
372
+ [IReqorePanelAction[], IReqorePanelAction[]]
373
+ >(
374
+ () =>
375
+ collapseOptionActions ?
376
+ [[], injectedOptionActions]
377
+ : [
378
+ injectedOptionActions.slice(0, MAX_INLINE_OPTION_ACTIONS),
379
+ injectedOptionActions.slice(MAX_INLINE_OPTION_ACTIONS),
380
+ ],
381
+ [collapseOptionActions, injectedOptionActions]
382
+ );
383
+ const injectedOptionActionMenuItems = React.useMemo<IReqoreDropdownItem[]>(
384
+ () =>
385
+ menuOptionActions.map(
386
+ (action, index) =>
387
+ ({
388
+ // A panel action labels itself with `label`, but an icon-only one
389
+ // (the IDE's AI-assist button) carries its name in the tooltip —
390
+ // a menu row has no hover affordance to fall back on.
391
+ label: action.label ?? action.tooltip ?? `Action ${index + 1}`,
392
+ icon: action.icon,
393
+ intent: action.intent,
394
+ disabled: action.disabled,
395
+ onClick: () => action.onClick?.(),
396
+ }) as IReqoreDropdownItem
397
+ ),
398
+ [menuOptionActions]
399
+ );
400
+ const renderInjectedOptionAction = (
401
+ action: IReqorePanelAction,
402
+ index: number,
403
+ size: 'tiny' | 'small' = 'small'
404
+ ) => {
405
+ // `show` drives visibility here instead of reaching the DOM. The classic
406
+ // path hands these to ReqorePanel, which honours `show: 'hover'` itself;
407
+ // the compact slots render plain buttons, so 'hover' becomes a CSS gate on
408
+ // the containing row/card (see compactRowStyles) — same config, same
409
+ // behaviour in both modes.
410
+ // `size` is destructured out and deliberately NOT honoured: the row owns
411
+ // its action strip's rhythm, and a consumer-supplied size made the
412
+ // injected button visibly smaller than the revert / more / confirm
413
+ // buttons beside it. The contextual `size` argument is the row's own
414
+ // (small when editing or in a card, tiny on a read row), so injected
415
+ // actions always match their neighbours.
416
+ const {
417
+ label: actionLabel,
418
+ onClick,
419
+ show,
420
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
421
+ size: _ignoredSize,
422
+ className,
423
+ ...actionProps
424
+ } = action;
425
+
426
+ return (
427
+ <ReqoreButton
428
+ key={`${optionName}-injected-action-${index}`}
429
+ size={size}
430
+ minimal
431
+ flat
432
+ fixed
433
+ {...actionProps}
434
+ className={`${className ? `${className} ` : ''}options-injected-action${
435
+ show === 'hover' ? ' options-injected-action-hover' : ''
436
+ }`}
437
+ onClick={(event: React.MouseEvent<HTMLElement>) => {
438
+ event.stopPropagation();
439
+ onClick?.();
440
+ }}
441
+ >
442
+ {actionLabel}
443
+ </ReqoreButton>
444
+ );
445
+ };
333
446
  const required = !!(schema?.required || schema?.required_groups);
334
447
  const removable =
335
448
  !readOnly && !schema?.preselected && !schema?.required && !schema?.required_groups;
@@ -408,7 +521,11 @@ export const CompactRow = memo(
408
521
  />
409
522
  </span>
410
523
  : null;
411
- const editType = ((schema?.ui_type as string) || (schema?.type as string)) ?? '';
524
+ const fixedAllowedValueOption = isFixedCompactAllowedValueOption(schema);
525
+ const editType =
526
+ (fixedAllowedValueOption
527
+ ? (schema?.type as string) || (schema?.ui_type as string)
528
+ : (schema?.ui_type as string) || (schema?.type as string)) ?? '';
412
529
  // Scalars edit in place inside the row; complex fields (tall or nested
413
530
  // editors) still open the expanded card below.
414
531
  const inlineEditable =
@@ -420,6 +537,24 @@ export const CompactRow = memo(
420
537
  // A choice with per-option logos (e.g. language) reads better collapsed.
421
538
  !optionHasImages(schema) &&
422
539
  !COMPACT_COMPLEX_TYPES.has(editType);
540
+
541
+ // Auto-focus the editor's first input when a field is opened, so you can type
542
+ // straight away (matches the prototype's tap-to-edit feel).
543
+ const editorRef = React.useRef<HTMLDivElement>(null);
544
+ React.useEffect(() => {
545
+ if (!isExpanded) return undefined;
546
+ const id = window.setTimeout(() => {
547
+ const el = editorRef.current?.querySelector<HTMLElement>(
548
+ 'input:not([type="hidden"]):not([disabled]), textarea, [contenteditable="true"]'
549
+ );
550
+ // A user-driven expand scrolls the just-opened editor into view; the
551
+ // programmatic `autoFocusFirstRequired` expand must NOT — otherwise an
552
+ // off-screen / below-the-fold form scrolls itself into view on mount.
553
+ el?.focus(autoFocusNameRef?.current === optionName ? { preventScroll: true } : undefined);
554
+ }, 60);
555
+ return () => window.clearTimeout(id);
556
+ }, [isExpanded, optionName, autoFocusNameRef]);
557
+
423
558
  const revertButton =
424
559
  changed ?
425
560
  <ReqoreButton
@@ -443,8 +578,9 @@ export const CompactRow = memo(
443
578
  // — distinct from the read-row "Remove field" (which deletes an optional
444
579
  // field entirely). Once cleared, `changed` flips on and `hasValue` off, so
445
580
  // this button gives way to `revertButton` in the same slot — the morph the
446
- // edit row's actions are designed around. Neutral (not danger): it's a
447
- // reversible step, undone by Revert or by re-typing.
581
+ // edit row's actions are designed around. Clearing is destructive even
582
+ // though it can be reverted before saving, so both the trigger and its
583
+ // confirmation use danger intent.
448
584
  //
449
585
  // Only editors with NO built-in clear of their own get this button —
450
586
  // toggles and fixed-choice pickers. The text/number/date inputs already
@@ -457,6 +593,17 @@ export const CompactRow = memo(
457
593
  // CREATABLE one still renders the raw input (with its own ✕), so it keeps
458
594
  // its clear — matching the dispatch in AutoFormField/TemplateField.
459
595
  (!!size(schema?.allowed_values) && !schema?.allowed_values_creatable);
596
+ const confirmClearValue = (event?: React.MouseEvent) => {
597
+ event?.stopPropagation();
598
+ confirmAction({
599
+ title: 'Clear value',
600
+ description: `Clear the current value of "${label}"?`,
601
+ confirmLabel: 'Clear value',
602
+ confirmButtonIntent: 'danger',
603
+ intent: 'danger',
604
+ onConfirm: () => handleValueChange(optionName, undefined),
605
+ });
606
+ };
460
607
  const clearValueButton =
461
608
  hasValue && !readOnly && editorLacksOwnClear ?
462
609
  <ReqoreButton
@@ -464,12 +611,11 @@ export const CompactRow = memo(
464
611
  size='small'
465
612
  flat
466
613
  minimal
467
- icon='CloseLine'
614
+ icon='DeleteBinLine'
615
+ intent='danger'
616
+ aria-label='Clear value'
468
617
  tooltip='Clear value'
469
- onClick={(e: React.MouseEvent) => {
470
- e.stopPropagation();
471
- handleValueChange(optionName, undefined);
472
- }}
618
+ onClick={confirmClearValue}
473
619
  />
474
620
  : null;
475
621
  // Batched commit: a changed row is a draft until Save — mark it with the
@@ -489,6 +635,21 @@ export const CompactRow = memo(
489
635
  effect={{ uppercase: true, spaced: 1 }}
490
636
  />
491
637
  : null;
638
+ const isPassiveCloseAction = fixedAllowedValueOption || readOnly;
639
+ const closeExpandedFieldButton = (
640
+ <ReqoreButton
641
+ className='options-readfirst-done'
642
+ size='small'
643
+ intent={!isPassiveCloseAction ? 'success' : undefined}
644
+ flat={isPassiveCloseAction}
645
+ minimal={isPassiveCloseAction}
646
+ fixed
647
+ icon={isPassiveCloseAction ? 'CloseLine' : 'CheckLine'}
648
+ aria-label={isPassiveCloseAction ? 'Close field' : 'Done'}
649
+ tooltip={isPassiveCloseAction ? 'Close field' : 'Done'}
650
+ onClick={() => toggleExpandedOption(optionName)}
651
+ />
652
+ );
492
653
 
493
654
  // Info tiers: Tier 1 (danger/warning + dependency hints) must be visible
494
655
  // without interaction; Tier 2 (info/success, default notes) sits behind ⓘ.
@@ -516,10 +677,13 @@ export const CompactRow = memo(
516
677
  .filter((m) => m.label !== 'This field is required')
517
678
  .map((m) => ({ intent: m.intent as string, content: String(m.label) }))
518
679
  : [];
519
- const isCriticalMsg = (m: TInfoMsg) => m.intent === 'danger' || m.intent === 'warning';
520
- const tier1 = [...schemaMessages, ...fieldMessages].filter(isCriticalMsg);
521
- const tier2: TInfoMsg[] = [
522
- ...[...schemaMessages, ...fieldMessages].filter((m) => !isCriticalMsg(m)),
680
+ // TWO display channels, matching the Focus prototype:
681
+ // • dedicated schema `messages` → prominent coloured PANELS below the row;
682
+ // • everything else (validation reasons, the unmet-dependency hint, the
683
+ // default-value note) a compact INLINE reason strip — not a panel.
684
+ const panelMessages = schemaMessages;
685
+ const inlineMessages: TInfoMsg[] = [
686
+ ...fieldMessages,
523
687
  ...(infoActive && schema?.default_value_desc ?
524
688
  [
525
689
  {
@@ -529,26 +693,44 @@ export const CompactRow = memo(
529
693
  ]
530
694
  : []),
531
695
  ];
696
+ const allMsgs = [...panelMessages, ...inlineMessages];
532
697
  const worstIntent =
533
- tier1.some((m) => m.intent === 'danger') ? 'danger'
534
- : tier1.length ? 'warning'
698
+ allMsgs.some((m) => m.intent === 'danger') ? 'danger'
699
+ : allMsgs.some((m) => m.intent === 'warning') ? 'warning'
535
700
  : undefined;
536
701
  const intentColor =
537
702
  worstIntent === 'danger' ? cDanger
538
703
  : worstIntent === 'warning' ? cWarning
539
704
  : undefined;
540
705
  const showStripe = infoActive && !!intentColor;
541
- // The short_desc renders UNDER the field name (revealed by the ⓘ toggle); the
542
- // value-side panel carries only the messages (tier1/tier2).
543
706
  const labelShortDesc = schema?.short_desc;
544
- const panelHasContent = tier1.length > 0 || tier2.length > 0;
545
- const hasInfoPanelContent = infoActive && (panelHasContent || !!labelShortDesc);
546
- // Open-state precedence: a per-row override is most specific; else the
547
- // global toggle when ENGAGED (show all / hide all — overriding the message
548
- // auto-open); else the default, where only critical (tier1) messages open.
549
- const defaultOpen =
550
- showAllDescriptions === undefined ? tier1.length > 0 : showAllDescriptions;
551
- const infoPanelOpen = hasInfoPanelContent && (infoPanelOverride ?? defaultOpen);
707
+ // The per-row is gone: short_desc shows under the name only when the global
708
+ // "descriptions" toggle is engaged (and inside the editor when expanded).
709
+ const showLabelDesc = !!labelShortDesc && showAllDescriptions === true;
710
+
711
+ // The `?` that opens the long-form help dialog. Shared by the read row AND
712
+ // the inline editor: opening a field used to drop it, leaving an editing row
713
+ // with no route to the field's description at all. Stops propagation because
714
+ // both labels are click targets themselves (the row expands / collapses).
715
+ const helpIcon =
716
+ schema?.desc ?
717
+ <ReqoreIcon
718
+ icon='QuestionLine'
719
+ size='12px'
720
+ effect={{ opacity: 0.55 }}
721
+ margin='left'
722
+ marginSize='tiny'
723
+ role='button'
724
+ tabIndex={-1}
725
+ aria-label='Help'
726
+ className='options-readfirst-help'
727
+ style={{ cursor: 'help' }}
728
+ onClick={(event) => {
729
+ event.stopPropagation();
730
+ handleOptionLabelClick(optionName);
731
+ }}
732
+ />
733
+ : null;
552
734
 
553
735
  const renderInfoStrip = (m: TInfoMsg, index: number) => (
554
736
  <ReqoreMessage
@@ -562,33 +744,14 @@ export const CompactRow = memo(
562
744
  {m.content}
563
745
  </ReqoreMessage>
564
746
  );
747
+ const reasonColor = (intent?: string) =>
748
+ intent === 'danger' ? cDanger
749
+ : intent === 'warning' ? cWarning
750
+ : intent === 'success' ?
751
+ (theme?.intents as Record<string, string> | undefined)?.success || cInfo
752
+ : `${cMuted}cc`;
565
753
 
566
- const infoToggle =
567
- hasInfoPanelContent ?
568
- <ReqoreButton
569
- className='options-readfirst-info-toggle'
570
- size='tiny'
571
- minimal
572
- flat
573
- compact
574
- fixed
575
- active={infoPanelOpen}
576
- intent={worstIntent as never}
577
- icon={infoPanelOpen ? 'InformationFill' : 'InformationLine'}
578
- tooltip={infoPanelOpen ? 'Hide field information' : 'Show field information'}
579
- onClick={(e: React.MouseEvent) => {
580
- e.stopPropagation();
581
- setInfoPanelOverrides((prev) => ({ ...prev, [optionName]: !infoPanelOpen }));
582
- }}
583
- />
584
- : null;
585
-
586
- const infoBlock =
587
- infoPanelOpen && panelHasContent ?
588
- <StyledInfoPanel className='options-readfirst-info-panel'>
589
- {[...tier1, ...tier2].map(renderInfoStrip)}
590
- </StyledInfoPanel>
591
- : null;
754
+ const infoToggle = null;
592
755
 
593
756
  // Cluster (required-group connection) — shared by the read row, its block
594
757
  // wrapper AND the inline editor, so the rail/node/highlight persist across
@@ -604,20 +767,9 @@ export const CompactRow = memo(
604
767
  clustered ?
605
768
  `readfirst-cluster-rail${clusterFirst ? ' readfirst-cluster-first' : ''}${clusterLast ? ' readfirst-cluster-last' : ''}${clusterSatisfied ? ' readfirst-cluster-satisfied' : ''}`
606
769
  : '';
607
- const memberSet = clustered && !hidden && hasValue;
608
- const clusterNode =
609
- clustered ?
610
- <StyledClusterNode
611
- className='options-readfirst-node'
612
- $filled={!!memberSet}
613
- $color={
614
- clusterSatisfied ?
615
- (theme?.intents as Record<string, string> | undefined)?.success || cInfo
616
- : `${cWarning}99`
617
- }
618
- $bg={cBg}
619
- />
620
- : null;
770
+ // The connection rail + status nodes are gone — the "One of the below is
771
+ // required" box (and the Covers / Covered-by chips) carry the grouping now.
772
+ const clusterNode = null;
621
773
  const clusterHoverProps =
622
774
  clustered && clusterMembers.length ?
623
775
  {
@@ -626,6 +778,46 @@ export const CompactRow = memo(
626
778
  }
627
779
  : {};
628
780
 
781
+ // Secondary edit actions tuck into a "More" (⋮) menu so the card header stays
782
+ // calm: Fullscreen always, plus Remove field for a removable option. Rendered
783
+ // just before the Done ✓ in both expanded layouts.
784
+ const moreMenu = (
785
+ <ReqoreDropdown
786
+ className='options-readfirst-more'
787
+ icon='More2Fill'
788
+ flat
789
+ minimal
790
+ fixed
791
+ size='small'
792
+ tooltip='More actions'
793
+ items={[
794
+ {
795
+ label: 'Edit fullscreen',
796
+ icon: 'FullscreenLine',
797
+ onClick: () => setFocusedEditing(optionName),
798
+ } as IReqoreDropdownItem,
799
+ ...(removable && !hidden ?
800
+ [
801
+ {
802
+ label: 'Remove field',
803
+ icon: 'DeleteBinLine',
804
+ intent: 'danger',
805
+ onClick: () =>
806
+ confirmAction({
807
+ title: 'Remove field',
808
+ onConfirm: () => removeSelectedOption(optionName),
809
+ }),
810
+ } as IReqoreDropdownItem,
811
+ ]
812
+ : []),
813
+ // Consumer-injected actions that did not fit inline (or collapsed
814
+ // wholesale on touch) land here — this row already has a menu, so they
815
+ // reuse it rather than adding a second one beside it.
816
+ ...injectedOptionActionMenuItems,
817
+ ]}
818
+ />
819
+ );
820
+
629
821
  if (isExpanded) {
630
822
  if (inlineEditable) {
631
823
  const collapse = () => toggleExpandedOption(optionName);
@@ -641,25 +833,42 @@ export const CompactRow = memo(
641
833
  }
642
834
  {...clusterHoverProps}
643
835
  >
644
- <StyledRowLabel
645
- role='button'
646
- tabIndex={0}
647
- aria-label={`Collapse ${label}`}
648
- title={schema?.short_desc || undefined}
649
- $color={cKey}
650
- $pointer
651
- onClick={collapse}
652
- onKeyDown={(event) => {
653
- if (event.key === 'Enter' || event.key === ' ') {
654
- event.preventDefault();
655
- collapse();
656
- }
657
- }}
658
- >
659
- {label}
660
- {required ? <ReqoreIcon icon='Asterisk' color='danger' size='10px' /> : null}
661
- </StyledRowLabel>
836
+ <StyledLabelBlock>
837
+ <StyledRowLabel
838
+ role='button'
839
+ tabIndex={0}
840
+ aria-label={`Collapse ${label}`}
841
+ title={schema?.short_desc || undefined}
842
+ $color={cKey}
843
+ $pointer
844
+ onClick={collapse}
845
+ onKeyDown={(event) => {
846
+ if (event.key === 'Enter' || event.key === ' ') {
847
+ event.preventDefault();
848
+ collapse();
849
+ }
850
+ }}
851
+ >
852
+ {label}
853
+ {required ? <ReqoreIcon icon='Asterisk' color='danger' size='10px' /> : null}
854
+ {helpIcon}
855
+ </StyledRowLabel>
856
+ {/* An OPEN field always shows its short_desc — that's where the hint
857
+ is needed and there's no per-row button to reveal it (the edit
858
+ card does the same). The global descriptions toggle governs READ
859
+ rows only, so collapsing the field is what hides it again. */}
860
+ {labelShortDesc ?
861
+ <StyledLabelDesc
862
+ className='options-readfirst-label-desc'
863
+ size='small'
864
+ effect={{ opacity: 0.55 }}
865
+ >
866
+ {labelShortDesc}
867
+ </StyledLabelDesc>
868
+ : null}
869
+ </StyledLabelBlock>
662
870
  <div
871
+ ref={editorRef}
663
872
  style={{ minWidth: 0 }}
664
873
  onKeyDown={(event) => {
665
874
  if (event.key === 'Escape') {
@@ -678,16 +887,11 @@ export const CompactRow = memo(
678
887
  message strip is visible. */}
679
888
  {revertButton}
680
889
  {clearValueButton}
681
- <ReqoreButton
682
- className='options-readfirst-done'
683
- size='small'
684
- flat
685
- minimal
686
- intent='success'
687
- icon='CheckLine'
688
- tooltip='Done'
689
- onClick={collapse}
690
- />
890
+ {inlineOptionActions.map((action, index) =>
891
+ renderInjectedOptionAction(action, index)
892
+ )}
893
+ {moreMenu}
894
+ {closeExpandedFieldButton}
691
895
  </StyledRowActions>
692
896
  {/* Cluster node is rendered LAST (it's absolutely positioned, so DOM
693
897
  order doesn't move it) — keeping it out of the leading cells lets
@@ -709,6 +913,27 @@ export const CompactRow = memo(
709
913
  className={clusterBlockClass || undefined}
710
914
  >
711
915
  {editingRow}
916
+ {/* Focused editing for an INLINE row. The edit card wraps its editor
917
+ in <FocusedEditing>, which renders the field in place AND in the
918
+ modal; an inline row already renders its editor in the row grid,
919
+ so it mounts the modal alone. Without this the More menu's "Edit
920
+ fullscreen" set the state and nothing appeared — the modal simply
921
+ had no mount point on this branch. */}
922
+ {focusedEditing === optionName ?
923
+ <FocusedEditing
924
+ isFullscreen
925
+ modalOnly
926
+ onClose={() => setFocusedEditing(undefined)}
927
+ description={(schema?.display_name as string) || optionName}
928
+ >
929
+ <Description
930
+ longDescription={schema?.desc}
931
+ shortDescription={schema?.short_desc}
932
+ longDescriptionShownByDefault
933
+ />
934
+ {renderOption(optionName, optionField)}
935
+ </FocusedEditing>
936
+ : null}
712
937
  </StyledColumn>
713
938
  );
714
939
  }
@@ -749,7 +974,7 @@ export const CompactRow = memo(
749
974
  }}
750
975
  >
751
976
  <StyledCardHeading>
752
- <StyledCardLabel $color={cMuted}>
977
+ <StyledCardLabel $color={cKey}>
753
978
  {(schema as { icon?: string } | undefined)?.icon || (schema as { image?: string } | undefined)?.image ?
754
979
  <ReqoreIcon
755
980
  icon={(schema as { icon?: string } | undefined)?.icon as never}
@@ -811,40 +1036,27 @@ export const CompactRow = memo(
811
1036
  </ReqoreButton>
812
1037
  );
813
1038
  })}
814
- <ReqoreButton
815
- size='small'
816
- icon='FullscreenLine'
817
- minimal
818
- flat
819
- fixed
820
- className='options-readfirst-fullscreen'
821
- tooltip='Edit fullscreen'
822
- onClick={() => setFocusedEditing(optionName)}
823
- />
824
- {/* Clear-value sits between focus-edit and Done — the card analog of
825
- the inline row's Clear. Empties the value (keeps the field). */}
1039
+ {inlineOptionActions.map((action, index) =>
1040
+ renderInjectedOptionAction(action, index)
1041
+ )}
1042
+ {/* Clear-value sits before the More menu — the card analog of the
1043
+ inline row's Clear. Empties the value (keeps the field). */}
826
1044
  {hasValue && !readOnly ?
827
1045
  <ReqoreButton
828
1046
  size='small'
829
- icon='CloseLine'
1047
+ icon='DeleteBinLine'
830
1048
  minimal
831
1049
  flat
832
1050
  fixed
1051
+ intent='danger'
833
1052
  className='options-readfirst-clear'
1053
+ aria-label='Clear value'
834
1054
  tooltip='Clear value'
835
- onClick={() => handleValueChange(optionName, undefined)}
1055
+ onClick={confirmClearValue}
836
1056
  />
837
1057
  : null}
838
- <ReqoreButton
839
- size='small'
840
- icon='CheckLine'
841
- intent='success'
842
- fixed
843
- className='options-readfirst-done'
844
- onClick={() => toggleExpandedOption(optionName)}
845
- >
846
- {readOnly ? 'Close' : 'Done'}
847
- </ReqoreButton>
1058
+ {moreMenu}
1059
+ {closeExpandedFieldButton}
848
1060
  </ReqoreControlGroup>
849
1061
  </div>
850
1062
  {/* Same fullscreen focused-editing affordance as the classic cards —
@@ -869,6 +1081,15 @@ export const CompactRow = memo(
869
1081
 
870
1082
  const formatted = formatOptionValue(optionField, schema);
871
1083
  const empty = formatted === '';
1084
+ // Inline reasons shown on the value line: validation / dependency / one-of /
1085
+ // default-value hints, plus a read-only covered-by note (editable covered rows
1086
+ // carry that in their chip instead).
1087
+ const valueReasons: TInfoMsg[] = [
1088
+ ...inlineMessages,
1089
+ ...(empty && coveredByLabel ?
1090
+ [{ content: `Covered by “${coveredByLabel}”`, intent: 'success' }]
1091
+ : []),
1092
+ ];
872
1093
  // A hash row reveals its sub-fields as read-only sub-rows under a "view
873
1094
  // more" disclosure; the row itself still expands the real editor on click.
874
1095
  const valueType = getValueType(optionField, schema);
@@ -878,6 +1099,33 @@ export const CompactRow = memo(
878
1099
  (schema as { ui_type?: string } | undefined)?.ui_type !== 'schema-definition' ?
879
1100
  getHashEntries(optionField, schema)
880
1101
  : [];
1102
+ // A LIST OF HASHES/objects gets the same expandable structured preview a hash
1103
+ // does — the flat "N items" summary can't convey object contents (and must
1104
+ // never print "[object Object]"). StructuredDataView unwraps each item's
1105
+ // {type,value} envelope. Plain scalar lists keep just their joined summary.
1106
+ const isHashList =
1107
+ !hidden &&
1108
+ (valueType === 'list' || valueType === 'free-list' || valueType === 'array') &&
1109
+ Array.isArray(optionField?.value) &&
1110
+ optionField.value.length > 0 &&
1111
+ optionField.value.some((item) => {
1112
+ const inner =
1113
+ item && typeof item === 'object' && 'value' in (item as Record<string, unknown>) ?
1114
+ (item as Record<string, unknown>).value
1115
+ : item;
1116
+ return inner !== null && typeof inner === 'object';
1117
+ });
1118
+ const showStructuredPreview = hashEntries.length > 0 || isHashList;
1119
+ // Multi-line code preview: a `code-editor` field with a non-empty string
1120
+ // value gets a `<pre>`-based code block under the row, capped by a
1121
+ // `ReqoreCollapsibleContent` so the collapsed height stays row-sized and
1122
+ // "Show more" reveals the rest. Sits in the same inset the hash/list
1123
+ // preview uses — mutually exclusive with `showStructuredPreview`.
1124
+ const showCodePreview =
1125
+ !hidden &&
1126
+ valueType === 'code-editor' &&
1127
+ typeof optionField?.value === 'string' &&
1128
+ optionField.value.length > 0;
881
1129
  const typeLabel =
882
1130
  showFieldTypes ?
883
1131
  `<${(schema?.ui_type as string) || (schema?.type as string) || 'auto'}${(schema as { ui_element_type?: string } | undefined)?.ui_element_type ? `[${(schema as { ui_element_type?: string }).ui_element_type}]` : ''}>`
@@ -936,8 +1184,7 @@ export const CompactRow = memo(
936
1184
  flat
937
1185
  compact
938
1186
  icon='LockLine'
939
- label='Depends on'
940
- effect={{uppercase: true, spaced: 1}}
1187
+ tooltip='Disabled — depends on other fields. Click to locate them.'
941
1188
  items={[
942
1189
  { divider: true, label: 'Unlocked by:', dividerAlign: 'left' } as IReqoreDropdownItem,
943
1190
  ...dependencyEntries.flatMap((entry): IReqoreDropdownItem[] => [
@@ -972,9 +1219,10 @@ export const CompactRow = memo(
972
1219
  if (target?.classList?.contains('readfirst-row')) {
973
1220
  readRowHeights.current[optionName] = Math.round(target.getBoundingClientRect().height);
974
1221
  }
975
- if (hidden) {
976
- handleAddOptionalFieldChange('options', optionName);
977
- }
1222
+ // Optional fields aren't "added" — every one is editable in place. Opening a
1223
+ // not-yet-set field just expands its editor (with an empty value); it joins
1224
+ // the form's output the moment a value is set (and moves to Set / Needs
1225
+ // attention), and drops back to Optional when cleared. No explicit add step.
978
1226
  toggleExpandedOption(optionName);
979
1227
  };
980
1228
 
@@ -1002,18 +1250,36 @@ export const CompactRow = memo(
1002
1250
  rowStripeColor ?
1003
1251
  ({ ['--readfirst-stripe']: rowStripeColor } as React.CSSProperties)
1004
1252
  : undefined;
1005
- const blockWrapped = hashEntries.length > 0 || !!infoBlock;
1006
1253
 
1254
+ // "Focus" trailing status dot, derived from the SHARED status helper (the
1255
+ // same one FormEngine uses to bucket rows into the Needs-attention/Set/Optional
1256
+ // boxes — so the dot and the box can never disagree). Hidden (not-yet-added)
1257
+ // and dependency-locked rows show no dot (the add/lock affordance speaks for
1258
+ // them). worstIntent already folds in schema + validation messages.
1259
+ const cSuccess = (theme?.intents as Record<string, string> | undefined)?.success;
1260
+ const rowStatus = getReadFirstStatus({
1261
+ empty,
1262
+ required,
1263
+ covered: !!coveredByLabel || groupResolved,
1264
+ invalid: worstIntent === 'danger',
1265
+ warned: worstIntent === 'warning',
1266
+ });
1267
+ const dotStatus = hidden || fieldDisabled ? undefined : rowStatus;
1268
+ const dotColor =
1269
+ dotStatus === 'invalid' ? cDanger
1270
+ : dotStatus === 'todo' ? cWarning
1271
+ : dotStatus === 'set' ? cSuccess || cInfo
1272
+ : undefined;
1007
1273
  const row = (
1008
1274
  <div
1009
1275
  key={optionName}
1010
1276
  data-field={optionName}
1011
1277
  role='button'
1012
1278
  tabIndex={0}
1013
- aria-label={`${label}${hidden ? ' (add field)' : ''}`}
1014
- className={`readfirst-row options-readfirst-value${hidden ? ' readfirst-row-hidden' : ''}${fieldDisabled ? ' readfirst-row-disabled' : ''}${isHighlighted ? ' readfirst-row-group-highlight' : ''}${isFlashed ? ' readfirst-row-flash' : ''}${labelShortDesc && infoPanelOpen ? ' readfirst-row-info-open' : ''}${!blockWrapped && clusterBlockClass ? ' ' + clusterBlockClass : ''}`}
1279
+ aria-label={label}
1280
+ className={`readfirst-row options-readfirst-value${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 ? ' readfirst-row-tall' : ''}${clusterBlockClass ? ' ' + clusterBlockClass : ''}`}
1015
1281
  aria-disabled={fieldDisabled || undefined}
1016
- style={blockWrapped ? undefined : stripeStyle}
1282
+ style={stripeStyle}
1017
1283
  onClick={activate}
1018
1284
  onKeyDown={(event) => {
1019
1285
  if (event.key === 'Enter' || event.key === ' ') {
@@ -1027,31 +1293,20 @@ export const CompactRow = memo(
1027
1293
  <StyledLabelBlock>
1028
1294
  <StyledRowLabel title={schema?.short_desc || undefined} $color={cKey}>
1029
1295
  {rowChromeIcon}
1030
- {label}
1031
- {required ? <ReqoreIcon icon='Asterisk' color='danger' size='10px' /> : null}
1296
+ {/* label + asterisk + help flow as ONE inline run, so the asterisk
1297
+ stays right after the last word even when the name wraps. */}
1298
+ <span className='options-readfirst-label-text' style={{ minWidth: 0 }}>
1299
+ {label}
1300
+ {required ?
1301
+ <ReqoreIcon icon='Asterisk' color='danger' size='10px' margin='left' marginSize='tiny' />
1302
+ : null}
1303
+ {helpIcon}
1304
+ </span>
1032
1305
  {typeLabel ?
1033
1306
  <ReqoreTag size='tiny' minimal label={typeLabel} labelEffect={{ opacity: 0.55 }} />
1034
1307
  : null}
1035
- {schema?.desc ?
1036
- <ReqoreIcon
1037
- icon='QuestionLine'
1038
- size='12px'
1039
- effect={{ opacity: 0.55 }}
1040
- margin='left'
1041
- marginSize={5}
1042
- role='button'
1043
- tabIndex={-1}
1044
- aria-label='Help'
1045
- className='options-readfirst-help'
1046
- style={{ cursor: 'help' }}
1047
- onClick={(event) => {
1048
- event.stopPropagation();
1049
- handleOptionLabelClick(optionName);
1050
- }}
1051
- />
1052
- : null}
1053
1308
  </StyledRowLabel>
1054
- {labelShortDesc && infoPanelOpen ?
1309
+ {showLabelDesc ?
1055
1310
  <StyledLabelDesc
1056
1311
  className='options-readfirst-label-desc'
1057
1312
  size='small'
@@ -1063,22 +1318,84 @@ export const CompactRow = memo(
1063
1318
  </StyledLabelBlock>
1064
1319
  <StyledRowValue
1065
1320
  title={!empty && !hidden && typeof formatted === 'string' ? formatted : undefined}
1066
- $color={empty || hidden ? cFaint : `${cMuted}cc`}
1321
+ // A SET value reads at full key brightness so it stands out as the
1322
+ // actual data — crucial when stacked under a (muted) description on
1323
+ // mobile, where a dim value blends into the prose. The bold name still
1324
+ // outranks it; the empty dash stays faint.
1325
+ $color={empty || hidden ? `${cMuted}66` : cKey}
1067
1326
  $empty={empty || hidden}
1068
1327
  >
1069
- {hidden ?
1070
- 'Not in formadd'
1071
- : empty ?
1072
- coveredByLabel ?
1073
- // Editable rows carry the "covered by" explanation in the group
1074
- // chip; read-only rows have no chip, so keep it inline here.
1075
- readOnly ?
1076
- `Not set covered by “${coveredByLabel}”`
1077
- : 'Not set'
1078
- : required ?
1079
- 'Required — not set'
1080
- : 'Not set'
1081
- : renderReadFirstValue(optionField, schema, formatted)}
1328
+ <span className='options-readfirst-valuetext'>
1329
+ {hidden || empty ? '' : renderReadFirstValue(optionField, schema, formatted)}
1330
+ </span>
1331
+ {valueReasons.map((m, i) => (
1332
+ <span
1333
+ key={`${m.content}-${i}`}
1334
+ className='options-readfirst-reason'
1335
+ style={{ color: reasonColor(m.intent) }}
1336
+ >
1337
+ {m.content}
1338
+ </span>
1339
+ ))}
1340
+ {/* Dedicated schema message PANELS render right here, directly under the
1341
+ value (full width of the value column) — never pushed down by the
1342
+ label's short_desc. */}
1343
+ {panelMessages.length ?
1344
+ <div className='options-readfirst-info-panel'>{panelMessages.map(renderInfoStrip)}</div>
1345
+ : null}
1346
+ {/* The structured hash/list preview also lives in the value cell, so it
1347
+ starts directly under the value summary ("N fields"), not below the
1348
+ label's short_desc. */}
1349
+ {showStructuredPreview ?
1350
+ // The inset lives inside the row now, so a click on a value chip would
1351
+ // bubble to the row's onClick and fire activate() a SECOND time (the
1352
+ // chip's onItemClick already calls it) — toggling the editor shut again.
1353
+ // Stop the bubble here; the chip's own handler still opens the editor.
1354
+ <StyledRowInset
1355
+ className='options-readfirst-inset'
1356
+ onClick={(e) => e.stopPropagation()}
1357
+ >
1358
+ <ReqoreCollapsibleContent
1359
+ maxCollapsedHeight={96}
1360
+ buttonProps={{ className: 'options-readfirst-viewmore' }}
1361
+ >
1362
+ <div className='options-readfirst-structured'>
1363
+ <StructuredDataView
1364
+ value={optionField?.value}
1365
+ collapsibleRoot={false}
1366
+ showTypes={showFieldTypes}
1367
+ defaultExpandDepth={2}
1368
+ onItemClick={() => activate()}
1369
+ />
1370
+ </div>
1371
+ </ReqoreCollapsibleContent>
1372
+ </StyledRowInset>
1373
+ : null}
1374
+ {/* Code-editor preview: same inset placement as hash/list — a monospace
1375
+ `<pre>` block wrapped in `ReqoreCollapsibleContent` so the collapsed
1376
+ row stays row-height while multi-line source reveals via "Show more".
1377
+ Stopping propagation here (like the structured inset) — a click on
1378
+ the collapse button must not also open the field editor. */}
1379
+ {showCodePreview ?
1380
+ <StyledRowInset
1381
+ className='options-readfirst-inset options-readfirst-code-inset'
1382
+ onClick={(e) => e.stopPropagation()}
1383
+ >
1384
+ <ReqoreCollapsibleContent
1385
+ maxCollapsedHeight={96}
1386
+ buttonProps={{ className: 'options-readfirst-viewmore' }}
1387
+ >
1388
+ <StyledCodePreview
1389
+ className='options-readfirst-code'
1390
+ $bg={cHover}
1391
+ $border={`${cDivider}88`}
1392
+ $fg={cKey}
1393
+ >
1394
+ {optionField.value as string}
1395
+ </StyledCodePreview>
1396
+ </ReqoreCollapsibleContent>
1397
+ </StyledRowInset>
1398
+ : null}
1082
1399
  </StyledRowValue>
1083
1400
  <StyledRowActions>
1084
1401
  {/* Column discipline (table treatment): variable-width chips lead and
@@ -1093,63 +1410,24 @@ export const CompactRow = memo(
1093
1410
  the "One of"/"Covers" chip — keep only "Covered by <X>", the one fact
1094
1411
  the rail can't show. Non-clustered members (split-across-panels or
1095
1412
  narrow mode, where there's no rail) keep the full chip as a fallback. */}
1096
- {!hidden && !fieldDisabled && (!clustered || !!coveredByLabel) ? requiredGroupChip : null}
1413
+ {!hidden && !fieldDisabled && !clustered && !coveredByLabel ? requiredGroupChip : null}
1097
1414
  {!hidden ? dependsOnChip : null}
1098
1415
  {draftChip}
1099
- {changed ?
1100
- <ReqoreButton
1101
- className='options-readfirst-revert'
1102
- size='tiny'
1103
- flat
1104
- minimal
1105
- compact
1106
- icon='HistoryLine'
1107
- tooltip='Revert changes'
1108
- onClick={(e: React.MouseEvent) => {
1109
- e.stopPropagation();
1110
- handleValueChange(
1111
- optionName,
1112
- originalValue.current?.[optionName]?.value,
1113
- originalValue.current?.[optionName]?.type
1114
- );
1115
- }}
1116
- />
1117
- : null}
1118
- {removable && !hidden ?
1119
- <ReqoreButton
1120
- className='readfirst-action'
1121
- size='small'
1122
- flat
1123
- minimal
1124
- intent='danger'
1125
- icon='DeleteBinLine'
1126
- tooltip='Remove field'
1127
- onClick={(e: React.MouseEvent) => {
1128
- e.stopPropagation();
1129
- confirmAction({
1130
- title: 'Remove field',
1131
- onConfirm: () => removeSelectedOption(optionName),
1132
- });
1133
- }}
1134
- />
1135
- : null}
1136
- {/* Lock/add slot BEFORE the info slot so the ⓘ keeps the same far-right
1137
- x on every row — a disabled field's lock sits to the ⓘ's left rather
1138
- than pushing it inward. ADD for a hidden field; a plain LOCK for a
1139
- field disabled for a non-dependency reason. Dependency-locked fields
1140
- show the "Depends on" chip in the chips area instead; the whole row is
1141
- click-to-edit, so there's no hover edit pencil. */}
1142
- {hidden || (fieldDisabled && !dependencyEntries.length) ?
1416
+ {/* Remove-field lives in the expanded editor's "More" (⋮) menu now — no
1417
+ standalone delete button on the read row. */}
1418
+ {/* Lock slot BEFORE the info slot so the ⓘ keeps the same far-right x on
1419
+ every row — a disabled field's lock sits to the ⓘ's left rather than
1420
+ pushing it inward. (Not-yet-set optional fields show NO marker now —
1421
+ they're just editable in place, like every other row. Dependency-
1422
+ locked fields show the "Depends on" chip in the chips area instead.) */}
1423
+ {fieldDisabled && !dependencyEntries.length ?
1143
1424
  <StyledActionSlot className='options-readfirst-trailing-slot' $width={18}>
1144
- {hidden ?
1145
- <ReqoreIcon icon='AddLine' intent='info' size='14px' />
1146
- : <span
1147
- title={fieldDisabledReason}
1148
- style={{ display: 'inline-flex', opacity: 0.45 }}
1149
- >
1150
- <ReqoreIcon className='options-readfirst-locked' icon='LockLine' size='14px' />
1151
- </span>
1152
- }
1425
+ <span
1426
+ title={fieldDisabledReason}
1427
+ style={{ display: 'inline-flex', opacity: 0.45 }}
1428
+ >
1429
+ <ReqoreIcon className='options-readfirst-locked' icon='LockLine' size='14px' />
1430
+ </span>
1153
1431
  </StyledActionSlot>
1154
1432
  : null}
1155
1433
  {infoToggle ?
@@ -1157,61 +1435,61 @@ export const CompactRow = memo(
1157
1435
  {infoToggle}
1158
1436
  </StyledActionSlot>
1159
1437
  : null}
1438
+ {inlineOptionActions.map((action, index) =>
1439
+ renderInjectedOptionAction(action, index, 'tiny')
1440
+ )}
1441
+ {injectedOptionActionMenuItems.length ?
1442
+ <StyledActionSlot
1443
+ className='options-readfirst-actions-slot'
1444
+ $width={26}
1445
+ // The read row opens the editor when clicked. Without this the tap
1446
+ // that opens this menu also expands the row, which unmounts the
1447
+ // menu — on touch that made the collapsed actions unreachable,
1448
+ // the exact problem the collapse exists to solve.
1449
+ onClick={(event: React.MouseEvent<HTMLElement>) => event.stopPropagation()}
1450
+ >
1451
+ <ReqoreDropdown
1452
+ className='options-injected-actions-menu'
1453
+ icon='MoreFill'
1454
+ flat
1455
+ minimal
1456
+ fixed
1457
+ size='tiny'
1458
+ tooltip='Field actions'
1459
+ items={injectedOptionActionMenuItems}
1460
+ />
1461
+ </StyledActionSlot>
1462
+ : null}
1463
+ {/* The revert affordance lives in the status-dot column (a changed field
1464
+ swaps its dot for the revert icon) so it sits at the same fixed x as
1465
+ the dot — out of the value's way instead of floating over it. */}
1466
+ <StyledActionSlot className='options-readfirst-statusdot-slot' $width={12}>
1467
+ {changed && !readOnly && !hidden ?
1468
+ <ReqoreButton
1469
+ className='options-readfirst-revert'
1470
+ size='tiny'
1471
+ flat
1472
+ minimal
1473
+ compact
1474
+ icon='HistoryLine'
1475
+ tooltip='Revert changes'
1476
+ onClick={(e: React.MouseEvent) => {
1477
+ e.stopPropagation();
1478
+ handleValueChange(
1479
+ optionName,
1480
+ originalValue.current?.[optionName]?.value,
1481
+ originalValue.current?.[optionName]?.type
1482
+ );
1483
+ }}
1484
+ />
1485
+ : dotColor ?
1486
+ <StyledStatusDot $color={dotColor} $ring={dotStatus !== 'set'} aria-hidden />
1487
+ : null}
1488
+ </StyledActionSlot>
1160
1489
  </StyledRowActions>
1161
1490
  </div>
1162
1491
  );
1163
1492
 
1164
- if (hashEntries.length) {
1165
- return (
1166
- <StyledColumn
1167
- key={optionName}
1168
- data-field={optionName}
1169
- className={`options-readfirst-hash-row${clusterBlockClass ? ' ' + clusterBlockClass : ''}`}
1170
- style={stripeStyle}
1171
- >
1172
- {row}
1173
- <StyledRowInset className='options-readfirst-inset'>
1174
- <ReqoreCollapsibleContent
1175
- maxCollapsedHeight={96}
1176
- buttonProps={{ className: 'options-readfirst-viewmore' }}
1177
- >
1178
- {/* The IDE workflow-orders renderer (ReqoreDataView): a nested,
1179
- type-coloured tree. Section summaries own their
1180
- expand/collapse clicks, but clicking a VALUE chip opens the
1181
- hash's editor. The Fields-menu "Show field types" toggle also
1182
- drives the per-scalar type chips here. Depth 2 = root + first
1183
- level open; deeper nests start collapsed so the preview stays
1184
- short before the fade's "Show more". */}
1185
- <div className='options-readfirst-structured'>
1186
- <StructuredDataView
1187
- value={optionField?.value}
1188
- collapsibleRoot={false}
1189
- showTypes={showFieldTypes}
1190
- defaultExpandDepth={2}
1191
- onItemClick={() => activate()}
1192
- />
1193
- </div>
1194
- </ReqoreCollapsibleContent>
1195
- </StyledRowInset>
1196
- {infoBlock}
1197
- </StyledColumn>
1198
- );
1199
- }
1200
-
1201
- if (infoBlock) {
1202
- return (
1203
- <StyledColumn
1204
- key={optionName}
1205
- data-field={optionName}
1206
- className={`options-readfirst-info-row${clusterBlockClass ? ' ' + clusterBlockClass : ''}`}
1207
- style={stripeStyle}
1208
- >
1209
- {row}
1210
- {infoBlock}
1211
- </StyledColumn>
1212
- );
1213
- }
1214
-
1215
1493
  return row;
1216
1494
  }
1217
1495
  );