@vertesia/ui 1.5.0-dev.20260722.120446Z → 1.5.0-dev.20260725.083715Z

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 (307) hide show
  1. package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  2. package/lib/core/components/shadcn/filters/filterBar.js +1 -1
  3. package/lib/core/components/shadcn/filters/filterBar.js.map +1 -1
  4. package/lib/core/components/shadcn/modal/dialog.d.ts +1 -1
  5. package/lib/core/components/shadcn/modal/dialog.d.ts.map +1 -1
  6. package/lib/core/components/shadcn/modal/dialog.js +2 -0
  7. package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
  8. package/lib/core/components/shadcn/tabs.d.ts +12 -1
  9. package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
  10. package/lib/core/components/shadcn/tabs.js +12 -4
  11. package/lib/core/components/shadcn/tabs.js.map +1 -1
  12. package/lib/features/agent/chat/AgentRightPanel.d.ts +5 -1
  13. package/lib/features/agent/chat/AgentRightPanel.d.ts.map +1 -1
  14. package/lib/features/agent/chat/AgentRightPanel.js +2 -2
  15. package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
  16. package/lib/features/agent/chat/ArtifactsTab.d.ts +5 -1
  17. package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
  18. package/lib/features/agent/chat/ArtifactsTab.js +48 -11
  19. package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
  20. package/lib/features/agent/chat/DocumentEditingActionCard.d.ts +6 -0
  21. package/lib/features/agent/chat/DocumentEditingActionCard.d.ts.map +1 -0
  22. package/lib/features/agent/chat/DocumentEditingActionCard.js +129 -0
  23. package/lib/features/agent/chat/DocumentEditingActionCard.js.map +1 -0
  24. package/lib/features/agent/chat/ModernAgentConversation.d.ts +19 -1
  25. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  26. package/lib/features/agent/chat/ModernAgentConversation.js +125 -27
  27. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  28. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts +45 -0
  29. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.d.ts.map +1 -0
  30. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js +40 -0
  31. package/lib/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.js.map +1 -0
  32. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +3 -1
  33. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  34. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +94 -49
  35. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  36. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts +2 -1
  37. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
  38. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +9 -5
  39. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
  40. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts +25 -0
  41. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.d.ts.map +1 -0
  42. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js +33 -0
  43. package/lib/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.js.map +1 -0
  44. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +3 -1
  45. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  46. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +22 -24
  47. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  48. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts +14 -0
  49. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.d.ts.map +1 -0
  50. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js +21 -0
  51. package/lib/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.js.map +1 -0
  52. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts +30 -0
  53. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.d.ts.map +1 -0
  54. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js +53 -0
  55. package/lib/features/agent/chat/ModernAgentOutput/ResourceChip.js.map +1 -0
  56. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.d.ts.map +1 -1
  57. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js +21 -2
  58. package/lib/features/agent/chat/ModernAgentOutput/ToolCallGroup.js.map +1 -1
  59. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts +6 -0
  60. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.d.ts.map +1 -0
  61. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js +29 -0
  62. package/lib/features/agent/chat/ModernAgentOutput/resourceLinks.js.map +1 -0
  63. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts +33 -0
  64. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.d.ts.map +1 -0
  65. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js +129 -0
  66. package/lib/features/agent/chat/ModernAgentOutput/resourceSummary.js.map +1 -0
  67. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts +7 -0
  68. package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
  69. package/lib/features/agent/chat/ModernAgentOutput/utils.js +11 -1
  70. package/lib/features/agent/chat/ModernAgentOutput/utils.js.map +1 -1
  71. package/lib/features/agent/chat/hooks/useAgentStream.d.ts +1 -1
  72. package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
  73. package/lib/features/agent/chat/hooks/useAgentStream.js +24 -9
  74. package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
  75. package/lib/features/agent/chat/index.d.ts +2 -1
  76. package/lib/features/agent/chat/index.d.ts.map +1 -1
  77. package/lib/features/agent/chat/index.js +3 -0
  78. package/lib/features/agent/chat/index.js.map +1 -1
  79. package/lib/features/store/objects/DocumentSearchResults.js +2 -2
  80. package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
  81. package/lib/features/store/objects/components/ContentOverview.d.ts +2 -1
  82. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  83. package/lib/features/store/objects/components/ContentOverview.js +86 -15
  84. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  85. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts +16 -0
  86. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.d.ts.map +1 -0
  87. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js +116 -0
  88. package/lib/features/store/objects/components/DocumentEditingConfigurationSelector.js.map +1 -0
  89. package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts +7 -0
  90. package/lib/features/store/objects/components/DocumentEditingLockStatus.d.ts.map +1 -0
  91. package/lib/features/store/objects/components/DocumentEditingLockStatus.js +13 -0
  92. package/lib/features/store/objects/components/DocumentEditingLockStatus.js.map +1 -0
  93. package/lib/features/store/objects/components/DocumentEditingPanel.d.ts +48 -0
  94. package/lib/features/store/objects/components/DocumentEditingPanel.d.ts.map +1 -0
  95. package/lib/features/store/objects/components/DocumentEditingPanel.js +720 -0
  96. package/lib/features/store/objects/components/DocumentEditingPanel.js.map +1 -0
  97. package/lib/features/store/objects/components/TextEditorPanel.d.ts.map +1 -1
  98. package/lib/features/store/objects/components/TextEditorPanel.js +22 -9
  99. package/lib/features/store/objects/components/TextEditorPanel.js.map +1 -1
  100. package/lib/features/store/objects/components/documentArtifactRefs.d.ts +26 -0
  101. package/lib/features/store/objects/components/documentArtifactRefs.d.ts.map +1 -0
  102. package/lib/features/store/objects/components/documentArtifactRefs.js +52 -0
  103. package/lib/features/store/objects/components/documentArtifactRefs.js.map +1 -0
  104. package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts +4 -0
  105. package/lib/features/store/objects/components/documentEditingAgentConfig.d.ts.map +1 -0
  106. package/lib/features/store/objects/components/documentEditingAgentConfig.js +40 -0
  107. package/lib/features/store/objects/components/documentEditingAgentConfig.js.map +1 -0
  108. package/lib/features/store/objects/components/documentEditingPrompts.d.ts +3 -0
  109. package/lib/features/store/objects/components/documentEditingPrompts.d.ts.map +1 -0
  110. package/lib/features/store/objects/components/documentEditingPrompts.js +13 -0
  111. package/lib/features/store/objects/components/documentEditingPrompts.js.map +1 -0
  112. package/lib/features/store/objects/components/documentEditingRun.d.ts +27 -0
  113. package/lib/features/store/objects/components/documentEditingRun.d.ts.map +1 -0
  114. package/lib/features/store/objects/components/documentEditingRun.js +126 -0
  115. package/lib/features/store/objects/components/documentEditingRun.js.map +1 -0
  116. package/lib/features/store/objects/components/documentEditingTarget.d.ts +20 -0
  117. package/lib/features/store/objects/components/documentEditingTarget.d.ts.map +1 -0
  118. package/lib/features/store/objects/components/documentEditingTarget.js +40 -0
  119. package/lib/features/store/objects/components/documentEditingTarget.js.map +1 -0
  120. package/lib/features/store/objects/components/index.d.ts +1 -0
  121. package/lib/features/store/objects/components/index.d.ts.map +1 -1
  122. package/lib/features/store/objects/components/index.js +1 -0
  123. package/lib/features/store/objects/components/index.js.map +1 -1
  124. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +5 -1
  125. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  126. package/lib/features/store/objects/components/useContentPanelHooks.js +53 -29
  127. package/lib/features/store/objects/components/useContentPanelHooks.js.map +1 -1
  128. package/lib/features/store/objects/components/useDownloadFile.d.ts +2 -1
  129. package/lib/features/store/objects/components/useDownloadFile.d.ts.map +1 -1
  130. package/lib/features/store/objects/components/useDownloadFile.js +3 -0
  131. package/lib/features/store/objects/components/useDownloadFile.js.map +1 -1
  132. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js +1 -1
  133. package/lib/features/store/objects/selection/actions/StartWorkflowButton.js.map +1 -1
  134. package/lib/i18n/locales/ar.json +118 -1
  135. package/lib/i18n/locales/de.json +118 -1
  136. package/lib/i18n/locales/en.json +118 -0
  137. package/lib/i18n/locales/es.json +118 -1
  138. package/lib/i18n/locales/fr.json +118 -1
  139. package/lib/i18n/locales/it.json +118 -1
  140. package/lib/i18n/locales/ja.json +118 -1
  141. package/lib/i18n/locales/ko.json +118 -1
  142. package/lib/i18n/locales/pt.json +118 -1
  143. package/lib/i18n/locales/ru.json +118 -1
  144. package/lib/i18n/locales/tr.json +118 -1
  145. package/lib/i18n/locales/zh-TW.json +118 -1
  146. package/lib/i18n/locales/zh.json +118 -1
  147. package/lib/rich-text/EditorToolbar.d.ts +37 -0
  148. package/lib/rich-text/EditorToolbar.d.ts.map +1 -0
  149. package/lib/rich-text/EditorToolbar.js +215 -0
  150. package/lib/rich-text/EditorToolbar.js.map +1 -0
  151. package/lib/rich-text/VertesiaRichTextEditor.d.ts +26 -0
  152. package/lib/rich-text/VertesiaRichTextEditor.d.ts.map +1 -0
  153. package/lib/rich-text/VertesiaRichTextEditor.js +67 -0
  154. package/lib/rich-text/VertesiaRichTextEditor.js.map +1 -0
  155. package/lib/rich-text/index.d.ts +2 -0
  156. package/lib/rich-text/index.d.ts.map +1 -0
  157. package/lib/rich-text/index.js +2 -0
  158. package/lib/rich-text/index.js.map +1 -0
  159. package/lib/vertesia-ui-core.js +2 -2
  160. package/lib/vertesia-ui-core.js.map +1 -1
  161. package/lib/vertesia-ui-features.js +35 -30
  162. package/lib/vertesia-ui-features.js.map +1 -1
  163. package/lib/vertesia-ui-i18n.js +14 -14
  164. package/lib/vertesia-ui-i18n.js.map +1 -1
  165. package/lib/vertesia-ui-rich-text.js +5 -0
  166. package/lib/vertesia-ui-rich-text.js.map +1 -0
  167. package/lib/vertesia-ui-widgets.js +25 -7
  168. package/lib/vertesia-ui-widgets.js.map +1 -1
  169. package/lib/widgets/markdown/AgentResourceResolver.d.ts +31 -0
  170. package/lib/widgets/markdown/AgentResourceResolver.d.ts.map +1 -0
  171. package/lib/widgets/markdown/AgentResourceResolver.js +39 -0
  172. package/lib/widgets/markdown/AgentResourceResolver.js.map +1 -0
  173. package/lib/widgets/markdown/ArtifactEditingSurface.d.ts +63 -0
  174. package/lib/widgets/markdown/ArtifactEditingSurface.d.ts.map +1 -0
  175. package/lib/widgets/markdown/ArtifactEditingSurface.js +582 -0
  176. package/lib/widgets/markdown/ArtifactEditingSurface.js.map +1 -0
  177. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts +69 -0
  178. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.d.ts.map +1 -0
  179. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js +374 -0
  180. package/lib/widgets/markdown/CollaborativeMarkdownRenderer.js.map +1 -0
  181. package/lib/widgets/markdown/MarkdownLink.d.ts +3 -1
  182. package/lib/widgets/markdown/MarkdownLink.d.ts.map +1 -1
  183. package/lib/widgets/markdown/MarkdownLink.js +41 -14
  184. package/lib/widgets/markdown/MarkdownLink.js.map +1 -1
  185. package/lib/widgets/markdown/MarkdownRenderer.d.ts +8 -1
  186. package/lib/widgets/markdown/MarkdownRenderer.d.ts.map +1 -1
  187. package/lib/widgets/markdown/MarkdownRenderer.js +20 -4
  188. package/lib/widgets/markdown/MarkdownRenderer.js.map +1 -1
  189. package/lib/widgets/markdown/index.d.ts +5 -1
  190. package/lib/widgets/markdown/index.d.ts.map +1 -1
  191. package/lib/widgets/markdown/index.js +5 -1
  192. package/lib/widgets/markdown/index.js.map +1 -1
  193. package/lib/widgets/markdown/normalizeCustomSchemeLinks.d.ts.map +1 -1
  194. package/lib/widgets/markdown/normalizeCustomSchemeLinks.js +14 -2
  195. package/lib/widgets/markdown/normalizeCustomSchemeLinks.js.map +1 -1
  196. package/lib/widgets/markdown/textDiff.d.ts +52 -0
  197. package/lib/widgets/markdown/textDiff.d.ts.map +1 -0
  198. package/lib/widgets/markdown/textDiff.js +362 -0
  199. package/lib/widgets/markdown/textDiff.js.map +1 -0
  200. package/lib/widgets/markdown/useArtifactContent.d.ts +5 -3
  201. package/lib/widgets/markdown/useArtifactContent.d.ts.map +1 -1
  202. package/lib/widgets/markdown/useArtifactContent.js +6 -3
  203. package/lib/widgets/markdown/useArtifactContent.js.map +1 -1
  204. package/lib/widgets/markdown/useResolvedUrl.d.ts +1 -5
  205. package/lib/widgets/markdown/useResolvedUrl.d.ts.map +1 -1
  206. package/lib/widgets/markdown/useResolvedUrl.js +19 -31
  207. package/lib/widgets/markdown/useResolvedUrl.js.map +1 -1
  208. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts +9 -0
  209. package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts.map +1 -1
  210. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js +20 -17
  211. package/lib/widgets/xml-viewer/hooks/useXMLViewer.js.map +1 -1
  212. package/package.json +13 -9
  213. package/src/__tests__/a11y.test.tsx +4 -1
  214. package/src/__tests__/axe-helper.ts +17 -7
  215. package/src/__tests__/setup.ts +25 -0
  216. package/src/core/components/shadcn/filters/filterBar.tsx +1 -0
  217. package/src/core/components/shadcn/modal/dialog.tsx +3 -1
  218. package/src/core/components/shadcn/tabs.test.tsx +65 -0
  219. package/src/core/components/shadcn/tabs.tsx +25 -3
  220. package/src/features/agent/chat/AgentRightPanel.tsx +16 -1
  221. package/src/features/agent/chat/ArtifactsTab.test.tsx +55 -0
  222. package/src/features/agent/chat/ArtifactsTab.tsx +203 -52
  223. package/src/features/agent/chat/DocumentEditingActionCard.test.tsx +105 -0
  224. package/src/features/agent/chat/DocumentEditingActionCard.tsx +210 -0
  225. package/src/features/agent/chat/ModernAgentConversation.test.tsx +237 -40
  226. package/src/features/agent/chat/ModernAgentConversation.tsx +247 -75
  227. package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.test.tsx +79 -0
  228. package/src/features/agent/chat/ModernAgentOutput/AgentMarkdownAnchor.tsx +94 -0
  229. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +257 -34
  230. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +217 -105
  231. package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +17 -0
  232. package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.test.tsx +44 -0
  233. package/src/features/agent/chat/ModernAgentOutput/CollapsibleAgentMarkdown.tsx +88 -0
  234. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +30 -46
  235. package/src/features/agent/chat/ModernAgentOutput/ResourceChangeSummary.tsx +40 -0
  236. package/src/features/agent/chat/ModernAgentOutput/ResourceChip.test.tsx +148 -0
  237. package/src/features/agent/chat/ModernAgentOutput/ResourceChip.tsx +141 -0
  238. package/src/features/agent/chat/ModernAgentOutput/ToolCallGroup.tsx +33 -1
  239. package/src/features/agent/chat/ModernAgentOutput/resourceLinks.tsx +43 -0
  240. package/src/features/agent/chat/ModernAgentOutput/resourceSummary.test.ts +229 -0
  241. package/src/features/agent/chat/ModernAgentOutput/resourceSummary.ts +152 -0
  242. package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +17 -0
  243. package/src/features/agent/chat/ModernAgentOutput/utils.ts +14 -1
  244. package/src/features/agent/chat/hooks/useAgentStream.test.tsx +188 -0
  245. package/src/features/agent/chat/hooks/useAgentStream.ts +28 -8
  246. package/src/features/agent/chat/index.ts +11 -0
  247. package/src/features/store/objects/DocumentSearchResults.tsx +2 -2
  248. package/src/features/store/objects/components/ContentOverview.tsx +216 -65
  249. package/src/features/store/objects/components/DocumentEditingConfigurationSelector.test.ts +34 -0
  250. package/src/features/store/objects/components/DocumentEditingConfigurationSelector.tsx +211 -0
  251. package/src/features/store/objects/components/DocumentEditingLockStatus.test.tsx +32 -0
  252. package/src/features/store/objects/components/DocumentEditingLockStatus.tsx +45 -0
  253. package/src/features/store/objects/components/DocumentEditingPanel.test.tsx +336 -0
  254. package/src/features/store/objects/components/DocumentEditingPanel.tsx +1236 -0
  255. package/src/features/store/objects/components/TextEditorPanel.test.tsx +48 -0
  256. package/src/features/store/objects/components/TextEditorPanel.tsx +41 -20
  257. package/src/features/store/objects/components/documentArtifactRefs.test.ts +65 -0
  258. package/src/features/store/objects/components/documentArtifactRefs.ts +72 -0
  259. package/src/features/store/objects/components/documentEditingAgentConfig.test.ts +28 -0
  260. package/src/features/store/objects/components/documentEditingAgentConfig.ts +41 -0
  261. package/src/features/store/objects/components/documentEditingPrompts.test.ts +26 -0
  262. package/src/features/store/objects/components/documentEditingPrompts.ts +12 -0
  263. package/src/features/store/objects/components/documentEditingRun.test.ts +176 -0
  264. package/src/features/store/objects/components/documentEditingRun.ts +184 -0
  265. package/src/features/store/objects/components/documentEditingTarget.test.ts +96 -0
  266. package/src/features/store/objects/components/documentEditingTarget.ts +63 -0
  267. package/src/features/store/objects/components/index.ts +1 -0
  268. package/src/features/store/objects/components/useContentPanelHooks.ts +52 -27
  269. package/src/features/store/objects/components/useDownloadFile.test.ts +20 -0
  270. package/src/features/store/objects/components/useDownloadFile.ts +5 -1
  271. package/src/features/store/objects/selection/actions/StartWorkflowButton.tsx +2 -2
  272. package/src/i18n/locales/ar.json +118 -1
  273. package/src/i18n/locales/de.json +118 -1
  274. package/src/i18n/locales/en.json +118 -0
  275. package/src/i18n/locales/es.json +118 -1
  276. package/src/i18n/locales/fr.json +118 -1
  277. package/src/i18n/locales/it.json +118 -1
  278. package/src/i18n/locales/ja.json +118 -1
  279. package/src/i18n/locales/ko.json +118 -1
  280. package/src/i18n/locales/pt.json +118 -1
  281. package/src/i18n/locales/ru.json +118 -1
  282. package/src/i18n/locales/tr.json +118 -1
  283. package/src/i18n/locales/zh-TW.json +118 -1
  284. package/src/i18n/locales/zh.json +118 -1
  285. package/src/rich-text/EditorToolbar.tsx +708 -0
  286. package/src/rich-text/VertesiaRichTextEditor.test.tsx +211 -0
  287. package/src/rich-text/VertesiaRichTextEditor.tsx +241 -0
  288. package/src/rich-text/index.ts +9 -0
  289. package/src/session/UserSession.test.ts +11 -11
  290. package/src/widgets/markdown/AgentResourceResolver.test.ts +24 -0
  291. package/src/widgets/markdown/AgentResourceResolver.tsx +70 -0
  292. package/src/widgets/markdown/ArtifactEditingSurface.test.tsx +537 -0
  293. package/src/widgets/markdown/ArtifactEditingSurface.tsx +878 -0
  294. package/src/widgets/markdown/CollaborativeMarkdownRenderer.test.tsx +484 -0
  295. package/src/widgets/markdown/CollaborativeMarkdownRenderer.tsx +825 -0
  296. package/src/widgets/markdown/MarkdownLink.test.tsx +122 -0
  297. package/src/widgets/markdown/MarkdownLink.tsx +102 -17
  298. package/src/widgets/markdown/MarkdownRenderer.tsx +30 -3
  299. package/src/widgets/markdown/index.ts +39 -1
  300. package/src/widgets/markdown/normalizeCustomSchemeLinks.ts +15 -2
  301. package/src/widgets/markdown/textDiff.test.ts +156 -0
  302. package/src/widgets/markdown/textDiff.ts +399 -0
  303. package/src/widgets/markdown/useArtifactContent.ts +9 -3
  304. package/src/widgets/markdown/useResolvedUrl.test.ts +29 -40
  305. package/src/widgets/markdown/useResolvedUrl.ts +33 -35
  306. package/src/widgets/xml-viewer/hooks/useXMLViewer.test.ts +26 -0
  307. package/src/widgets/xml-viewer/hooks/useXMLViewer.ts +23 -18
