@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
@@ -0,0 +1,211 @@
1
+ /** @vitest-environment jsdom */
2
+
3
+ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
4
+ import { MarkdownRichTextEditor, type MarkdownRichTextEditorProps, setEditorMarkdown } from '@vertesia/rich-text';
5
+ import { describe, expect, it, vi } from 'vitest';
6
+ import { I18nProvider } from '../i18n/index.js';
7
+ import {
8
+ VertesiaMarkdownComponentEditor,
9
+ VertesiaMarkdownDocumentEditor,
10
+ VertesiaMarkdownRichTextEditor,
11
+ vertesiaRichTextRenderers,
12
+ } from './VertesiaRichTextEditor.js';
13
+
14
+ describe('VertesiaMarkdownRichTextEditor', () => {
15
+ it('renders Markdown and emits serialized Markdown after an editor update', async () => {
16
+ const onChange = vi.fn();
17
+ let editor: Parameters<NonNullable<MarkdownRichTextEditorProps['onEditor']>>[0] = null;
18
+
19
+ render(
20
+ <VertesiaMarkdownRichTextEditor
21
+ value="# Original heading"
22
+ onChange={onChange}
23
+ onEditor={(nextEditor) => {
24
+ editor = nextEditor;
25
+ }}
26
+ />,
27
+ );
28
+
29
+ expect(await screen.findByRole('heading', { name: 'Original heading' })).not.toBeNull();
30
+ await waitFor(() => expect(editor).not.toBeNull());
31
+
32
+ act(() => {
33
+ if (editor) setEditorMarkdown(editor, '## Updated heading', true);
34
+ });
35
+
36
+ expect(await screen.findByRole('heading', { name: 'Updated heading' })).not.toBeNull();
37
+ expect(onChange).toHaveBeenLastCalledWith(expect.stringContaining('## Updated heading'));
38
+ });
39
+
40
+ it('provides adapters for each Vertesia-specific Markdown surface', () => {
41
+ expect(vertesiaRichTextRenderers.codeBlock).toBeTypeOf('function');
42
+ expect(vertesiaRichTextRenderers.image).toBeTypeOf('function');
43
+ expect(vertesiaRichTextRenderers.link).toBeTypeOf('function');
44
+ expect(vertesiaRichTextRenderers.opaqueBlock).toBeTypeOf('function');
45
+ });
46
+
47
+ it('keeps the editor instance when a consumer passes new renderer component identities', async () => {
48
+ const onEditor = vi.fn();
49
+ const view = render(<MarkdownRichTextEditor value="Paragraph" codeBlock={() => null} onEditor={onEditor} />);
50
+ await screen.findByText('Paragraph');
51
+ const initialEditor = onEditor.mock.calls.find(([value]) => value)?.[0];
52
+
53
+ view.rerender(<MarkdownRichTextEditor value="Paragraph" codeBlock={() => null} onEditor={onEditor} />);
54
+
55
+ expect(onEditor.mock.calls.filter(([value]) => value).at(-1)?.[0]).toBe(initialEditor);
56
+ expect(new Set(onEditor.mock.calls.flatMap(([value]) => (value ? [value] : [])))).toHaveLength(1);
57
+ });
58
+
59
+ it('defers external values while focused so the caret is not reset', async () => {
60
+ const view = render(<MarkdownRichTextEditor value="Original paragraph" />);
61
+ const textbox = await screen.findByRole('textbox');
62
+ fireEvent.focus(textbox);
63
+
64
+ view.rerender(<MarkdownRichTextEditor value="External paragraph" />);
65
+ expect(screen.getByText('Original paragraph')).not.toBeNull();
66
+ expect(screen.queryByText('External paragraph')).toBeNull();
67
+
68
+ fireEvent.blur(textbox);
69
+ expect(await screen.findByText('External paragraph')).not.toBeNull();
70
+ });
71
+
72
+ it('offers a formatting toolbar for both the compact component editor and the full-document editor', async () => {
73
+ const { rerender } = render(
74
+ <I18nProvider lng="en">
75
+ <VertesiaMarkdownComponentEditor value="Component text" />
76
+ </I18nProvider>,
77
+ );
78
+
79
+ expect(await screen.findByText('Component text')).not.toBeNull();
80
+ // The compact component editor now carries the same formatting toolbar as the document editor.
81
+ expect(await screen.findByRole('toolbar')).not.toBeNull();
82
+
83
+ rerender(
84
+ <I18nProvider lng="en">
85
+ <VertesiaMarkdownDocumentEditor value="# Full document" />
86
+ </I18nProvider>,
87
+ );
88
+
89
+ expect(await screen.findByRole('heading', { name: 'Full document' })).not.toBeNull();
90
+ expect(await screen.findByRole('toolbar')).not.toBeNull();
91
+ expect(screen.getByRole('textbox', { name: 'Markdown document editor' })).not.toBeNull();
92
+ });
93
+
94
+ it('puts the document hand-off in the editor toolbar and highlights it when edits are pending', async () => {
95
+ const onSendChangesToAgent = vi.fn();
96
+ const view = render(
97
+ <I18nProvider lng="en">
98
+ <VertesiaMarkdownDocumentEditor
99
+ value="Document text"
100
+ onSendChangesToAgent={onSendChangesToAgent}
101
+ hasUnsentChanges={false}
102
+ />
103
+ </I18nProvider>,
104
+ );
105
+
106
+ const inactiveButton = await screen.findByRole('button', { name: 'Send changes to agent' });
107
+ expect(screen.getByRole('toolbar').contains(inactiveButton)).toBe(true);
108
+ expect(inactiveButton).toHaveProperty('disabled', true);
109
+ expect(inactiveButton.className).not.toContain('bg-primary');
110
+
111
+ view.rerender(
112
+ <I18nProvider lng="en">
113
+ <VertesiaMarkdownDocumentEditor
114
+ value="Document text"
115
+ onSendChangesToAgent={onSendChangesToAgent}
116
+ hasUnsentChanges
117
+ />
118
+ </I18nProvider>,
119
+ );
120
+
121
+ const activeButton = screen.getByRole('button', { name: 'Send changes to agent' });
122
+ expect(activeButton).toHaveProperty('disabled', false);
123
+ expect(activeButton.className).toContain('bg-primary');
124
+ fireEvent.click(activeButton);
125
+ expect(onSendChangesToAgent).toHaveBeenCalledTimes(1);
126
+ });
127
+
128
+ it('sends selected-text comments while full-document content editing is locked', async () => {
129
+ const onSendComment = vi.fn();
130
+ let editor: Parameters<NonNullable<MarkdownRichTextEditorProps['onEditor']>>[0] = null;
131
+ render(
132
+ <I18nProvider lng="en">
133
+ <VertesiaMarkdownDocumentEditor
134
+ value="Locked paragraph."
135
+ editable={false}
136
+ onSendComment={onSendComment}
137
+ onEditor={(nextEditor) => {
138
+ editor = nextEditor;
139
+ }}
140
+ />
141
+ </I18nProvider>,
142
+ );
143
+
144
+ await waitFor(() => expect(editor).not.toBeNull());
145
+ act(() => {
146
+ editor?.commands.setTextSelection({ from: 1, to: 7 });
147
+ });
148
+ expect(screen.getByRole('button', { name: 'Bold' })).toHaveProperty('disabled', true);
149
+ const commentButton = await screen.findByRole('button', { name: 'Comment on selection' });
150
+ expect(screen.getByRole('toolbar').contains(commentButton)).toBe(false);
151
+ expect(commentButton.parentElement?.className).toContain('pointer-events-auto');
152
+ expect(commentButton.parentElement?.className).toContain('z-100');
153
+ fireEvent.click(commentButton);
154
+ expect(screen.getByRole('dialog', { name: 'Comment on selection' })).not.toBeNull();
155
+ fireEvent.change(screen.getByPlaceholderText('Describe how the agent should improve this section...'), {
156
+ target: { value: 'Make this claim measurable.' },
157
+ });
158
+ fireEvent.click(screen.getByRole('button', { name: 'Add comment' }));
159
+ fireEvent.click(screen.getByRole('button', { name: 'Send to agent' }));
160
+
161
+ await waitFor(() => expect(onSendComment).toHaveBeenCalledTimes(1));
162
+ expect(onSendComment).toHaveBeenCalledWith(expect.stringContaining('“Locked”'));
163
+ expect(onSendComment).toHaveBeenCalledWith(expect.stringContaining('Make this claim measurable.'));
164
+ });
165
+
166
+ it('opens structurally preserving normalized Markdown directly in rich-text mode', async () => {
167
+ const onChange = vi.fn();
168
+ render(
169
+ <I18nProvider lng="en">
170
+ <VertesiaMarkdownDocumentEditor value={'Setext heading\n=============='} onChange={onChange} />
171
+ </I18nProvider>,
172
+ );
173
+
174
+ expect(await screen.findByRole('heading', { name: 'Setext heading' })).not.toBeNull();
175
+ expect(screen.getByRole('textbox', { name: 'Markdown document editor' })).not.toBeNull();
176
+ expect(screen.queryByRole('heading', { name: 'Some Markdown formatting may change' })).toBeNull();
177
+ expect(onChange).not.toHaveBeenCalled();
178
+ });
179
+
180
+ it('lets the user preserve Markdown source when normalization is lossy', async () => {
181
+ const onChange = vi.fn();
182
+ render(
183
+ <I18nProvider lng="en">
184
+ <VertesiaMarkdownDocumentEditor value="#" onChange={onChange} />
185
+ </I18nProvider>,
186
+ );
187
+
188
+ expect(await screen.findByRole('heading', { name: 'Some Markdown formatting may change' })).not.toBeNull();
189
+ fireEvent.click(screen.getByRole('button', { name: 'Edit Markdown source' }));
190
+
191
+ const sourceEditor = screen.getByRole('textbox', { name: 'Markdown source editor' });
192
+ expect((sourceEditor as HTMLTextAreaElement).value).toBe('#');
193
+ fireEvent.change(sourceEditor, { target: { value: '##' } });
194
+ expect(onChange).toHaveBeenLastCalledWith('##');
195
+ });
196
+
197
+ it('lets the user accept a lossy conversion and continue in rich-text mode', async () => {
198
+ render(
199
+ <I18nProvider lng="en">
200
+ <VertesiaMarkdownDocumentEditor value="#" />
201
+ </I18nProvider>,
202
+ );
203
+
204
+ fireEvent.click(await screen.findByRole('button', { name: 'Continue with rich text' }));
205
+
206
+ expect(await screen.findByRole('textbox', { name: 'Markdown document editor' })).not.toBeNull();
207
+ });
208
+
209
+ // Table row/column actions live in the toolbar's Table context dropdown (only when the caret is
210
+ // in a table); list indent/outdent is handled by Tab / Shift-Tab rather than a toolbar control.
211
+ });
@@ -0,0 +1,241 @@
1
+ import {
2
+ type Editor,
3
+ getMarkdownCompatibility,
4
+ MarkdownComponentEditor,
5
+ type MarkdownComponentEditorProps,
6
+ MarkdownDocumentEditor,
7
+ type MarkdownDocumentEditorProps,
8
+ MarkdownRichTextEditor,
9
+ type MarkdownRichTextEditorProps,
10
+ type RichTextCodeBlockRendererProps,
11
+ type RichTextImageRendererProps,
12
+ type RichTextLinkRendererProps,
13
+ type RichTextOpaqueBlockRendererProps,
14
+ type RichTextRenderers,
15
+ } from '@vertesia/rich-text';
16
+ import { Button, cn, Modal, ModalBody, ModalFooter, ModalTitle, Textarea } from '@vertesia/ui/core';
17
+ import { useUITranslation } from '@vertesia/ui/i18n';
18
+ import { type ReactNode, useCallback, useState } from 'react';
19
+ import { CodeBlockHandlerProvider, useCodeBlockContext } from '../widgets/markdown/CodeBlockContext.js';
20
+ import { useCodeBlockRendererRegistry } from '../widgets/markdown/CodeBlockRendering.js';
21
+ import { createDefaultCodeBlockHandlers, ExpandCodeBlockHandler } from '../widgets/markdown/codeBlockHandlers.js';
22
+ import { MarkdownFigure } from '../widgets/markdown/MarkdownFigure.js';
23
+ import { MarkdownImage } from '../widgets/markdown/MarkdownImage.js';
24
+ import { MarkdownLink } from '../widgets/markdown/MarkdownLink.js';
25
+ import { MarkdownRenderer } from '../widgets/markdown/MarkdownRenderer.js';
26
+ import { EditorToolbar, SendChangesToAgentButton } from './EditorToolbar.js';
27
+
28
+ const defaultCodeBlockHandlers = createDefaultCodeBlockHandlers();
29
+
30
+ function VertesiaCodeBlockPreview({ code, language }: RichTextCodeBlockRendererProps) {
31
+ const registry = useCodeBlockRendererRegistry();
32
+ const CustomRenderer = language ? registry?.getComponent(language) : undefined;
33
+ const DefaultRenderer = language ? defaultCodeBlockHandlers[language] : undefined;
34
+ const Renderer = language?.startsWith('expand:') ? ExpandCodeBlockHandler : (CustomRenderer ?? DefaultRenderer);
35
+
36
+ if (!Renderer) return null;
37
+ return <Renderer code={code} language={language} />;
38
+ }
39
+
40
+ function VertesiaImagePreview({ src, alt, title }: RichTextImageRendererProps) {
41
+ const { artifactRunId } = useCodeBlockContext();
42
+ if (title) {
43
+ return <MarkdownFigure src={src} alt={alt} caption={title} artifactRunId={artifactRunId} />;
44
+ }
45
+ return <MarkdownImage src={src} alt={alt} artifactRunId={artifactRunId} />;
46
+ }
47
+
48
+ function VertesiaLinkPreview({ href, title, children }: RichTextLinkRendererProps) {
49
+ const { artifactRunId } = useCodeBlockContext();
50
+ return (
51
+ <MarkdownLink href={href} artifactRunId={artifactRunId}>
52
+ <span title={title}>{children}</span>
53
+ </MarkdownLink>
54
+ );
55
+ }
56
+
57
+ function VertesiaOpaqueBlockPreview({ raw }: RichTextOpaqueBlockRendererProps) {
58
+ const { artifactRunId } = useCodeBlockContext();
59
+ return <MarkdownRenderer artifactRunId={artifactRunId}>{raw}</MarkdownRenderer>;
60
+ }
61
+
62
+ export const vertesiaRichTextRenderers: RichTextRenderers = {
63
+ codeBlock: VertesiaCodeBlockPreview,
64
+ image: VertesiaImagePreview,
65
+ link: VertesiaLinkPreview,
66
+ opaqueBlock: VertesiaOpaqueBlockPreview,
67
+ };
68
+
69
+ interface VertesiaEditorProps {
70
+ artifactRunId?: string;
71
+ }
72
+
73
+ export interface VertesiaMarkdownRichTextEditorProps
74
+ extends Omit<MarkdownRichTextEditorProps, keyof RichTextRenderers>,
75
+ VertesiaEditorProps {}
76
+
77
+ export function VertesiaMarkdownRichTextEditor({ artifactRunId, ...props }: VertesiaMarkdownRichTextEditorProps) {
78
+ return (
79
+ <CodeBlockHandlerProvider artifactRunId={artifactRunId} MarkdownRenderer={MarkdownRenderer}>
80
+ <MarkdownRichTextEditor {...props} {...vertesiaRichTextRenderers} />
81
+ </CodeBlockHandlerProvider>
82
+ );
83
+ }
84
+
85
+ export interface VertesiaMarkdownComponentEditorProps
86
+ extends Omit<MarkdownComponentEditorProps, keyof RichTextRenderers>,
87
+ VertesiaEditorProps {}
88
+
89
+ export function VertesiaMarkdownComponentEditor({
90
+ artifactRunId,
91
+ className,
92
+ contentClassName,
93
+ editorClassName,
94
+ onEditor,
95
+ ...props
96
+ }: VertesiaMarkdownComponentEditorProps) {
97
+ const [editor, setEditor] = useState<Editor | null>(null);
98
+ const handleEditor = useCallback(
99
+ (next: Editor | null) => {
100
+ setEditor(next);
101
+ onEditor?.(next);
102
+ },
103
+ [onEditor],
104
+ );
105
+
106
+ return (
107
+ <CodeBlockHandlerProvider artifactRunId={artifactRunId} MarkdownRenderer={MarkdownRenderer}>
108
+ <div className={cn('overflow-hidden rounded-md border border-mixer-muted/30 bg-background', className)}>
109
+ <EditorToolbar editor={editor} editable={props.editable !== false} />
110
+ <MarkdownComponentEditor
111
+ {...props}
112
+ {...vertesiaRichTextRenderers}
113
+ onEditor={handleEditor}
114
+ contentClassName={cn('max-h-80 overflow-auto', contentClassName)}
115
+ editorClassName={cn('vprose prose-sm min-h-24 max-w-none px-3 py-2 outline-none', editorClassName)}
116
+ />
117
+ </div>
118
+ </CodeBlockHandlerProvider>
119
+ );
120
+ }
121
+
122
+ export interface VertesiaMarkdownDocumentEditorProps
123
+ extends Omit<MarkdownDocumentEditorProps, keyof RichTextRenderers>,
124
+ VertesiaEditorProps {
125
+ /** When set, the toolbar shows comment controls that queue and send a batch to the agent. */
126
+ onSendComment?: (message: string) => void | Promise<void>;
127
+ /** When set, the editor shows the document-level hand-off action in its own toolbar. */
128
+ onSendChangesToAgent?: () => void | Promise<void>;
129
+ hasUnsentChanges?: boolean;
130
+ isSendingChanges?: boolean;
131
+ sendChangesDisabled?: boolean;
132
+ /** Compact status rendered immediately beside the document hand-off action. */
133
+ toolbarStatus?: ReactNode;
134
+ }
135
+
136
+ export function VertesiaMarkdownDocumentEditor({
137
+ artifactRunId,
138
+ className,
139
+ contentClassName,
140
+ editorClassName,
141
+ onEditor,
142
+ onSendComment,
143
+ onSendChangesToAgent,
144
+ hasUnsentChanges = false,
145
+ isSendingChanges = false,
146
+ sendChangesDisabled = false,
147
+ toolbarStatus,
148
+ ...props
149
+ }: VertesiaMarkdownDocumentEditorProps) {
150
+ const { t } = useUITranslation();
151
+ const [editor, setEditor] = useState<Editor | null>(null);
152
+ const handleEditor = useCallback(
153
+ (next: Editor | null) => {
154
+ setEditor(next);
155
+ onEditor?.(next);
156
+ },
157
+ [onEditor],
158
+ );
159
+ const [editingMode, setEditingMode] = useState<'choose' | 'rich-text' | 'source'>(() =>
160
+ getMarkdownCompatibility(props.value) === 'lossy' ? 'choose' : 'rich-text',
161
+ );
162
+ const resolvedEditingMode = editingMode === 'choose' ? 'source' : editingMode;
163
+
164
+ return (
165
+ <CodeBlockHandlerProvider artifactRunId={artifactRunId} MarkdownRenderer={MarkdownRenderer}>
166
+ {resolvedEditingMode === 'rich-text' ? (
167
+ <div className={cn('flex h-full min-h-0 flex-col bg-background', className)}>
168
+ <EditorToolbar
169
+ editor={editor}
170
+ editable={props.editable !== false}
171
+ onSendComment={onSendComment}
172
+ onSendChangesToAgent={onSendChangesToAgent}
173
+ hasUnsentChanges={hasUnsentChanges}
174
+ isSendingChanges={isSendingChanges}
175
+ sendChangesDisabled={sendChangesDisabled}
176
+ toolbarStatus={toolbarStatus}
177
+ />
178
+ <MarkdownDocumentEditor
179
+ {...props}
180
+ {...vertesiaRichTextRenderers}
181
+ onEditor={handleEditor}
182
+ ariaLabel={props.ariaLabel ?? t('richText.documentEditor')}
183
+ className="flex min-h-0 flex-1 flex-col"
184
+ contentClassName={cn('min-h-0 flex-1 overflow-auto', contentClassName)}
185
+ editorClassName={cn(
186
+ 'vprose prose-sm mx-auto min-h-full w-full max-w-5xl px-6 py-5 outline-none',
187
+ editorClassName,
188
+ )}
189
+ />
190
+ </div>
191
+ ) : (
192
+ <div className={cn('flex h-full min-h-0 flex-col bg-background', className)}>
193
+ {onSendChangesToAgent ? (
194
+ <div className="flex min-h-10 shrink-0 items-center justify-end gap-1 border-b border-mixer-muted/25 px-1.5 py-1">
195
+ {toolbarStatus}
196
+ <SendChangesToAgentButton
197
+ onSend={onSendChangesToAgent}
198
+ hasUnsentChanges={hasUnsentChanges}
199
+ isSending={isSendingChanges}
200
+ disabled={sendChangesDisabled}
201
+ />
202
+ </div>
203
+ ) : null}
204
+ <Textarea
205
+ value={props.value}
206
+ onChange={(event) => props.onChange?.(event.target.value)}
207
+ onFocus={() => props.onFocusChange?.(true)}
208
+ onBlur={() => props.onFocusChange?.(false)}
209
+ disabled={props.editable === false}
210
+ aria-label={t('richText.markdownSourceEditor')}
211
+ className={cn(
212
+ 'vertesia-markdown-document-editor-content min-h-0 flex-1 resize-none rounded-none',
213
+ 'border-0 px-6 py-5 font-mono text-sm leading-6 outline-none focus-visible:ring-0',
214
+ contentClassName,
215
+ editorClassName,
216
+ )}
217
+ />
218
+ </div>
219
+ )}
220
+ <Modal
221
+ isOpen={editingMode === 'choose'}
222
+ onClose={() => setEditingMode('source')}
223
+ className="sm:max-w-lg"
224
+ disableCloseOnClickOutside
225
+ >
226
+ <ModalTitle>{t('richText.sourceCompatibilityWarningTitle')}</ModalTitle>
227
+ <ModalBody>
228
+ <p className="text-sm text-muted">{t('richText.sourceCompatibilityWarningDescription')}</p>
229
+ </ModalBody>
230
+ <ModalFooter align="right">
231
+ <Button variant="outline" onClick={() => setEditingMode('source')}>
232
+ {t('richText.editMarkdownSource')}
233
+ </Button>
234
+ <Button variant="secondary" onClick={() => setEditingMode('rich-text')}>
235
+ {t('richText.continueRichText')}
236
+ </Button>
237
+ </ModalFooter>
238
+ </Modal>
239
+ </CodeBlockHandlerProvider>
240
+ );
241
+ }
@@ -0,0 +1,9 @@
1
+ export {
2
+ VertesiaMarkdownComponentEditor,
3
+ type VertesiaMarkdownComponentEditorProps,
4
+ VertesiaMarkdownDocumentEditor,
5
+ type VertesiaMarkdownDocumentEditorProps,
6
+ VertesiaMarkdownRichTextEditor,
7
+ type VertesiaMarkdownRichTextEditorProps,
8
+ vertesiaRichTextRenderers,
9
+ } from './VertesiaRichTextEditor';
@@ -6,34 +6,34 @@ function createSession(onboardingProgress: () => Promise<Record<string, boolean>
6
6
  const client = {
7
7
  account: { onboardingProgress },
8
8
  } as unknown as VertesiaClient;
9
- const session = new UserSession(client);
10
- session.setSession = vi.fn();
11
- return session;
9
+ const setSession = vi.fn<(session: UserSession) => void>();
10
+ const session = new UserSession(client, setSession);
11
+ return { session, setSession };
12
12
  }
