@opengeni/react 0.46.1 → 0.52.1

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 (341) hide show
  1. package/README.md +158 -14
  2. package/dist/artifacts-document.d.ts +3 -0
  3. package/dist/artifacts-document.js +17 -0
  4. package/dist/artifacts-document.js.map +1 -0
  5. package/dist/artifacts-presentation.d.ts +3 -0
  6. package/dist/artifacts-presentation.js +17 -0
  7. package/dist/artifacts-presentation.js.map +1 -0
  8. package/dist/artifacts-spreadsheet.d.ts +3 -0
  9. package/dist/artifacts-spreadsheet.js +17 -0
  10. package/dist/artifacts-spreadsheet.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +170 -0
  13. package/dist/artifacts.js.map +1 -0
  14. package/dist/chunk-6BBWWOOU.js +82 -0
  15. package/dist/chunk-6BBWWOOU.js.map +1 -0
  16. package/dist/{chunk-HFO4ERGQ.js → chunk-724BW4TW.js} +586 -35
  17. package/dist/chunk-724BW4TW.js.map +1 -0
  18. package/dist/chunk-7I2KB3OK.js +3895 -0
  19. package/dist/chunk-7I2KB3OK.js.map +1 -0
  20. package/dist/chunk-7WCLAEFV.js +2828 -0
  21. package/dist/chunk-7WCLAEFV.js.map +1 -0
  22. package/dist/{chunk-U3CSCMWA.js → chunk-FK6VG4LL.js} +9 -13
  23. package/dist/chunk-FK6VG4LL.js.map +1 -0
  24. package/dist/{chunk-SRFUT2ZU.js → chunk-FWS3KC3Z.js} +120 -64
  25. package/dist/chunk-FWS3KC3Z.js.map +1 -0
  26. package/dist/{chunk-EKZH6IDG.js → chunk-HDBG7XGO.js} +62 -1
  27. package/dist/chunk-HDBG7XGO.js.map +1 -0
  28. package/dist/chunk-HUIKIDM5.js +2148 -0
  29. package/dist/chunk-HUIKIDM5.js.map +1 -0
  30. package/dist/{chunk-LGVMUIRV.js → chunk-JAGDUCZQ.js} +1053 -390
  31. package/dist/chunk-JAGDUCZQ.js.map +1 -0
  32. package/dist/{chunk-LWR4MXSS.js → chunk-JR3QFY6B.js} +420 -35
  33. package/dist/chunk-JR3QFY6B.js.map +1 -0
  34. package/dist/chunk-JVYQRRC4.js +53 -0
  35. package/dist/chunk-JVYQRRC4.js.map +1 -0
  36. package/dist/chunk-KCGXPP5V.js +157 -0
  37. package/dist/chunk-KCGXPP5V.js.map +1 -0
  38. package/dist/{chunk-EB67J347.js → chunk-KZ73RL76.js} +111 -31
  39. package/dist/chunk-KZ73RL76.js.map +1 -0
  40. package/dist/chunk-PEI6RANI.js +3234 -0
  41. package/dist/chunk-PEI6RANI.js.map +1 -0
  42. package/dist/{chunk-3PB7MIT6.js → chunk-ROIRTM37.js} +220 -72
  43. package/dist/chunk-ROIRTM37.js.map +1 -0
  44. package/dist/chunk-VZTQ73XT.js +86 -0
  45. package/dist/chunk-VZTQ73XT.js.map +1 -0
  46. package/dist/{chunk-VCBFUSUZ.js → chunk-WSK7G5LE.js} +72 -4
  47. package/dist/chunk-WSK7G5LE.js.map +1 -0
  48. package/dist/{chunk-FGZUCXZF.js → chunk-WYA65Y3F.js} +16 -16
  49. package/dist/chunk-WYA65Y3F.js.map +1 -0
  50. package/dist/{chunk-6EDV6YFE.js → chunk-X4S56Y2H.js} +147 -35
  51. package/dist/chunk-X4S56Y2H.js.map +1 -0
  52. package/dist/client.d.ts +7 -1
  53. package/dist/clipboard.d.ts +1 -0
  54. package/dist/clipboard.js +7 -0
  55. package/dist/clipboard.js.map +1 -0
  56. package/dist/commands/index.d.ts +3 -3
  57. package/dist/commands/registry.d.ts +1 -1
  58. package/dist/commands/types.d.ts +1 -1
  59. package/dist/components/approval-surface.d.ts +2 -2
  60. package/dist/components/artifacts/artifact-surface.d.ts +18 -0
  61. package/dist/components/artifacts/document-editor.d.ts +172 -0
  62. package/dist/components/artifacts/editable-artifact-ui.d.ts +13 -0
  63. package/dist/components/artifacts/editable-artifact-workbench.d.ts +49 -0
  64. package/dist/components/artifacts/editable-document.d.ts +24 -0
  65. package/dist/components/artifacts/editable-presentation.d.ts +24 -0
  66. package/dist/components/artifacts/editable-spreadsheet.d.ts +32 -0
  67. package/dist/components/artifacts/index.d.ts +8 -0
  68. package/dist/components/artifacts/presentation-editor.d.ts +297 -0
  69. package/dist/components/artifacts/spreadsheet-canvas.d.ts +120 -0
  70. package/dist/components/artifacts/spreadsheet-grid.d.ts +113 -0
  71. package/dist/components/browser-viewer.d.ts +29 -0
  72. package/dist/components/chat-composer.d.ts +13 -8
  73. package/dist/components/code-editor.d.ts +25 -3
  74. package/dist/components/command-palette.d.ts +2 -2
  75. package/dist/components/composer-transcription-control.d.ts +4 -2
  76. package/dist/components/composer.d.ts +29 -15
  77. package/dist/components/computer-viewer.d.ts +23 -0
  78. package/dist/components/copy-button.d.ts +2 -2
  79. package/dist/components/desktop-viewer.d.ts +25 -21
  80. package/dist/components/diff-view.d.ts +1 -1
  81. package/dist/components/enrollment-consent.d.ts +1 -1
  82. package/dist/components/enrollment-device-flow.d.ts +1 -1
  83. package/dist/components/file-browser.d.ts +2 -2
  84. package/dist/components/fleet-tile.d.ts +1 -1
  85. package/dist/components/generated-video-player.d.ts +10 -0
  86. package/dist/components/human-input-form.d.ts +1 -1
  87. package/dist/components/human-input-surface.d.ts +2 -2
  88. package/dist/components/interactive-workbench-viewers.d.ts +2 -0
  89. package/dist/components/machine-card.d.ts +2 -2
  90. package/dist/components/machine-dock-bar.d.ts +3 -3
  91. package/dist/components/machine-health-pill.d.ts +2 -2
  92. package/dist/components/machine-input-display.d.ts +1 -1
  93. package/dist/components/machine-metrics.d.ts +2 -2
  94. package/dist/components/machine-status-pill.d.ts +4 -4
  95. package/dist/components/machines/health.d.ts +1 -1
  96. package/dist/components/machines/machine-detail.d.ts +5 -3
  97. package/dist/components/machines/metric-history-chart.d.ts +1 -1
  98. package/dist/components/machines/metric-sparkline.d.ts +2 -2
  99. package/dist/components/machines/series.d.ts +2 -2
  100. package/dist/components/machines-dashboard.d.ts +5 -5
  101. package/dist/components/markdown.d.ts +1 -1
  102. package/dist/components/message-timeline.d.ts +22 -5
  103. package/dist/components/model-picker.d.ts +2 -2
  104. package/dist/components/model-policy-picker.d.ts +7 -7
  105. package/dist/components/pierre-diff.d.ts +1 -1
  106. package/dist/components/pierre-file.d.ts +1 -1
  107. package/dist/components/queue-draft-policy.d.ts +1 -1
  108. package/dist/components/queue-surface-implementation.d.ts +3 -3
  109. package/dist/components/queue-surface-state.d.ts +4 -4
  110. package/dist/components/queue-surface.d.ts +5 -5
  111. package/dist/components/sandbox-files.d.ts +5 -5
  112. package/dist/components/sandbox-terminal.d.ts +50 -4
  113. package/dist/components/sandbox-workspace.d.ts +30 -10
  114. package/dist/components/session-chrome.d.ts +4 -4
  115. package/dist/components/session-status.d.ts +2 -2
  116. package/dist/components/timeline-annotation-selection.d.ts +9 -0
  117. package/dist/components/timeline-annotations-dialog.d.ts +16 -0
  118. package/dist/components/timeline-annotations.d.ts +13 -0
  119. package/dist/components/tooltip.d.ts +4 -4
  120. package/dist/components/user-message-body.d.ts +2 -2
  121. package/dist/components/workbench-changes.d.ts +2 -2
  122. package/dist/components/workspace-dock.d.ts +3 -1
  123. package/dist/composer.d.ts +11 -11
  124. package/dist/composer.js +7 -7
  125. package/dist/hooks/internal.d.ts +7 -1
  126. package/dist/hooks/use-attached-browsers.d.ts +21 -0
  127. package/dist/hooks/use-available-models.d.ts +2 -2
  128. package/dist/hooks/use-billing-usage.d.ts +1 -1
  129. package/dist/hooks/use-browser-frame-stream.d.ts +28 -0
  130. package/dist/hooks/use-browser-identities.d.ts +26 -0
  131. package/dist/hooks/use-browser-session.d.ts +28 -0
  132. package/dist/hooks/use-browser-sessions.d.ts +32 -0
  133. package/dist/hooks/use-codex-accounts.d.ts +3 -3
  134. package/dist/hooks/use-composer.d.ts +36 -9
  135. package/dist/hooks/use-computer-frame-stream.d.ts +24 -0
  136. package/dist/hooks/use-computer-session.d.ts +28 -0
  137. package/dist/hooks/use-computer-sessions.d.ts +28 -0
  138. package/dist/hooks/use-desktop-stream.d.ts +1 -1
  139. package/dist/hooks/use-environments.d.ts +11 -5
  140. package/dist/hooks/use-file-attachments.d.ts +1 -1
  141. package/dist/hooks/use-goal.d.ts +2 -2
  142. package/dist/hooks/use-human-input.d.ts +2 -2
  143. package/dist/hooks/use-last-started-turn-policy.d.ts +2 -2
  144. package/dist/hooks/use-machine-chip.d.ts +2 -2
  145. package/dist/hooks/use-machines.d.ts +10 -3
  146. package/dist/hooks/use-packs.d.ts +7 -2
  147. package/dist/hooks/use-rigs.d.ts +1 -1
  148. package/dist/hooks/use-sandbox-files.d.ts +25 -4
  149. package/dist/hooks/use-sandbox-git.d.ts +13 -5
  150. package/dist/hooks/use-sandbox-terminal.d.ts +1 -1
  151. package/dist/hooks/use-scheduled-tasks.d.ts +1 -1
  152. package/dist/hooks/use-session-capabilities.d.ts +5 -4
  153. package/dist/hooks/use-session-control.d.ts +1 -1
  154. package/dist/hooks/use-session-events.d.ts +2 -2
  155. package/dist/hooks/use-session-lineage.d.ts +1 -1
  156. package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
  157. package/dist/hooks/use-session.d.ts +2 -2
  158. package/dist/hooks/use-slash-commands.d.ts +1 -1
  159. package/dist/hooks/use-terminal-stream.d.ts +8 -2
  160. package/dist/hooks/use-transcription.d.ts +2 -2
  161. package/dist/hooks/use-turn-queue.d.ts +2 -2
  162. package/dist/hooks/use-video-artifact-playback.d.ts +9 -0
  163. package/dist/hooks/use-voice-input.d.ts +6 -4
  164. package/dist/hooks/use-workspace-capture.d.ts +1 -1
  165. package/dist/hooks/use-workspace-edit.d.ts +1 -1
  166. package/dist/hooks/use-workspace-sessions.d.ts +1 -1
  167. package/dist/hooks/use-workspaces.d.ts +1 -1
  168. package/dist/index.d.ts +158 -154
  169. package/dist/index.js +1718 -908
  170. package/dist/index.js.map +1 -1
  171. package/dist/interaction.d.ts +23 -0
  172. package/dist/interaction.js +36 -0
  173. package/dist/interaction.js.map +1 -0
  174. package/dist/interactive-workbench-viewers-KHNOHWQE.js +13 -0
  175. package/dist/interactive-workbench-viewers-KHNOHWQE.js.map +1 -0
  176. package/dist/lib/composer-responsive-context.d.ts +9 -0
  177. package/dist/lib/relay-wire.d.ts +4 -0
  178. package/dist/lib/sandbox-liveness.d.ts +9 -0
  179. package/dist/lib/terminal-capability.d.ts +5 -0
  180. package/dist/lib/use-portal-token-style.d.ts +14 -3
  181. package/dist/lib/xterm-theme.d.ts +1 -1
  182. package/dist/machines.d.ts +30 -30
  183. package/dist/machines.js +3 -3
  184. package/dist/provider.d.ts +4 -4
  185. package/dist/{queue-surface-implementation-J4XJFKRW.js → queue-surface-implementation-2KVJTYXH.js} +117 -64
  186. package/dist/queue-surface-implementation-2KVJTYXH.js.map +1 -0
  187. package/dist/realtime/dropdown-menu.d.ts +8 -8
  188. package/dist/realtime/realtime-control.d.ts +15 -6
  189. package/dist/realtime.d.ts +4 -4
  190. package/dist/realtime.js +30 -33
  191. package/dist/realtime.js.map +1 -1
  192. package/dist/session-context.d.ts +19 -1
  193. package/dist/session-ui.d.ts +14 -14
  194. package/dist/session-ui.js +6 -5
  195. package/dist/session.d.ts +28 -28
  196. package/dist/session.js +5 -5
  197. package/dist/timeline/activity-rail.d.ts +6 -3
  198. package/dist/timeline/disclosure-context.d.ts +1 -1
  199. package/dist/timeline/fleet-decision-projection.d.ts +1 -1
  200. package/dist/timeline/fleet-decision-row.d.ts +3 -3
  201. package/dist/timeline/index.d.ts +19 -19
  202. package/dist/timeline/parsers.d.ts +6 -3
  203. package/dist/timeline/projection.d.ts +3 -3
  204. package/dist/timeline/registry.d.ts +10 -1
  205. package/dist/timeline/screenshot-lightbox.d.ts +2 -2
  206. package/dist/timeline/shared.d.ts +21 -10
  207. package/dist/timeline/tool-diff.d.ts +2 -2
  208. package/dist/timeline/tool-renderers.d.ts +1 -1
  209. package/dist/timeline/turn-summary.d.ts +3 -3
  210. package/dist/timeline/types.d.ts +42 -2
  211. package/dist/timeline-annotation-selection-3L7LHGG2.js +187 -0
  212. package/dist/timeline-annotation-selection-3L7LHGG2.js.map +1 -0
  213. package/dist/timeline-annotations-dialog-U7EVLR75.js +196 -0
  214. package/dist/timeline-annotations-dialog-U7EVLR75.js.map +1 -0
  215. package/dist/voice-recording-store.d.ts +8 -2
  216. package/package.json +52 -7
  217. package/src/artifacts-document.ts +31 -0
  218. package/src/artifacts-presentation.ts +36 -0
  219. package/src/artifacts-spreadsheet.ts +21 -0
  220. package/src/artifacts.ts +86 -0
  221. package/src/client.ts +90 -1
  222. package/src/clipboard.ts +2 -0
  223. package/src/components/artifacts/artifact-surface.tsx +93 -0
  224. package/src/components/artifacts/document-editor.tsx +2255 -0
  225. package/src/components/artifacts/editable-artifact-ui.tsx +89 -0
  226. package/src/components/artifacts/editable-artifact-workbench.tsx +203 -0
  227. package/src/components/artifacts/editable-document.tsx +657 -0
  228. package/src/components/artifacts/editable-presentation.tsx +1020 -0
  229. package/src/components/artifacts/editable-spreadsheet.tsx +733 -0
  230. package/src/components/artifacts/index.ts +99 -0
  231. package/src/components/artifacts/presentation-editor.tsx +3148 -0
  232. package/src/components/artifacts/spreadsheet-canvas.ts +799 -0
  233. package/src/components/artifacts/spreadsheet-grid.tsx +2129 -0
  234. package/src/components/browser-viewer.tsx +1592 -0
  235. package/src/components/chat-composer.tsx +25 -2
  236. package/src/components/code-editor.tsx +90 -31
  237. package/src/components/command-palette.tsx +1 -1
  238. package/src/components/composer-transcription-control.tsx +51 -22
  239. package/src/components/composer.tsx +49 -23
  240. package/src/components/computer-viewer.tsx +1139 -0
  241. package/src/components/copy-button.tsx +1 -1
  242. package/src/components/desktop-viewer.tsx +103 -75
  243. package/src/components/file-browser.tsx +39 -17
  244. package/src/components/generated-video-player.tsx +89 -0
  245. package/src/components/human-input-form.tsx +2 -2
  246. package/src/components/interactive-workbench-viewers.ts +2 -0
  247. package/src/components/machine-card.tsx +1 -1
  248. package/src/components/machine-input-display.ts +3 -0
  249. package/src/components/machines/machine-detail.tsx +39 -15
  250. package/src/components/machines-dashboard.tsx +7 -6
  251. package/src/components/message-timeline.tsx +327 -37
  252. package/src/components/model-picker.tsx +2 -2
  253. package/src/components/model-policy-picker.tsx +16 -15
  254. package/src/components/queue-surface-implementation.tsx +135 -75
  255. package/src/components/queue-surface-state.tsx +2 -2
  256. package/src/components/sandbox-files.tsx +142 -62
  257. package/src/components/sandbox-terminal.tsx +119 -35
  258. package/src/components/sandbox-workspace.tsx +394 -228
  259. package/src/components/session-chrome.tsx +67 -27
  260. package/src/components/timeline-annotation-selection.tsx +221 -0
  261. package/src/components/timeline-annotations-dialog.tsx +213 -0
  262. package/src/components/timeline-annotations.tsx +84 -0
  263. package/src/components/tooltip.tsx +13 -16
  264. package/src/components/user-message-body.tsx +1 -1
  265. package/src/components/workbench-changes.tsx +192 -35
  266. package/src/components/workspace-dock.tsx +13 -5
  267. package/src/composer.ts +3 -0
  268. package/src/hooks/internal.ts +132 -38
  269. package/src/hooks/use-attached-browsers.ts +152 -0
  270. package/src/hooks/use-browser-frame-stream.ts +316 -0
  271. package/src/hooks/use-browser-identities.ts +225 -0
  272. package/src/hooks/use-browser-session.ts +476 -0
  273. package/src/hooks/use-browser-sessions.ts +281 -0
  274. package/src/hooks/use-codex-accounts.ts +4 -4
  275. package/src/hooks/use-composer.ts +769 -24
  276. package/src/hooks/use-computer-frame-stream.ts +311 -0
  277. package/src/hooks/use-computer-session.ts +442 -0
  278. package/src/hooks/use-computer-sessions.ts +245 -0
  279. package/src/hooks/use-environments.ts +24 -5
  280. package/src/hooks/use-file-attachments.ts +70 -32
  281. package/src/hooks/use-goal.ts +17 -5
  282. package/src/hooks/use-machine-chip.ts +5 -3
  283. package/src/hooks/use-machines.ts +79 -3
  284. package/src/hooks/use-packs.ts +51 -0
  285. package/src/hooks/use-sandbox-files.ts +382 -88
  286. package/src/hooks/use-sandbox-git.ts +208 -46
  287. package/src/hooks/use-sandbox-terminal.ts +3 -2
  288. package/src/hooks/use-session-capabilities.ts +94 -70
  289. package/src/hooks/use-session-events.ts +18 -4
  290. package/src/hooks/use-terminal-stream.ts +102 -23
  291. package/src/hooks/use-transcription.ts +4 -14
  292. package/src/hooks/use-turn-queue.ts +17 -5
  293. package/src/hooks/use-video-artifact-playback.ts +28 -0
  294. package/src/hooks/use-voice-input.ts +163 -19
  295. package/src/hooks/use-windowed-sections.ts +9 -7
  296. package/src/hooks/use-workspace-edit.ts +2 -1
  297. package/src/index.ts +21 -6
  298. package/src/interaction.ts +71 -0
  299. package/src/lib/composer-responsive-context.ts +15 -0
  300. package/src/lib/relay-wire.ts +4 -0
  301. package/src/lib/sandbox-liveness.ts +11 -0
  302. package/src/lib/terminal-capability.ts +10 -0
  303. package/src/lib/use-portal-token-style.ts +60 -15
  304. package/src/provider.tsx +52 -7
  305. package/src/realtime/dropdown-menu.tsx +15 -19
  306. package/src/realtime/realtime-control.tsx +16 -5
  307. package/src/session-context.ts +90 -0
  308. package/src/timeline/activity-rail.tsx +31 -6
  309. package/src/timeline/index.ts +4 -1
  310. package/src/timeline/parsers.ts +46 -20
  311. package/src/timeline/projection.ts +233 -13
  312. package/src/timeline/registry.ts +19 -0
  313. package/src/timeline/screenshot-lightbox.tsx +30 -9
  314. package/src/timeline/shared.tsx +59 -11
  315. package/src/timeline/tool-renderers.tsx +421 -24
  316. package/src/timeline/turn-summary.tsx +2 -2
  317. package/src/timeline/types.ts +58 -2
  318. package/src/voice-recording-store.ts +15 -0
  319. package/styles/compiled.css +5298 -0
  320. package/styles/compiled.d.ts +2 -0
  321. package/styles/effective-tokens.css +27 -0
  322. package/styles/index.css +18 -0
  323. package/styles/responsive.css +208 -0
  324. package/styles/responsive.d.ts +2 -0
  325. package/styles/tokens.css +50 -17
  326. package/dist/chunk-3PB7MIT6.js.map +0 -1
  327. package/dist/chunk-6EDV6YFE.js.map +0 -1
  328. package/dist/chunk-EB67J347.js.map +0 -1
  329. package/dist/chunk-EKZH6IDG.js.map +0 -1
  330. package/dist/chunk-FGZUCXZF.js.map +0 -1
  331. package/dist/chunk-HFO4ERGQ.js.map +0 -1
  332. package/dist/chunk-JB5725BI.js +0 -48
  333. package/dist/chunk-JB5725BI.js.map +0 -1
  334. package/dist/chunk-LGVMUIRV.js.map +0 -1
  335. package/dist/chunk-LWR4MXSS.js.map +0 -1
  336. package/dist/chunk-SRFUT2ZU.js.map +0 -1
  337. package/dist/chunk-U3CSCMWA.js.map +0 -1
  338. package/dist/chunk-VCBFUSUZ.js.map +0 -1
  339. package/dist/fleet-decision-projection-SLNZMS42.js +0 -241
  340. package/dist/fleet-decision-projection-SLNZMS42.js.map +0 -1
  341. package/dist/queue-surface-implementation-J4XJFKRW.js.map +0 -1
