@dxos/ui-editor 0.10.0 → 0.11.0

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 (551) hide show
  1. package/dist/lib/chunk-diff.mjs +935 -0
  2. package/dist/lib/chunk-diff.mjs.map +1 -0
  3. package/dist/lib/headless.mjs +2 -0
  4. package/dist/lib/index.mjs +9937 -0
  5. package/dist/lib/index.mjs.map +1 -0
  6. package/dist/lib/types.mjs +18 -0
  7. package/dist/lib/types.mjs.map +1 -0
  8. package/dist/types/src/extensions/{bookmarks.d.ts → behavior/bookmarks.d.ts} +3 -0
  9. package/dist/types/src/extensions/behavior/bookmarks.d.ts.map +1 -0
  10. package/dist/types/src/extensions/behavior/dnd.d.ts.map +1 -0
  11. package/dist/types/src/extensions/behavior/index.d.ts +6 -0
  12. package/dist/types/src/extensions/behavior/index.d.ts.map +1 -0
  13. package/dist/types/src/extensions/{listener.d.ts → behavior/listener.d.ts} +3 -0
  14. package/dist/types/src/extensions/behavior/listener.d.ts.map +1 -0
  15. package/dist/types/src/extensions/{spacing.d.ts → behavior/spacing.d.ts} +3 -0
  16. package/dist/types/src/extensions/behavior/spacing.d.ts.map +1 -0
  17. package/dist/types/src/extensions/behavior/submit.d.ts.map +1 -0
  18. package/dist/types/src/extensions/collab/automerge/automerge.d.ts.map +1 -0
  19. package/dist/types/src/extensions/collab/automerge/automerge.test.d.ts.map +1 -0
  20. package/dist/types/src/extensions/{automerge → collab/automerge}/cursor.d.ts +1 -1
  21. package/dist/types/src/extensions/collab/automerge/cursor.d.ts.map +1 -0
  22. package/dist/types/src/extensions/{automerge → collab/automerge}/defs.d.ts +3 -1
  23. package/dist/types/src/extensions/collab/automerge/defs.d.ts.map +1 -0
  24. package/dist/types/src/extensions/collab/automerge/index.d.ts +3 -0
  25. package/dist/types/src/extensions/collab/automerge/index.d.ts.map +1 -0
  26. package/dist/types/src/extensions/collab/automerge/sync.d.ts.map +1 -0
  27. package/dist/types/src/extensions/{automerge → collab/automerge}/update-automerge.d.ts +4 -0
  28. package/dist/types/src/extensions/collab/automerge/update-automerge.d.ts.map +1 -0
  29. package/dist/types/src/extensions/{automerge → collab/automerge}/update-codemirror.d.ts +4 -0
  30. package/dist/types/src/extensions/collab/automerge/update-codemirror.d.ts.map +1 -0
  31. package/dist/types/src/extensions/collab/awareness/awareness-provider.d.ts.map +1 -0
  32. package/dist/types/src/extensions/{awareness → collab/awareness}/awareness.d.ts +4 -1
  33. package/dist/types/src/extensions/collab/awareness/awareness.d.ts.map +1 -0
  34. package/dist/types/src/extensions/collab/awareness/index.d.ts.map +1 -0
  35. package/dist/types/src/extensions/collab/index.d.ts +3 -0
  36. package/dist/types/src/extensions/collab/index.d.ts.map +1 -0
  37. package/dist/types/src/extensions/completion/autocomplete.d.ts.map +1 -0
  38. package/dist/types/src/extensions/{autocomplete → completion}/index.d.ts +1 -0
  39. package/dist/types/src/extensions/completion/index.d.ts.map +1 -0
  40. package/dist/types/src/extensions/{autocomplete → completion}/match.d.ts +2 -2
  41. package/dist/types/src/extensions/completion/match.d.ts.map +1 -0
  42. package/dist/types/src/extensions/{mention.d.ts → completion/mention.d.ts} +3 -0
  43. package/dist/types/src/extensions/completion/mention.d.ts.map +1 -0
  44. package/dist/types/src/extensions/completion/placeholder.d.ts.map +1 -0
  45. package/dist/types/src/extensions/{autocomplete → completion}/typeahead.d.ts +2 -2
  46. package/dist/types/src/extensions/completion/typeahead.d.ts.map +1 -0
  47. package/dist/types/src/extensions/{factories.d.ts → core/factories.d.ts} +23 -2
  48. package/dist/types/src/extensions/core/factories.d.ts.map +1 -0
  49. package/dist/types/src/extensions/core/factories.test.d.ts.map +1 -0
  50. package/dist/types/src/extensions/core/index.d.ts +3 -0
  51. package/dist/types/src/extensions/core/index.d.ts.map +1 -0
  52. package/dist/types/src/extensions/{modes.d.ts → core/modes.d.ts} +3 -0
  53. package/dist/types/src/extensions/core/modes.d.ts.map +1 -0
  54. package/dist/types/src/extensions/{debug.d.ts → debug/debug.d.ts} +3 -0
  55. package/dist/types/src/extensions/debug/debug.d.ts.map +1 -0
  56. package/dist/types/src/extensions/debug/index.d.ts +2 -0
  57. package/dist/types/src/extensions/debug/index.d.ts.map +1 -0
  58. package/dist/types/src/extensions/{annotations.d.ts → decoration/annotations.d.ts} +1 -1
  59. package/dist/types/src/extensions/decoration/annotations.d.ts.map +1 -0
  60. package/dist/types/src/extensions/decoration/assistant.d.ts +27 -0
  61. package/dist/types/src/extensions/decoration/assistant.d.ts.map +1 -0
  62. package/dist/types/src/extensions/decoration/fader.d.ts.map +1 -0
  63. package/dist/types/src/extensions/decoration/hashtag.d.ts +6 -0
  64. package/dist/types/src/extensions/decoration/hashtag.d.ts.map +1 -0
  65. package/dist/types/src/extensions/decoration/index.d.ts +8 -0
  66. package/dist/types/src/extensions/decoration/index.d.ts.map +1 -0
  67. package/dist/types/src/extensions/decoration/marker.d.ts.map +1 -0
  68. package/dist/types/src/extensions/{pos.d.ts → decoration/pos.d.ts} +40 -14
  69. package/dist/types/src/extensions/decoration/pos.d.ts.map +1 -0
  70. package/dist/types/src/extensions/decoration/pos.test.d.ts.map +1 -0
  71. package/dist/types/src/extensions/{replacer.d.ts → decoration/replacer.d.ts} +8 -8
  72. package/dist/types/src/extensions/decoration/replacer.d.ts.map +1 -0
  73. package/dist/types/src/extensions/decoration/replacer.test.d.ts.map +1 -0
  74. package/dist/types/src/extensions/{blast.d.ts → demo/blast.d.ts} +3 -0
  75. package/dist/types/src/extensions/demo/blast.d.ts.map +1 -0
  76. package/dist/types/src/extensions/demo/index.d.ts +3 -0
  77. package/dist/types/src/extensions/demo/index.d.ts.map +1 -0
  78. package/dist/types/src/extensions/demo/snippets.d.ts.map +1 -0
  79. package/dist/types/src/extensions/index.d.ts +10 -33
  80. package/dist/types/src/extensions/index.d.ts.map +1 -1
  81. package/dist/types/src/extensions/language/index.d.ts +5 -0
  82. package/dist/types/src/extensions/language/index.d.ts.map +1 -0
  83. package/dist/types/src/extensions/language/json/index.d.ts +2 -0
  84. package/dist/types/src/extensions/language/json/index.d.ts.map +1 -0
  85. package/dist/types/src/extensions/language/json/json.d.ts +12 -0
  86. package/dist/types/src/extensions/language/json/json.d.ts.map +1 -0
  87. package/dist/types/src/extensions/language/markdown/action.d.ts.map +1 -0
  88. package/dist/types/src/extensions/language/markdown/bundle.d.ts.map +1 -0
  89. package/dist/types/src/extensions/{markdown → language/markdown}/changes.d.ts +1 -1
  90. package/dist/types/src/extensions/language/markdown/changes.d.ts.map +1 -0
  91. package/dist/types/src/extensions/language/markdown/changes.test.d.ts.map +1 -0
  92. package/dist/types/src/extensions/{markdown → language/markdown}/debug.d.ts +3 -0
  93. package/dist/types/src/extensions/language/markdown/debug.d.ts.map +1 -0
  94. package/dist/types/src/extensions/{markdown → language/markdown}/decorate.d.ts +5 -1
  95. package/dist/types/src/extensions/language/markdown/decorate.d.ts.map +1 -0
  96. package/dist/types/src/extensions/{markdown → language/markdown}/formatting.d.ts +11 -2
  97. package/dist/types/src/extensions/language/markdown/formatting.d.ts.map +1 -0
  98. package/dist/types/src/extensions/language/markdown/formatting.test.d.ts.map +1 -0
  99. package/dist/types/src/extensions/{markdown → language/markdown}/highlight.d.ts +16 -12
  100. package/dist/types/src/extensions/language/markdown/highlight.d.ts.map +1 -0
  101. package/dist/types/src/extensions/language/markdown/image.d.ts.map +1 -0
  102. package/dist/types/src/extensions/language/markdown/image.test.d.ts.map +1 -0
  103. package/dist/types/src/extensions/language/markdown/index.d.ts.map +1 -0
  104. package/dist/types/src/extensions/language/markdown/link.d.ts +25 -0
  105. package/dist/types/src/extensions/language/markdown/link.d.ts.map +1 -0
  106. package/dist/types/src/extensions/language/markdown/link.test.d.ts +2 -0
  107. package/dist/types/src/extensions/language/markdown/link.test.d.ts.map +1 -0
  108. package/dist/types/src/extensions/language/markdown/parser.test.d.ts.map +1 -0
  109. package/dist/types/src/extensions/{markdown → language/markdown}/styles.d.ts +3 -0
  110. package/dist/types/src/extensions/language/markdown/styles.d.ts.map +1 -0
  111. package/dist/types/src/extensions/language/markdown/table.d.ts.map +1 -0
  112. package/dist/types/src/extensions/language/mermaid/index.d.ts +2 -0
  113. package/dist/types/src/extensions/language/mermaid/index.d.ts.map +1 -0
  114. package/dist/types/src/extensions/language/mermaid/mermaid.d.ts +18 -0
  115. package/dist/types/src/extensions/language/mermaid/mermaid.d.ts.map +1 -0
  116. package/dist/types/src/extensions/language/xml/extended-markdown.d.ts.map +1 -0
  117. package/dist/types/src/extensions/language/xml/extended-markdown.test.d.ts.map +1 -0
  118. package/dist/types/src/extensions/language/xml/index.d.ts.map +1 -0
  119. package/dist/types/src/extensions/language/xml/stub.d.ts +76 -0
  120. package/dist/types/src/extensions/language/xml/stub.d.ts.map +1 -0
  121. package/dist/types/src/extensions/language/xml/widgets/anchor.d.ts.map +1 -0
  122. package/dist/types/src/extensions/language/xml/widgets/index.d.ts.map +1 -0
  123. package/dist/types/src/extensions/language/xml/xml-block-decoration.d.ts +48 -0
  124. package/dist/types/src/extensions/language/xml/xml-block-decoration.d.ts.map +1 -0
  125. package/dist/types/src/extensions/language/xml/xml-formatting.d.ts.map +1 -0
  126. package/dist/types/src/extensions/{tags → language/xml}/xml-tags.d.ts +12 -1
  127. package/dist/types/src/extensions/language/xml/xml-tags.d.ts.map +1 -0
  128. package/dist/types/src/extensions/language/xml/xml-tags.test.d.ts +2 -0
  129. package/dist/types/src/extensions/language/xml/xml-tags.test.d.ts.map +1 -0
  130. package/dist/types/src/extensions/language/xml/xml-util.d.ts.map +1 -0
  131. package/dist/types/src/extensions/language/xml/xml-util.test.d.ts.map +1 -0
  132. package/dist/types/src/extensions/review/change-bar.d.ts +29 -0
  133. package/dist/types/src/extensions/review/change-bar.d.ts.map +1 -0
  134. package/dist/types/src/extensions/{comments.d.ts → review/comments.d.ts} +56 -38
  135. package/dist/types/src/extensions/review/comments.d.ts.map +1 -0
  136. package/dist/types/src/extensions/review/comments.test.d.ts +2 -0
  137. package/dist/types/src/extensions/review/comments.test.d.ts.map +1 -0
  138. package/dist/types/src/extensions/review/diff.d.ts +131 -0
  139. package/dist/types/src/extensions/review/diff.d.ts.map +1 -0
  140. package/dist/types/src/extensions/review/diff.test.d.ts +2 -0
  141. package/dist/types/src/extensions/review/diff.test.d.ts.map +1 -0
  142. package/dist/types/src/extensions/review/index.d.ts +7 -0
  143. package/dist/types/src/extensions/review/index.d.ts.map +1 -0
  144. package/dist/types/src/extensions/review/suggest.d.ts +89 -0
  145. package/dist/types/src/extensions/review/suggest.d.ts.map +1 -0
  146. package/dist/types/src/extensions/review/suggest.test.d.ts +2 -0
  147. package/dist/types/src/extensions/review/suggest.test.d.ts.map +1 -0
  148. package/dist/types/src/extensions/review/suggestions-overlay.d.ts +25 -0
  149. package/dist/types/src/extensions/review/suggestions-overlay.d.ts.map +1 -0
  150. package/dist/types/src/extensions/review/suggestions-overlay.test.d.ts +2 -0
  151. package/dist/types/src/extensions/review/suggestions-overlay.test.d.ts.map +1 -0
  152. package/dist/types/src/extensions/review/track-changes.d.ts +27 -0
  153. package/dist/types/src/extensions/review/track-changes.d.ts.map +1 -0
  154. package/dist/types/src/extensions/review/track-changes.test.d.ts +2 -0
  155. package/dist/types/src/extensions/review/track-changes.test.d.ts.map +1 -0
  156. package/dist/types/src/extensions/{busy.d.ts → state/busy.d.ts} +1 -0
  157. package/dist/types/src/extensions/state/busy.d.ts.map +1 -0
  158. package/dist/types/src/extensions/{focus.d.ts → state/focus.d.ts} +4 -0
  159. package/dist/types/src/extensions/state/focus.d.ts.map +1 -0
  160. package/dist/types/src/extensions/state/index.d.ts +5 -0
  161. package/dist/types/src/extensions/state/index.d.ts.map +1 -0
  162. package/dist/types/src/extensions/state/modal.d.ts.map +1 -0
  163. package/dist/types/src/extensions/{selection.d.ts → state/selection.d.ts} +1 -1
  164. package/dist/types/src/extensions/state/selection.d.ts.map +1 -0
  165. package/dist/types/src/extensions/state/selection.test.d.ts.map +1 -0
  166. package/dist/types/src/extensions/streaming/index.d.ts +4 -0
  167. package/dist/types/src/extensions/streaming/index.d.ts.map +1 -0
  168. package/dist/types/src/extensions/streaming/pending/index.d.ts.map +1 -0
  169. package/dist/types/src/extensions/streaming/pending/pending-text-stream.d.ts.map +1 -0
  170. package/dist/types/src/extensions/streaming/pending/pending-text-stream.test.d.ts.map +1 -0
  171. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text.d.ts +1 -0
  172. package/dist/types/src/extensions/streaming/pending/pending-text.d.ts.map +1 -0
  173. package/dist/types/src/extensions/streaming/pending/pending-text.test.d.ts.map +1 -0
  174. package/dist/types/src/extensions/streaming/scrolling/auto-scroll.d.ts.map +1 -0
  175. package/dist/types/src/extensions/streaming/scrolling/crawler.d.ts.map +1 -0
  176. package/dist/types/src/extensions/streaming/scrolling/index.d.ts.map +1 -0
  177. package/dist/types/src/extensions/streaming/scrolling/scroll-past-end.d.ts +4 -0
  178. package/dist/types/src/extensions/streaming/scrolling/scroll-past-end.d.ts.map +1 -0
  179. package/dist/types/src/extensions/streaming/scrolling/scrollbar-autohide.d.ts.map +1 -0
  180. package/dist/types/src/extensions/streaming/scrolling/scroller.d.ts.map +1 -0
  181. package/dist/types/src/extensions/{typewriter.d.ts → streaming/typewriter.d.ts} +12 -12
  182. package/dist/types/src/extensions/streaming/typewriter.d.ts.map +1 -0
  183. package/dist/types/src/extensions/streaming/typewriter.test.d.ts.map +1 -0
  184. package/dist/types/src/extensions/structure/blocks/blocks.d.ts +52 -0
  185. package/dist/types/src/extensions/structure/blocks/blocks.d.ts.map +1 -0
  186. package/dist/types/src/extensions/structure/blocks/blocks.test.d.ts +2 -0
  187. package/dist/types/src/extensions/structure/blocks/blocks.test.d.ts.map +1 -0
  188. package/dist/types/src/extensions/structure/blocks/drag.d.ts +57 -0
  189. package/dist/types/src/extensions/structure/blocks/drag.d.ts.map +1 -0
  190. package/dist/types/src/extensions/structure/blocks/index.d.ts +5 -0
  191. package/dist/types/src/extensions/structure/blocks/index.d.ts.map +1 -0
  192. package/dist/types/src/extensions/structure/blocks/selection.d.ts +47 -0
  193. package/dist/types/src/extensions/structure/blocks/selection.d.ts.map +1 -0
  194. package/dist/types/src/extensions/structure/blocks/selection.test.d.ts +2 -0
  195. package/dist/types/src/extensions/structure/blocks/selection.test.d.ts.map +1 -0
  196. package/dist/types/src/extensions/structure/blocks/types.d.ts +17 -0
  197. package/dist/types/src/extensions/structure/blocks/types.d.ts.map +1 -0
  198. package/dist/types/src/extensions/structure/folding/folding.d.ts.map +1 -0
  199. package/dist/types/src/extensions/structure/folding/index.d.ts +3 -0
  200. package/dist/types/src/extensions/structure/folding/index.d.ts.map +1 -0
  201. package/dist/types/src/extensions/structure/folding/turns.d.ts +55 -0
  202. package/dist/types/src/extensions/structure/folding/turns.d.ts.map +1 -0
  203. package/dist/types/src/extensions/structure/folding/turns.test.d.ts +2 -0
  204. package/dist/types/src/extensions/structure/folding/turns.test.d.ts.map +1 -0
  205. package/dist/types/src/extensions/structure/index.d.ts +4 -0
  206. package/dist/types/src/extensions/structure/index.d.ts.map +1 -0
  207. package/dist/types/src/extensions/structure/outliner/commands.d.ts +31 -0
  208. package/dist/types/src/extensions/structure/outliner/commands.d.ts.map +1 -0
  209. package/dist/types/src/extensions/structure/outliner/dnd.d.ts +43 -0
  210. package/dist/types/src/extensions/structure/outliner/dnd.d.ts.map +1 -0
  211. package/dist/types/src/extensions/structure/outliner/dnd.test.d.ts +2 -0
  212. package/dist/types/src/extensions/structure/outliner/dnd.test.d.ts.map +1 -0
  213. package/dist/types/src/extensions/structure/outliner/editor.d.ts.map +1 -0
  214. package/dist/types/src/extensions/structure/outliner/editor.test.d.ts.map +1 -0
  215. package/dist/types/src/extensions/{outliner → structure/outliner}/index.d.ts +1 -0
  216. package/dist/types/src/extensions/structure/outliner/index.d.ts.map +1 -0
  217. package/dist/types/src/extensions/{outliner → structure/outliner}/menu.d.ts +1 -0
  218. package/dist/types/src/extensions/structure/outliner/menu.d.ts.map +1 -0
  219. package/dist/types/src/extensions/structure/outliner/outliner.d.ts.map +1 -0
  220. package/dist/types/src/extensions/structure/outliner/outliner.test.d.ts.map +1 -0
  221. package/dist/types/src/extensions/{outliner → structure/outliner}/tree.d.ts +1 -1
  222. package/dist/types/src/extensions/structure/outliner/tree.d.ts.map +1 -0
  223. package/dist/types/src/extensions/structure/outliner/tree.test.d.ts.map +1 -0
  224. package/dist/types/src/headless.d.ts +4 -0
  225. package/dist/types/src/headless.d.ts.map +1 -0
  226. package/dist/types/src/styles/theme.d.ts +1 -1
  227. package/dist/types/src/styles/theme.d.ts.map +1 -1
  228. package/dist/types/src/util/util.d.ts +2 -0
  229. package/dist/types/src/util/util.d.ts.map +1 -1
  230. package/dist/types/tsconfig.tsbuildinfo +1 -1
  231. package/package.json +48 -42
  232. package/src/defaults.ts +1 -1
  233. package/src/extensions/{bookmarks.ts → behavior/bookmarks.ts} +24 -14
  234. package/src/extensions/behavior/index.ts +9 -0
  235. package/src/extensions/{listener.ts → behavior/listener.ts} +4 -1
  236. package/src/extensions/{spacing.ts → behavior/spacing.ts} +8 -2
  237. package/src/extensions/{submit.ts → behavior/submit.ts} +2 -10
  238. package/src/extensions/{automerge → collab/automerge}/automerge.test.tsx +4 -4
  239. package/src/extensions/{automerge → collab/automerge}/automerge.ts +7 -4
  240. package/src/extensions/{automerge → collab/automerge}/cursor.ts +1 -1
  241. package/src/extensions/{automerge → collab/automerge}/defs.ts +4 -1
  242. package/src/extensions/{automerge → collab/automerge}/index.ts +1 -0
  243. package/src/extensions/{automerge → collab/automerge}/sync.ts +1 -2
  244. package/src/extensions/{automerge → collab/automerge}/update-automerge.ts +4 -0
  245. package/src/extensions/{automerge → collab/automerge}/update-codemirror.ts +5 -1
  246. package/src/extensions/{awareness → collab/awareness}/awareness-provider.ts +3 -4
  247. package/src/extensions/{awareness → collab/awareness}/awareness.ts +19 -16
  248. package/src/extensions/collab/index.ts +6 -0
  249. package/src/extensions/{autocomplete → completion}/autocomplete.ts +7 -5
  250. package/src/extensions/{autocomplete → completion}/index.ts +2 -1
  251. package/src/extensions/{autocomplete → completion}/match.ts +2 -2
  252. package/src/extensions/{mention.ts → completion/mention.ts} +3 -0
  253. package/src/extensions/{autocomplete → completion}/placeholder.ts +1 -1
  254. package/src/extensions/completion/typeahead.ts +97 -0
  255. package/src/extensions/{factories.ts → core/factories.ts} +35 -13
  256. package/src/extensions/core/index.ts +6 -0
  257. package/src/extensions/{modes.ts → core/modes.ts} +4 -1
  258. package/src/extensions/{debug.ts → debug/debug.ts} +3 -0
  259. package/src/extensions/debug/index.ts +5 -0
  260. package/src/extensions/{annotations.ts → decoration/annotations.ts} +12 -10
  261. package/src/extensions/decoration/assistant.ts +225 -0
  262. package/src/extensions/{fader.ts → decoration/fader.ts} +17 -19
  263. package/src/extensions/{hashtag.ts → decoration/hashtag.ts} +35 -28
  264. package/src/extensions/decoration/index.ts +11 -0
  265. package/src/extensions/{marker.ts → decoration/marker.ts} +18 -18
  266. package/src/extensions/{pos.test.ts → decoration/pos.test.ts} +57 -1
  267. package/src/extensions/{pos.ts → decoration/pos.ts} +165 -31
  268. package/src/extensions/{blast.ts → demo/blast.ts} +3 -0
  269. package/src/extensions/demo/index.ts +6 -0
  270. package/src/extensions/{snippets.ts → demo/snippets.ts} +5 -1
  271. package/src/extensions/index.ts +10 -33
  272. package/src/extensions/language/index.ts +8 -0
  273. package/src/extensions/language/json/index.ts +5 -0
  274. package/src/extensions/{json.ts → language/json/json.ts} +11 -6
  275. package/src/extensions/{markdown → language/markdown}/action.ts +1 -1
  276. package/src/extensions/{markdown → language/markdown}/bundle.ts +7 -1
  277. package/src/extensions/{markdown → language/markdown}/changes.test.ts +2 -2
  278. package/src/extensions/{markdown → language/markdown}/changes.ts +2 -2
  279. package/src/extensions/{markdown → language/markdown}/debug.ts +3 -0
  280. package/src/extensions/{markdown → language/markdown}/decorate.ts +96 -85
  281. package/src/extensions/{markdown → language/markdown}/formatting.test.ts +88 -1
  282. package/src/extensions/{markdown → language/markdown}/formatting.ts +193 -74
  283. package/src/extensions/{markdown → language/markdown}/highlight.ts +42 -41
  284. package/src/extensions/{markdown → language/markdown}/image.test.ts +1 -1
  285. package/src/extensions/{markdown → language/markdown}/image.ts +6 -6
  286. package/src/extensions/language/markdown/link.test.ts +58 -0
  287. package/src/extensions/language/markdown/link.ts +109 -0
  288. package/src/extensions/{markdown → language/markdown}/parser.test.ts +0 -28
  289. package/src/extensions/{markdown → language/markdown}/styles.ts +38 -7
  290. package/src/extensions/{markdown → language/markdown}/table.ts +5 -5
  291. package/src/extensions/language/mermaid/index.ts +5 -0
  292. package/src/extensions/language/mermaid/mermaid.ts +44 -0
  293. package/src/extensions/{tags → language/xml}/DESIGN.md +3 -3
  294. package/src/extensions/{tags → language/xml}/extended-markdown.test.ts +2 -1
  295. package/src/extensions/{tags → language/xml}/extended-markdown.ts +2 -4
  296. package/src/extensions/language/xml/stub.ts +233 -0
  297. package/src/extensions/language/xml/xml-block-decoration.ts +165 -0
  298. package/src/extensions/{tags → language/xml}/xml-formatting.ts +54 -38
  299. package/src/extensions/language/xml/xml-tags.test.ts +588 -0
  300. package/src/extensions/{tags → language/xml}/xml-tags.ts +165 -66
  301. package/src/extensions/{tags → language/xml}/xml-util.test.ts +2 -2
  302. package/src/extensions/review/change-bar.ts +94 -0
  303. package/src/extensions/review/comments.test.ts +72 -0
  304. package/src/extensions/{comments.ts → review/comments.ts} +395 -267
  305. package/src/extensions/review/diff.test.ts +405 -0
  306. package/src/extensions/review/diff.ts +502 -0
  307. package/src/extensions/review/index.ts +10 -0
  308. package/src/extensions/review/suggest.test.ts +170 -0
  309. package/src/extensions/review/suggest.ts +512 -0
  310. package/src/extensions/review/suggestions-overlay.test.ts +58 -0
  311. package/src/extensions/review/suggestions-overlay.ts +50 -0
  312. package/src/extensions/review/track-changes.test.ts +101 -0
  313. package/src/extensions/review/track-changes.ts +215 -0
  314. package/src/extensions/{busy.ts → state/busy.ts} +1 -0
  315. package/src/extensions/{focus.ts → state/focus.ts} +4 -0
  316. package/src/extensions/state/index.ts +8 -0
  317. package/src/extensions/{selection.ts → state/selection.ts} +3 -3
  318. package/src/extensions/streaming/index.ts +7 -0
  319. package/src/extensions/{pending → streaming/pending}/pending-text-stream.ts +10 -3
  320. package/src/extensions/{pending → streaming/pending}/pending-text.test.ts +1 -1
  321. package/src/extensions/{pending → streaming/pending}/pending-text.ts +20 -19
  322. package/src/extensions/{scrolling → streaming/scrolling}/auto-scroll.ts +9 -6
  323. package/src/extensions/{scrolling → streaming/scrolling}/scroll-past-end.ts +1 -0
  324. package/src/extensions/{scrolling → streaming/scrolling}/scrollbar-autohide.ts +13 -11
  325. package/src/extensions/{typewriter.ts → streaming/typewriter.ts} +46 -47
  326. package/src/extensions/structure/blocks/DESIGN.md +134 -0
  327. package/src/extensions/structure/blocks/blocks.test.ts +147 -0
  328. package/src/extensions/structure/blocks/blocks.ts +219 -0
  329. package/src/extensions/structure/blocks/drag.ts +996 -0
  330. package/src/extensions/structure/blocks/index.ts +8 -0
  331. package/src/extensions/structure/blocks/selection.test.ts +56 -0
  332. package/src/extensions/structure/blocks/selection.ts +279 -0
  333. package/src/extensions/structure/blocks/types.ts +22 -0
  334. package/src/extensions/{folding.ts → structure/folding/folding.ts} +12 -10
  335. package/src/extensions/structure/folding/index.ts +6 -0
  336. package/src/extensions/structure/folding/turns.test.ts +72 -0
  337. package/src/extensions/structure/folding/turns.ts +209 -0
  338. package/src/extensions/structure/index.ts +7 -0
  339. package/src/extensions/structure/outliner/DESIGN.md +183 -0
  340. package/src/extensions/structure/outliner/SPEC.mdl +305 -0
  341. package/src/extensions/{outliner → structure/outliner}/commands.ts +125 -42
  342. package/src/extensions/structure/outliner/dnd.test.ts +142 -0
  343. package/src/extensions/structure/outliner/dnd.ts +375 -0
  344. package/src/extensions/structure/outliner/editor.test.ts +85 -0
  345. package/src/extensions/{outliner → structure/outliner}/editor.ts +14 -9
  346. package/src/extensions/{outliner → structure/outliner}/index.ts +1 -0
  347. package/src/extensions/{outliner → structure/outliner}/menu.ts +48 -25
  348. package/src/extensions/structure/outliner/outliner.test.ts +193 -0
  349. package/src/extensions/structure/outliner/outliner.ts +183 -0
  350. package/src/extensions/{outliner → structure/outliner}/tree.test.ts +2 -2
  351. package/src/extensions/{outliner → structure/outliner}/tree.ts +1 -1
  352. package/src/headless.ts +13 -0
  353. package/src/styles/theme.ts +3 -1
  354. package/src/util/util.ts +3 -0
  355. package/dist/lib/browser/index.mjs +0 -9594
  356. package/dist/lib/browser/index.mjs.map +0 -7
  357. package/dist/lib/browser/meta.json +0 -1
  358. package/dist/lib/browser/types/index.mjs +0 -33
  359. package/dist/lib/browser/types/index.mjs.map +0 -7
  360. package/dist/lib/node-esm/index.mjs +0 -9596
  361. package/dist/lib/node-esm/index.mjs.map +0 -7
  362. package/dist/lib/node-esm/meta.json +0 -1
  363. package/dist/lib/node-esm/types/index.mjs +0 -35
  364. package/dist/lib/node-esm/types/index.mjs.map +0 -7
  365. package/dist/types/src/extensions/annotations.d.ts.map +0 -1
  366. package/dist/types/src/extensions/autocomplete/autocomplete.d.ts.map +0 -1
  367. package/dist/types/src/extensions/autocomplete/index.d.ts.map +0 -1
  368. package/dist/types/src/extensions/autocomplete/match.d.ts.map +0 -1
  369. package/dist/types/src/extensions/autocomplete/placeholder.d.ts.map +0 -1
  370. package/dist/types/src/extensions/autocomplete/typeahead.d.ts.map +0 -1
  371. package/dist/types/src/extensions/automerge/automerge.d.ts.map +0 -1
  372. package/dist/types/src/extensions/automerge/automerge.test.d.ts.map +0 -1
  373. package/dist/types/src/extensions/automerge/cursor.d.ts.map +0 -1
  374. package/dist/types/src/extensions/automerge/defs.d.ts.map +0 -1
  375. package/dist/types/src/extensions/automerge/index.d.ts +0 -2
  376. package/dist/types/src/extensions/automerge/index.d.ts.map +0 -1
  377. package/dist/types/src/extensions/automerge/sync.d.ts.map +0 -1
  378. package/dist/types/src/extensions/automerge/update-automerge.d.ts.map +0 -1
  379. package/dist/types/src/extensions/automerge/update-codemirror.d.ts.map +0 -1
  380. package/dist/types/src/extensions/awareness/awareness-provider.d.ts.map +0 -1
  381. package/dist/types/src/extensions/awareness/awareness.d.ts.map +0 -1
  382. package/dist/types/src/extensions/awareness/index.d.ts.map +0 -1
  383. package/dist/types/src/extensions/blast.d.ts.map +0 -1
  384. package/dist/types/src/extensions/blocks.d.ts +0 -2
  385. package/dist/types/src/extensions/blocks.d.ts.map +0 -1
  386. package/dist/types/src/extensions/bookmarks.d.ts.map +0 -1
  387. package/dist/types/src/extensions/busy.d.ts.map +0 -1
  388. package/dist/types/src/extensions/comments.d.ts.map +0 -1
  389. package/dist/types/src/extensions/debug.d.ts.map +0 -1
  390. package/dist/types/src/extensions/dnd.d.ts.map +0 -1
  391. package/dist/types/src/extensions/factories.d.ts.map +0 -1
  392. package/dist/types/src/extensions/factories.test.d.ts.map +0 -1
  393. package/dist/types/src/extensions/fader.d.ts.map +0 -1
  394. package/dist/types/src/extensions/focus.d.ts.map +0 -1
  395. package/dist/types/src/extensions/folding.d.ts.map +0 -1
  396. package/dist/types/src/extensions/hashtag.d.ts +0 -3
  397. package/dist/types/src/extensions/hashtag.d.ts.map +0 -1
  398. package/dist/types/src/extensions/json.d.ts +0 -7
  399. package/dist/types/src/extensions/json.d.ts.map +0 -1
  400. package/dist/types/src/extensions/listener.d.ts.map +0 -1
  401. package/dist/types/src/extensions/markdown/action.d.ts.map +0 -1
  402. package/dist/types/src/extensions/markdown/bundle.d.ts.map +0 -1
  403. package/dist/types/src/extensions/markdown/changes.d.ts.map +0 -1
  404. package/dist/types/src/extensions/markdown/changes.test.d.ts.map +0 -1
  405. package/dist/types/src/extensions/markdown/debug.d.ts.map +0 -1
  406. package/dist/types/src/extensions/markdown/decorate.d.ts.map +0 -1
  407. package/dist/types/src/extensions/markdown/formatting.d.ts.map +0 -1
  408. package/dist/types/src/extensions/markdown/formatting.test.d.ts.map +0 -1
  409. package/dist/types/src/extensions/markdown/highlight.d.ts.map +0 -1
  410. package/dist/types/src/extensions/markdown/image.d.ts.map +0 -1
  411. package/dist/types/src/extensions/markdown/image.test.d.ts.map +0 -1
  412. package/dist/types/src/extensions/markdown/index.d.ts.map +0 -1
  413. package/dist/types/src/extensions/markdown/link.d.ts +0 -7
  414. package/dist/types/src/extensions/markdown/link.d.ts.map +0 -1
  415. package/dist/types/src/extensions/markdown/parser.test.d.ts.map +0 -1
  416. package/dist/types/src/extensions/markdown/styles.d.ts.map +0 -1
  417. package/dist/types/src/extensions/markdown/table.d.ts.map +0 -1
  418. package/dist/types/src/extensions/marker.d.ts.map +0 -1
  419. package/dist/types/src/extensions/mention.d.ts.map +0 -1
  420. package/dist/types/src/extensions/modal.d.ts.map +0 -1
  421. package/dist/types/src/extensions/modes.d.ts.map +0 -1
  422. package/dist/types/src/extensions/outliner/commands.d.ts +0 -10
  423. package/dist/types/src/extensions/outliner/commands.d.ts.map +0 -1
  424. package/dist/types/src/extensions/outliner/editor.d.ts.map +0 -1
  425. package/dist/types/src/extensions/outliner/editor.test.d.ts.map +0 -1
  426. package/dist/types/src/extensions/outliner/index.d.ts.map +0 -1
  427. package/dist/types/src/extensions/outliner/menu.d.ts.map +0 -1
  428. package/dist/types/src/extensions/outliner/outliner.d.ts.map +0 -1
  429. package/dist/types/src/extensions/outliner/outliner.test.d.ts.map +0 -1
  430. package/dist/types/src/extensions/outliner/selection.d.ts +0 -12
  431. package/dist/types/src/extensions/outliner/selection.d.ts.map +0 -1
  432. package/dist/types/src/extensions/outliner/tree.d.ts.map +0 -1
  433. package/dist/types/src/extensions/outliner/tree.test.d.ts.map +0 -1
  434. package/dist/types/src/extensions/pending/index.d.ts.map +0 -1
  435. package/dist/types/src/extensions/pending/pending-text-stream.d.ts.map +0 -1
  436. package/dist/types/src/extensions/pending/pending-text-stream.test.d.ts.map +0 -1
  437. package/dist/types/src/extensions/pending/pending-text.d.ts.map +0 -1
  438. package/dist/types/src/extensions/pending/pending-text.test.d.ts.map +0 -1
  439. package/dist/types/src/extensions/pos.d.ts.map +0 -1
  440. package/dist/types/src/extensions/pos.test.d.ts.map +0 -1
  441. package/dist/types/src/extensions/replacer.d.ts.map +0 -1
  442. package/dist/types/src/extensions/replacer.test.d.ts.map +0 -1
  443. package/dist/types/src/extensions/scrolling/auto-scroll.d.ts.map +0 -1
  444. package/dist/types/src/extensions/scrolling/crawler.d.ts.map +0 -1
  445. package/dist/types/src/extensions/scrolling/index.d.ts.map +0 -1
  446. package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts +0 -3
  447. package/dist/types/src/extensions/scrolling/scroll-past-end.d.ts.map +0 -1
  448. package/dist/types/src/extensions/scrolling/scrollbar-autohide.d.ts.map +0 -1
  449. package/dist/types/src/extensions/scrolling/scroller.d.ts.map +0 -1
  450. package/dist/types/src/extensions/selection.d.ts.map +0 -1
  451. package/dist/types/src/extensions/selection.test.d.ts.map +0 -1
  452. package/dist/types/src/extensions/snippets.d.ts.map +0 -1
  453. package/dist/types/src/extensions/spacing.d.ts.map +0 -1
  454. package/dist/types/src/extensions/state.d.ts +0 -2
  455. package/dist/types/src/extensions/state.d.ts.map +0 -1
  456. package/dist/types/src/extensions/submit.d.ts.map +0 -1
  457. package/dist/types/src/extensions/tags/extended-markdown.d.ts.map +0 -1
  458. package/dist/types/src/extensions/tags/extended-markdown.test.d.ts.map +0 -1
  459. package/dist/types/src/extensions/tags/index.d.ts.map +0 -1
  460. package/dist/types/src/extensions/tags/stub.d.ts +0 -35
  461. package/dist/types/src/extensions/tags/stub.d.ts.map +0 -1
  462. package/dist/types/src/extensions/tags/widgets/anchor.d.ts.map +0 -1
  463. package/dist/types/src/extensions/tags/widgets/index.d.ts.map +0 -1
  464. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts +0 -31
  465. package/dist/types/src/extensions/tags/xml-block-decoration.d.ts.map +0 -1
  466. package/dist/types/src/extensions/tags/xml-formatting.d.ts.map +0 -1
  467. package/dist/types/src/extensions/tags/xml-tags.d.ts.map +0 -1
  468. package/dist/types/src/extensions/tags/xml-util.d.ts.map +0 -1
  469. package/dist/types/src/extensions/tags/xml-util.test.d.ts.map +0 -1
  470. package/dist/types/src/extensions/typewriter.d.ts.map +0 -1
  471. package/dist/types/src/extensions/typewriter.test.d.ts.map +0 -1
  472. package/src/extensions/autocomplete/typeahead.ts +0 -87
  473. package/src/extensions/blocks.ts +0 -131
  474. package/src/extensions/markdown/link.ts +0 -49
  475. package/src/extensions/outliner/editor.test.ts +0 -33
  476. package/src/extensions/outliner/outliner.test.ts +0 -99
  477. package/src/extensions/outliner/outliner.ts +0 -166
  478. package/src/extensions/outliner/selection.ts +0 -50
  479. package/src/extensions/state.ts +0 -7
  480. package/src/extensions/tags/stub.ts +0 -80
  481. package/src/extensions/tags/xml-block-decoration.ts +0 -123
  482. package/dist/types/src/extensions/{dnd.d.ts → behavior/dnd.d.ts} +0 -0
  483. package/dist/types/src/extensions/{submit.d.ts → behavior/submit.d.ts} +0 -0
  484. package/dist/types/src/extensions/{automerge → collab/automerge}/automerge.d.ts +0 -0
  485. package/dist/types/src/extensions/{automerge → collab/automerge}/automerge.test.d.ts +0 -0
  486. package/dist/types/src/extensions/{automerge → collab/automerge}/sync.d.ts +0 -0
  487. package/dist/types/src/extensions/{awareness → collab/awareness}/awareness-provider.d.ts +0 -0
  488. package/dist/types/src/extensions/{awareness → collab/awareness}/index.d.ts +0 -0
  489. package/dist/types/src/extensions/{autocomplete → completion}/autocomplete.d.ts +0 -0
  490. package/dist/types/src/extensions/{autocomplete → completion}/placeholder.d.ts +0 -0
  491. package/dist/types/src/extensions/{factories.test.d.ts → core/factories.test.d.ts} +0 -0
  492. package/dist/types/src/extensions/{fader.d.ts → decoration/fader.d.ts} +0 -0
  493. package/dist/types/src/extensions/{marker.d.ts → decoration/marker.d.ts} +0 -0
  494. package/dist/types/src/extensions/{pos.test.d.ts → decoration/pos.test.d.ts} +0 -0
  495. package/dist/types/src/extensions/{replacer.test.d.ts → decoration/replacer.test.d.ts} +0 -0
  496. package/dist/types/src/extensions/{snippets.d.ts → demo/snippets.d.ts} +0 -0
  497. package/dist/types/src/extensions/{markdown → language/markdown}/action.d.ts +0 -0
  498. package/dist/types/src/extensions/{markdown → language/markdown}/bundle.d.ts +0 -0
  499. package/dist/types/src/extensions/{markdown → language/markdown}/changes.test.d.ts +0 -0
  500. package/dist/types/src/extensions/{markdown → language/markdown}/formatting.test.d.ts +0 -0
  501. package/dist/types/src/extensions/{markdown → language/markdown}/image.d.ts +0 -0
  502. package/dist/types/src/extensions/{markdown → language/markdown}/image.test.d.ts +0 -0
  503. package/dist/types/src/extensions/{markdown → language/markdown}/index.d.ts +0 -0
  504. package/dist/types/src/extensions/{markdown → language/markdown}/parser.test.d.ts +0 -0
  505. package/dist/types/src/extensions/{markdown → language/markdown}/table.d.ts +0 -0
  506. package/dist/types/src/extensions/{tags → language/xml}/extended-markdown.d.ts +0 -0
  507. package/dist/types/src/extensions/{tags → language/xml}/extended-markdown.test.d.ts +0 -0
  508. package/dist/types/src/extensions/{tags → language/xml}/index.d.ts +0 -0
  509. package/dist/types/src/extensions/{tags → language/xml}/widgets/anchor.d.ts +0 -0
  510. package/dist/types/src/extensions/{tags → language/xml}/widgets/index.d.ts +0 -0
  511. package/dist/types/src/extensions/{tags → language/xml}/xml-formatting.d.ts +0 -0
  512. package/dist/types/src/extensions/{tags → language/xml}/xml-util.d.ts +0 -0
  513. package/dist/types/src/extensions/{tags → language/xml}/xml-util.test.d.ts +0 -0
  514. package/dist/types/src/extensions/{modal.d.ts → state/modal.d.ts} +0 -0
  515. package/dist/types/src/extensions/{selection.test.d.ts → state/selection.test.d.ts} +0 -0
  516. package/dist/types/src/extensions/{pending → streaming/pending}/index.d.ts +0 -0
  517. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text-stream.d.ts +0 -0
  518. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text-stream.test.d.ts +0 -0
  519. package/dist/types/src/extensions/{pending → streaming/pending}/pending-text.test.d.ts +0 -0
  520. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/auto-scroll.d.ts +0 -0
  521. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/crawler.d.ts +0 -0
  522. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/index.d.ts +0 -0
  523. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/scrollbar-autohide.d.ts +0 -0
  524. package/dist/types/src/extensions/{scrolling → streaming/scrolling}/scroller.d.ts +0 -0
  525. package/dist/types/src/extensions/{typewriter.test.d.ts → streaming/typewriter.test.d.ts} +0 -0
  526. package/dist/types/src/extensions/{folding.d.ts → structure/folding/folding.d.ts} +0 -0
  527. package/dist/types/src/extensions/{outliner → structure/outliner}/editor.d.ts +0 -0
  528. package/dist/types/src/extensions/{outliner → structure/outliner}/editor.test.d.ts +0 -0
  529. package/dist/types/src/extensions/{outliner → structure/outliner}/outliner.d.ts +0 -0
  530. package/dist/types/src/extensions/{outliner → structure/outliner}/outliner.test.d.ts +0 -0
  531. package/dist/types/src/extensions/{outliner → structure/outliner}/tree.test.d.ts +0 -0
  532. package/src/extensions/{dnd.ts → behavior/dnd.ts} +29 -29
  533. package/src/extensions/{awareness → collab/awareness}/index.ts +0 -0
  534. package/src/extensions/{factories.test.ts → core/factories.test.ts} +0 -0
  535. package/src/extensions/{replacer.test.ts → decoration/replacer.test.ts} +0 -0
  536. package/src/extensions/{replacer.ts → decoration/replacer.ts} +25 -25
  537. /package/src/extensions/{markdown → language/markdown}/index.ts +0 -0
  538. /package/src/extensions/{tags → language/xml}/index.ts +0 -0
  539. /package/src/extensions/{tags → language/xml}/testing/text.md +0 -0
  540. /package/src/extensions/{tags → language/xml}/testing/text.txt +0 -0
  541. /package/src/extensions/{tags → language/xml}/widgets/anchor.ts +0 -0
  542. /package/src/extensions/{tags → language/xml}/widgets/index.ts +0 -0
  543. /package/src/extensions/{tags → language/xml}/xml-util.ts +0 -0
  544. /package/src/extensions/{modal.ts → state/modal.ts} +0 -0
  545. /package/src/extensions/{selection.test.ts → state/selection.test.ts} +0 -0
  546. /package/src/extensions/{pending → streaming/pending}/index.ts +0 -0
  547. /package/src/extensions/{pending → streaming/pending}/pending-text-stream.test.ts +0 -0
  548. /package/src/extensions/{scrolling → streaming/scrolling}/crawler.ts +0 -0
  549. /package/src/extensions/{scrolling → streaming/scrolling}/index.ts +0 -0
  550. /package/src/extensions/{scrolling → streaming/scrolling}/scroller.ts +0 -0
  551. /package/src/extensions/{typewriter.test.ts → streaming/typewriter.test.ts} +0 -0
