@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,122 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { type AgentResourceResolver, AgentResourceResolverProvider } from './AgentResourceResolver.js';
6
+ import type { MarkdownLinkProps } from './MarkdownLink.js';
7
+ import { MarkdownLink } from './MarkdownLink.js';
8
+
9
+ vi.mock('./useResolvedUrl.js', async (importOriginal) => {
10
+ const actual = await importOriginal<typeof import('./useResolvedUrl.js')>();
11
+ return {
12
+ ...actual,
13
+ useResolvedUrl: () => ({
14
+ url: 'https://example.test/artifact',
15
+ isLoading: false,
16
+ error: undefined,
17
+ scheme: 'artifact' as const,
18
+ retry: vi.fn(),
19
+ }),
20
+ };
21
+ });
22
+
23
+ afterEach(cleanup);
24
+
25
+ describe('MarkdownLink artifact navigation', () => {
26
+ it('opens a Markdown artifact in the host viewer', () => {
27
+ const onArtifactOpen = vi.fn();
28
+ render(
29
+ <MarkdownLink href="artifact:files/report.md" artifactRunId="run-1" onArtifactOpen={onArtifactOpen}>
30
+ Report
31
+ </MarkdownLink>,
32
+ );
33
+
34
+ fireEvent.click(screen.getByRole('link', { name: 'Report' }));
35
+
36
+ expect(onArtifactOpen).toHaveBeenCalledWith('files/report.md');
37
+ });
38
+
39
+ it('keeps modified clicks and non-Markdown artifacts as downloads', () => {
40
+ const onArtifactOpen = vi.fn();
41
+ const view = render(
42
+ <MarkdownLink href="artifact:files/report.md" artifactRunId="run-1" onArtifactOpen={onArtifactOpen}>
43
+ Report
44
+ </MarkdownLink>,
45
+ );
46
+
47
+ fireEvent.click(screen.getByRole('link', { name: 'Report' }), { metaKey: true });
48
+ expect(onArtifactOpen).not.toHaveBeenCalled();
49
+
50
+ view.rerender(
51
+ <MarkdownLink href="artifact:files/report.pdf" artifactRunId="run-1" onArtifactOpen={onArtifactOpen}>
52
+ Report PDF
53
+ </MarkdownLink>,
54
+ );
55
+ fireEvent.click(screen.getByRole('link', { name: 'Report PDF' }));
56
+
57
+ expect(onArtifactOpen).not.toHaveBeenCalled();
58
+ });
59
+ });
60
+
61
+ describe('MarkdownLink agent resources', () => {
62
+ const ExistingLink = ({ href, children, className }: MarkdownLinkProps) => (
63
+ <a href={href} className={className}>
64
+ {children}
65
+ </a>
66
+ );
67
+
68
+ it('routes a custom resource scheme through the host resolver', () => {
69
+ const resolver = vi.fn<AgentResourceResolver>(() => ({ kind: 'navigate', href: '/custom/interactions/int-1' }));
70
+ render(
71
+ <AgentResourceResolverProvider value={resolver}>
72
+ <MarkdownLink href="interaction:int-1" artifactRunId="run-1">
73
+ Interaction
74
+ </MarkdownLink>
75
+ </AgentResourceResolverProvider>,
76
+ );
77
+
78
+ expect(screen.getByRole('link', { name: 'Interaction' }).getAttribute('href')).toBe(
79
+ '/custom/interactions/int-1',
80
+ );
81
+ expect(resolver).toHaveBeenCalledWith(
82
+ { type: 'interaction', id: 'int-1' },
83
+ { workflowRunId: 'run-1', source: 'markdown', rawHref: 'interaction:int-1' },
84
+ );
85
+ });
86
+
87
+ it('supports a host activation callback', () => {
88
+ const onActivate = vi.fn();
89
+ render(
90
+ <AgentResourceResolverProvider value={() => ({ kind: 'activate', onActivate })}>
91
+ <MarkdownLink href="store:doc-1">Document</MarkdownLink>
92
+ </AgentResourceResolverProvider>,
93
+ );
94
+
95
+ fireEvent.click(screen.getByRole('button', { name: 'Document' }));
96
+ expect(onActivate).toHaveBeenCalledOnce();
97
+ });
98
+
99
+ it('renders plain text when no host resolver is configured', () => {
100
+ render(<MarkdownLink href="collection:col-1">Collection</MarkdownLink>);
101
+
102
+ expect(screen.queryByRole('link')).toBeNull();
103
+ expect(screen.getByText('Collection').tagName).toBe('SPAN');
104
+ });
105
+
106
+ it('preserves the class name when delegating standard and resolved resource links', () => {
107
+ const resolver: AgentResourceResolver = () => ({ kind: 'navigate', href: '/custom/interactions/int-1' });
108
+ render(
109
+ <AgentResourceResolverProvider value={resolver}>
110
+ <MarkdownLink href="https://example.com" className="standard-link" ExistingLink={ExistingLink}>
111
+ Standard
112
+ </MarkdownLink>
113
+ <MarkdownLink href="interaction:int-1" className="resource-link" ExistingLink={ExistingLink}>
114
+ Resource
115
+ </MarkdownLink>
116
+ </AgentResourceResolverProvider>,
117
+ );
118
+
119
+ expect(screen.getByRole('link', { name: 'Standard' }).className).toBe('standard-link');
120
+ expect(screen.getByRole('link', { name: 'Resource' }).className).toBe('resource-link');
121
+ });
122
+ });
@@ -1,7 +1,9 @@
1
+ import { Button } from '@vertesia/ui/core';
1
2
  import type { Element } from 'hast';