13
13
 
14
14
  describe('UserSession.fetchOnboardingStatus', () => {
15
15
  it('does not replace the session when an incomplete status is unchanged', async () => {
16
- const session = createSession(async () => ({ project_created: false }));
16
+ const { session, setSession } = createSession(async () => ({ project_created: false }));
17
17
  session.onboardingComplete = false;
18
18
 
19
19
  await expect(session.fetchOnboardingStatus()).resolves.toBe(false);
20
- expect(session.setSession).not.toHaveBeenCalled();
20
+ expect(setSession).not.toHaveBeenCalled();
21
21
  });
22
22
 
23
23
  it('updates the session and reports when onboarding becomes complete', async () => {
24
- const session = createSession(async () => ({ project_created: true }));
24
+ const { session, setSession } = createSession(async () => ({ project_created: true }));
25
25
  session.onboardingComplete = false;
26
26
 
27
27
  await expect(session.fetchOnboardingStatus()).resolves.toBe(true);
28
- expect(session.setSession).toHaveBeenCalledOnce();
29
- expect(vi.mocked(session.setSession!).mock.calls[0]?.[0]?.onboardingComplete).toBe(true);
28
+ expect(setSession).toHaveBeenCalledOnce();
29
+ expect(setSession.mock.calls[0]?.[0]?.onboardingComplete).toBe(true);
30
30
  });
31
31
 
32
32
  it('publishes the initial incomplete status without reporting completion', async () => {
33
- const session = createSession(async () => ({ project_created: false }));
33
+ const { session, setSession } = createSession(async () => ({ project_created: false }));
34
34
 
35
35
  await expect(session.fetchOnboardingStatus()).resolves.toBe(false);
36
- expect(session.setSession).toHaveBeenCalledOnce();
37
- expect(vi.mocked(session.setSession!).mock.calls[0]?.[0]?.onboardingComplete).toBe(false);
36
+ expect(setSession).toHaveBeenCalledOnce();
37
+ expect(setSession.mock.calls[0]?.[0]?.onboardingComplete).toBe(false);
38
38
  });
39
39
  });
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { parseAgentResourceHref } from './AgentResourceResolver';
3
+
4
+ describe('parseAgentResourceHref', () => {
5
+ it.each([
6
+ ['store:doc-1', 'document', 'doc-1'],
7
+ ['document://doc-1', 'document', 'doc-1'],
8
+ ['collection:col-1', 'collection', 'col-1'],
9
+ ['interaction:int-1', 'interaction', 'int-1'],
10
+ ['prompt:prompt-1', 'prompt', 'prompt-1'],
11
+ ['agent:agent-1', 'agent', 'agent-1'],
12
+ ['workflow:workflow-1', 'workflow', 'workflow-1'],
13
+ ['process:process-1', 'process', 'process-1'],
14
+ ['run:run-1', 'interaction_run', 'run-1'],
15
+ ] as const)('maps %s to a resource reference', (rawHref, type, id) => {
16
+ expect(parseAgentResourceHref(rawHref)).toEqual({ type, id });
17
+ });
18
+
19
+ it('returns undefined for standard, file, and empty resource URLs', () => {
20
+ expect(parseAgentResourceHref('https://example.com')).toBeUndefined();
21
+ expect(parseAgentResourceHref('artifact:report.pdf')).toBeUndefined();
22
+ expect(parseAgentResourceHref('store:')).toBeUndefined();
23
+ });
24
+ });
@@ -0,0 +1,70 @@
1
+ import type { AgentResourceAction, AgentResourceType } from '@vertesia/common';
2
+ import { createContext, useContext } from 'react';
3
+
4
+ /** Resource identity accepted by link resolvers from either structured tool metadata or Markdown. */
5
+ export interface AgentResourceLinkReference {
6
+ type: AgentResourceType;
7
+ id: string;
8
+ label?: string;
9
+ action?: AgentResourceAction;
10
+ revision_id?: string;
11
+ }
12
+
13
+ export type AgentResourceTarget =
14
+ | { kind: 'navigate'; href: string }
15
+ | { kind: 'activate'; onActivate: () => void }
16
+ | { kind: 'none' };
17
+
18
+ export interface ResourceResolveContext {
19
+ workflowRunId?: string;
20
+ source: 'structured' | 'markdown';
21
+ /** Original custom-scheme URL when the reference came from Markdown. */
22
+ rawHref?: string;
23
+ }
24
+
25
+ export type AgentResourceResolver = (
26
+ ref: AgentResourceLinkReference,
27
+ context: ResourceResolveContext,
28
+ ) => AgentResourceTarget;
29
+
30
+ const unresolvedAgentResourceResolver: AgentResourceResolver = () => ({ kind: 'none' });
31
+ const AgentResourceResolverContext = createContext<AgentResourceResolver>(unresolvedAgentResourceResolver);
32
+
33
+ /** Supplies host-specific navigation for reusable chat and Markdown components. */
34
+ export const AgentResourceResolverProvider = AgentResourceResolverContext.Provider;
35
+
36
+ export function useAgentResourceResolver(): AgentResourceResolver {
37
+ return useContext(AgentResourceResolverContext);
38
+ }
39
+
40
+ const RESOURCE_SCHEME_TYPES = {
41
+ store: 'document',
42
+ document: 'document',
43
+ collection: 'collection',
44
+ interaction: 'interaction',
45
+ prompt: 'prompt',
46
+ agent: 'agent',
47
+ workflow: 'workflow',
48
+ process: 'process',
49
+ run: 'interaction_run',
50
+ } as const satisfies Record<string, AgentResourceType>;
51
+
52
+ /** Converts a supported agent Markdown scheme into the same resource identity used by tool metadata. */
53
+ export function parseAgentResourceHref(rawHref: string): AgentResourceLinkReference | undefined {
54
+ const colonIndex = rawHref.indexOf(':');
55
+ if (colonIndex <= 0) return undefined;
56
+
57
+ const scheme = rawHref.slice(0, colonIndex);
58
+ if (!Object.hasOwn(RESOURCE_SCHEME_TYPES, scheme)) return undefined;
59
+
60
+ const id = rawHref
61
+ .slice(colonIndex + 1)
62
+ .trim()
63
+ .replace(/^\/+/, '');
64
+ if (!id) return undefined;
65
+
66
+ return {
67
+ type: RESOURCE_SCHEME_TYPES[scheme as keyof typeof RESOURCE_SCHEME_TYPES],
68
+ id,
69
+ };
70
+ }