@@ -0,0 +1,512 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type EditorState, type Extension, RangeSet, StateEffect, StateField } from '@codemirror/state';
6
+ import {
7
+ Decoration,
8
+ type DecorationSet,
9
+ EditorView,
10
+ type GutterMarker,
11
+ type Tooltip,
12
+ WidgetType,
13
+ hoverTooltip,
14
+ } from '@codemirror/view';
15
+
16
+ import { Domino } from '@dxos/ui';
17
+
18
+ import { ChangeBarMarker, changeBars } from './change-bar';
19
+ import {
20
+ type DiffHunk,
21
+ type GroupPolicy,
22
+ type Hunk,
23
+ computeCharHunks,
24
+ diffHunks,
25
+ groupHunks,
26
+ pairMarkupHunks,
27
+ rebaseHunksWith,
28
+ } from './diff';
29
+
30
+ /** One author's proposed revision of the editor's document (the base). */
31
+ export type SuggestionSource = {
32
+ /** Stable author identity (e.g. an identity DID); distinguishes overlapping suggestions. */
33
+ author: string;
34
+ /** Author colour, supplied by the caller to match the collaboration awareness cursor palette. */
35
+ colour: string;
36
+ /** The author's full proposed content, diffed against the base (or the editor's document if no base). */
37
+ content: string;
38
+ /**
39
+ * The text this proposal was authored against (its branch's fork anchor). Diffing against it rather
40
+ * than the live document keeps the diff to what THIS author changed — otherwise text the reader has
41
+ * typed since the fork, which the proposal cannot contain, reads as the author deleting it.
42
+ * Falls back to {@link SuggestionsOptions.base}, then the editor's document.
43
+ */
44
+ base?: string;
45
+ };
46
+
47
+ export type SuggestionsOptions = {
48
+ /** The proposals to overlay; each is diffed against the editor's document. */
49
+ sources: SuggestionSource[];
50
+ /**
51
+ * The accepted base (`main`) each source is diffed against. When given, a source is diffed
52
+ * `base` vs `source.content` and its hunks are rebased into the editor document's coordinates (see
53
+ * {@link rebaseHunks}) — so a foreign author's proposal decorates the right span even after the user's
54
+ * own branch (the editor document) has diverged from `base`. Omit to diff each source directly against
55
+ * the editor document (the standalone single-branch behaviour).
56
+ */
57
+ base?: string;
58
+ /**
59
+ * Show the hover Accept/Reject controls (default true). The ambient review overlay disables them
60
+ * (DESIGN §4, Decision 4: the companion cards are the review surface); the explicit compare path
61
+ * keeps them as its inline accept mechanism.
62
+ */
63
+ hoverControls?: boolean;
64
+ /**
65
+ * Optional grouping policy: coalesce an author's adjacent hunks into one reviewable unit (see
66
+ * {@link groupHunks}). Omit to render one control per raw hunk.
67
+ */
68
+ group?: GroupPolicy;
69
+ /**
70
+ * Invoked when a change is accepted. When provided, the container owns the mutation (routing the
71
+ * accept through a durable operation that merges the hunk to the parent), so the extension does NOT
72
+ * splice locally — it only dismisses the widget. Absent ⇒ the extension splices the source's text
73
+ * into the document directly (standalone use).
74
+ */
75
+ onAccept?: (hunk: DiffHunk, author: string) => void;
76
+ /**
77
+ * Invoked when a change is rejected. Lets the container route the reject through a durable operation
78
+ * (revert the hunk on the author's branch). The widget is dismissed either way.
79
+ */
80
+ onReject?: (hunk: DiffHunk, author: string) => void;
81
+ /** Invoked when a change is clicked in the document, so the host can reveal it elsewhere. */
82
+ onSelect?: (hunk: DiffHunk, author: string) => void;
83
+ };
84
+
85
+ /** A hunk tagged with the source it came from, so overlapping suggestions stay attributable. */
86
+ type TaggedHunk = DiffHunk & { author: string; colour: string };
87
+
88
+ /**
89
+ * Position-independent key so a dismissal survives offset shifts from unrelated edits. Scoped by
90
+ * author so dismissing one author's change never hides an identical change by another.
91
+ */
92
+ export const suggestionKey = (hunk: DiffHunk, author: string): string => `${author} ${hunk.removed} ${hunk.inserted}`;
93
+
94
+ /** Adds a hunk to the dismissed (rejected, hidden) set without changing the document. */
95
+ const dismissEffect = StateEffect.define<string>();
96
+
97
+ type SuggestionState = {
98
+ dismissed: ReadonlySet<string>;
99
+ decorations: DecorationSet;
100
+ /** The currently visible (non-dismissed) hunks in document coordinates, for the tooltip layer. */
101
+ hunks: readonly TaggedHunk[];
102
+ /** Per-changed-line gutter change-bar markers, tinted with the author colour. */
103
+ changeBars: RangeSet<GutterMarker>;
104
+ };
105
+
106
+ /**
107
+ * Google-Docs-style multi-author suggestion overlay: renders each change between the editor's
108
+ * document (the base) and every {@link SuggestionSource} in place — the removed original struck
109
+ * through, the proposed replacement shown beside it, tinted with the author's colour — with hover
110
+ * Accept/Reject controls in the (non-clipped) tooltip layer. Overlapping suggestions from different
111
+ * authors stack deterministically (by
112
+ * offset, then author). Accept splices the source's version of that hunk into the document (merging
113
+ * the change); Reject hides the suggestion without altering the document (a view-only dismissal for
114
+ * the session).
115
+ */
116
+ export const suggestions = ({
117
+ sources,
118
+ base,
119
+ group,
120
+ hoverControls = true,
121
+ onAccept,
122
+ onReject,
123
+ onSelect,
124
+ }: SuggestionsOptions): Extension => {
125
+ const tagged = (state: EditorState): TaggedHunk[] => {
126
+ const doc = state.doc.toString();
127
+ // With an explicit base, diff each source against it and rebase the hunks into the (possibly
128
+ // diverged) document; without one, diff directly against the document (the original behaviour).
129
+ // The base↔doc char diff is the same for every source sharing a base, so compute each one once.
130
+ const charHunksFor = new Map<string, Hunk[]>();
131
+ const all: TaggedHunk[] = [];
132
+ for (const source of sources) {
133
+ const sourceBase = source.base ?? base;
134
+ const anchor = sourceBase ?? doc;
135
+ let charHunks: Hunk[] | undefined;
136
+ if (sourceBase !== undefined) {
137
+ charHunks = charHunksFor.get(sourceBase) ?? computeCharHunks(sourceBase, doc);
138
+ charHunksFor.set(sourceBase, charHunks);
139
+ }
140
+ // Edits the document already carries relative to the SAME anchor have been accepted, so they are
141
+ // no longer suggestions — matched by content (accepting shifts every later offset) and BEFORE
142
+ // grouping, since a group coalesces several hunks and would never match one of them.
143
+ const applied = sourceBase === undefined ? undefined : new Set(diffHunks(sourceBase, doc).map(editKey));
144
+ const authored = diffHunks(anchor, source.content);
145
+ const pending = applied === undefined ? authored : authored.filter((hunk) => !applied.has(editKey(hunk)));
146
+ // Matched delimiter pairs (e.g. `**` … `**`) become one hunk so the pair reviews and applies
147
+ // atomically — accepting half a pair leaves broken syntax.
148
+ const paired = pairMarkupHunks(pending, anchor);
149
+ const grouped = group ? groupHunks(paired, anchor, group) : paired;
150
+ const hunks = charHunks === undefined ? grouped : rebaseHunksWith(charHunks, grouped);
151
+ for (const hunk of hunks) {
152
+ all.push({ ...hunk, author: source.author, colour: source.colour });
153
+ }
154
+ }
155
+ // Deterministic stack order: earlier offset first, then by author so identical offsets are stable.
156
+ return all.sort(
157
+ (a, b) => a.from - b.from || a.to - b.to || (a.author < b.author ? -1 : a.author > b.author ? 1 : 0),
158
+ );
159
+ };
160
+
161
+ /**
162
+ * Has this change already landed in the document? Diffing a proposal against the revision its author
163
+ * wrote on keeps the diff to their edits, but it also keeps reporting them after they are accepted —
164
+ * the branch still differs from its own fork anchor. A change whose proposed text is already present
165
+ * (and whose replaced text is gone) has been applied, so it is no longer a suggestion.
166
+ */
167
+ const editKey = (hunk: DiffHunk): string => `${hunk.removed}\u241f${hunk.inserted}`;
168
+
169
+ const build = (state: EditorState, dismissed: ReadonlySet<string>): SuggestionState => {
170
+ const ranges = [];
171
+ const visible: TaggedHunk[] = [];
172
+ // First author to touch a line owns its gutter bar colour (hunks arrive sorted by offset, author).
173
+ const lineColour = new Map<number, { colour: string; rows?: number; offsetRows?: number }>();
174
+ for (const hunk of tagged(state)) {
175
+ if (dismissed.has(suggestionKey(hunk, hunk.author))) {
176
+ continue;
177
+ }
178
+ visible.push(hunk);
179
+ if (hunk.to > hunk.from) {
180
+ ranges.push(deleteMark.range(hunk.from, hunk.to));
181
+ }
182
+ // The inline preview of the proposed text; a pure deletion has none (the strikethrough carries it).
183
+ if (hunk.inserted) {
184
+ // `side: 1` keeps the preview adjacent to the text it follows. At the very end of the document
185
+ // that would leave no caret position past the widget (a trailing suggestion makes the end of
186
+ // the document unreachable), so anchor it before the final position instead.
187
+ const side = hunk.to === state.doc.length ? -1 : 1;
188
+ ranges.push(Decoration.widget({ widget: new SuggestionWidget(hunk), side }).range(hunk.to));
189
+ }
190
+ // A whitespace-only change (a proposed paragraph break) has no visible text on the line it
191
+ // anchors to; barring that line marks prose the author never touched.
192
+ if (hunk.removed.trim().length === 0 && hunk.inserted.trim().length === 0) {
193
+ continue;
194
+ }
195
+ // A pure BLOCK insertion renders wholly inside its widget, occupying the TOP rows of its host
196
+ // line — a full-height gutter bar would tint the host line's own text too, so its bar is
197
+ // capped to the proposal's rows.
198
+ if (hunk.removed === '' && hunk.inserted.endsWith('\n')) {
199
+ const from = state.doc.lineAt(hunk.from).from;
200
+ if (!lineColour.has(from)) {
201
+ const leading = hunk.inserted.length - hunk.inserted.replace(/^\n+/, '').length;
202
+ const core = hunk.inserted.replace(/^\n+/, '').replace(/\n+$/, '');
203
+ if (core.length > 0) {
204
+ lineColour.set(from, {
205
+ colour: hunk.colour,
206
+ rows: core.split('\n').length,
207
+ offsetRows: leading > 0 ? leading : undefined,
208
+ });
209
+ }
210
+ }
211
+ continue;
212
+ }
213
+ // Trim a trailing newline so a paragraph-break change does not tag the following (empty) line.
214
+ let end = hunk.to;
215
+ while (end > hunk.from && state.doc.sliceString(end - 1, end) === '\n') {
216
+ end--;
217
+ }
218
+ for (let line = state.doc.lineAt(hunk.from).number; line <= state.doc.lineAt(end).number; line++) {
219
+ const from = state.doc.line(line).from;
220
+ if (!lineColour.has(from)) {
221
+ lineColour.set(from, { colour: hunk.colour });
222
+ }
223
+ }
224
+ }
225
+ const changeBarSet = RangeSet.of(
226
+ [...lineColour.entries()]
227
+ .sort(([a], [b]) => a - b)
228
+ .map(([from, { colour, rows, offsetRows }]) => new ChangeBarMarker(colour, rows, offsetRows).range(from)),
229
+ );
230
+ // `sort: true` orders the mixed mark/widget ranges (multiple sources interleave arbitrarily).
231
+ return { dismissed, decorations: Decoration.set(ranges, true), hunks: visible, changeBars: changeBarSet };
232
+ };
233
+
234
+ const field = StateField.define<SuggestionState>({
235
+ create: (state) => build(state, new Set()),
236
+ update: (value, transaction) => {
237
+ const dismissed = new Set(value.dismissed);
238
+ let changed = false;
239
+ for (const effect of transaction.effects) {
240
+ if (effect.is(dismissEffect)) {
241
+ dismissed.add(effect.value);
242
+ changed = true;
243
+ }
244
+ }
245
+ if (!transaction.docChanged && !changed) {
246
+ return value;
247
+ }
248
+ return build(transaction.state, dismissed);
249
+ },
250
+ provide: (self) => EditorView.decorations.from(self, (value) => value.decorations),
251
+ });
252
+
253
+ return [
254
+ field,
255
+ selectHandler(field, onSelect),
256
+ hoverControls ? suggestTooltip(field, onAccept, onReject) : [],
257
+ changeBars((state) => state.field(field).changeBars),
258
+ suggestTheme,
259
+ ];
260
+ };
261
+
262
+ /**
263
+ * Reports the change under a click, so the host can reveal it on its other surfaces (the review
264
+ * companion accents the matching card). The click is not consumed — the caret still lands where the
265
+ * reader put it.
266
+ */
267
+ const selectHandler = (
268
+ field: StateField<SuggestionState>,
269
+ onSelect?: (hunk: DiffHunk, author: string) => void,
270
+ ): Extension =>
271
+ EditorView.domEventHandlers({
272
+ mousedown: (event, view) => {
273
+ if (!onSelect) {
274
+ return false;
275
+ }
276
+ const pos = view.posAtCoords({ x: event.clientX, y: event.clientY });
277
+ if (pos === null) {
278
+ return false;
279
+ }
280
+ const hunk = view.state
281
+ .field(field)
282
+ .hunks.find((candidate) =>
283
+ candidate.from === candidate.to ? pos === candidate.from : pos >= candidate.from && pos <= candidate.to,
284
+ );
285
+ if (hunk) {
286
+ onSelect(hunk, hunk.author);
287
+ }
288
+ return false;
289
+ },
290
+ });
291
+
292
+ /**
293
+ * Accept/reject controls in the CodeMirror tooltip layer (`.cm-tooltip`), which renders outside
294
+ * `.cm-scroller` and so is never clipped by its overflow (the failure of the old inline
295
+ * `position: absolute` popover). Hover-triggered per hunk: hovering a change's struck text or its
296
+ * inline preview surfaces the controls for that hunk.
297
+ */
298
+ const suggestTooltip = (
299
+ field: StateField<SuggestionState>,
300
+ onAccept?: (hunk: DiffHunk, author: string) => void,
301
+ onReject?: (hunk: DiffHunk, author: string) => void,
302
+ ): Extension => {
303
+ // The controls are anchored to the hunk, not the pointer, and the same instance is returned for
304
+ // every position within it — a fresh tooltip per pointer position makes them flicker and jump as the
305
+ // pointer crosses the change.
306
+ const tooltips = new Map<string, Tooltip>();
307
+ return hoverTooltip(
308
+ (view, pos): Tooltip | null => {
309
+ // Match the hunk under the pointer: a range covers `[from, to]`; a pure insertion (from === to,
310
+ // rendered as a widget) is matched at its single offset.
311
+ const hunk = view.state
312
+ .field(field)
313
+ .hunks.find((candidate) =>
314
+ candidate.from === candidate.to ? pos === candidate.from : pos >= candidate.from && pos <= candidate.to,
315
+ );
316
+ if (!hunk) {
317
+ return null;
318
+ }
319
+
320
+ const key = `${suggestionKey(hunk, hunk.author)} ${hunk.from}:${hunk.to}`;
321
+ const cached = tooltips.get(key);
322
+ if (cached) {
323
+ return cached;
324
+ }
325
+
326
+ // Only the hunk being hovered is worth keeping; the document moves on.
327
+ tooltips.clear();
328
+ const tooltip: Tooltip = {
329
+ pos: hunk.from,
330
+ end: hunk.to,
331
+ above: true,
332
+ create: () => ({ dom: createControls(view, hunk, onAccept, onReject) }),
333
+ };
334
+ tooltips.set(key, tooltip);
335
+ return tooltip;
336
+ },
337
+ {
338
+ hoverTime: 75,
339
+ // Accepting changes the document and rejecting dismisses the hunk; either way the controls act on
340
+ // a change that no longer exists, so they must go rather than wait for the pointer to move.
341
+ hideOn: (transaction) => transaction.docChanged || transaction.effects.some((effect) => effect.is(dismissEffect)),
342
+ },
343
+ );
344
+ };
345
+
346
+ /** Builds the accept/reject control row for a hunk; shared by the tooltip layer. */
347
+ const createControls = (
348
+ view: EditorView,
349
+ hunk: TaggedHunk,
350
+ onAccept?: (hunk: DiffHunk, author: string) => void,
351
+ onReject?: (hunk: DiffHunk, author: string) => void,
352
+ ): HTMLElement =>
353
+ Domino.of('div')
354
+ .classNames('cm-suggest-controls')
355
+ .append(
356
+ // Each control is a `dx-button` div wrapping a Phosphor icon.
357
+ Domino.of('div')
358
+ .classNames('dx-button aspect-square cm-suggest-accept')
359
+ .attributes({ 'role': 'button', 'data-density': 'sm', 'title': 'Accept change' })
360
+ .append(Domino.svg('ph--check--regular'))
361
+ .on('mousedown', (event) => {
362
+ event.preventDefault();
363
+ if (onAccept) {
364
+ // The container owns the mutation (durable cherry-pick op); the op's edit flows back and
365
+ // re-diffs the hunk away. Dismiss for immediate feedback — do not splice locally (that
366
+ // would double-apply the change onto the parent).
367
+ view.dispatch({ effects: dismissEffect.of(suggestionKey(hunk, hunk.author)) });
368
+ onAccept(hunk, hunk.author);
369
+ } else {
370
+ view.dispatch({ changes: { from: hunk.from, to: hunk.to, insert: hunk.inserted } });
371
+ }
372
+ }),
373
+ Domino.of('div')
374
+ .classNames('dx-button aspect-square cm-suggest-reject')
375
+ .attributes({ 'role': 'button', 'data-density': 'sm', 'title': 'Reject change' })
376
+ .append(Domino.svg('ph--x--regular'))
377
+ .on('mousedown', (event) => {
378
+ event.preventDefault();
379
+ view.dispatch({ effects: dismissEffect.of(suggestionKey(hunk, hunk.author)) });
380
+ onReject?.(hunk, hunk.author);
381
+ }),
382
+ ).root;
383
+
384
+ export type SuggestChangesOptions = {
385
+ /** The proposal (e.g. a branch's content) whose changes are suggested over the editor's document. */
386
+ proposal: string;
387
+ /**
388
+ * Author colour for the inline markers — pass the branch author's palette colour (e.g.
389
+ * `var(--color-<hue>-text)`) so the suggestion reads with the author's consistent colour. Defaults
390
+ * to the success colour for a standalone (author-agnostic) review.
391
+ */
392
+ colour?: string;
393
+ onAccept?: (hunk: DiffHunk) => void;
394
+ onReject?: (hunk: DiffHunk) => void;
395
+ };
396
+
397
+ /**
398
+ * Single-source suggestion overlay — the common case of one proposal (e.g. a branch) reviewed against
399
+ * the editor's document. A thin wrapper over {@link suggestions}.
400
+ */
401
+ export const suggestChanges = ({
402
+ proposal,
403
+ colour = 'var(--color-success-text)',
404
+ onAccept,
405
+ onReject,
406
+ }: SuggestChangesOptions): Extension =>
407
+ suggestions({
408
+ sources: [{ author: '', colour, content: proposal }],
409
+ onAccept: onAccept && ((hunk) => onAccept(hunk)),
410
+ onReject: onReject && ((hunk) => onReject(hunk)),
411
+ });
412
+
413
+ /** The replaced original as a plain strikethrough; attribution is carried by the coloured insert. */
414
+ const deleteMark = Decoration.mark({ class: 'cm-suggest-delete' });
415
+
416
+ /** Inline preview of a change's proposed text; the accept/reject controls live in the tooltip layer. */
417
+ class SuggestionWidget extends WidgetType {
418
+ #hunk: TaggedHunk;
419
+
420
+ constructor(hunk: TaggedHunk) {
421
+ super();
422
+ this.#hunk = hunk;
423
+ }
424
+
425
+ override eq(other: SuggestionWidget): boolean {
426
+ // The preview only renders the proposed text in the author's colour, so equality is content-only;
427
+ // author distinguishes overlapping suggestions with the same text.
428
+ return (
429
+ other.#hunk.inserted === this.#hunk.inserted &&
430
+ other.#hunk.colour === this.#hunk.colour &&
431
+ other.#hunk.author === this.#hunk.author
432
+ );
433
+ }
434
+
435
+ override toDOM(): HTMLElement {
436
+ // Only the CORE text carries the coloured chrome (underline/bar): leading and trailing newline
437
+ // runs render as plain unstyled text, so a proposal that starts or ends with a paragraph break
438
+ // still breaks the layout without drawing stray bar/underline fragments on empty rows — and a
439
+ // whitespace-only proposal (a bare paragraph break) previews as an unmarked break.
440
+ const inserted = this.#hunk.inserted;
441
+ const core = inserted.replace(/^\n+/, '').replace(/\n+$/, '');
442
+ const leading = inserted.slice(0, inserted.indexOf(core));
443
+ const trailing = core.length === 0 ? '' : inserted.slice(leading.length + core.length);
444
+ const root = Domino.of('span').classNames('cm-suggest-actions');
445
+ if (core.length === 0) {
446
+ return root.append(Domino.of('span').text(inserted)).root;
447
+ }
448
+ if (leading) {
449
+ root.append(Domino.of('span').text(leading));
450
+ }
451
+ root.append(
452
+ Domino.of('span')
453
+ .classNames('cm-suggest-insert')
454
+ // The author's colour carries attribution: coloured text underlined in the same colour.
455
+ .style({ color: this.#hunk.colour, borderBottomColor: this.#hunk.colour })
456
+ .text(core),
457
+ );
458
+ if (trailing) {
459
+ root.append(Domino.of('span').text(trailing));
460
+ }
461
+ return root.root;
462
+ }
463
+
464
+ // Pointer events must reach the editor: ignoring them left the preview inert, so hovering it never
465
+ // opened the accept/reject tooltip and clicking at or past a trailing preview never moved the caret.
466
+ // Everything else (input, composition) stays ignored — the preview is not editable text.
467
+ override ignoreEvent(event: Event): boolean {
468
+ return !(event instanceof MouseEvent || event instanceof PointerEvent);
469
+ }
470
+ }
471
+
472
+ const suggestTheme = EditorView.baseTheme({
473
+ // The replaced original: a plain, de-emphasised strikethrough — no fill, so overlapping deletions
474
+ // from multiple authors on the same span don't stack into a coloured mess.
475
+ '& .cm-suggest-delete': {
476
+ textDecoration: 'line-through',
477
+ textDecorationColor: 'var(--color-separator)',
478
+ opacity: 0.6,
479
+ },
480
+ // The proposed text: shown in the author's colour and underlined in it (both set inline per hunk),
481
+ // so attribution reads from colour rather than a uniform green fill.
482
+ '& .cm-suggest-insert': {
483
+ borderBottom: '2px solid transparent',
484
+ },
485
+ '& .cm-suggest-actions': {
486
+ marginInlineStart: '2px',
487
+ },
488
+ // Accept/reject render in the CodeMirror tooltip layer (a `.cm-tooltip` sibling of `.cm-scroller`),
489
+ // so the controls are never clipped by the scroller's overflow.
490
+ '& .cm-suggest-controls': {
491
+ display: 'flex',
492
+ gap: '2px',
493
+ padding: '2px',
494
+ borderRadius: '4px',
495
+ background: 'var(--color-modal-surface)',
496
+ border: '1px solid var(--color-separator)',
497
+ boxShadow: '0 2px 6px color-mix(in srgb, black 25%, transparent)',
498
+ whiteSpace: 'nowrap',
499
+ },
500
+ // Compact popover buttons sized to the text.
501
+ '& .cm-suggest-accept, & .cm-suggest-reject': {
502
+ blockSize: '1.3em',
503
+ inlineSize: '1.3em',
504
+ minBlockSize: '0',
505
+ },
506
+ '& .cm-suggest-accept': {
507
+ color: 'var(--color-success-text)',
508
+ },
509
+ '& .cm-suggest-reject': {
510
+ color: 'var(--color-error-text)',
511
+ },
512
+ });
@@ -0,0 +1,58 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { EditorView } from '@codemirror/view';
6
+ import { describe, test } from 'vitest';
7
+
8
+ import { decorationSetToArray } from '../../util';
9
+ import { type SuggestionSource } from './suggest';
10
+ import { suggestionsOverlay } from './suggestions-overlay';
11
+
12
+ const ORIGINAL = 'The quick brown fox jumps over the lazy dog.';
13
+ // A pure insertion (adds "swiftly") relative to ORIGINAL, so the overlay renders one hunk.
14
+ const WITH_INSERTED_WORD = 'The quick brown fox jumps swiftly over the lazy dog.';
15
+
16
+ /** Count every decoration range the state's decoration sources currently contribute. */
17
+ const decorationCount = (view: EditorView): number => {
18
+ let count = 0;
19
+ for (const source of view.state.facet(EditorView.decorations)) {
20
+ const set = typeof source === 'function' ? source(view) : source;
21
+ if (set) {
22
+ count += decorationSetToArray(set).length;
23
+ }
24
+ }
25
+ return count;
26
+ };
27
+
28
+ describe('suggestionsOverlay', () => {
29
+ test('reconfigure swaps the live suggestion decorations in and out', ({ expect }) => {
30
+ const overlay = suggestionsOverlay();
31
+ const view = new EditorView({ doc: ORIGINAL, extensions: [overlay.extension] });
32
+
33
+ // Nothing overlaid until reconfigured.
34
+ expect(decorationCount(view)).toBe(0);
35
+
36
+ const sources: SuggestionSource[] = [
37
+ { author: 'did:alice', colour: 'var(--color-blue-text)', content: WITH_INSERTED_WORD },
38
+ ];
39
+ overlay.reconfigure(view, sources, true);
40
+ expect(decorationCount(view)).toBeGreaterThan(0);
41
+
42
+ // Disabling (or clearing sources) tears the overlay back down live, without remounting.
43
+ overlay.reconfigure(view, [], false);
44
+ expect(decorationCount(view)).toBe(0);
45
+
46
+ view.destroy();
47
+ });
48
+
49
+ test('reconfigure with enabled=true but no sources produces no decorations', ({ expect }) => {
50
+ const overlay = suggestionsOverlay();
51
+ const view = new EditorView({ doc: ORIGINAL, extensions: [overlay.extension] });
52
+
53
+ overlay.reconfigure(view, [], true);
54
+ expect(decorationCount(view)).toBe(0);
55
+
56
+ view.destroy();
57
+ });
58
+ });
@@ -0,0 +1,50 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Compartment, type Extension } from '@codemirror/state';
6
+ import { EditorView } from '@codemirror/view';
7
+
8
+ import { type DiffHunk } from './diff';
9
+ import { type SuggestionSource, suggestions } from './suggest';
10
+
11
+ export type SuggestionsOverlay = {
12
+ /** Initially empty; install once alongside the editor's other extensions. */
13
+ extension: Extension;
14
+ /**
15
+ * Live-swaps the aggregated suggestion sources without remounting the editor. Pass `base` (the
16
+ * accepted `main`) when the editor is bound to a diverged document (e.g. the user's own suggestion
17
+ * branch in Suggesting mode) so each source is diffed against `base` and rebased into the document's
18
+ * coordinates; omit it to diff sources directly against the editor document.
19
+ */
20
+ reconfigure: (view: EditorView, sources: SuggestionSource[], enabled: boolean, base?: string) => void;
21
+ };
22
+
23
+ /**
24
+ * Compartment-backed overlay of ALL active suggestion branches' changes for the ambient review
25
+ * model: the editor's default view renders every open suggestion branch as a live suggestion layer,
26
+ * and the controller reconfigures the aggregated `sources` in place as branches open/close/edit —
27
+ * mirroring how `MarkdownArticle` reconfigures its compare overlay via `compareCompartment`, so
28
+ * toggling the overlay never remounts the editor (which would rebind automerge and lose
29
+ * scroll/selection).
30
+ */
31
+ export const suggestionsOverlay = (
32
+ onAccept?: (hunk: DiffHunk, author: string) => void,
33
+ onReject?: (hunk: DiffHunk, author: string) => void,
34
+ onSelect?: (hunk: DiffHunk, author: string) => void,
35
+ ): SuggestionsOverlay => {
36
+ const compartment = new Compartment();
37
+ return {
38
+ extension: compartment.of([]),
39
+ reconfigure: (view: EditorView, sources: SuggestionSource[], enabled: boolean, base?: string) => {
40
+ view.dispatch({
41
+ effects: compartment.reconfigure(
42
+ // Hover accept/reject is disabled on the ambient path (Decision 4): cards are the surface.
43
+ enabled && sources.length > 0
44
+ ? suggestions({ sources, base, hoverControls: false, onAccept, onReject, onSelect })
45
+ : [],
46
+ ),
47
+ });
48
+ },
49
+ };
50
+ };