@@ -0,0 +1,657 @@
1
+ import {
2
+ DOCUMENT_ARTIFACT_COMMAND_VERSION,
3
+ type DocumentArtifactCommand,
4
+ type DocumentArtifactPageGeometryProjection,
5
+ type DocumentArtifactProjection,
6
+ type DocumentArtifactProjectionItem,
7
+ type DocumentArtifactQuery,
8
+ type DocumentArtifactTextStylePatch,
9
+ type EditableArtifactSession,
10
+ } from "@opengeni/sdk/editable-artifacts";
11
+ import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
12
+
13
+ import {
14
+ DocumentProjectionArtifactSurface,
15
+ type DocumentCommit,
16
+ type DocumentCommitHandler,
17
+ type DocumentEditorProjection,
18
+ type DocumentProjectionArtifactSurfaceProps,
19
+ type DocumentProjectionPageGeometry,
20
+ } from "./document-editor";
21
+ import { ArtifactSurface } from "./artifact-surface";
22
+ import {
23
+ asEditableArtifactError,
24
+ editableArtifactAccessRevoked,
25
+ editableArtifactProjectionKey,
26
+ editableArtifactStatusLabel,
27
+ EditableArtifactMessage,
28
+ useEditableArtifactView,
29
+ } from "./editable-artifact-ui";
30
+
31
+ const DOCUMENT_PAGE_ITEMS = 512;
32
+ const DOCUMENT_MAX_BLOCKS = 4_096;
33
+ const DOCUMENT_MAX_SECTIONS = 1_024;
34
+ const DOCUMENT_MAX_REVIEW_ITEMS = 4_096;
35
+ const DOCUMENT_MAX_TEXT_UTF16 = 1_000_000;
36
+ const DOCUMENT_MAX_TABLE_CELLS = 100_000;
37
+ const DOCUMENT_MAX_QUERY_PAGES = 64;
38
+ const DOCUMENT_COMPOSITION_ATTEMPTS = 3;
39
+
40
+ export type EditableDocumentArtifactSurfaceProps = Omit<
41
+ DocumentProjectionArtifactSurfaceProps,
42
+ "projection" | "commit" | "readOnly" | "busy" | "subtitle"
43
+ > & {
44
+ session: EditableArtifactSession;
45
+ subtitle?: ReactNode | undefined;
46
+ readOnly?: boolean | undefined;
47
+ /** Called only after the durable SDK command has been accepted locally. */
48
+ onCommit?: DocumentCommitHandler | undefined;
49
+ };
50
+
51
+ type DocumentProjectionState = {
52
+ session: EditableArtifactSession;
53
+ projection: DocumentEditorProjection | null;
54
+ loading: boolean;
55
+ error: Error | null;
56
+ };
57
+
58
+ type DocumentCommandState = {
59
+ session: EditableArtifactSession;
60
+ tail: Promise<void>;
61
+ paragraphText: Map<string, string>;
62
+ localParagraphIds: Set<string>;
63
+ optimisticParagraphIds: Map<string, string>;
64
+ };
65
+
66
+ /**
67
+ * Durable document editor over the public SDK session. Canonical state and
68
+ * speculative replay remain Worker-owned; React receives bounded projections.
69
+ */
70
+ export function EditableDocumentArtifactSurface({
71
+ session,
72
+ title,
73
+ subtitle,
74
+ readOnly = false,
75
+ onCommit,
76
+ onCommandError,
77
+ ...surfaceProps
78
+ }: EditableDocumentArtifactSurfaceProps) {
79
+ const view = useEditableArtifactView(session);
80
+ const invalidator = editableArtifactProjectionKey(view);
81
+ const [retryEpoch, setRetryEpoch] = useState(0);
82
+ const [state, setState] = useState<DocumentProjectionState>({
83
+ session,
84
+ projection: null,
85
+ loading: true,
86
+ error: null,
87
+ });
88
+ const loadGeneration = useRef(0);
89
+ const commandStateRef = useRef<DocumentCommandState | null>(null);
90
+ if (commandStateRef.current?.session !== session) {
91
+ commandStateRef.current = {
92
+ session,
93
+ tail: Promise.resolve(),
94
+ paragraphText: new Map(),
95
+ localParagraphIds: new Set(),
96
+ optimisticParagraphIds: new Map(),
97
+ };
98
+ }
99
+ const commandState = commandStateRef.current;
100
+
101
+ useEffect(() => {
102
+ const generation = ++loadGeneration.current;
103
+ let cancelled = false;
104
+ setState((current) =>
105
+ current.session === session
106
+ ? { ...current, loading: true, error: null }
107
+ : { session, projection: null, loading: true, error: null },
108
+ );
109
+ void composeDocumentEditorProjection(session).then(
110
+ (projection) => {
111
+ if (
112
+ cancelled ||
113
+ generation !== loadGeneration.current ||
114
+ commandStateRef.current !== commandState
115
+ ) {
116
+ return;
117
+ }
118
+ const nextParagraphText = paragraphTextById(projection);
119
+ for (const [localId, canonicalId] of commandState.optimisticParagraphIds) {
120
+ if (nextParagraphText.has(canonicalId)) continue;
121
+ const optimisticText =
122
+ commandState.paragraphText.get(canonicalId) ?? commandState.paragraphText.get(localId);
123
+ if (optimisticText !== undefined) nextParagraphText.set(canonicalId, optimisticText);
124
+ }
125
+ commandState.paragraphText = nextParagraphText;
126
+ setState({ session, projection, loading: false, error: null });
127
+ },
128
+ (cause) => {
129
+ if (
130
+ cancelled ||
131
+ generation !== loadGeneration.current ||
132
+ commandStateRef.current !== commandState
133
+ ) {
134
+ return;
135
+ }
136
+ setState({
137
+ session,
138
+ projection: null,
139
+ loading: false,
140
+ error: asEditableArtifactError(cause, "Could not open this document"),
141
+ });
142
+ },
143
+ );
144
+ return () => {
145
+ cancelled = true;
146
+ };
147
+ }, [commandState, invalidator, retryEpoch, session]);
148
+
149
+ const refresh = useCallback(() => setRetryEpoch((value) => value + 1), []);
150
+ const writable = !readOnly && view.writable;
151
+ const applyCommit = useCallback(
152
+ (commit: DocumentCommit): Promise<void> => {
153
+ const projection = state.session === session ? state.projection : null;
154
+ if (!projection) return Promise.reject(new Error("Document projection is not ready"));
155
+ if (!writable) return Promise.reject(new Error("This document is read only"));
156
+ if (String(commit.revision) !== String(projection.revision)) {
157
+ return Promise.reject(
158
+ new Error("The document changed; retry this edit on the latest version"),
159
+ );
160
+ }
161
+
162
+ const execute = async () => {
163
+ if (commandStateRef.current !== commandState) {
164
+ throw new Error("The document session changed before this edit could be saved");
165
+ }
166
+
167
+ const allocateLocalParagraph = async (localId: string): Promise<string> => {
168
+ const allocated = commandState.optimisticParagraphIds.get(localId);
169
+ if (allocated !== undefined) return allocated;
170
+ if (!commandState.localParagraphIds.has(localId)) {
171
+ throw new Error("The edited paragraph is no longer available");
172
+ }
173
+ const created = await session.createDocumentParagraph();
174
+ if (commandStateRef.current !== commandState) {
175
+ throw new Error("The document session changed before this edit could be saved");
176
+ }
177
+ commandState.optimisticParagraphIds.set(localId, created.paragraphId);
178
+ commandState.paragraphText.set(created.paragraphId, "");
179
+ return created.paragraphId;
180
+ };
181
+
182
+ if (commit.kind === "insert-paragraph") {
183
+ commandState.localParagraphIds.add(commit.blockId);
184
+ await allocateLocalParagraph(commit.blockId);
185
+ notifyDocumentCommit(onCommit, onCommandError, commit);
186
+ refresh();
187
+ return;
188
+ }
189
+
190
+ const targetId = commandState.localParagraphIds.has(commit.blockId)
191
+ ? await allocateLocalParagraph(commit.blockId)
192
+ : commit.blockId;
193
+ let command: DocumentArtifactCommand | null = null;
194
+ let rollbackText: (() => void) | null = null;
195
+ if (commit.kind === "text") {
196
+ const before = commandState.paragraphText.get(targetId);
197
+ if (before === undefined) {
198
+ throw new Error("The edited paragraph is no longer available");
199
+ }
200
+ const edit = minimalUtf16TextEdit(before, commit.text);
201
+ if (!edit) return;
202
+ commandState.paragraphText.set(targetId, commit.text);
203
+ rollbackText = () => {
204
+ if (commandState.paragraphText.get(targetId) === commit.text) {
205
+ commandState.paragraphText.set(targetId, before);
206
+ }
207
+ };
208
+ command = {
209
+ kind: "paragraph.edit",
210
+ id: targetId,
211
+ range: edit.range,
212
+ replacement: edit.replacement,
213
+ style: null,
214
+ };
215
+ } else if (commit.kind === "format") {
216
+ command = {
217
+ kind: "paragraph.format",
218
+ id: targetId,
219
+ range: commit.range,
220
+ style: documentTextStylePatch(commit.style),
221
+ };
222
+ }
223
+ if (!command) throw new Error("Unsupported document edit");
224
+
225
+ try {
226
+ await session.applyDocumentCommands({
227
+ version: DOCUMENT_ARTIFACT_COMMAND_VERSION,
228
+ commands: [command],
229
+ });
230
+ if (commandStateRef.current !== commandState) return;
231
+ notifyDocumentCommit(onCommit, onCommandError, commit);
232
+ refresh();
233
+ } catch (cause) {
234
+ rollbackText?.();
235
+ throw cause;
236
+ }
237
+ };
238
+
239
+ // A rejected edit must not poison later user input. Each operation still
240
+ // runs serially, but starts after the prior operation settles.
241
+ const operation = commandState.tail.then(execute, execute);
242
+ commandState.tail = operation;
243
+ void operation.then(
244
+ () => {
245
+ if (commandState.tail === operation) commandState.tail = Promise.resolve();
246
+ },
247
+ () => {
248
+ if (commandState.tail === operation) commandState.tail = Promise.resolve();
249
+ },
250
+ );
251
+ return operation;
252
+ },
253
+ [commandState, onCommandError, onCommit, refresh, session, state, writable],
254
+ );
255
+
256
+ const projection = state.session === session ? state.projection : null;
257
+ const status = editableArtifactStatusLabel(view);
258
+ const accessRevoked = editableArtifactAccessRevoked(view);
259
+ return projection && !accessRevoked ? (
260
+ <DocumentProjectionArtifactSurface
261
+ {...surfaceProps}
262
+ title={title}
263
+ subtitle={
264
+ subtitle ??
265
+ `${projection.blocks.length} block${projection.blocks.length === 1 ? "" : "s"}${writable ? "" : " · Read only"}`
266
+ }
267
+ projection={projection}
268
+ commit={writable ? applyCommit : undefined}
269
+ readOnly={!writable}
270
+ busy={state.loading}
271
+ onCommandError={onCommandError}
272
+ />
273
+ ) : (
274
+ <ArtifactSurface
275
+ modality="document"
276
+ title={title}
277
+ subtitle={subtitle ?? status}
278
+ busy={!accessRevoked && !state.error}
279
+ className={surfaceProps.className}
280
+ >
281
+ <EditableArtifactMessage
282
+ title={
283
+ accessRevoked
284
+ ? "Access changed"
285
+ : state.error
286
+ ? "Could not open this document"
287
+ : "Opening document"
288
+ }
289
+ detail={accessRevoked ? status : (state.error?.message ?? status)}
290
+ retry={!accessRevoked && state.error ? refresh : undefined}
291
+ />
292
+ </ArtifactSurface>
293
+ );
294
+ }
295
+
296
+ /** Deterministically composes summary/body/sections/review at one Worker revision. */
297
+ export async function composeDocumentEditorProjection(
298
+ session: EditableArtifactSession,
299
+ ): Promise<DocumentEditorProjection> {
300
+ if (session.modality !== "document") throw new Error("Expected a document artifact session");
301
+ let lastRevisionError: Error | null = null;
302
+ for (let attempt = 0; attempt < DOCUMENT_COMPOSITION_ATTEMPTS; attempt += 1) {
303
+ try {
304
+ return await composeDocumentAttempt(session);
305
+ } catch (cause) {
306
+ const error = asEditableArtifactError(cause, "Could not project this document");
307
+ if (!isRevisionDrift(error) || attempt === DOCUMENT_COMPOSITION_ATTEMPTS - 1) throw error;
308
+ lastRevisionError = error;
309
+ }
310
+ }
311
+ throw lastRevisionError ?? new Error("Document changed while it was opening");
312
+ }
313
+
314
+ async function composeDocumentAttempt(
315
+ session: EditableArtifactSession,
316
+ ): Promise<DocumentEditorProjection> {
317
+ const summaryPage = await session.queryDocument({ kind: "summary" });
318
+ const summary = summaryItem(summaryPage);
319
+ const revision = summaryPage.revision;
320
+ if (summary.revision !== revision) throw new Error("Document projection revision drifted");
321
+ if (summary.blockCount > DOCUMENT_MAX_BLOCKS) {
322
+ throw new Error(`Document exceeds the interactive limit of ${DOCUMENT_MAX_BLOCKS} blocks`);
323
+ }
324
+ if (summary.sectionCount > DOCUMENT_MAX_SECTIONS) {
325
+ throw new Error(`Document exceeds the interactive limit of ${DOCUMENT_MAX_SECTIONS} sections`);
326
+ }
327
+ if (summary.commentCount + summary.trackedChangeCount > DOCUMENT_MAX_REVIEW_ITEMS) {
328
+ throw new Error(
329
+ `Document exceeds the interactive limit of ${DOCUMENT_MAX_REVIEW_ITEMS} review items`,
330
+ );
331
+ }
332
+
333
+ const body = await collectDocumentPages(
334
+ session,
335
+ "body",
336
+ revision,
337
+ DOCUMENT_MAX_BLOCKS,
338
+ DOCUMENT_MAX_TEXT_UTF16,
339
+ DOCUMENT_MAX_TABLE_CELLS,
340
+ (cursor, limits) => ({ kind: "body", startBlock: cursor, limits }),
341
+ );
342
+ const sections = await collectDocumentPages(
343
+ session,
344
+ "sections",
345
+ revision,
346
+ DOCUMENT_MAX_SECTIONS,
347
+ DOCUMENT_MAX_TEXT_UTF16 - body.text,
348
+ DOCUMENT_MAX_TABLE_CELLS - body.cells,
349
+ (cursor, limits) => ({ kind: "sections", startSection: cursor, limits }),
350
+ );
351
+ const review = await collectDocumentPages(
352
+ session,
353
+ "review",
354
+ revision,
355
+ DOCUMENT_MAX_REVIEW_ITEMS,
356
+ DOCUMENT_MAX_TEXT_UTF16 - body.text - sections.text,
357
+ DOCUMENT_MAX_TABLE_CELLS - body.cells - sections.cells,
358
+ (cursor, limits) => ({ kind: "review", startItem: cursor, limits }),
359
+ );
360
+
361
+ if (body.items.length !== summary.blockCount) {
362
+ throw new Error("Document body changed while it was opening");
363
+ }
364
+ if (sections.items.length !== summary.sectionCount) {
365
+ throw new Error("Document sections changed while they were opening");
366
+ }
367
+ if (review.items.length !== summary.commentCount + summary.trackedChangeCount) {
368
+ throw new Error("Document review state changed while it was opening");
369
+ }
370
+ const totalText = body.text + sections.text + review.text;
371
+ const totalCells = body.cells + sections.cells + review.cells;
372
+ if (totalText > DOCUMENT_MAX_TEXT_UTF16) {
373
+ throw new Error(
374
+ `Document exceeds the interactive limit of ${DOCUMENT_MAX_TEXT_UTF16} UTF-16 text units`,
375
+ );
376
+ }
377
+ if (totalCells > DOCUMENT_MAX_TABLE_CELLS) {
378
+ throw new Error(
379
+ `Document exceeds the interactive limit of ${DOCUMENT_MAX_TABLE_CELLS} table cells`,
380
+ );
381
+ }
382
+
383
+ return Object.freeze({
384
+ revision: revision.toString(),
385
+ page: projectDocumentPage(summary.page),
386
+ blocks: Object.freeze(body.items.map(projectDocumentBlock)),
387
+ sections: Object.freeze(sections.items.map(projectDocumentSection)),
388
+ comments: Object.freeze(
389
+ review.items.filter(isProjectionKind("comment")).map((item) => ({
390
+ id: item.id,
391
+ blockId: item.paragraphId,
392
+ start: item.range.start,
393
+ end: item.range.end,
394
+ resolved: item.resolved,
395
+ replies: item.replies.map((reply) => ({ ...reply })),
396
+ })),
397
+ ),
398
+ changes: Object.freeze(
399
+ review.items.filter(isProjectionKind("tracked-change")).map((item) => ({
400
+ id: item.id,
401
+ blockId: item.paragraphId,
402
+ kind: item.changeKind,
403
+ start: item.range.start,
404
+ end: item.range.end,
405
+ author: item.author,
406
+ createdAt: item.createdAt,
407
+ })),
408
+ ),
409
+ });
410
+ }
411
+
412
+ type DocumentPageCollection = {
413
+ items: DocumentArtifactProjectionItem[];
414
+ text: number;
415
+ cells: number;
416
+ };
417
+
418
+ async function collectDocumentPages(
419
+ session: EditableArtifactSession,
420
+ label: "body" | "sections" | "review",
421
+ revision: bigint,
422
+ maxItems: number,
423
+ maxText: number,
424
+ maxCells: number,
425
+ query: (
426
+ cursor: number,
427
+ limits: { maxItems: number; maxTextUtf16: number; maxTableCells: number },
428
+ ) => DocumentArtifactQuery,
429
+ ): Promise<DocumentPageCollection> {
430
+ const output: DocumentPageCollection = { items: [], text: 0, cells: 0 };
431
+ let cursor = 0;
432
+ for (let pageIndex = 0; pageIndex < DOCUMENT_MAX_QUERY_PAGES; pageIndex += 1) {
433
+ if (output.items.length >= maxItems && cursor > 0) {
434
+ throw new Error(`Document ${label} exceeds its interactive item limit`);
435
+ }
436
+ const page = await session.queryDocument(
437
+ query(cursor, {
438
+ maxItems: Math.min(DOCUMENT_PAGE_ITEMS, Math.max(1, maxItems - output.items.length)),
439
+ maxTextUtf16: Math.max(1, maxText - output.text),
440
+ maxTableCells: Math.max(1, maxCells - output.cells),
441
+ }),
442
+ );
443
+ assertDocumentRevision(page, revision);
444
+ assertDocumentPageKinds(label, page.items);
445
+ if (output.items.length + page.items.length > maxItems) {
446
+ throw new Error(`Document ${label} exceeds its interactive item limit`);
447
+ }
448
+ output.items.push(...page.items);
449
+ output.text += page.projectedTextUtf16;
450
+ output.cells += page.projectedTableCells;
451
+ if (output.text > maxText || output.cells > maxCells) {
452
+ throw new Error(`Document ${label} exceeds its interactive content limit`);
453
+ }
454
+ if (page.nextCursor === null) return output;
455
+ if (page.nextCursor <= cursor || page.items.length === 0) {
456
+ throw new Error(`Document ${label} pagination did not advance`);
457
+ }
458
+ cursor = page.nextCursor;
459
+ }
460
+ throw new Error(`Document ${label} exceeds ${DOCUMENT_MAX_QUERY_PAGES} query pages`);
461
+ }
462
+
463
+ function summaryItem(
464
+ projection: DocumentArtifactProjection,
465
+ ): Extract<DocumentArtifactProjectionItem, { kind: "summary" }> {
466
+ if (
467
+ projection.items.length !== 1 ||
468
+ projection.items[0]?.kind !== "summary" ||
469
+ projection.nextCursor !== null ||
470
+ projection.truncated
471
+ ) {
472
+ throw new Error("Document summary projection is malformed");
473
+ }
474
+ return projection.items[0];
475
+ }
476
+
477
+ function assertDocumentRevision(page: DocumentArtifactProjection, revision: bigint): void {
478
+ if (page.revision !== revision) throw new Error("Document projection revision drifted");
479
+ }
480
+
481
+ function assertDocumentPageKinds(
482
+ label: "body" | "sections" | "review",
483
+ items: readonly DocumentArtifactProjectionItem[],
484
+ ): void {
485
+ const valid =
486
+ label === "body"
487
+ ? new Set(["paragraph", "table", "page-break"])
488
+ : label === "sections"
489
+ ? new Set(["section"])
490
+ : new Set(["comment", "tracked-change"]);
491
+ if (items.some((item) => !valid.has(item.kind))) {
492
+ throw new Error(`Document ${label} projection contains an unexpected item`);
493
+ }
494
+ }
495
+
496
+ function projectDocumentBlock(
497
+ item: DocumentArtifactProjectionItem,
498
+ ): DocumentEditorProjection["blocks"][number] {
499
+ if (item.kind === "paragraph") {
500
+ const { headingLevel, list, ...paragraphStyle } = item.style;
501
+ return {
502
+ kind: "paragraph",
503
+ id: item.id,
504
+ runs: item.runs.map((run) => ({ text: run.text, style: { ...run.style } })),
505
+ style: {
506
+ ...paragraphStyle,
507
+ ...(headingLevel === undefined ? {} : { headingLevel: documentHeadingLevel(headingLevel) }),
508
+ ...(list
509
+ ? {
510
+ list: {
511
+ kind: list.kind,
512
+ ...(list.level === null ? {} : { level: list.level }),
513
+ ...(list.instanceId === null ? {} : { instanceId: list.instanceId }),
514
+ },
515
+ }
516
+ : {}),
517
+ },
518
+ };
519
+ }
520
+ if (item.kind === "table") {
521
+ return {
522
+ kind: "table",
523
+ id: item.id,
524
+ rows: item.rows.map((row) =>
525
+ row.map((cell) => cell.map((run) => ({ text: run.text, style: { ...run.style } }))),
526
+ ),
527
+ style: { ...item.style },
528
+ };
529
+ }
530
+ if (item.kind === "page-break") return { kind: "pageBreak", id: item.id };
531
+ throw new Error("Document body projection contains an unexpected item");
532
+ }
533
+
534
+ function documentHeadingLevel(value: number): 1 | 2 | 3 | 4 | 5 | 6 {
535
+ if (value < 1 || value > 6 || !Number.isInteger(value)) {
536
+ throw new Error("Document projection contains an invalid heading level");
537
+ }
538
+ return value as 1 | 2 | 3 | 4 | 5 | 6;
539
+ }
540
+
541
+ function projectDocumentSection(item: DocumentArtifactProjectionItem) {
542
+ if (item.kind !== "section") {
543
+ throw new Error("Document section projection contains an unexpected item");
544
+ }
545
+ return {
546
+ id: item.id,
547
+ startBlockIndex: item.startBlockIndex,
548
+ page: projectDocumentPage(item.page),
549
+ titlePage: item.titlePage,
550
+ headerBlockCounts: item.headerBlockCounts,
551
+ footerBlockCounts: item.footerBlockCounts,
552
+ };
553
+ }
554
+
555
+ function projectDocumentPage(
556
+ page: DocumentArtifactPageGeometryProjection,
557
+ ): DocumentProjectionPageGeometry {
558
+ const points = (value: bigint) => Number(value) / 1_000;
559
+ return {
560
+ widthPt: points(page.widthMillipoints),
561
+ heightPt: points(page.heightMillipoints),
562
+ marginTopPt: points(page.marginTopMillipoints),
563
+ marginRightPt: points(page.marginRightMillipoints),
564
+ marginBottomPt: points(page.marginBottomMillipoints),
565
+ marginLeftPt: points(page.marginLeftMillipoints),
566
+ headerPt: points(page.headerMillipoints),
567
+ footerPt: points(page.footerMillipoints),
568
+ gutterPt: points(page.gutterMillipoints),
569
+ };
570
+ }
571
+
572
+ function isProjectionKind<K extends DocumentArtifactProjectionItem["kind"]>(kind: K) {
573
+ return (
574
+ item: DocumentArtifactProjectionItem,
575
+ ): item is Extract<DocumentArtifactProjectionItem, { kind: K }> => item.kind === kind;
576
+ }
577
+
578
+ function paragraphTextById(projection: DocumentEditorProjection): Map<string, string> {
579
+ return new Map(
580
+ projection.blocks
581
+ .filter((block) => block.kind === "paragraph")
582
+ .map((paragraph) => [paragraph.id, paragraph.runs.map((run) => run.text).join("")] as const),
583
+ );
584
+ }
585
+
586
+ export function minimalUtf16TextEdit(
587
+ before: string,
588
+ after: string,
589
+ ): { range: { start: number; end: number }; replacement: string } | null {
590
+ if (before === after) return null;
591
+ let start = 0;
592
+ const maximumStart = Math.min(before.length, after.length);
593
+ while (start < maximumStart && before.charCodeAt(start) === after.charCodeAt(start)) start += 1;
594
+ while (start > 0 && (splitsSurrogate(before, start) || splitsSurrogate(after, start))) start -= 1;
595
+
596
+ let suffix = 0;
597
+ while (
598
+ suffix < before.length - start &&
599
+ suffix < after.length - start &&
600
+ before.charCodeAt(before.length - suffix - 1) === after.charCodeAt(after.length - suffix - 1)
601
+ ) {
602
+ suffix += 1;
603
+ }
604
+ while (
605
+ suffix > 0 &&
606
+ (splitsSurrogate(before, before.length - suffix) ||
607
+ splitsSurrogate(after, after.length - suffix))
608
+ ) {
609
+ suffix -= 1;
610
+ }
611
+ return {
612
+ range: { start, end: before.length - suffix },
613
+ replacement: after.slice(start, after.length - suffix),
614
+ };
615
+ }
616
+
617
+ function splitsSurrogate(value: string, index: number): boolean {
618
+ if (index <= 0 || index >= value.length) return false;
619
+ const left = value.charCodeAt(index - 1);
620
+ const right = value.charCodeAt(index);
621
+ return left >= 0xd800 && left <= 0xdbff && right >= 0xdc00 && right <= 0xdfff;
622
+ }
623
+
624
+ function documentTextStylePatch(
625
+ style: Extract<DocumentCommit, { kind: "format" }>["style"],
626
+ ): DocumentArtifactTextStylePatch {
627
+ return {
628
+ ...(style.fontFamily === undefined ? {} : { fontFamily: style.fontFamily }),
629
+ ...(style.fontSizePt === undefined ? {} : { fontSizePt: style.fontSizePt }),
630
+ ...(style.color === undefined ? {} : { color: style.color }),
631
+ ...(style.bold === undefined ? {} : { bold: style.bold }),
632
+ ...(style.italic === undefined ? {} : { italic: style.italic }),
633
+ ...(style.underline === undefined ? {} : { underline: style.underline }),
634
+ ...(style.strike === undefined ? {} : { strike: style.strike }),
635
+ };
636
+ }
637
+
638
+ function isRevisionDrift(error: Error): boolean {
639
+ return error.message.includes("changed while") || error.message.includes("revision drifted");
640
+ }
641
+
642
+ function notifyDocumentCommit(
643
+ observer: DocumentCommitHandler | undefined,
644
+ onError: ((error: Error) => void) | undefined,
645
+ commit: DocumentCommit,
646
+ ): void {
647
+ try {
648
+ const result = observer?.(commit);
649
+ if (result && typeof result.then === "function") {
650
+ void Promise.resolve(result).catch((cause) =>
651
+ onError?.(asEditableArtifactError(cause, "Document commit observer failed")),
652
+ );
653
+ }
654
+ } catch (cause) {
655
+ onError?.(asEditableArtifactError(cause, "Document commit observer failed"));
656
+ }
657
+ }