@@ -15,6 +15,17 @@ export interface Tab {
15
15
  is_allowed?: boolean;
16
16
  }
17
17
 
18
+ /**
19
+ * Guard consulted before switching to another tab. Return `false` to cancel the switch — the active
20
+ * tab, the URL hash and the browser history are all left untouched, so the guard can hold the user
21
+ * on a tab that has unsaved changes while a confirmation is shown.
22
+ *
23
+ * It is only called when the target tab differs from the active one, but it may be called more than
24
+ * once for a single attempted switch (a click reaches both the trigger and the Radix root), so any
25
+ * side effect it performs must be idempotent.
26
+ */
27
+ export type CanChangeTab = (nextTab: string, currentTab?: string) => boolean;
28
+
18
29
  const TabsContext = React.createContext<{
19
30
  size?: number;
20
31
  tabs?: Tab[];
@@ -23,6 +34,7 @@ const TabsContext = React.createContext<{
23
34
  responsive?: boolean;
24
35
  variant?: 'tabs' | 'pills';
25
36
  updateHash?: boolean;
37
+ canChangeTab?: CanChangeTab;
26
38
  }>({
27
39
  size: undefined,
28
40
  tabs: undefined,
@@ -45,6 +57,7 @@ interface TabsProps {
45
57
  responsive?: boolean;
46
58
  variant?: 'tabs' | 'pills';
47
59
  updateHash?: boolean;
60
+ canChangeTab?: CanChangeTab;
48
61
  }
49
62
 
50
63
  const Tabs = ({
@@ -59,6 +72,7 @@ const Tabs = ({
59
72
  responsive = false,
60
73
  variant = 'tabs',
61
74
  updateHash = true,
75
+ canChangeTab,
62
76
  }: TabsProps) => {
63
77
  // Filter tabs based on is_allowed (undefined or true means visible)
64
78
  const visibleTabs = React.useMemo(
@@ -121,6 +135,9 @@ const Tabs = ({
121
135
 
122
136
  const handleValueChange = React.useCallback(
123
137
  (newValue: string) => {
138
+ // Bail before touching the value or the history so a cancelled switch is a no-op.
139
+ if (newValue !== value && canChangeTab && !canChangeTab(newValue, value)) return;
140
+
124
141
  setValue(newValue);
125
142
 
126
143
  // Update the URL hash when tab changes (only if updateHash is true and not controlled by parent)
@@ -135,7 +152,7 @@ const Tabs = ({
135
152
  onTabChange(newValue);
136
153
  }
137
154
  },
138
- [current, onTabChange, updateHash],
155
+ [current, onTabChange, updateHash, canChangeTab, value],
139
156
  );
140
157
 
141
158
  const setTab = React.useCallback(
@@ -155,6 +172,7 @@ const Tabs = ({
155
172
  responsive: responsive,
156
173
  variant,
157
174
  updateHash,
175
+ canChangeTab,
158
176
  }}
159
177
  >
160
178
  <TabsPrimitive.Root
@@ -176,13 +194,17 @@ interface TabsBarProps {
176
194
  }
177
195
 
178
196
  const TabsBar = ({ className, sticky, direction }: TabsBarProps) => {
179
- const { tabs, size, current, setTab, responsive, variant, updateHash } = React.useContext(TabsContext);
197
+ const { tabs, size, current, setTab, responsive, variant, updateHash, canChangeTab } =
198
+ React.useContext(TabsContext);
180
199
 
181
200
  const fullWidth = size !== 0;
182
201
 
183
202
  const handleTabChange = React.useCallback(
184
203
  (tabName: string) => {
185
204
  if (!tabs || !setTab) return;
205
+ // Consulted here too: this path pushes the tab href before `setTab` reaches the guard in
206
+ // `Tabs`, so a cancelled switch would otherwise leave the URL pointing at the other tab.
207
+ if (tabName !== current && canChangeTab && !canChangeTab(tabName, current)) return;
186
208
 
187
209
  const tab = tabs.find((t) => t.name === tabName);
188
210
 
@@ -194,7 +216,7 @@ const TabsBar = ({ className, sticky, direction }: TabsBarProps) => {
194
216
 
195
217
  setTab(tabName);
196
218
  },
197
- [tabs, setTab, updateHash],
219
+ [tabs, setTab, updateHash, canChangeTab, current],
198
220
  );
199
221
 
200
222
  if (!tabs || !setTab) {
@@ -633,6 +633,10 @@ export interface AgentRightPanelProps {
633
633
  /** Show the Artifacts tab (opt-in, default false) */
634
634
  showArtifacts?: boolean;
635
635
  artifactRefreshKey?: number;
636
+ artifactRefreshDetails?: Record<string, unknown>;
637
+ selectedArtifactPath?: string | null;
638
+ onSelectedArtifactPathChange?: (path: string | null) => void;
639
+ onSendMessage?: (message: string, inputMetadata?: Record<string, unknown>) => void;
636
640
 
637
641
  // Panel control
638
642
  onClose: () => void;
@@ -672,6 +676,10 @@ function AgentRightPanelComponent({
672
676
  // Artifacts
673
677
  showArtifacts = false,
674
678
  artifactRefreshKey = 0,
679
+ artifactRefreshDetails,
680
+ selectedArtifactPath,
681
+ onSelectedArtifactPathChange,
682
+ onSendMessage,
675
683
 
676
684
  // Payload
677
685
  payloadContent,
@@ -811,7 +819,14 @@ function AgentRightPanelComponent({
811
819
  label: t('agent.artifacts'),
812
820
  content: withTabBoundary(
813
821
  t('agent.artifacts'),
814
- <ArtifactsTab runId={runId} refreshKey={artifactRefreshKey} />,
822
+ <ArtifactsTab
823
+ runId={runId}
824
+ refreshKey={artifactRefreshKey}
825
+ refreshDetails={artifactRefreshDetails}
826
+ selectedPath={selectedArtifactPath}
827
+ onSelectedPathChange={onSelectedArtifactPathChange}
828
+ onSendMessage={onSendMessage}
829
+ />,
815
830
  ),
816
831
  is_allowed: showArtifacts,
817
832
  },
@@ -0,0 +1,55 @@
1
+ /** @vitest-environment jsdom */
2
+
3
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
4
+ import type { ComponentProps } from 'react';
5
+ import { afterEach, describe, expect, it, vi } from 'vitest';
6
+ import { I18nProvider } from '../../../i18n/index.js';
7
+ import { ArtifactsTab } from './ArtifactsTab.js';
8
+
9
+ vi.mock('@vertesia/ui/session', () => ({
10
+ useUserSession: () => ({ client: {} }),
11
+ }));
12
+
13
+ vi.mock('@vertesia/ui/widgets', () => ({
14
+ ArtifactEditingSurface: ({ viewMode }: { viewMode?: 'components' | 'document' }) => (
15
+ <div data-testid="artifact-editing-surface" data-view-mode={viewMode} />
16
+ ),
17
+ }));
18
+
19
+ vi.mock('./hooks/useArtifacts.js', () => ({
20
+ useArtifacts: () => ({
21
+ tree: [],
22
+ flatFiles: [{ path: 'drafts/brief.md' }],
23
+ isLoading: false,
24
+ error: undefined,
25
+ refresh: vi.fn(),
26
+ }),
27
+ }));
28
+
29
+ afterEach(cleanup);
30
+
31
+ function renderArtifactEditor(props?: Partial<ComponentProps<typeof ArtifactsTab>>) {
32
+ return render(
33
+ <I18nProvider lng="en">
34
+ <ArtifactsTab runId="run-1" selectedPath="drafts/brief.md" onSendMessage={vi.fn()} {...props} />
35
+ </I18nProvider>,
36
+ );
37
+ }
38
+
39
+ describe('ArtifactsTab', () => {
40
+ it('opens editable artifacts in block mode by default', () => {
41
+ renderArtifactEditor();
42
+
43
+ expect(screen.getByTestId('artifact-editing-surface').getAttribute('data-view-mode')).toBe('components');
44
+ expect(screen.getByRole('button', { name: 'Block mode' }).getAttribute('aria-pressed')).toBe('true');
45
+ });
46
+
47
+ it('allows switching from block mode to the full editor', () => {
48
+ renderArtifactEditor();
49
+
50
+ fireEvent.click(screen.getByRole('button', { name: 'Edit mode' }));
51
+
52
+ expect(screen.getByTestId('artifact-editing-surface').getAttribute('data-view-mode')).toBe('document');
53
+ expect(screen.getByRole('button', { name: 'Edit mode' }).getAttribute('aria-pressed')).toBe('true');
54
+ });
55
+ });
@@ -1,17 +1,21 @@
1
- import { Button, Center, ErrorBox, Input, Modal, ModalBody, ModalTitle, Switch } from '@vertesia/ui/core';
1
+ import { Button, Center, cn, ErrorBox, Input, Modal, ModalBody, ModalTitle, Switch, VTooltip } from '@vertesia/ui/core';
2
2
  import { useUITranslation } from '@vertesia/ui/i18n';
3
3
  import { useUserSession } from '@vertesia/ui/session';
4
+ import { ArtifactEditingSurface, type ArtifactSaveStatus, type MarkdownEditingAction } from '@vertesia/ui/widgets';
4
5
  import {
6
+ ArrowLeftIcon,
7
+ CheckIcon,
5
8
  ChevronDownIcon,
6
9
  ChevronRightIcon,
7
10
  DownloadIcon,
8
- EyeIcon,
9
11
  FileIcon,
10
12
  FolderIcon,
11
13
  FolderOpenIcon,
12
14
  Loader2Icon,
13
15
  PackageIcon,
16
+ PenLineIcon,
14
17
  RefreshCwIcon,
18
+ Rows3Icon,
15
19
  } from 'lucide-react';
16
20
  import React, { useCallback, useId, useMemo, useState } from 'react';
17
21
  import type { UniversalDocumentSource } from '../../document-viewer/UniversalDocumentViewer.js';
@@ -22,6 +26,14 @@ const UniversalDocumentViewer = React.lazy(async () => {
22
26
  return { default: module.UniversalDocumentViewer };
23
27
  });
24
28
 
29
+ /** Artifact file extensions that open in the Markdown editor rather than downloading. */
30
+ const EDITABLE_ARTIFACT_EXTENSIONS = new Set(['md', 'markdown', 'mdx', 'txt']);
31
+
32
+ function isEditableArtifact(path: string): boolean {
33
+ const extension = path.split('.').pop()?.toLowerCase();
34
+ return extension !== undefined && EDITABLE_ARTIFACT_EXTENSIONS.has(extension);
35
+ }
36
+
25
37
  // ---------------------------------------------------------------------------
26
38
  // Tree node component
27
39
  // ---------------------------------------------------------------------------
@@ -30,26 +42,18 @@ interface TreeNodeProps {
30
42
  node: ArtifactTreeNode;
31
43
  depth: number;
32
44
  runId: string;
33
- onPreview: (relativePath: string) => void;
45
+ onOpen: (relativePath: string) => void;
34
46
  onDownload: (relativePath: string) => void;
35
47
  downloadingPath: string | null;
36
48
  forceExpanded?: boolean;
37
49
  }
38
-
39
50
  //** Convert a raw directory segment (e.g. "out_files") into a readable label ("Out Files"). */
40
51
  function formatDirectoryLabel(name: string): string {
41
52
  return name.replace(/[_-]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
42
53
  }
43
54
 
44
- function TreeNode({
45
- node,
46
- depth,
47
- runId,
48
- onPreview,
49
- onDownload,
50
- downloadingPath,
51
- forceExpanded = false,
52
- }: TreeNodeProps) {
55
+ function TreeNode({ node, depth, runId, onOpen, onDownload, downloadingPath, forceExpanded = false }: TreeNodeProps) {
56
+ const { t } = useUITranslation();
53
57
  const [expanded, setExpanded] = useState(false);
54
58
  const isExpanded = forceExpanded || expanded;
55
59
 
@@ -84,7 +88,7 @@ function TreeNode({
84
88
  node={child}
85
89
  depth={depth + 1}
86
90
  runId={runId}
87
- onPreview={onPreview}
91
+ onOpen={onOpen}
88
92
  onDownload={onDownload}
89
93
  downloadingPath={downloadingPath}
90
94
  forceExpanded={forceExpanded}
@@ -97,45 +101,33 @@ function TreeNode({
97
101
  const isDownloading = downloadingPath === node.path;
98
102
 
99
103
  return (
100
- <div
101
- className="group flex min-w-0 items-center gap-1.5 rounded text-sm hover:bg-muted/30"
102
- style={{ paddingInlineStart: `${depth * 14 + 4}px` }}
103
- >
104
+ <div className="flex min-w-0 items-center gap-1">
104
105
  <Button
105
106
  variant="unstyled"
106
- className="flex min-w-0 flex-1 items-center justify-start gap-1.5 px-1 py-1 text-start"
107
- onClick={() => onPreview(node.path)}
107
+ className="flex min-w-0 flex-1 items-center justify-start gap-1.5 rounded px-1 py-1 text-start text-sm hover:bg-muted/30"
108
+ style={{ paddingInlineStart: `${depth * 14 + 4}px` }}
109
+ onClick={() => onOpen(node.path)}
108
110
  title={node.path}
109
111
  >
110
112
  <span className="size-3.5 shrink-0" />
111
113
  <FileIcon className="size-4 shrink-0 text-muted" />
112
114
  <span className="min-w-0 truncate">{node.name}</span>
113
115
  </Button>
114
- <div className="flex shrink-0 items-center pe-1 opacity-0 transition-opacity group-hover:opacity-100 focus-within:opacity-100">
115
- <Button
116
- variant="ghost"
117
- size="xs"
118
- className="h-6 w-6 p-0"
119
- onClick={() => onPreview(node.path)}
120
- aria-label="Preview artifact"
121
- >
122
- <EyeIcon className="size-3.5" />
123
- </Button>
124
- <Button
125
- variant="ghost"
126
- size="xs"
127
- className="h-6 w-6 p-0"
128
- onClick={() => onDownload(node.path)}
129
- disabled={isDownloading}
130
- aria-label="Download artifact"
131
- >
132
- {isDownloading ? (
133
- <Loader2Icon className="size-3.5 animate-spin text-info" />
134
- ) : (
135
- <DownloadIcon className="size-3.5" />
136
- )}
137
- </Button>
138
- </div>
116
+ <Button
117
+ variant="ghost"
118
+ size="sm"
119
+ className="h-7 w-7 shrink-0 p-0"
120
+ onClick={() => onDownload(node.path)}
121
+ disabled={isDownloading}
122
+ aria-label={t('agent.download')}
123
+ title={t('agent.download')}
124
+ >
125
+ {isDownloading ? (
126
+ <Loader2Icon className="size-3.5 animate-spin text-info" />
127
+ ) : (
128
+ <DownloadIcon className="size-3.5" />
129
+ )}
130
+ </Button>
139
131
  </div>
140
132
  );
141
133
  }
@@ -184,8 +176,8 @@ function downloadUrl(url: string, filename: string) {
184
176
  }
185
177
 
186
178
  function getArtifactContentType(path: string): string | undefined {
187
- const ext = path.split('.').pop()?.toLowerCase();
188
- switch (ext) {
179
+ const extension = path.split('.').pop()?.toLowerCase();
180
+ switch (extension) {
189
181
  case 'css':
190
182
  return 'text/css';
191
183
  case 'gif':
@@ -226,6 +218,128 @@ function getArtifactContentType(path: string): string | undefined {
226
218
  interface ArtifactsTabProps {
227
219
  runId?: string;
228
220
  refreshKey?: number;
221
+ refreshDetails?: Record<string, unknown>;
222
+ selectedPath?: string | null;
223
+ onSelectedPathChange?: (path: string | null) => void;
224
+ onSendMessage?: (message: string, inputMetadata?: Record<string, unknown>) => void;
225
+ }
226
+
227
+ interface ArtifactMarkdownEditorProps {
228
+ runId: string;
229
+ path: string;
230
+ onBack: () => void;
231
+ onDownload: (path: string) => void;
232
+ refreshKey?: number;
233
+ refreshDetails?: Record<string, unknown>;
234
+ onSendMessage?: (message: string, inputMetadata?: Record<string, unknown>) => void;
235
+ }
236
+
237
+ function ArtifactMarkdownEditor({
238
+ runId,
239
+ path,
240
+ onBack,
241
+ onDownload,
242
+ refreshKey,
243
+ refreshDetails,
244
+ onSendMessage,
245
+ }: ArtifactMarkdownEditorProps) {
246
+ const { t } = useUITranslation();
247
+ const [viewMode, setViewMode] = useState<'components' | 'document'>('components');
248
+ const [saveStatus, setSaveStatus] = useState<ArtifactSaveStatus>('idle');
249
+
250
+ const handleAction = (action: MarkdownEditingAction) => {
251
+ if (!onSendMessage) return;
252
+ const blockType = action.anchor.block_type.replaceAll('_', ' ');
253
+ const displayMessage = action.comment?.trim() || t('agent.editedSelectionMessage', { blockType });
254
+ onSendMessage(displayMessage, { editing_action: action });
255
+ };
256
+
257
+ return (
258
+ <div className="flex h-full min-h-0 flex-col">
259
+ <div className="flex shrink-0 items-center gap-1 border-b border-border/60 px-2 py-1">
260
+ <Button
261
+ variant="ghost"
262
+ size="sm"
263
+ className="size-8 shrink-0 rounded-md p-0"
264
+ onClick={onBack}
265
+ aria-label={t('agent.backToArtifacts')}
266
+ title={t('agent.backToArtifacts')}
267
+ >
268
+ <ArrowLeftIcon className="size-4 cn-rtl-flip" />
269
+ </Button>
270
+ <div className="flex min-w-0 flex-1 items-center gap-1.5">
271
+ <span className="truncate text-sm font-medium" title={path}>
272
+ {path.split('/').pop()}
273
+ </span>
274
+ {saveStatus === 'saving' ? (
275
+ <VTooltip description={t('agent.savingWorkingCopy')} asChild>
276
+ <Loader2Icon
277
+ className="size-3.5 shrink-0 animate-spin text-muted"
278
+ aria-label={t('agent.savingWorkingCopy')}
279
+ />
280
+ </VTooltip>
281
+ ) : saveStatus === 'saved' ? (
282
+ <VTooltip description={t('agent.savedToWorkingCopy')} asChild>
283
+ <CheckIcon
284
+ className="size-3.5 shrink-0 text-success"
285
+ aria-label={t('agent.savedToWorkingCopy')}
286
+ />
287
+ </VTooltip>
288
+ ) : null}
289
+ </div>
290
+ <div className="flex items-center gap-0.5">
291
+ <Button
292
+ variant={viewMode === 'components' ? 'primary' : 'ghost'}
293
+ size="sm"
294
+ className="size-8 shrink-0 rounded-md p-0"
295
+ onClick={() => setViewMode('components')}
296
+ aria-label={t('agent.blockMode')}
297
+ aria-pressed={viewMode === 'components'}
298
+ title={t('agent.blockMode')}
299
+ >
300
+ <Rows3Icon className="size-4" />
301
+ </Button>
302
+ <Button
303
+ variant={viewMode === 'document' ? 'primary' : 'ghost'}
304
+ size="sm"
305
+ className="size-8 shrink-0 rounded-md p-0"
306
+ onClick={() => setViewMode('document')}
307
+ aria-label={t('agent.editMode')}
308
+ aria-pressed={viewMode === 'document'}
309
+ title={t('agent.editMode')}
310
+ >
311
+ <PenLineIcon className="size-4" />
312
+ </Button>
313
+ </div>
314
+ <Button
315
+ variant="ghost"
316
+ size="sm"
317
+ className="size-8 shrink-0 rounded-md p-0"
318
+ onClick={() => onDownload(path)}
319
+ aria-label={t('agent.download')}
320
+ title={t('agent.download')}
321
+ >
322
+ <DownloadIcon className="size-4" />
323
+ </Button>
324
+ </div>
325
+ <ArtifactEditingSurface
326
+ runId={runId}
327
+ path={path}
328
+ viewMode={viewMode}
329
+ refreshKey={refreshKey}
330
+ refreshDetails={refreshDetails}
331
+ readOnly={!onSendMessage}
332
+ onAction={handleAction}
333
+ onSaveStatusChange={setSaveStatus}
334
+ onSendMessage={onSendMessage ? (message) => onSendMessage(message) : undefined}
335
+ className={cn(
336
+ 'relative min-h-0 flex-1',
337
+ // Edit mode self-scrolls; block mode needs the surface to scroll and pad its blocks.
338
+ viewMode === 'document' ? 'overflow-hidden' : 'overflow-y-auto px-6 py-5',
339
+ )}
340
+ />
341
+ </div>
342
+ );
229
343
  }
230
344
 
231
345
  function ArtifactEmptyState({
@@ -246,7 +360,14 @@ function ArtifactEmptyState({
246
360
  );
247
361
  }
248
362
 
249
- function ArtifactsTabComponent({ runId, refreshKey = 0 }: ArtifactsTabProps) {
363
+ function ArtifactsTabComponent({
364
+ runId,
365
+ refreshKey = 0,
366
+ refreshDetails,
367
+ selectedPath,
368
+ onSelectedPathChange,
369
+ onSendMessage,
370
+ }: ArtifactsTabProps) {
250
371
  const { t } = useUITranslation();
251
372
  const { client } = useUserSession();
252
373
  const [showSystem, setShowSystem] = useState(false);
@@ -259,6 +380,15 @@ function ArtifactsTabComponent({ runId, refreshKey = 0 }: ArtifactsTabProps) {
259
380
  const [downloadingPath, setDownloadingPath] = useState<string | null>(null);
260
381
  const [previewPath, setPreviewPath] = useState<string | null>(null);
261
382
  const [filterValue, setFilterValue] = useState('');
383
+ const [internalSelectedPath, setInternalSelectedPath] = useState<string | null>(null);
384
+ const activeSelectedPath = selectedPath === undefined ? internalSelectedPath : selectedPath;
385
+ const setSelectedPath = useCallback(
386
+ (path: string | null) => {
387
+ if (selectedPath === undefined) setInternalSelectedPath(path);
388
+ onSelectedPathChange?.(path);
389
+ },
390
+ [onSelectedPathChange, selectedPath],
391
+ );
262
392
  const normalizedFilterValue = filterValue.trim();
263
393
  const filteredTree = useMemo(() => filterArtifactTree(tree, normalizedFilterValue), [tree, normalizedFilterValue]);
264
394
  const visibleFileCount = useMemo(() => countTreeFiles(filteredTree), [filteredTree]);
@@ -280,9 +410,16 @@ function ArtifactsTabComponent({ runId, refreshKey = 0 }: ArtifactsTabProps) {
280
410
  [client, runId],
281
411
  );
282
412
 
283
- const handlePreview = useCallback((relativePath: string) => {
284
- setPreviewPath(relativePath);
285
- }, []);
413
+ const handleOpen = useCallback(
414
+ (relativePath: string) => {
415
+ if (isEditableArtifact(relativePath)) {
416
+ setSelectedPath(relativePath);
417
+ return;
418
+ }
419
+ setPreviewPath(relativePath);
420
+ },
421
+ [setSelectedPath],
422
+ );
286
423
 
287
424
  const previewSource: UniversalDocumentSource | null =
288
425
  runId && previewPath
@@ -345,6 +482,20 @@ function ArtifactsTabComponent({ runId, refreshKey = 0 }: ArtifactsTabProps) {
345
482
  );
346
483
  }
347
484
 
485
+ if (activeSelectedPath) {
486
+ return (
487
+ <ArtifactMarkdownEditor
488
+ runId={runId}
489
+ path={activeSelectedPath}
490
+ refreshKey={refreshKey}
491
+ refreshDetails={refreshDetails}
492
+ onBack={() => setSelectedPath(null)}
493
+ onDownload={(path) => void handleDownload(path)}
494
+ onSendMessage={onSendMessage}
495
+ />
496
+ );
497
+ }
498
+
348
499
  return (
349
500
  <div className="flex flex-col h-full">
350
501
  {/* Top bar */}
@@ -396,7 +547,7 @@ function ArtifactsTabComponent({ runId, refreshKey = 0 }: ArtifactsTabProps) {
396
547
  node={node}
397
548
  depth={0}
398
549
  runId={runId}
399
- onPreview={handlePreview}
550
+ onOpen={handleOpen}
400
551
  onDownload={handleDownload}
401
552
  downloadingPath={downloadingPath}
402
553
  forceExpanded={!!normalizedFilterValue}
@@ -0,0 +1,105 @@
1
+ /** @vitest-environment jsdom */
2
+
3
+ import { cleanup, render, screen } from '@testing-library/react';
4
+ import { afterEach, describe, expect, it } from 'vitest';
5
+ import { I18nProvider } from '../../../i18n/index.js';
6
+ import { DocumentEditingActionCard, parseMarkdownEditingAction } from './DocumentEditingActionCard.js';
7
+
8
+ afterEach(cleanup);
9
+
10
+ const action = {
11
+ operation_id: 'operation-1',
12
+ resource: { kind: 'store_document', document_id: 'document-1', name: 'Launch plan' },
13
+ action: 'comment',
14
+ anchor: {
15
+ block_id: 'list_item:10:24',
16
+ block_type: 'list_item',
17
+ exact_text: '- Preserve unrelated sections.',
18
+ },
19
+ comment: 'Add one concrete example.',
20
+ };
21
+
22
+ describe('DocumentEditingActionCard', () => {
23
+ it('parses and renders a compact comment card', () => {
24
+ const parsed = parseMarkdownEditingAction(action);
25
+ expect(parsed).toBeDefined();
26
+ if (!parsed) throw new Error('Expected a valid editing action');
27
+
28
+ render(
29
+ <I18nProvider lng="en">
30
+ <DocumentEditingActionCard action={parsed} />
31
+ </I18nProvider>,
32
+ );
33
+
34
+ expect(screen.getByText('Add one concrete example.')).not.toBeNull();
35
+ expect(screen.getByText('list item')).not.toBeNull();
36
+ expect(screen.getByText('Launch plan')).not.toBeNull();
37
+ expect(screen.queryByText('operation-1')).toBeNull();
38
+ });
39
+
40
+ it('renders an applied edit as a saved notification', () => {
41
+ const parsed = parseMarkdownEditingAction({
42
+ operation_id: 'operation-2',
43
+ resource: { kind: 'store_document', document_id: 'document-1', name: 'Launch plan' },
44
+ action: 'edit',
45
+ anchor: {
46
+ block_id: 'list:10:24',
47
+ block_type: 'list',
48
+ exact_text: '- Preserve unrelated sections.',
49
+ },
50
+ user_change: {
51
+ before: '- Preserve unrelated sections.',
52
+ after: '- Preserve unrelated sections.\n- And be fun',
53
+ },
54
+ applied: true,
55
+ updated_document_id: 'document-2',
56
+ });
57
+ expect(parsed).toBeDefined();
58
+ if (!parsed) throw new Error('Expected a valid editing action');
59
+ expect(parsed.applied).toBe(true);
60
+ expect(parsed.updated_document_id).toBe('document-2');
61
+
62
+ render(
63
+ <I18nProvider lng="en">
64
+ <DocumentEditingActionCard action={parsed} />
65
+ </I18nProvider>,
66
+ );
67
+
68
+ expect(screen.getByText('Edit applied')).not.toBeNull();
69
+ expect(screen.getByText('list')).not.toBeNull();
70
+ expect(screen.getByText('Launch plan')).not.toBeNull();
71
+ });
72
+
73
+ it('highlights the change as an inline diff', () => {
74
+ const parsed = parseMarkdownEditingAction({
75
+ operation_id: 'operation-3',
76
+ resource: { kind: 'store_document', document_id: 'document-1', name: 'Launch plan' },
77
+ action: 'edit',
78
+ anchor: {
79
+ block_id: 'paragraph:30:40',
80
+ block_type: 'paragraph',
81
+ exact_text: 'Keep the tone light.',
82
+ },
83
+ user_change: {
84
+ before: 'Keep the tone light.',
85
+ after: 'Keep the tone light. Session edit A.',
86
+ },
87
+ });
88
+ if (!parsed) throw new Error('Expected a valid editing action');
89
+
90
+ const { container } = render(
91
+ <I18nProvider lng="en">
92
+ <DocumentEditingActionCard action={parsed} />
93
+ </I18nProvider>,
94
+ );
95
+
96
+ const inserted = container.querySelector('ins');
97
+ expect(inserted?.textContent).toBe(' Session edit A.');
98
+ expect(container.querySelector('del')).toBeNull();
99
+ expect(screen.getByText('Keep the tone light.')).not.toBeNull();
100
+ });
101
+
102
+ it('rejects malformed editing metadata', () => {
103
+ expect(parseMarkdownEditingAction({ action: 'comment' })).toBeUndefined();
104
+ });
105
+ });