2
3
  import type React from 'react';
4
+ import { parseAgentResourceHref, useAgentResourceResolver } from './AgentResourceResolver';
3
5
  import { CodeBlockPlaceholder } from './CodeBlockPlaceholder';
4
- import { mapSchemeToRoute, parseUrlScheme, useResolvedUrl } from './useResolvedUrl';
6
+ import { parseUrlScheme, useResolvedUrl } from './useResolvedUrl';
5
7
 
6
8
  export interface MarkdownLinkProps {
7
9
  node?: Element;
@@ -9,6 +11,8 @@ export interface MarkdownLinkProps {
9
11
  children?: React.ReactNode;
10
12
  className?: string;
11
13
  artifactRunId?: string;
14
+ /** Open a Markdown artifact in the host artifact viewer instead of downloading it. */
15
+ onArtifactOpen?: (path: string) => void;
12
16
  /** Existing link component to delegate to for standard URLs */
13
17
  ExistingLink?: React.ComponentType<MarkdownLinkProps>;
14
18
  }
@@ -23,51 +27,108 @@ export function MarkdownLink({
23
27
  children,
24
28
  className,
25
29
  artifactRunId,
30
+ onArtifactOpen,
26
31
  ExistingLink,
27
32
  ...rest
28
33
  }: MarkdownLinkProps) {
29
34
  const rawHref = href || '';
35
+ const resource = parseAgentResourceHref(rawHref);
36
+ if (resource) {
37
+ return (
38
+ <AgentResourceMarkdownLink
39
+ resource={resource}
40
+ rawHref={rawHref}
41
+ artifactRunId={artifactRunId}
42
+ className={className}
43
+ ExistingLink={ExistingLink}
44
+ rest={rest}
45
+ >
46
+ {children}
47
+ </AgentResourceMarkdownLink>
48
+ );
49
+ }
50
+
30
51
  const { scheme, path } = parseUrlScheme(rawHref);
31
52
 
32
- // For schemes that map directly to routes (store:, document://, collection:)
33
- const mappedRoute = mapSchemeToRoute(scheme, path);
34
- if (mappedRoute) {
53
+ // For standard URLs, delegate to existing component or render directly
54
+ if (scheme === 'standard') {
35
55
  if (typeof ExistingLink === 'function') {
36
56
  return (
37
- <ExistingLink node={node} href={mappedRoute} {...rest}>
57
+ <ExistingLink node={node} href={href} className={className} {...rest}>
38
58
  {children}
39
59
  </ExistingLink>
40
60
  );
41
61
  }
42
62
  return (
43
- <a href={mappedRoute} {...rest} className={className} target="_blank" rel="noopener noreferrer">
63
+ <a href={href} {...rest} className={className} target="_blank" rel="noopener noreferrer">
44
64
  {children}
45
65
  </a>
46
66
  );
47
67
  }
48
68
 
49
- // For standard URLs, delegate to existing component or render directly
50
- if (scheme === 'standard') {
69
+ // For artifact: and image: URLs, use the resolver
70
+ return (
71
+ <ResolvedLink
72
+ rawHref={rawHref}
73
+ artifactRunId={artifactRunId}
74
+ artifactPath={scheme === 'artifact' ? path : undefined}
75
+ onArtifactOpen={onArtifactOpen}
76
+ className={className}
77
+ rest={rest}
78
+ >
79
+ {children}
80
+ </ResolvedLink>
81
+ );
82
+ }
83
+
84
+ function AgentResourceMarkdownLink({
85
+ resource,
86
+ rawHref,
87
+ artifactRunId,
88
+ className,
89
+ children,
90
+ ExistingLink,
91
+ rest,
92
+ }: {
93
+ resource: NonNullable<ReturnType<typeof parseAgentResourceHref>>;
94
+ rawHref: string;
95
+ artifactRunId?: string;
96
+ className?: string;
97
+ children?: React.ReactNode;
98
+ ExistingLink?: React.ComponentType<MarkdownLinkProps>;
99
+ rest: Record<string, unknown>;
100
+ }) {
101
+ const resolve = useAgentResourceResolver();
102
+ const target = resolve(resource, {
103
+ workflowRunId: artifactRunId,
104
+ source: 'markdown',
105
+ rawHref,
106
+ });
107
+
108
+ if (target.kind === 'navigate') {
51
109
  if (typeof ExistingLink === 'function') {
52
110
  return (
53
- <ExistingLink node={node} href={href} {...rest}>
111
+ <ExistingLink href={target.href} className={className} {...rest}>
54
112
  {children}
55
113
  </ExistingLink>
56
114
  );
57
115
  }
58
116
  return (
59
- <a href={href} {...rest} className={className} target="_blank" rel="noopener noreferrer">
117
+ <a href={target.href} {...rest} className={className} target="_blank" rel="noopener noreferrer">
60
118
  {children}
61
119
  </a>
62
120
  );
63
121
  }
64
122
 
65
- // For artifact: and image: URLs, use the resolver
66
- return (
67
- <ResolvedLink rawHref={rawHref} artifactRunId={artifactRunId} className={className} rest={rest}>
68
- {children}
69
- </ResolvedLink>
70
- );
123
+ if (target.kind === 'activate') {
124
+ return (
125
+ <Button variant="link" size="none" className={className} onClick={target.onActivate}>
126
+ {children}
127
+ </Button>
128
+ );
129
+ }
130
+
131
+ return <span className={className}>{children}</span>;
71
132
  }
72
133
 
73
134
  /**
@@ -76,12 +137,16 @@ export function MarkdownLink({
76
137
  function ResolvedLink({
77
138
  rawHref,
78
139
  artifactRunId,
140
+ artifactPath,
141
+ onArtifactOpen,
79
142
  className,
80
143
  children,
81
144
  rest,
82
145
  }: {
83
146
  rawHref: string;
84
147
  artifactRunId?: string;
148
+ artifactPath?: string;
149
+ onArtifactOpen?: (path: string) => void;
85
150
  className?: string;
86
151
  children?: React.ReactNode;
87
152
  rest: Record<string, unknown>;
@@ -109,7 +174,27 @@ function ResolvedLink({
109
174
  }
110
175
 
111
176
  return (
112
- <a href={url || '#'} {...rest} className={className} target="_blank" rel="noopener noreferrer">
177
+ <a
178
+ href={url || '#'}
179
+ {...rest}
180
+ className={className}
181
+ target="_blank"
182
+ rel="noopener noreferrer"
183
+ onClick={(event) => {
184
+ if (
185
+ artifactPath &&
186
+ /\.md$/i.test(artifactPath) &&
187
+ onArtifactOpen &&
188
+ !event.metaKey &&
189
+ !event.ctrlKey &&
190
+ !event.shiftKey &&
191
+ event.button === 0
192
+ ) {
193
+ event.preventDefault();
194
+ onArtifactOpen(artifactPath);
195
+ }
196
+ }}
197
+ >
113
198
  {children}
114
199
  </a>
115
200
  );
@@ -26,7 +26,20 @@ type MarkdownParent = { children?: unknown[] };
26
26
  type RemarkPluginList = NonNullable<React.ComponentProps<typeof Markdown>['remarkPlugins']>;
27
27
 
28
28
  // Custom URL schemes that we handle in our components
29
- const ALLOWED_CUSTOM_SCHEMES = ['artifact:', 'image:', 'store:', 'document:', 'document://', 'collection:'];
29
+ const ALLOWED_CUSTOM_SCHEMES = [
30
+ 'artifact:',
31
+ 'image:',
32
+ 'store:',
33
+ 'document:',
34
+ 'document://',
35
+ 'collection:',
36
+ 'interaction:',
37
+ 'prompt:',
38
+ 'agent:',
39
+ 'workflow:',
40
+ 'process:',
41
+ 'run:',
42
+ ];
30
43
 
31
44
  /**
32
45
  * Custom URL transform that allows our custom schemes while using
@@ -66,6 +79,8 @@ export interface MarkdownRendererProps {
66
79
  * Optional workflow run id used to resolve shorthand artifact paths (e.g. artifact:out/result.csv)
67
80
  */
68
81
  artifactRunId?: string;
82
+ /** Open Markdown artifact links in a host-provided viewer instead of downloading them. */
83
+ onArtifactOpen?: (path: string) => void;
69
84
  /** Additional className for the markdown wrapper */
70
85
  className?: string;
71
86
  /** Additional className for code blocks */
@@ -80,6 +95,11 @@ export interface MarkdownRendererProps {
80
95
  onProposalSelect?: (optionId: string) => void;
81
96
  /** Callback when user submits free-form response to proposal */
82
97
  onProposalSubmit?: (response: string) => void;
98
+ /**
99
+ * Keep parser source positions aligned with `children` by skipping source-changing
100
+ * normalization. Editing surfaces use this when node offsets are part of an anchor.
101
+ */
102
+ preserveSourcePositions?: boolean;
83
103
  }
84
104
 
85
105
  // Create default handlers once, outside component
@@ -91,6 +111,7 @@ export function MarkdownRenderer({
91
111
  remarkPlugins = [],
92
112
  removeComments = true,
93
113
  artifactRunId,
114
+ onArtifactOpen,
94
115
  className,
95
116
  codeClassName,
96
117
  inlineCodeClassName,
@@ -98,11 +119,15 @@ export function MarkdownRenderer({
98
119
  imageClassName,
99
120
  onProposalSelect,
100
121
  onProposalSubmit,
122
+ preserveSourcePositions = false,
101
123
  }: MarkdownRendererProps) {
102
124
  const codeBlockRegistry = useCodeBlockRendererRegistry();
103
125
  const normalizedMarkdown = React.useMemo(
104
- () => normalizeDirectives(normalizeCustomSchemeLinks(preprocessMathDelimiters(children))),
105
- [children],
126
+ () =>
127
+ preserveSourcePositions
128
+ ? children
129
+ : normalizeDirectives(normalizeCustomSchemeLinks(preprocessMathDelimiters(children))),
130
+ [children, preserveSourcePositions],
106
131
  );
107
132
 
108
133
  // Remark plugins (markdown parsing)
@@ -218,6 +243,7 @@ export function MarkdownRenderer({
218
243
  href={href}
219
244
  className={linkClassName}
220
245
  artifactRunId={artifactRunId}
246
+ onArtifactOpen={onArtifactOpen}
221
247
  ExistingLink={
222
248
  typeof ExistingLink === 'function'
223
249
  ? (ExistingLink as React.ComponentType<MarkdownLinkProps>)
@@ -272,6 +298,7 @@ export function MarkdownRenderer({
272
298
  }, [
273
299
  components,
274
300
  artifactRunId,
301
+ onArtifactOpen,
275
302
  codeBlockRegistry,
276
303
  codeClassName,
277
304
  inlineCodeClassName,
@@ -1,8 +1,25 @@
1
+ export {
2
+ type AgentResourceLinkReference,
3
+ type AgentResourceResolver,
4
+ AgentResourceResolverProvider,
5
+ type AgentResourceTarget,
6
+ parseAgentResourceHref,
7
+ type ResourceResolveContext,
8
+ useAgentResourceResolver,
9
+ } from './AgentResourceResolver';
1
10
  export {
2
11
  ArtifactContentRenderer,
3
12
  type ArtifactContentRendererProps,
4
13
  type ExpandRenderType,
5
14
  } from './ArtifactContentRenderer';
15
+ export {
16
+ ArtifactEditingSurface,
17
+ type ArtifactEditingSurfaceDocumentEdit,
18
+ type ArtifactEditingSurfaceProps,
19
+ type ArtifactSaveStatus,
20
+ applyArtifactRefreshChanges,
21
+ isArtifactRefreshEvent,
22
+ } from './ArtifactEditingSurface';
6
23
  export {
7
24
  type CodeBlockHandlerContext,
8
25
  CodeBlockHandlerProvider,
@@ -20,6 +37,17 @@ export {
20
37
  useCodeBlockComponent,
21
38
  useCodeBlockRendererRegistry,
22
39
  } from './CodeBlockRendering';
40
+ export {
41
+ applyMarkdownEditingChange,
42
+ CollaborativeMarkdownRenderer,
43
+ type CollaborativeMarkdownRendererProps,
44
+ createMarkdownBlockAnchor,
45
+ formatMarkdownEditingAction,
46
+ type MarkdownBlockAnchor,
47
+ type MarkdownBlockType,
48
+ type MarkdownEditingAction,
49
+ type MarkdownEditingResource,
50
+ } from './CollaborativeMarkdownRenderer';
23
51
  export {
24
52
  ChartCodeBlockHandler,
25
53
  createDefaultCodeBlockHandlers,
@@ -33,13 +61,23 @@ export { MarkdownImage, type MarkdownImageProps } from './MarkdownImage';
33
61
  export { MarkdownLink, type MarkdownLinkProps } from './MarkdownLink';
34
62
  export { MarkdownRenderer, type MarkdownRendererProps } from './MarkdownRenderer';
35
63
  export { MermaidDiagram } from './MermaidDiagram';
64
+ export {
65
+ createUnifiedLineDiff,
66
+ diffTextSegments,
67
+ diffWordSegments,
68
+ getTextLineChangeRegions,
69
+ rebaseTextChanges,
70
+ type TextDiffSegment,
71
+ type TextLineChangeRegion,
72
+ type TextRebaseResult,
73
+ type UnifiedLineDiffOptions,
74
+ } from './textDiff';
36
75
  export {
37
76
  type ArtifactContentState,
38
77
  type UseArtifactContentOptions,
39
78
  useArtifactContent,
40
79
  } from './useArtifactContent';
41
80
  export {
42
- mapSchemeToRoute,
43
81
  parseUrlScheme,
44
82
  type ResolvedUrlState,
45
83
  type UrlScheme,
@@ -1,9 +1,22 @@
1
- const CUSTOM_SCHEME_PREFIXES = ['artifact:', 'image:', 'store:', 'document://', 'collection:'];
1
+ const CUSTOM_SCHEME_PREFIXES = [
2
+ 'artifact:',
3
+ 'image:',
4
+ 'store:',
5
+ 'document://',
6
+ 'collection:',
7
+ 'interaction:',
8
+ 'prompt:',
9
+ 'agent:',
10
+ 'workflow:',
11
+ 'process:',
12
+ 'run:',
13
+ ];
2
14
 
3
15
  // Excludes `[` from the link-text and destination classes: a long run of `[` (or
4
16
  // `[](image:` groups) with no closing delimiter would otherwise be re-scanned at every
5
17
  // start position, giving polynomial backtracking (CodeQL js/polynomial-redos).
6
- const CUSTOM_LINK_REGEX = /(!?\[[^[\]\n]*\]\()((?:artifact:|image:|store:|document:\/\/|collection:)[^)[\n]+)(\))/g;
18
+ const CUSTOM_LINK_REGEX =
19
+ /(!?\[[^[\]\n]*\]\()((?:artifact:|image:|store:|document:\/\/|collection:|interaction:|prompt:|agent:|workflow:|process:|run:)[^)[\n]+)(\))/g;
7
20
  const INLINE_CODE_REGEX = /`[^`\n]*`/g;
8
21
  const FENCED_CODE_BLOCK_REGEX = /(^|\n)(`{3,}|~{3,})[^\n]*\n[\s\S]*?\n\2(?=\n|$)/g;
9
22
  const LINK_TITLE_SUFFIX_REGEX = /\s+("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\([^)]*\))\s*$/;
@@ -0,0 +1,156 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ createUnifiedLineDiff,
4
+ diffTextSegments,
5
+ diffWordSegments,
6
+ getTextLineChangeRegions,
7
+ rebaseTextChanges,
8
+ type TextDiffSegment,
9
+ } from './textDiff.js';
10
+
11
+ function joinByTypes(segments: TextDiffSegment[], types: TextDiffSegment['type'][]): string {
12
+ return segments
13
+ .filter((segment) => types.includes(segment.type))
14
+ .map((segment) => segment.text)
15
+ .join('');
16
+ }
17
+
18
+ describe('diffWordSegments', () => {
19
+ it('returns a single equal segment for identical texts', () => {
20
+ expect(diffWordSegments('same text', 'same text')).toEqual([{ type: 'equal', text: 'same text' }]);
21
+ });
22
+
23
+ it('marks an appended sentence as added', () => {
24
+ const before = 'Keep this paragraph unchanged.';
25
+ const after = 'Keep this paragraph unchanged. Session edit A.';
26
+
27
+ expect(diffWordSegments(before, after)).toEqual([
28
+ { type: 'equal', text: 'Keep this paragraph unchanged.' },
29
+ { type: 'added', text: ' Session edit A.' },
30
+ ]);
31
+ });
32
+
33
+ it('marks a replaced word with a removal and an addition', () => {
34
+ const segments = diffWordSegments('Keep the tone light.', 'Keep the tone formal.');
35
+
36
+ expect(segments).toEqual([
37
+ { type: 'equal', text: 'Keep the tone ' },
38
+ { type: 'removed', text: 'light.' },
39
+ { type: 'added', text: 'formal.' },
40
+ ]);
41
+ });
42
+
43
+ it('reconstructs both sides from the segments across multiline edits', () => {
44
+ const before = '- Preserve all sections.\n- Keep the tone light.\n- Ship it.';
45
+ const after = '- Preserve all sections.\n- Keep the tone light and clear.\n- Log every revision.\n- Ship it.';
46
+ const segments = diffWordSegments(before, after);
47
+
48
+ expect(joinByTypes(segments, ['equal', 'removed'])).toBe(before);
49
+ expect(joinByTypes(segments, ['equal', 'added'])).toBe(after);
50
+ });
51
+
52
+ it('handles a full replacement', () => {
53
+ expect(diffWordSegments('old', 'new')).toEqual([
54
+ { type: 'removed', text: 'old' },
55
+ { type: 'added', text: 'new' },
56
+ ]);
57
+ });
58
+
59
+ it('handles empty sides', () => {
60
+ expect(diffWordSegments('', 'added')).toEqual([{ type: 'added', text: 'added' }]);
61
+ expect(diffWordSegments('removed', '')).toEqual([{ type: 'removed', text: 'removed' }]);
62
+ expect(diffWordSegments('', '')).toEqual([]);
63
+ });
64
+ });
65
+
66
+ describe('diffTextSegments', () => {
67
+ it('keeps unchanged lines aligned across a document-scale changed middle', () => {
68
+ const preserved = Array.from({ length: 220 }, (_, index) => `Preserved line ${index}.`).join('\n');
69
+ const before = `# Release notes\nOpening before.\n${preserved}\nClosing before.`;
70
+ const after = `# Release notes\nOpening after.\n${preserved}\nClosing after.`;
71
+
72
+ const segments = diffTextSegments(before, after);
73
+
74
+ expect(joinByTypes(segments, ['equal', 'removed'])).toBe(before);
75
+ expect(joinByTypes(segments, ['equal', 'added'])).toBe(after);
76
+ expect(
77
+ segments.some((segment) => segment.type === 'equal' && segment.text.includes('Preserved line 100.')),
78
+ ).toBe(true);
79
+ });
80
+
81
+ it('returns distinct line regions separated by unchanged content', () => {
82
+ expect(getTextLineChangeRegions('One\nTwo\nThree', 'One\nChanged\nThree\nFour')).toEqual([
83
+ { startLine: 1, endLine: 1 },
84
+ { startLine: 3, endLine: 3 },
85
+ ]);
86
+ });
87
+ });
88
+
89
+ describe('createUnifiedLineDiff', () => {
90
+ const BEFORE = ['# Title', '', 'First paragraph.', '', '- one', '- two', '- three', '', 'Closing line.'].join('\n');
91
+
92
+ it('returns undefined for identical texts', () => {
93
+ expect(createUnifiedLineDiff(BEFORE, BEFORE)).toBeUndefined();
94
+ });
95
+
96
+ it('produces a hunk with context and correct markers for a line change', () => {
97
+ const after = BEFORE.replace('- two', '- two (updated)');
98
+ const diff = createUnifiedLineDiff(BEFORE, after, { context: 1 });
99
+ expect(diff).toBe(['@@ -5,3 +5,3 @@', ' - one', '-- two', '+- two (updated)', ' - three'].join('\n'));
100
+ });
101
+
102
+ it('merges nearby changes into one hunk and separates distant ones', () => {
103
+ const after = BEFORE.replace('# Title', '# New Title').replace('Closing line.', 'Closing line!');
104
+ const diff = createUnifiedLineDiff(BEFORE, after, { context: 1 });
105
+ const headers = diff?.split('\n').filter((line) => line.startsWith('@@'));
106
+ expect(headers).toHaveLength(2);
107
+ });
108
+
109
+ it('handles pure insertions and deletions', () => {
110
+ const inserted = createUnifiedLineDiff('a\nb', 'a\nnew\nb', { context: 0 });
111
+ expect(inserted).toBe('@@ -2,0 +2,1 @@\n+new');
112
+ const deleted = createUnifiedLineDiff('a\nold\nb', 'a\nb', { context: 0 });
113
+ expect(deleted).toBe('@@ -2,1 +2,0 @@\n-old');
114
+ });
115
+
116
+ it('returns undefined when the diff exceeds maxChars', () => {
117
+ const after = BEFORE.replace('First paragraph.', 'Rewritten paragraph.');
118
+ expect(createUnifiedLineDiff(BEFORE, after, { maxChars: 10 })).toBeUndefined();
119
+ });
120
+ });
121
+
122
+ describe('rebaseTextChanges', () => {
123
+ const BASE = ['# Brief', '', 'Overview.', '', '## Goals', '- First', '- Second', '', 'Closing.'].join('\n');
124
+
125
+ it('combines non-overlapping local and remote changes', () => {
126
+ const local = BASE.replace('- Second', '- Second (local)');
127
+ const remote = BASE.replace('Overview.', 'Overview updated remotely.');
128
+
129
+ expect(rebaseTextChanges(BASE, local, remote)).toEqual({
130
+ status: 'rebased',
131
+ content: remote.replace('- Second', '- Second (local)'),
132
+ });
133
+ });
134
+
135
+ it('returns the local version unchanged when the remote content did not change', () => {
136
+ const local = BASE.replace('Closing.', 'Local closing.');
137
+ expect(rebaseTextChanges(BASE, local, BASE)).toEqual({ status: 'rebased', content: local });
138
+ });
139
+
140
+ it('deduplicates an identical concurrent edit', () => {
141
+ const changed = BASE.replace('- First', '- First (shared)');
142
+ expect(rebaseTextChanges(BASE, changed, changed)).toEqual({ status: 'rebased', content: changed });
143
+ });
144
+
145
+ it('reports overlapping replacements as a conflict and preserves the local version', () => {
146
+ const local = BASE.replace('Overview.', 'Local overview.');
147
+ const remote = BASE.replace('Overview.', 'Remote overview.');
148
+ expect(rebaseTextChanges(BASE, local, remote)).toEqual({ status: 'conflict', content: local });
149
+ });
150
+
151
+ it('treats competing insertions at the same base position as a conflict', () => {
152
+ const local = BASE.replace('## Goals', 'Local note.\n\n## Goals');
153
+ const remote = BASE.replace('## Goals', 'Remote note.\n\n## Goals');
154
+ expect(rebaseTextChanges(BASE, local, remote)).toEqual({ status: 'conflict', content: local });
155
+ });
156
+ });